## Ergebnis - Read-only-Inventur auf Baseline `7411df5ad88ab4b2bebd2ec5c7008aea18d4d7ca` abgeschlossen. - Parser-, Dubletten- und Transfer-Pairing-Tests ausgeführt: **24 bestanden**, eine unkritische Starlette/httpx-Deprecation-Warnung. - Synthetische Zusatzproben bestätigten zwei zentrale Lücken: - VISA `-10.00 EUR` wird als `amount_original=10.00`, `signed_amount_original=NULL`, `currency_original=CHF`, ohne Kartenkonto übernommen. - Dieselbe VISA-Zeile unter anderem Dateinamen erzeugt einen zweiten Kandidaten; derselbe Migros-Bon wird dagegen über `receipt_key` abgefangen. - Worktree blieb unverändert; `git diff --check` sauber. ## Wiederverwendbare Architektur - Profile und Erkennung: `src/jarvis_finance/services/budget_csv_imports.py` - `IMPORT_PROFILES`, `detect_import_profile()`, `parse_budget_csv_text()`, `import_budget_csv_text()` - vorhandene Profile: `visa_credit_card`, `migros_receipts`, `raiffeisen_bank`, `akb_bank` - Kandidaten-/Review-Pipeline: `src/jarvis_finance/services/budget_imports.py` - `_bank_rows()`, `seed_*_candidates_from_rows()`, `_insert_candidate()` - Preview/Confirm, Duplicate-Override, Splits, Ignore/Reopen und Audit sind vorhanden. - Uploadpfad: `src/jarvis_finance/services/budget_import_upload.py` - deterministischer SHA-256-basierter `preview_id`, archivierter Upload, separate Kandidatenerzeugung. - Transfer-Pairing: `src/jarvis_finance/services/transfer_pairing.py` - `propose_transfer_pairs()`, `confirm_transfer_pair()`, `reject_transfer_pair()` - exakter Decimal-Betrag, Gegenzeichen, gleiche Währung, Datumsfenster, unterschiedliche bekannte Eigenkonten, bidirektionale Eindeutigkeit. - `proposed` erfordert manuellen Confirm; `unmatched/ambiguous`, `rejected`, `superseded` und idempotenter atomarer Confirm sind implementiert. - Schema: `src/jarvis_finance/storage/migrations.py:1568` - `signed_amount_original`, `value_date`, `budget_transfer_pairs` und partielle Confirm-Unique-Indizes. - Manuelle Buchungen und Importkandidaten verwenden dasselbe `budget_transactions`-Ledger; manuelle bestätigte Buchungen werden bei der Import-Dublettenerkennung berücksichtigt. ## Wesentliche Lücken für Sprint 15 1. **Profilmetadaten werden nicht durchgängig genutzt** - `fingerprint_columns`, `encoding`, `currency_columns`, `account_columns` und mehrere Beschreibungs-/Datumsfelder sind weitgehend deklarativ. - VISA ignoriert insbesondere `TransactionId`, `CardId`, `ValutaDate` und die tatsächliche `Currency`. 2. **Vorzeichen/Währungen** - `_decimal_text()` verwendet immer `abs()`. - Nur Bankimporte setzen `signed_amount_original`; Kreditkarte und Migros verlieren die Richtung. - Kreditkarte und Bank werden unabhängig von Quelldaten auf CHF gesetzt. - Negative Migros-Artikel, Rabatte oder Retouren können dadurch den Bonbetrag fälschlich erhöhen. 3. **Fingerprints und Overlap** - Kandidaten-ID und `raw_fingerprint` enthalten Dateilabel und Zeilennummer; Umbenennen, neu sortierte Exporte oder überlappende Monatsdateien umgehen den Schutz. - `raw_fingerprint` hat keinen Unique-Index. - Die deklarierten Provider-IDs wie VISA `TransactionId` werden nicht als semantische Identität verwendet. - Keine allgemeine Kandidat-gegen-Kandidat-Dublettenprüfung; nur: - exakte Importzeile innerhalb desselben Labels, - Migros-`receipt_key`, - Kandidat gegen bereits bestätigte Transaktion. - Drive-Workflow speichert File-ID, hat aber weder Content-Hash noch Unique-Gate und erzeugt je Lauf neue Sessions. 4. **Upload-Hash/Preview** - Hash basiert auf dekodiertem und erneut UTF-8-kodiertem Text, nicht auf unveränderten Originalbytes. - Dry Run meldet grundsätzlich alle Zeilen als potenziell neu und preflightet vorhandene Fingerprints/Overlaps nicht. - Confirm liest das Archiv, rehasht/revalidiert aber weder Originalidentität noch Preview-/DB-Revision. 5. **Dublettenerkennung** - `_duplicate_of()` prüft exakt Datum, absoluten Betrag und Beschreibung. - `detect_duplicate_candidates()` prüft ±2 Tage, Betrag, Währung und ähnliche Merchant-Texte. - Konto, Vorzeichen und Transaktionstyp fehlen; dadurch sind False Positives zwischen Konten, Einkommen/Ausgabe oder Transferseiten möglich. - Positiv: normale Bestätigung ist blockiert; Override benötigt Begründung und wird auditiert. 6. **Account-Mapping/Transfer-Pairing** - Mapping erfolgt exakt über `budget_account_id`, `linked_account_id` oder Kontoname; sonst über einen eindeutigen Namens-Hint für AKB/Raiffeisen. - AKB-Profil deklariert kein Kontofeld; Raiffeisen-IBAN passt typischerweise nicht ohne explizites Mapping. - Kreditkartenkonto wird nicht aus `CardId` gemappt. Kreditkartenzahlungen können deshalb nicht als vollständiges Bank↔Karten-Paar reconciliert werden. - Pairing hat `quality_status=strong`, aber keinen separaten numerischen Pair-Confidence-Wert; die Kandidaten werden pauschal auf `0.95` gesetzt. - Confirm revalidiert Konten, Betrag, Zeichen und Währung, aber nicht erneut das Datumsfenster. 7. **Migros-Details** - Gut wiederverwendbar: ein Bonkandidat plus `budget_import_line_items`, Artikel bleiben Detaildaten. - Bei späterem, reichhaltigerem Overlap verhindert vorhandener `receipt_key` die komplette Verarbeitung; fehlende/neue Detailzeilen werden nicht ergänzt. - Bonidentität kann bei fehlender Transaktionsnummer auf Datum/Zeit/Filiale/Kasse kollidieren. 8. **Manuelle Transfer-Kompatibilität** - `transfer_pairing_v2` schreibt korrekt eine negative und eine positive Ledgerseite atomar. - Der ältere manuelle Pfad `budget_transactions.confirm_transfer()` schreibt dagegen beide Seiten mit positivem Betrag und ruft zwei jeweils selbst commitende Einzel-Confirms auf; er ist damit semantisch und atomar nicht kompatibel zum Pairing-v2-Vertrag. ## Relevante Tests - `tests/unit/test_budget_import_production_v2.py` - `tests/unit/test_budget_import_upload_ux_v1.py` - `tests/unit/test_budget_phase15_source_aware_imports.py` - `tests/unit/test_transfer_pairing_v2.py` - `tests/unit/test_budget_internal_transfer_hotfix.py` - `tests/unit/test_budget_monthly_import_rule_learning_v1.py` ## Dateien/Probleme - **Keine Dateien erstellt oder verändert.** - Repository-eigene `.venv` fehlte; Tests wurden isoliert über `uv run --isolated` ausgeführt. - Keine produktiven Dateien, Datenbanken, Reports oder Secrets geöffnet.