## Outcome The requested capabilities are mostly already present. The smallest vertical slice is a **derived, read-only cockpit nested into the existing finance command-center response**—not a new import engine, schema family, or performance calculator. ### Smallest edit set 1. `src/jarvis_finance/services/finance_command_center.py` - Add `build_current_import_performance_read_model(conn, *, as_of=None)`. - Call it from existing `build_finance_command_center(...)`. - Return a nested `current_import_performance` object containing: - six source rows, - dated cash/account snapshot status, - payroll setup note, - structured performance blockers. 2. `frontend/src/api/types.ts` - Add types for that nested object. 3. `frontend/src/pages/CommandCenter.vue` - Render it under the existing **Systemstatus** details section. - Keep user actions linking to existing Cash, Household Import, Planning, and Portfolio Performance screens. 4. Tests: - New backend unit test, preferably `tests/unit/test_current_import_performance_read_model.py`. - Extend `frontend/src/pages/CommandCenter.test.ts`. No router, API client method, general import abstraction, or migration is needed for the minimum slice. --- ## Exact reuse map ### 1. Dated account snapshots: already end-to-end **Schema** - `src/jarvis_finance/storage/migrations.py` - `_create_cash_account_snapshot_tables` - `cash_account_snapshots` - Called from `_apply_compat_migrations`. - Related canonical valuation history: - `_create_account_value_snapshot_tables` - `account_value_snapshots` - `src/jarvis_finance/storage/schema.py` - `cash_balances` **Backend** - `src/jarvis_finance/services/cash_service.py` - `preview_cash_snapshot` - `confirm_cash_snapshot` - `get_cash_summary` - `get_cash_position` - Snapshots already carry `balance_date`, amount, source type, note, and audit linkage. **API** - `src/jarvis_finance/api/routers/cash.py` - `POST /cash/snapshots/preview` - `POST /cash/snapshots/confirm` - `GET /cash/{position_id}` **Frontend** - `frontend/src/api/cash.ts` - `previewCashSnapshot` - `confirmCashSnapshot` - `getCashPosition` - `frontend/src/pages/CashPage.vue` - Already supplies a date field, preview/confirm flow, and dated history. **Existing regression test** - `tests/unit/test_cash_truewealth_management.py` - `test_manual_cash_snapshot_preview_confirm_audit_and_dashboard_value_for_manual_account` - `test_csv_calculated_account_uses_calculated_value_and_manual_as_control_difference` **Conclusion:** no new snapshot endpoint or table. The cockpit should only summarize the latest dated snapshot per relevant account and link to `/cash`. --- ### 2. Existing import paths for the six sources Do not create a general import engine. #### AKB, Raiffeisen, VISA, Migros **Backend** - `src/jarvis_finance/services/household_import.py` - `IMPORT_PROFILES` - `preview_household_import` - `confirm_household_import` - `list_household_batches` - `get_household_import_options` - Existing profiles: - `akb_bank` - `raiffeisen_bank` - `visa_credit_card` - `migros_receipts` **API** - `src/jarvis_finance/api/routers/budget.py` - `POST /budget/household/imports/preview` - `POST /budget/household/imports/confirm` - `GET /budget/household/imports/history` - `GET /budget/household/imports/options` **Schema lineage** - `household_import_batches` - `household_import_files.source_type` - `household_import_items.source_type` - `budget_transaction_candidates.household_batch_id` - `budget_transaction_candidates.transaction_date` - `household_migros_links` These are created by `_create_household_import_v1_tables` in `storage/migrations.py`. **Tests** - `tests/unit/test_household_import_v1_golden.py` - `test_preview_is_strictly_read_only_deterministic_and_redacted` - `test_golden_raiffeisen_2026_01_26_swisslos_chf100_is_safe_v3_transfer_and_confirm_is_noop_twice` - `test_file_source_row_and_logical_duplicates_are_distinguished` - `test_migros_links_detail_without_double_counting_and_difference_over_cent_reviews` #### TrueWealth - `src/jarvis_finance/services/truewealth_service.py` - `preview_truewealth_import` - `confirm_truewealth_import` - `get_truewealth_summary` - `src/jarvis_finance/api/routers/truewealth.py` - `/truewealth/imports/preview` - `/truewealth/imports/confirm` - `/truewealth/summary` - Read freshness from: - `truewealth_import_batches.snapshot_date` - `truewealth_import_batches.confirmed_at` - `account_value_snapshots.valuation_date` #### PostFinance - `src/jarvis_finance/services/postfinance_service.py` - `preview_postfinance_import` - `confirm_postfinance_import` - `get_postfinance_summary` - `src/jarvis_finance/api/routers/postfinance.py` - `/postfinance/imports/preview` - `/postfinance/imports/confirm` - `/postfinance/summary` - Schema creation: - `src/jarvis_finance/storage/postfinance_schema.py` - `postfinance_import_batches` - `postfinance_snapshots` - Confirmation already writes: - `cash_balances`, - `account_value_snapshots`, - two canonical performance components via `store_postfinance_component_valuations`. --- ## Source freshness and coverage read model The read model should expose, per source: ```text source_key label role last_received_at data_from data_to freshness_status coverage_status coverage_basis next_action detail_route ``` Recommended derivation: - **AKB/Raiffeisen/VISA** - Join confirmed `household_import_batches` → `budget_transaction_candidates` by `household_batch_id`. - Group by `source_type`. - `data_from/to = MIN/MAX(transaction_date)`. - `last_received_at = MAX(confirmed_at)`. - **Migros** - Same candidate lineage, additionally count `household_migros_links` by status. - Receipt detail must not be counted as a second money movement. - **TrueWealth** - Latest `truewealth_import_batches.snapshot_date/confirmed_at`. - **PostFinance** - Latest `postfinance_import_batches.snapshot_at/confirmed_at`. - **Account snapshots** - Latest `cash_account_snapshots.balance_date` by account, surfaced separately or as `balance_as_of` on the corresponding bank row. Reuse `src/jarvis_finance/quality/freshness.py::assess_freshness`: - managed-account policy for TrueWealth, - market/portfolio policy for PostFinance, - bank-balance policy for actual account snapshots, - generic explicit `max_age_days` for monthly transaction/receipt coverage. Important semantic constraint: current storage proves **observed coverage**, not guaranteed gap-free statement completeness. Therefore: - report `coverage_status = observed | missing | partial`, - do not label household coverage “complete” merely because `MIN/MAX(transaction_date)` exists. A migration would only become necessary if the product must persist an explicit, auditable statement-period completeness attestation. In that later case, add immutable coverage metadata such as `coverage_from`, `coverage_to`, and `coverage_status` to `household_import_files` or a dedicated per-file coverage table. --- ## Payroll setup note This should be a derived setup note, not a payroll subsystem. **Reuse points** - `src/jarvis_finance/services/budget_plans.py` - `INCOME_TEMPLATE_SPECS` - `preview_income_plan_templates` - `confirm_income_plan_templates` - Existing templates intentionally start without amounts. - `budget_plan_items` - Inspect active income templates for the current year where both `monthly_amount_chf` and `annual_amount_chf` are null. - `src/jarvis_finance/services/household_classification.py` - Salary recognition at the `lohn|gehalt|salary|payroll` rule. - It calls `resolve_category(conn, "Lohn", "income", ...)`. - `src/jarvis_finance/services/budget_categories.py` - Canonical categories are `Lohn Marcel` and `Lohn Melanie`, not a single unambiguous `Lohn`. This ambiguity is intentional: detected salary payments must not silently choose a person/category. The cockpit note should say that salary planning or assignment remains to be completed and link to `/planning/budget/planning`. Suggested rule: - No active salary templates: “Lohnplanung noch einrichten.” - Templates exist but have no amount: “Lohnvorlagen vorhanden; Planbeträge fehlen.” - Salary review candidates remain unresolved: “Lohneingänge erkannt; Person/Kategorie bestätigen.” - Otherwise omit the note. **Regression test to preserve** - `tests/unit/test_household_classification_v2_golden.py` - `test_income_patterns_are_explicit_and_transfer_safe_without_fallback_category` --- ## Performance blockers: reuse, do not recalculate The performance stack already exposes structured availability and blockers. **Backend** - `src/jarvis_finance/services/portfolio_performance.py` - `build_performance_coverage` - `_coverage_reason_codes` - `calculate_portfolio_performance` - `src/jarvis_finance/services/performance_scope.py` - `build_performance_setup` - Per-source `status`, `cashflow_coverage`, `next_action`, and diagnostics. - `src/jarvis_finance/services/wealth_cockpit.py` - `build_wealth_cockpit` - Already maps coverage status/reasons into cockpit quality. **API** - `src/jarvis_finance/api/routers/overview.py` - `GET /portfolio/performance/coverage` - `GET /portfolio/performance/setup` - `GET /portfolio/wealth-cockpit` **Schema/API contracts** - `src/jarvis_finance/api/schemas/portfolio_performance.py` - `src/jarvis_finance/api/schemas/wealth_cockpit.py` - `performance_cashflow_coverage` from `_create_investment_performance_scope_v1` Existing blocker reasons include: - missing opening valuation, - missing closing valuation, - insufficient distinct valuation dates, - incomplete external-cashflow coverage, - source-specific valuation/component gaps, - unclear transaction scope/reclassification, - stale or missing FX/price inputs. The command-center read model should map existing reason codes and `next_action`; it must not infer blockers from null metrics or duplicate the calculator. **Existing tests** - `tests/unit/test_wealth_cockpit_v1.py` - `test_missing_opening_value_never_creates_false_return` - `test_source_performance_status_is_partial_when_only_some_metrics_are_ready` - `test_current_value_performance_and_policy_readiness_are_independent` - `tests/unit/test_sprint20c_performance_activation_hardening.py` - `test_unpaired_transfer_is_unclear_and_blocks_activation` - `test_setup_cannot_report_ready_when_reclassification_is_unclear` - `test_truewealth_coverage_extensions_preserve_prior_complete_period_and_reject_gaps` - `frontend/src/components/performance/PortfolioPerformancePanel.test.ts` - `never represents unavailable aggregate or per-source performance as zero or as authoritative` - `keeps source controls accessible and preserves partial coverage semantics` --- ## Tests to add for the thin cockpit ### Backend: `tests/unit/test_current_import_performance_read_model.py` 1. `test_read_model_lists_all_six_sources_when_storage_is_empty` 2. `test_household_coverage_is_grouped_by_source_and_uses_business_dates` 3. `test_receipt_coverage_does_not_double_count_visa_money_movement` 4. `test_latest_account_snapshot_uses_balance_date_not_import_timestamp` 5. `test_payroll_note_distinguishes_missing_templates_missing_amounts_and_review` 6. `test_performance_blockers_are_passthrough_from_canonical_coverage` 7. `test_read_model_is_read_only_deterministic_and_redacts_file_names_and_source_references` ### Frontend: extend `frontend/src/pages/CommandCenter.test.ts` - renders six source statuses inside Systemstatus; - displays business-data date separately from receive time; - renders payroll note only when returned; - renders blocker text and links to `/portfolio/performance`; - never renders unavailable performance as `0%` or CHF `0`. --- ## Migration decision - **Dated account snapshots:** no migration. - **Freshness/observed coverage read model:** no migration. - **Payroll setup note:** no migration. - **Performance blockers:** no migration. - **Only explicit gap-free/attested household statement coverage:** migration required later. No files were created or modified. The worktree already contained unrelated/pre-existing modifications in: - `src/jarvis_finance/imports/postfinance_documents.py` - `src/jarvis_finance/services/household_import.py` - `tests/unit/test_household_import_v1_golden.py` - `tests/unit/test_postfinance_documents.py` No tests were run because the task was read-only inspection.