## Outcome: minimal extension map ### Recommended UI surface Keep the extension on the existing **Vermögen / Portfolio** page; do not add a route or enlarge the “Heute” dashboard. | Concern | Exact host | Minimal change | |---|---|---| | Mixed-date captured-wealth total, per-source value/freshness/as-of | `frontend/src/components/wealth/WealthCockpitPanel.vue` | Extend the existing source list and total qualification using the already-loaded `WealthCockpit` response. | | Performance coverage, snapshot availability, metric blockers | `frontend/src/components/performance/PortfolioPerformancePanel.vue` | Extend its existing coverage/setup presentation; do not create a second performance workflow. | | Page composition/order | `frontend/src/pages/PortfolioPage.vue` | Preserve the current order: wealth cockpit, performance panel, ingestion/reconciliation. | | Import/diagnostic detail | `frontend/src/components/portfolio-data/DataIngestionReconciliationPanel.vue` | Leave as the technical drill-down and preview→confirm boundary. It already shows diagnostics, source status, reconciliation, and daily valuation status. | | August income/setup/reviews | Existing budget pages and links | Link to existing flows rather than duplicating forms or status logic in the portfolio cockpit. | Do **not** put detailed coverage in `frontend/src/pages/CommandCenter.vue`: its tests intentionally enforce four compact areas and assert that “Coverage” is absent from the main dashboard. --- ## Existing API contracts to reuse ### Primary read-only cockpit contract `frontend/src/api/portfolio.ts` ```text GET /api/v1/portfolio/wealth-cockpit?period=ytd|previous_year|12m|all ``` Backend: - `src/jarvis_finance/api/routers/overview.py` - `src/jarvis_finance/api/schemas/wealth_cockpit.py` Useful response fields: - `totals.captured_wealth_chf` - `totals.investments_chf` - `totals.bank_cash_chf` - `totals.complete` - `period.{preset,from,to}` - `data_cutoff` - `sources[]` - `key`, `label`, `provider_label`, `kind` - `performance_scope` - `current_value_chf` - `current_value_status` - `as_of` - `freshness_status` - `freshness_reason_code` - `expected_as_of` - `reconciliation_status` - `performance_status` - `readiness.dimensions` - `readiness.metrics[]` - `included_sources`, `missing_sources`, `as_of` - `blocker`, `action`, `reason_code` - `diagnostics[]` - `affected_sources`, `message`, `action`, `reason_code` - Embedded `performance_coverage` This one response is sufficient for: - Per-source freshness. - Per-source snapshot/as-of date. - Mixed-date-total qualification. - High-level performance readiness and blockers. - Coverage counts/statuses. No additional provider-backed GET is needed. ### Performance contracts ```text GET /api/v1/portfolio/performance/coverage GET /api/v1/portfolio/performance/setup GET /api/v1/portfolio/performance/daily-job GET /api/v1/portfolio/performance ``` Frontend types/functions are in `frontend/src/api/portfolio.ts`; backend schemas are: - `src/jarvis_finance/api/schemas/portfolio_performance.py` - `src/jarvis_finance/api/schemas/performance_activation.py` Key contracts: - `PerformanceCoverageResponse.rows[]` - `scope`, `label` - `reliable_from` - `valuation_from`, `valuation_to`, `valuation_dates` - `position_dates` - `ttwror_status`, `xirr_status`, `attribution_status` - `scope_classification_status`, `cashflow_coverage_status` - `reason_codes` - `PerformanceSetupResponse.sources[]` - `status` - `earliest_possible_start` - `opening_value`, `closing_value` - `cashflow_coverage` - `next_action` - `available_metrics` - diagnostic valuation/position counts - `DailyValuationJobStatus.sources[]` - `source_enabled`, `operational_status` - `last_confirmed_date`, `last_successful_at`, `last_run_at` - `reason_codes`, `valued_assets`, `missing_assets` - `next_action` Use GET endpoints only to display status. All activations, backfills, reclassifications, imports, and cashflow changes must retain the existing preview→confirm write flow. --- ## Mixed-date total behavior The captured total can combine source values with different `sources[].as_of` dates. The smallest safe frontend treatment is: 1. Continue showing `totals.captured_wealth_chf`. 2. Derive distinct non-null `sources[].as_of` values. 3. If more than one date exists, label the total explicitly, for example: - “Erfasster Gesamtwert aus unterschiedlichen Stichtagen.” 4. Show each source’s own `as_of` and `freshness_status`. 5. Do not invent a single total date from `data_cutoff`. 6. Do not substitute missing values with zero. 7. Continue displaying `totals.complete === false` as partial/incomplete. No backend field is strictly required for a minimal version, although a future explicit `totals.as_of_mode` would be safer than permanent frontend inference. --- ## August household links Existing routes/pages: | Workflow | Route | Page/API | |---|---|---| | Actual income | `/planning/budget/income/actual` | `BudgetIncomePage.vue`; `getBudgetIncomeStatus()` and household transaction query | | Budget setup | `/planning/budget/setup` | `BudgetSetupPage.vue`; seed-candidate/setup APIs in `api/budget.ts` | | Review backlog | `/household/review` | `HouseholdReviewPage.vue`; household review APIs | | Legacy budget review route | `/planning/budget/expenses/review` | Redirects to `/household/review`, preserving query parameters | For August, pass `month=2026-08` where supported. `CommandCenter.vue` already derives the current month through `currentLocalMonth()`; do not hard-code August into general-purpose components. --- ## Masking/privacy boundary Relevant display primitives: - `frontend/src/components/MoneyValue.vue` - `frontend/src/components/ui/MoneyValue.vue` Use those components for every new monetary value rather than adding inline `CHF ${value}` formatting. Important finding: no literal global amount-visibility state such as `amountsHidden`, `showAmounts`, or `privacyMode` was found in this checkout. Current portfolio components do contain some direct monetary formatting, especially `DataIngestionReconciliationPanel.vue`. Therefore: - Do not introduce a new raw formatter in the cockpit. - Reuse `MoneyValue`. - Preserve any masked labels/account hints returned by household APIs. - If amount masking is being added on another branch, connect the extension to that shared primitive rather than maintaining a separate toggle. - Never reveal raw counterparty identity or account identifiers from technical response fields. --- ## Responsive behavior to preserve Existing layout conventions use mobile-first grids: - Base: single column or `grid-cols-2` for compact metrics. - `sm:`: wider metric/form layouts. - `md:grid-cols-2`: source and daily-job cards. - `lg:grid-cols-2` and larger reconciliation layouts. - `xl:grid-cols-3`: source cards. - Frequent `min-w-0`, `flex-wrap`, `break-all`, and truncated filenames prevent horizontal overflow. - Interactive targets generally retain approximately 44px minimum height. For the cockpit: - Desktop: source status cards may use 2–3 columns. - iPad portrait: maximum two columns; long reason text must wrap. - Narrow mobile: one source per row; no fixed-width table. - Do not turn coverage into a horizontally scrolling desktop table. - Keep technical reason codes inside collapsed `
`; show localized blocker/action text prominently. --- ## Tests to extend ### Frontend - `frontend/src/components/wealth/WealthCockpitPanel.test.ts` - Mixed source dates qualify the total. - Each source retains its own date/freshness. - Partial and missing totals do not become zero. - Monetary values use the shared display primitive. - `frontend/src/components/performance/PortfolioPerformancePanel.test.ts` - PostFinance/TrueWealth/Crypto source ordering. - Valuation and position snapshot counts. - `reliable_from` and unavailable states. - Localized blocker and next-action text. - No provider calls or writes during mount/refresh. - Technical reason codes remain collapsed. - `frontend/src/pages/PortfolioPage.test.ts` - Existing panels remain composed once and in the established order. - No new route or duplicate API-driven panel. - `frontend/src/components/portfolio-data/DataIngestionReconciliationPanel.test.ts` - Preserve current checks for no fabricated zero. - Preserve explicit preview→confirm behavior. - Preserve daily-job wording that normal dashboard GETs do not call market providers. - `frontend/src/pages/MobileUx.test.ts` - Add cockpit selectors to overflow/responsive assertions. - Cover desktop and iPad portrait/landscape widths, not only phone layout. - Existing adjacent suites: - `BudgetIncomePage.test.ts` - `BudgetSetupPage.test.ts` - `BudgetReviewBacklogPage.test.ts` - `HouseholdUx.test.ts` - `CommandCenter.test.ts` The command-center test must continue passing its “four compact areas” and no-main-dashboard-“Coverage” expectations. ### Backend contract/regression tests - `tests/unit/test_wealth_cockpit_v1.py` - `tests/unit/test_sprint14_performance_contract.py` - `tests/unit/test_sprint20c_performance_activation_hardening.py` - `tests/unit/test_budget_phase111_status_income_formatting.py` - `tests/unit/test_review_backlog_cleanup_v1.py` - `tests/unit/test_finance_command_center_household_v1.py` Add backend fields only if frontend inference is insufficient; otherwise the current contracts already cover the minimal extension. --- ## Files and repository state - **Files created or modified by me:** none. - The worktree was already non-clean: - `src/jarvis_finance/services/household_import.py` - `tests/unit/test_household_import_v1_golden.py` - `git diff --check` completed without errors. - No tests were run because this was a read-only mapping task.