Files
mousehold/apps/local-agent/mousehold_agent/csv_importer.py
2026-06-28 13:48:15 +02:00

87 lines
3.0 KiB
Python

from __future__ import annotations
import csv
from datetime import date
from decimal import Decimal
from pathlib import Path
from typing import Any
def read_transaction_csv(
path: Path,
*,
household_id: str,
source_type: str,
source_account_id: str | None,
owner_user_id: str | None,
) -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
with path.open(newline="", encoding="utf-8-sig") as handle:
reader = csv.DictReader(handle)
for row in reader:
event_date = _pick(
row, "event_date", "date", "booking_date", "Buchungstag", "Valutadatum"
)
amount = _pick(row, "amount", "Betrag", "value")
if not event_date or not amount:
continue
merchant = _pick(
row,
"merchant",
"counterparty",
"Auftraggeber/Empfänger",
"Name Zahlungsbeteiligter",
)
description = _pick(row, "description", "purpose", "Verwendungszweck", "Buchungstext")
currency = _pick(row, "currency", "Waehrung", "Währung") or "EUR"
provider_transaction_id = _pick(
row, "provider_transaction_id", "transaction_id", "Umsatz ID"
)
normalized = {
"household_id": household_id,
"source_type": source_type,
"source_account_id": source_account_id,
"owner_user_id": owner_user_id,
"source_reference": provider_transaction_id,
"provider_transaction_id": provider_transaction_id,
"raw_payload_json": row,
"amount": str(_parse_amount(amount)),
"currency": currency,
"event_date": _parse_date(event_date).isoformat(),
"merchant": merchant,
"description": description,
"event_type": "expense" if _parse_amount(amount) < 0 else "income",
"status": "confirmed",
}
normalized["amount"] = str(abs(_parse_amount(amount)))
rows.append(normalized)
return rows
def _pick(row: dict[str, str], *keys: str) -> str | None:
lowered = {key.lower(): value for key, value in row.items()}
for key in keys:
value = row.get(key) or lowered.get(key.lower())
if value:
return value.strip()
return None
def _parse_amount(value: str) -> Decimal:
normalized = value.strip().replace(".", "").replace(",", ".")
return Decimal(normalized)
def _parse_date(value: str) -> date:
stripped = value.strip()
for fmt in ("%Y-%m-%d", "%d.%m.%Y", "%d/%m/%Y"):
try:
return (
date.fromisoformat(stripped)
if fmt == "%Y-%m-%d"
else __import__("datetime").datetime.strptime(stripped, fmt).date()
)
except ValueError:
continue
raise ValueError(f"Unsupported date format: {value}")