Files
bin/docs/superpowers/plans/2026-07-19-ausbaustufe-3.md
2026-07-19 22:10:23 +02:00

15 KiB
Raw Blame History

Ausbaustufe 3 — CSV-Import, Saldo-Anker, Salden-Seite, Parser-Tuning

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: CSV-Kontoumsatz-Import als primärer Import-Weg (DKB/VR/HVB-Exportformate), Konto-Saldo-Anker mit GUI-Pflege, neue Salden-Seite (Stichtag + Monatsübersicht), PDF-Parser per CSV-Ground-Truth verbessern; anschließend beaufsichtigte Vollmigration (PDF-Bestand → CSV-Basis 2025-01-01 bis heute).

Architecture: CSV-Dateien laufen durch die BESTEHENDE Import-Pipeline (Upload/Inbox → ParsedStatement → Statement/Draft → Vorschau → Confirm → Rollback). Neu: parsers/csv_formats.py erkennt die drei Bankformate am Header und liefert ParsedStatement. Saldenrechnung wird von Statement-closing auf Konto-Anker (Kontostand X am Datum Y, neue Account-Spalten via Alembic) umgestellt — rückwärts wie vorwärts vom Anker aus; Grafana-Views analog. Nutzerentscheidungen (2026-07-19): CSV primär; Anker GUI-pflegbar; Prüfschärfe je Format maximal (VR lückenlos, DKB gegen Datei-Kontostand-Anker, HVB ohne — gekennzeichnet); Migration führt der Controller beaufsichtigt durch.

Tech Stack: unverändert; Version → 0.4.0.

