## Ergebnis Read-only-Inventur auf Basis-Commit `852aac4a8060d2dfec716fc630560a11d297abde` abgeschlossen. `household_classification_v2` existiert im Stand **noch nicht**. ### 1. Household-API und Services Alle Household-Routen liegen unter `/api/budget`: | API | Service | Zweck | |---|---|---| | `GET /household/source-mappings` | `list_source_mappings` | Maskierte Quellkonto-Zuordnungen lesen | | `POST /household/source-mappings/confirm` | `configure_source_mapping` | Quellreferenz per HMAC an Budget-/kanonisches Konto binden | | `POST /household/import/preview`, `/household/imports/preview` | `preview_household_import` | Deterministische, DB- und dateispeicherfreie Vorschau | | `POST /household/import/confirm`, `/household/imports/confirm` | `confirm_household_import` | Fingerprint-gebundener atomarer Confirm | | `GET /household/import/batches`, `/household/imports/history` | `list_household_batches` | Batchhistorie | | `GET /household/imports` | dito, umhüllt | `{items,total}` | | `GET /household/imports/options` | `get_household_import_options` | Profile, Limits, Mappings | | `GET /household/overview` | `get_household_overview` | Bestätigte Monatswerte und Reviewstatus | | `GET /household/transactions` | `list_household_transactions` | Bestätigte Haushaltsbuchungen | | `GET /household/review` | `get_household_review` | Reviewgruppen | | `POST /household/review/preview` | `preview_household_review_action` | Gebundene Auswirkungsvorschau | | `POST /household/review/confirm` | `confirm_household_review_action` | Atomare Gruppenentscheidung | Router: `src/jarvis_finance/api/routers/budget.py:171-271`. Services: `src/jarvis_finance/services/household_import.py:112-180,507-697,786-963,973-1291`. Audit-Lesewege: - `GET /api/audit?entity_type=&entity_id=` liest maximal 50 Einträge, jedoch ohne `source`-/`action`-Filter: `api/routers/positions.py:83-85`, `services/manual_entry_service.py:686-697`. - `GET /api/budget/import-status-audit`: `api/routers/budget.py:274-276`. - Kein eigener `/household/audit`-Endpunkt; Batchhistorie gibt auch keine `audit_id` zurück (`household_import.py:945-952`). - Writes erzeugen Audit-Events für Mapping, Batch, Review und Transfer: `household_import.py:149-155,837-842,1224-1232`. ### 2. Sprint-15-Vertrag, Fingerprints und Idempotenz - Versionen: `household_import_v1`, `transfer_pairing_v3`: `household_import.py:21-24`. - Fingerprints sind installationsgebundene HMAC-SHA256; fehlendes `JARVIS_FINANCE_FINGERPRINT_KEY` ergibt fail-closed 503: `household_import.py:27-35`. - Drei Ebenen: - Datei: Profil + kompletter CSV-Inhalt (`:223`) - Quellzeile: Profil + gehashte Kontoreferenz + Provider-ID/kanonische Zeile (`:262-266`) - Logische Buchung: Kontoquelle, Datum, Betrag, Währung, normalisierte Beschreibung (`:265-266`) - Input-Fingerprint bindet Dateien, Reihenfolge, Mapping-ID und maskierte Quellreferenz: `:227-234`. - Baseline hash’t alle aktuell klassifikations-/schreibrelevanten Tabellen: `:183-195`. - Preview-Fingerprint bindet Vertrag, Pairing-Version, Input, Baseline und vollständige Klassifikation: `:601-620`. - Confirm rekonstruiert Preview und Baseline, prüft nochmals unter Write-Lock/Savepoint und rollt atomar zurück: `:786-833,925-937`. - Wiederholter Confirm ist über eindeutigen `preview_fingerprint` idempotent, prüft aber zuvor erneut Input und Baseline: `:793-798`. - Review-Idempotenz läuft über deterministische Audit-Entity-ID: `:1164-1177`. Vertragstests: `tests/unit/test_household_import_v1_golden.py:65-143,185-233,236-319,494-625`. ### 3. Transfer-, Kreditkarten-, Dubletten- und Migroslogik **Transfers** - V3-Household-Pairing verlangt verschiedene gemappte Konten, Gegenzeichen, exakten Betrag/Währung, maximal drei Tage sowie eindeutige bidirektionale Zuordnung; Textbeleg oder explizites Kartenabrechnungspaar: `household_import.py:442-504`. - Sichere Paare werden im Batch als `budget_transfer_pairs` angelegt und über den bestehenden `confirm_transfer_pair` verbucht: `:872-896`. - Bestandsservice v2 erzeugt zwei neutrale Ledger-Legs plus `budget_transfers`, mit Konkurrenz-/Statusprüfung und Unique-Indizes: `transfer_pairing.py:567-600,627-770`; Schema `migrations.py:1568-1615`. - Generisches v2 darf Konto-Namen/Hints verwenden (`transfer_pairing.py:72-101`); im Household-Pfad steht in `account_source` jedoch die stabile Budgetkonto-ID. **Kreditkarten** - Explizite Klassen: pending, payment, payment counterpost, refund, expense/income: `household_import.py:250-290`. - Pending/final derselben Provider-ID: final superseded pending; nur ein finaler Datensatz wird schreibbar: `:563-576`. - Positive Stornos/Reversals werden nur bei eindeutigem früherem Betrag/Text/Konto innerhalb 90 Tagen automatisch als Refund referenziert; sonst Review: `:385-439`. - Fremdwährung ohne sichere FX-Behandlung bleibt Review: `:281-283,730-743`. **Dubletten** - Datei- und Quellzeilentreffer werden unterdrückt. - Logischer oder Legacy-Geschäftstreffer bleibt ausdrücklich Review, nicht Auto-Dupe: `:324-349,540-561`. - Legacy-Dublettenlogik nutzt Konto, Datum, signierten Betrag, Währung und Beschreibung. - Zusätzlich existiert eine ältere, schwächere Erkennung über Datum/Betrag/Währung/ähnlichen Händler mit optionalem Auto-Ignore bei hoher Confidence: `budget_imports.py:1615-1654`. **Migros** - Bonzeilen werden über Datum/Zeit/Filiale/Kasse/Transaktionsnummer gruppiert: `household_import.py:294-321`. - Bon ist reine Detail-/Coverage-Quelle, keine zweite Geldbewegung. - Link auf offenen Kandidaten oder bestätigte Buchung anhand Datum und Betrag; nur Differenz ≤ CHF 0.01 wird `linked`, sonst `review`/`unmatched`: `:352-382,579-600`. - Line Items werden in `budget_import_line_items` gespeichert; Link in `household_migros_links`: `:859-871,897-911`. - Ältere Migros-Kategorisierung und `receipt_key`-Dedup existieren in `budget_imports.py:336-419`. ### 4. Konto- und Kontorollenmodelle - Kanonisches Konto `accounts`: Plattform, Name, Typ, Währung, Performance-/Reserveflags; `storage/schema.py:1` (`CREATE TABLE accounts`). - Haushaltskonto `budget_accounts`: optionales `linked_account_id`, Typen `checking|credit_card|cash|savings|investment_cash|virtual|reserve|other`: `migrations.py:529-542`. - Household-Mapping akzeptiert ausschließlich aktive Budgetkonten, die an ein aktives kanonisches Konto gebunden sind: `household_import.py:120-147`. - Budgetkonto-Erstellung erlaubt dagegen weiterhin `linked_account_id=NULL`: `budget_accounts.py:11-63`. - Ein explizites Rollenmodell existiert nur PostFinance-spezifisch: `efinance|etrading_depot|etrading_cash`, `storage/postfinance_schema.py:15-21`; Lesen ohne Namensinferenz: `portfolio_aggregation.py:65-73`. - Es gibt **keine generischen Household-Rollen** wie Hauptkonto, Kartenabrechnungskonto, gemeinsames/persönliches Konto oder Eigentümer/Haushaltsmitglied. ### 5. Händler, Aliase, Kategorien und Lernregeln Wiederverwendbar: - `budget_merchants` mit Default-Kategorie und normalisiertem Namen: `migrations.py:1116-1127`. - `budget_merchant_aliases` mit `contains|exact|regex`, Quellscope und Priorität: `:1129-1141`. - Matching auf offene Kandidaten: `budget_imports.py:190-213,1559-1588`. - `budget_review_rules`: Händlertext, Quelltyp, Kategorie, Zielstatus, Priorität/Confidence; CRUD/Test/Apply: `budget_imports.py:1410-1494`. - Lernvorschläge aus einer manuellen Kandidaten-Kategorieänderung: `budget_monthly_import.py:192-245`; Tabelle `budget_rule_suggestions`: `migrations.py:1287-1307`. - Kategorienbaum und Tags: `migrations.py:544-598`; GET-Routen `budget.py:650-664`. Diese Engines sind derzeit **nicht** in `preview_household_import` eingebunden. Household-Kandidaten erhalten keine vorgeschlagene Kategorie; automatisch bestätigte Refunds/Reversals bleiben mit `category_id=NULL`: `household_import.py:704-725,730-783`. ### 6. Migration 47 Migration: `047_household_import_v1`, `migrations.py:11-12`. Neu: - `household_account_source_mappings` - `household_import_batches` - `household_import_files` - `household_import_items` - `household_migros_links` - Kandidatenspalten `household_batch_id`, `source_row_fingerprint`, `logical_fingerprint` - Unique-Indizes für Quellzeile, logische Identität und einmalige Migros-Geldbewegungslinks - Immutable-/No-delete-Trigger für Batches, Dateien und Items DDL: `migrations.py:2323-2428`; Einbindung/Ausführung: `:2431-2485`. Auffällig: Der gespeicherte Migrationschecksum wird nur aus dem Migrationsnamen berechnet, nicht aus dem DDL (`:2481-2483`). ### 7. Bestätigte historische Kategorien lesen Bestehende, direkte Lesewege: 1. `GET /api/budget/household/transactions`: Join `budget_transactions.category_id → budget_categories`, nur bestätigte Buchungen; `household_import.py:1023-1071`. 2. `GET /api/budget/transactions`: vollständigeres Modell einschließlich `category_id`, `category_name`, Händler, Tags, Konto und `source_candidate_id`; `budget.py:1083-1085`, `budget_transactions.py:200-261`. 3. Direkte DB-Projektion über `budget_transactions` plus `budget_categories`, optional zurück zum Ursprung über `source_candidate_id`. 4. Audit-Historie für nachträgliche Kategorieänderungen: `update_budget_transaction` schreibt alte/neue Werte in `audit_log`, `budget_transactions.py:169-197`. Für Klassifikationslernen ist Weg 2 der beste Bestandspfad. Der Household-Leseweg setzt `merchant_name` derzeit lediglich auf `description` und verliert `merchant_id`/`payee`-Semantik (`household_import.py:1057-1065`). ## Risikomatrix / konkrete Lücken für `household_classification_v2` | Risiko | Lücke | Empfehlung | |---|---|---| | **Hoch** | Keine V2-Engine/-Version/-Persistenz; Klassifikation ist fest in Importnormalisierung eingebaut | Separaten deterministischen Klassifikator mit `classification_version`, Reason Codes, Confidence und Provenienz einführen | | **Hoch** | Historisch bestätigte Kategorien, Händleraliase und Reviewregeln werden nicht genutzt | Bestätigte `budget_transactions` als read-only Evidence-Index verwenden; Alias/Regelvorrang explizit definieren | | **Hoch** | Neue Klassifikationsabhängigkeiten fehlen im Baseline-Fingerprint | Kategorien, Händler, Aliase, aktive Regeln und Lernmodellversion in Baseline/Preview binden | | **Hoch** | Household-Preview ist nicht in `READ_ONLY_POST_PATHS`; bei `write_mode=disabled` wird die eigentlich read-only Preview geblockt | `/api/budget/household/import(s)/preview` und Review-Preview sicherheitstechnisch explizit klassifizieren (`api/main.py:21-27,69-74`) | | **Mittel** | Household V3-Paar wird beim generischen Confirm-Audit als `transfer_pairing_v2` ausgewiesen | Matcher-Version aus `evidence_json` übernehmen statt Modulkonstante (`transfer_pairing.py:30,173-183`) | | **Mittel** | Kein generisches Household-Kontorollen-/Owner-Modell | Rollen/Ownership nur ergänzen, wenn Klassifikation sie tatsächlich benötigt; Quellmapping bleibt Identitätsanker | | **Mittel** | Review-Confirm verlangt anders als Import-Confirm kein `confirm=true` | Explizites Confirm-Gate vereinheitlichen (`household_import.py:1164-1168`) | | **Mittel** | Kein dedizierter Household-Audit-Readmodel-/Filterpfad | Batch-, Review- und Klassifikationsaudit mit source/action/version filterbar machen | | **Mittel** | Bestehende ältere Dublettenengine kann im Parallelpfad aggressiv auto-ignorieren | Household-Fingerprint-Dedupe als maßgeblich festlegen; ältere Detection nicht ungeprüft auf V2-Items anwenden | | **Niedrig** | Migration-47-Checksum schützt nicht den tatsächlichen DDL-Inhalt | DDL-/Schema-Snapshot-Checksum oder echte versionierte SQL-Migration verwenden | **Dateien:** keine erstellt oder verändert. **Worktree:** enthält weiterhin untracked `.venv`; von dieser Inventur nicht angelegt oder verändert. Es wurden keine produktiven Datenbanken, Rohimporte oder Secrets gelesen.