import re from dataclasses import dataclass, field from datetime import date from decimal import Decimal class ParserError(Exception): pass @dataclass(frozen=True) class ParsedTransaction: booking_date: date value_date: date | None amount: Decimal purpose: str counterparty: str @dataclass class ParsedStatement: bank: str iban: str | None period_start: date | None period_end: date | None opening_balance: Decimal | None closing_balance: Decimal | None transactions: list[ParsedTransaction] = field(default_factory=list) def parse_german_amount(s: str) -> Decimal: s = s.strip().replace(" ", "").replace("\xa0", "") neg = s.endswith("-") or s.startswith("-") or s.endswith("S") s = s.strip("+-SH") value = Decimal(s.replace(".", "").replace(",", ".")) return -value if neg else value def parse_german_date(s: str, default_year: int | None = None) -> date: m = re.fullmatch(r"(\d{2})\.(\d{2})\.(\d{4})?", s.strip()) if not m: raise ParserError(f"Kein Datum: {s!r}") year = int(m.group(3)) if m.group(3) else default_year if year is None: raise ParserError(f"Jahr fehlt: {s!r}") try: return date(year, int(m.group(2)), int(m.group(1))) except ValueError: raise ParserError(f"Ungültiges Datum: {s!r}")