Global Constraints

  • Repo /home/wlfb/bin, Code finance/; Geldbeträge Decimal; Nutzertexte Deutsch; Suite grün vor jedem Commit (Basis: 79 passed); Commit-Trailer Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>.
  • DATENSCHUTZ: tests/fixtures/*.csv und *.pdf sind echte Kontodaten (gitignored). Keine echten Daten in Commits/Reports/Code. Erwartungswerte nur in gitignorten tests/fixtures/expected_*.json-Dateien.
  • TEST-VERIFIKATION (Nutzer-Vorgabe): Nach jeder Task-Implementierung führt ein SUBAGENT MIT FABLE-MODELL die Test-/Verifikationspassage aus (Suite + task-spezifische Checks) und berichtet unabhängig; der Implementer-Selbstbericht genügt nicht.
  • UX-Regel (etabliert): Bedienelemente immer sichtbar, Nichtverfügbares disabled mit title.
  • Deployment erst in Task 6.

CSV-Formatreferenz (aus Datei-Analyse 2026-07-19)

DKB_*.csv VR_*.csv HVB_*.csv
Encoding UTF-8 mit BOM UTF-8 mit BOM, CRLF UTF-16
Trenner ;, Werte in " ;, unquoted ;
Kopf 4 Metazeilen: Girokonto/IBAN; Zeitraum; „Kontostand vom TT.MM.JJJJ:" + Betrag mit €; Leerzeile — dann Spaltenkopf Spaltenkopf in Zeile 1 Spaltenkopf in Zeile 1
Spalten Buchungsdatum;Wertstellung;Status;Zahlungspflichtiger;Zahlungsempfängerin;Verwendungszweck;Umsatztyp;IBAN;Betrag (€);Gläubiger-ID;Mandatsreferenz;Kundenreferenz Bezeichnung Auftragskonto;IBAN Auftragskonto;BIC Auftragskonto;Bankname Auftragskonto;Buchungstag;Valutadatum;Name Zahlungsbeteiligter;IBAN Zahlungsbeteiligter;BIC (SWIFT-Code) Zahlungsbeteiligter;Buchungstext;Verwendungszweck;Betrag;Waehrung;Saldo nach Buchung;Bemerkung;Gekennzeichneter Umsatz;Glaeubiger ID;Mandatsreferenz Kontonummer;Buchungsdatum;Valuta;Verwendungszweck;Betrag;Waehrung
Datumsformat TT.MM.JJ (zweistellig!) TT.MM.JJJJ TT.MM.JJJJ
Sortierung absteigend absteigend aufsteigend
Konto-Zuordnung IBAN aus Kopfzeile IBAN Auftragskonto Kontonummer → Konto per iban.endswith(kontonummer)
counterparty Betrag<0 → Zahlungsempfängerin, sonst Zahlungspflichtiger Name Zahlungsbeteiligter "" (steckt im Verwendungszweck)
purpose Umsatztyp + " " + Verwendungszweck Buchungstext + " " + Verwendungszweck Verwendungszweck
value_date Wertstellung Valutadatum Valuta
Saldo-Prüfung Datei-Kontostand als KONTO-ANKER übernehmen (Datum aus „Kontostand vom"); keine per-Datei-Prüfung (Kontostand = Exportzeitpunkt, nicht Periodenende) Saldo nach Buchung: chronologisch je Zeile prüfen saldo_n = saldo_{n-1} + betrag_n → daraus opening/closing für ParsedStatement ableiten (balance_difference==0 greift); Anker = neuester Saldo keine (opening/closing None, preview „Saldo-Prüfung: nicht verfügbar")
Sonstiges nur Zeilen mit Status "Gebucht" importieren Abschluss-Zeilen (leerer Name) normal importieren Betrag-Spalte ist maßgeblich (Text enthält Betrag redundant)

Beträge deutsch formatiert (1.234,56, Minus führend) → parse_german_amount wiederverwenden (€-Zeichen/EUR vorher strippen).


Task 1: Konto-Saldo-Anker (Modell, Migration, Saldenrechnung, GUI, Views)

Files:

  • Modify: finance/app/models/tables.py (Account: anchor_date: Mapped[date | None], anchor_balance: Mapped[Decimal | None] = Numeric(12,2))
  • Create: finance/alembic/versions/<autogen>_konto_anker.py (autogenerate, add_column x2)
  • Modify: finance/app/services/balances.py, finance/app/routers/accounts.py (PATCH um anchor-Felder), finance/app/templates/index.html (Anker-Anzeige+Pflegeformular je Konto), finance/app/models/views.py (Views ankerbasiert)
  • Test: finance/tests/test_crud_api.py, finance/tests/test_gui.py

Interfaces:

  • Produces: balances.account_balance(session, account, at: date | None = None) -> Decimalat default heute. Mit Anker: anchor_balance + Σ(tx: anchor_date < booking_date <= at) Σ(tx: at < booking_date <= anchor_date) (nur bestätigte; eine der Summen ist leer). Ohne Anker: Σ(tx: booking_date <= at). Die bisherige Statement-closing-Logik ENTFÄLLT ersatzlos (Statements bleiben als Import-Einheiten bestehen).

  • PATCH /api/accounts/{id} zusätzlich: anchor_date: date | None, anchor_balance: Decimal | None (beide optional, model_fields_set-Semantik wie bei name; nur gemeinsam gesetzt oder gemeinsam null → sonst 422 „Anker braucht Datum und Betrag").

  • GUI Übersicht: je Konto Ankeranzeige („Anker: X € am TT.MM.JJJJ" oder „kein Anker") + Inline-Formular (Datum+Betrag, json-form hx-patch).

  • views.py: v_balance_history/v_balance_total seeden pro Konto mit anchor_balance Σ(tx > … bis anchor_date) statt Statement-opening — konkret: Basis je Konto = account_balance zum frühesten Buchungsdatum dessen Tagesumsatz; einfachste korrekte Form: CTE mit Anker-Join und kumulativer Fenstersumme relativ zum Anker. v_projection unverändert.

  • Step 1: Failing Tests — test_crud_api: (a) Konto mit Anker (Datum D, Betrag B) + bestätigte Buchungen vor/nach D → account_balance(at=D)==B, at=D+2d addiert spätere Buchung, at=D2d subtrahiert dazwischenliegende; (b) PATCH setzt/löscht Anker, 422 bei nur-einem-Feld; test_gui: Übersicht zeigt Ankertext.

  • Step 2: FAIL · Step 3: Implementieren + Alembic-Autogenerate gegen Wegwerf-SQLite (Muster Task 2 Ausbaustufe 1; Migration committen) · Step 4: PASS, Suite grün

  • Step 5: FABLE-TESTAGENT verifiziert unabhängig (Suite + Anker-Randfälle: at==anchor_date, Buchung AM Ankertag zählt nicht zur Vorwärtssumme/wohl zur Rückwärtssumme — Konvention: Anker gilt per Tagesende).

  • Step 6: Commitfeat: Konto-Saldo-Anker mit ankerbasierter Saldenrechnung


Task 2: CSV-Format-Parser

Files:

  • Create: finance/app/parsers/csv_formats.py, finance/tests/test_csv_formats.py
  • Modify: finance/.gitignore ist bereits um tests/fixtures/*.csv ergänzt (erledigt)

Interfaces:

  • Produces: csv_formats.detect_csv_format(head_bytes: bytes) -> str | None („dkb_csv"|„vr_csv"|„hvb_csv"; erkennt Encoding inkl. UTF-16/BOM + Headersignatur); csv_formats.parse_csv(path: Path) -> ParsedCsv mit ParsedCsv(statement: ParsedStatement, anchor: tuple[date, Decimal] | None, balance_checkable: bool). Transaktionen chronologisch aufsteigend im ParsedStatement, Mapping exakt gemäß Formatreferenz oben. VR: Saldo-Kette prüfen, bei Bruch ParserError(„VR-CSV: Saldo-Kette inkonsistent bei Zeile N"); opening/closing setzen (balance_checkable=True). DKB: anchor=(Datum aus „Kontostand vom", Betrag), opening/closing=None, nur „Gebucht". HVB: alles None/False.

  • ParserError bei unbekanntem Format/kaputter Struktur.

  • Step 1: Failing Tests — pro Format parametrisiert (skip wenn Datei fehlt): Zeilenzahl >0, Transaktionen aufsteigend, Beträge Decimal, VR: balance_difference(stmt)==0 und anchor gesetzt, DKB: anchor gesetzt + alle Status Gebucht + counterparty je Vorzeichen befüllt, HVB: balance_checkable False; plus expected_csv_<fmt>.json (gitignored, Schema wie expected_*: transaction_count + 5 spot_checks) — Dateien erstellt der CONTROLLER nach Implementierung aus Stichproben.

  • Step 2: FAIL · Step 3: Implementieren (csv-Modul der stdlib; Encoding-Erkennung: BOM-Sniff + UTF-16-Heuristik über Nullbytes) · Step 4: PASS

  • Step 5: CONTROLLER erstellt expected-Dateien (Stichproben aus den echten CSVs, nur lokal) · Step 6: FABLE-TESTAGENT verifiziert (Suite + eigene Stichproben-Gegenkontrolle CSV-Zeile↔geparste Transaktion für je 3 Zeilen pro Format, feldweise).

  • Step 7: Commitfeat: CSV-Parser fuer DKB/VR/HVB-Kontoumsatz-Exporte


Task 3: CSV in die Import-Pipeline

Files:

  • Modify: finance/app/services/importer.py (Dateiendungs-Weiche: .csv → parse_csv; Anker-Autofill: bei confirm... nein: beim IMPORT ParsedCsv.anchor am Statement zwischenspeichern → einfacher: importer setzt Anker DIREKT am Konto beim process, nur wenn anchor_date neuer als vorhandener Anker oder keiner existiert), finance/app/routers/imports.py (Upload akzeptiert .csv; 400-Text anpassen „Nur PDF- oder CSV-Dateien"), Inbox-Scan *.csv zusätzlich, finance/app/templates/_preview_table.html/_import_list.html (balance_ok dreiwertig: True „Saldo plausibel" / False rot / None „Saldo-Prüfung: nicht verfügbar (Format ohne Saldodaten)"), finance/app/templates/hilfe.html (CSV als primärer Weg, PDF als Fallback)
  • Test: finance/tests/test_import_api.py

Interfaces:

  • Produces: preview-Response balance_ok: bool | None. Statement.bank erhält Formatkennung („dkb_csv" etc.). Duplikatschutz unverändert über dedup_hash (funktioniert für künftige überlappende CSV-Exporte, da identische Texte).

  • Anker-Regel: Import (bereits beim Draft-Anlegen) aktualisiert account.anchor_* nur, wenn CSV-Anker-Datum ≥ bestehendes Anker-Datum oder Konto ohne Anker; manuelles Überschreiben bleibt via PATCH möglich.

  • Step 1: Failing Tests — Upload einer synthetischen Mini-CSV je Format (im Test als Bytes konstruiert nach Formatreferenz — KEINE echten Daten): 201 draft, preview balance_ok-Wert je Format (VR True, HVB None), confirm übernimmt, DKB-Anker am Konto gesetzt; Upload .txt → 400; Inbox-Scan findet .csv.

  • Step 2: FAIL · Step 3: Implementieren · Step 4: PASS, Suite grün

  • Step 5: FABLE-TESTAGENT verifiziert (Suite + Upload der ECHTEN 6 Dateien gegen Test-Instanz in-memory: je 201+plausible Zeilenzahlen — Zahlen nur als Counts berichten).

  • Step 6: Commitfeat: CSV-Import ueber bestehende Pipeline mit dreiwertiger Saldo-Pruefung


Task 4: Salden-Seite

Files:

  • Create: finance/app/templates/salden.html
  • Modify: finance/app/routers/gui.py (GET /salden mit gui_session; Stichtag-Param lenient wie Filter), finance/app/templates/base.html (Nav „Salden" zwischen Buchungen und Planung), finance/app/templates/hilfe.html (Konzept-Absatz), finance/tests/test_gui.py

Interfaces:

  • GET /salden[?stichtag=YYYY-MM-DD] → Abschnitt 1 „Salden am " (Default heute): Tabelle Konto→Saldo (account_balance(at=stichtag)) + Gesamtzeile; Datumsformular (GET). Abschnitt 2 „Monatsanfangs-Salden": Zeilen = 1. jedes Monats von frühestem Buchungsmonat bis heute (absteigend), Spalten = Konten + Gesamt; Werte = account_balance(at=Monatsersten). Beträge 2 Nachkommastellen, .neg-Klasse, Konten ohne Anker mit Fußnote „ohne Anker — relative Werte".

  • Step 1: Failing Tests — /salden in beide Seiten-Tests; mit Seed (Anker + Buchungen über 3 Monate): Stichtags-Tabelle zeigt erwarteten Kontosaldo, Monatsübersicht enthält 3 Monatszeilen mit korrektem Wert für einen definierten Monatsersten.

  • Step 2: FAIL · Step 3: Implementieren · Step 4: PASS · Step 5: FABLE-TESTAGENT (Suite + Handrechnung eines Monatsanfangs-Werts gegen Seed-Daten) · Step 6: Commitfeat: Salden-Seite mit Stichtag und Monatsuebersicht


Task 5: PDF-Parser-Tuning per CSV-Ground-Truth (Fable-Audit)

Files:

  • Create: finance/scripts/parser_vs_csv.py (lokales Vergleichswerkzeug)
  • Modify: finance/app/parsers/dkb.py (ggf. vr.py/hvb.py), finance/tests/fixtures/expected_*.json (Controller, lokal)

Interfaces:

  • parser_vs_csv.py <pdf> <csv...>: matcht PDF-Transaktionen gegen CSV-Zeilen im Überlappungszeitraum über (booking_date, amount); gibt je Match Feldvergleich counterparty/purpose aus (Ähnlichkeit + Differenzen), am Ende Metrik: N matched, counterparty-Übereinstimmung x %, purpose-Kernübereinstimmung y %. Nur lokal, echte Daten nur im gitignorten Workspace.

  • Step 1: Werkzeug bauen (Subagent) · Step 2: CONTROLLER-Audit (Fable): Werkzeug auf DKB/VR/HVB-Überlappung (JuniJuli 2026) laufen lassen, konkrete strukturelle Verbesserungen identifizieren (v. a. DKB: Empfänger/Zweck-Trennung — CSV-Empfängerspalte zeigt, wo die Grenze liegt; erwartbar: Empfänger = Anfang der ersten Fortsetzungszeile bis zum ersten Schlüsselwort-Muster). Mängelliste in .superpowers/sdd/parser-vs-csv-audit.md (gitignored).

  • Step 3: Parser-Fixes (Subagent, Mängelliste als Brief) — Saldo-Gates und bestehende expected-Tests bleiben Pflicht; expected_*.json passt der CONTROLLER an, wo sich purposes verbessern.

  • Step 4: FABLE-TESTAGENT: Suite + Metrik-Vergleich vorher/nachher (Werkzeug erneut ausführen; Verbesserung dokumentieren, Zahlen ohne echte Daten). Fable-Abnahme durch Controller.

  • Step 5: Commitfix: PDF-Parser per CSV-Ground-Truth verbessert (generische Beschreibung). Hinweis: „möglichst gut" = Best effort; verbleibende strukturbedingte Grenzen (z. B. DKB-Fließtext) werden dokumentiert, nicht erzwungen.


Task 6: Version 0.4.0, Redeploy, beaufsichtigte Vollmigration

Files: finance/VERSION0.4.0; keine weiteren Code-Änderungen.

  • Step 1: VERSION bump + Commit chore: Version 0.4.0 → Image-Build → create_pod_finance.sh (Alembic-Migration läuft im Entrypoint).
  • Step 2 (CONTROLLER, beaufsichtigt): (a) 3 PDF-Importe zurückrollen (Rollback-Feature); (b) HVB-Anker setzen: PATCH anchor_date=2026-07-03, anchor_balance=4559.51 (aus PDF-Auszug); (c) die 6 CSVs importieren (Reihenfolge egal, je: Upload → Preview prüfen [Zeilenzahl plausibel; VR balance_ok True; DKB/HVB Kennzeichnung sichtbar] → Confirm); DKB/VR-Anker kommen automatisch aus den Dateien; (d) Endkontrolle: Buchungszahl gesamt = Summe der CSV-Zeilen (minus evtl. Nicht-Gebucht), Salden-Seite: DKB-Saldo heute == Datei-Kontostand, VR-Saldo == neuester Saldo nach Buchung, HVB plausibel gegen Anker; Grafana-Kurven ab 2025; Suite grün; Smoke der Kernseiten.
  • Step 3: Ledger + Plan-Häkchen + Commit; Hilfe-/Memory-Konsistenz.

Abschluss-Checkliste

  • Suite grün (>= 90 Tests erwartet); alle Task-Verifikationen durch FABLE-Testagenten dokumentiert
  • Live: v0.4.0; Datenbestand vollständig aus 6 CSVs (2025-01-01 bis heute), Anker je Konto gesetzt, Salden-Seite konsistent mit CSV-Quellwerten
  • Parser-Tuning-Metriken dokumentiert (vorher/nachher), Fable-Abnahme erteilt
  • Keine echten Kontodaten/Secrets in git log beider Repos; CSV/PDF/expected-Dateien untracked
  • Plan-Häkchen gesetzt, beide Repos committet