## Findings ### 1. **High — Blocking: every confirm path loses the raw description and crashes** **Files/lines:** - `src/jarvis_finance/services/household_import.py:1109` - Failure surfaces at `src/jarvis_finance/services/household_import.py:1417` `safe_rows` now removes `"description"`, but those sanitized rows are returned as the internal `rows` collection and later reused by `confirm_household_import()`. `_insert_candidate()` still requires `row["description"]`, so confirmation raises `KeyError: 'description'`. This breaks ordinary imports, category overrides, transfers, review imports, and Migros receipt imports. Full verification produced **21 failures / 780 passes**, all affected confirmation cases failing at this boundary. **Concrete fix:** Keep raw normalized rows available exclusively to the internal reconstruction/confirm path, while using a separately sanitized projection for preview fingerprints and public responses. Do not make the write path consume the privacy-redacted rows. Add a regression asserting preview omits raw descriptions while confirm still persists the intended private description. --- ### 2. **High — Blocking: new recurring-pattern contract has no implementation** **Files/lines:** - Expected behavior: `tests/unit/test_household_classification_v2_golden.py:927-953` - Missing classification branch around `src/jarvis_finance/services/household_classification.py:568-624` The new test expects an active recurring payment to classify using exact merchant, amount tolerance, and cadence, with origin `active_recurring_pattern`. No recurring-pattern lookup exists in `classify_household_row()`, so the row remains unresolved and the new test fails. **Concrete fix:** Add an active recurring-pattern rule before broad family/source/similarity fallback. It should fail closed and require: - exact normalized counterparty, - matching currency and expense semantics, - amount within the configured tolerance, - cadence/day inside the configured date tolerance, - active recurring record, - active category of the correct type, - no automatic proposal if multiple patterns match. --- ### 3. **Medium — Multi-file UI advertises and permits a request the backend rejects** **Files/lines:** - UI copy/validation: `frontend/src/pages/HouseholdImportsPage.vue:16,142-150,192-213` - Backend aggregate limit: `src/jarvis_finance/services/household_import.py:327-342` The UI says the limit is 5 MB **per selection** and validates each file independently. The backend limits the **entire request** to 5 MB. Consequently, multiple individually valid files can enable “Gemeinsame Vorschau erstellen” but predictably receive HTTP 413. **Concrete fix:** Use `max_request_bytes` to compute the combined UTF-8 byte size of all selected CSVs, disable preview when the aggregate exceeds it, and describe the limit as a total request budget. Keep the individual file limit as a separate check if both limits are intentional. --- ### 4. **Medium — Attention pagination can remain on an empty stale page** **File/lines:** `frontend/src/pages/HouseholdImportsPage.vue:126-155,240-252,285-321` `attentionPage` resets when file/mapping inputs change, but not when a bound preview refresh changes `attentionItems`. On a later page, categorizing rows can shrink the result below the current page offset, leaving an empty decision area and page number beyond the new page count. **Concrete fix:** Clamp/reset `attentionPage` whenever `attentionPageCount` decreases or whenever `refreshBoundPreview()` accepts a new result. Add a test with more than 50 rows that resolves enough rows to collapse the current page. ## Verification - `git diff --check`: **passed** - Focused frontend test: **14/14 passed** - Focused classification suite: **7 failed, 48 passed** - Full `make PYTHON=.venv/bin/python verify`: **21 failed, 780 passed** - No private/runtime transaction files were read. - No files were created or modified. **Verdict:** Blocking findings remain; the Sprint 16.2 diff is not ready to merge.