## Review outcome: **BLOCKERS FOUND** ### Critical / blockers 1. **Crypto history is never consumed from the actual valuation engine** - **Consumer:** `src/jarvis_finance/services/modelled_wealth.py:313-330` - **Producer:** `src/jarvis_finance/services/daily_valuations.py:18`, `:256-278` - `_crypto_events()` filters for `daily_crypto_current_valuation_v1`, while the production writer stores `daily_crypto_valuation_v1`. - The new test masks the defect by inserting the nonexistent consumer string at `tests/unit/test_modelled_wealth.py:119-120`. - **Runtime counterexample:** a valid `daily_crypto_valuation_v1` snapshot produced `status="unavailable"` and `current=None`. - **Fix:** import/reuse the producer’s `SOURCE_KEY` constant or filter exactly on `daily_crypto_valuation_v1`; update the test to insert the real producer source. 2. **Mixed-date partial totals are falsely exposed as a “confirmed” anchor** - `src/jarvis_finance/services/modelled_wealth.py:439-453` chooses the latest confirmed day from any one component. - `src/jarvis_finance/services/modelled_wealth.py:575-593` then takes the entire household point on that day and unconditionally rewrites its quality to `confirmed`. - This can combine one same-day confirmed component with carried PostFinance/cash, modelled crypto, or unavailable components and label the aggregate “Bestätigter Ausgangswert.” - **Runtime counterexample:** a 2026-08-26 True Wealth anchor plus PostFinance carried from 2026-08-25 and cash carried from 2026-08-24 returned: - aggregate anchor: `1600.00 / confirmed` - actual point quality: `carried` - crypto: `unavailable` - It also makes period changes misleading because `1m`, `3m`, and `1y` changes are calculated from the latest component anchor rather than a truthful period-opening point. - **Fix:** never promote point quality. Either: - require a coherent aggregate anchor where all expected components are confirmed on that date, or - name it explicitly as a mixed/modelled baseline and preserve `point["quality"]`. Period presets should use the first eligible point at/after the requested start; `since_anchor` may use a source anchor but must disclose mixed component dates. ### High 3. **Missing investment components are silently omitted without making the total incomplete** - `src/jarvis_finance/services/modelled_wealth.py:481-487` adds quality only for components with values. - `src/jarvis_finance/services/modelled_wealth.py:498-547` marks a point incomplete only for unknown bank accounts. - Thus a classified True Wealth, PostFinance, or crypto account with no valuation can disappear from the sum while the aggregate remains `confirmed`, `carried`, or `modelled`, with `excluded_account_count=0`. - **Fix:** derive expected components/accounts from active classifications. Any expected component without evidence on that day must make the point `incomplete` and contribute to an explicit exclusion/unknown structure. 4. **“Official” anchors accept unrelated and low-quality account snapshots** - `src/jarvis_finance/services/modelled_wealth.py:228-253` - `_official_rows()` does not filter `source_type` or `quality_status`, yet callers label every selected row `confirmed` at `:274-279` and `:302-307`. - A manual/stale/invalid legacy row can override a PostFinance official import. The underlying table stores monetary values as unconstrained text (`src/jarvis_finance/storage/migrations.py:1552-1563`), so malformed legacy data can also raise during `Decimal(...)`. - Existing True Wealth logic correctly allowlists official/manual sources and `ok|complete` quality at `src/jarvis_finance/services/truewealth_valuation.py:45-66`. - **Fix:** pass an explicit source allowlist per component: - PostFinance: `postfinance_official_import` - True Wealth: documented `truewealth_official_import` and/or `manual_total_value` Also require `quality_status IN ('ok','complete')`, validate nonnegative Decimal values, and fail the component closed rather than the endpoint. 5. **Cash can double-count the same movement across canonical and budget ledgers** - `src/jarvis_finance/services/modelled_wealth.py:38-74`, `:102-109`, `:139-144` - `src/jarvis_finance/services/cash_service.py:162-185` - Both independently sum canonical `transactions` and linked `budget_transactions` with no durable lineage deduplication or source-precedence rule. `modelled_wealth.py` is broader still: it sums every confirmed canonical transaction type, unlike `cash_service.py`, which at least limits canonical rows to CSV sources. - A booking represented in both ledgers is counted twice after an anchor. - **Fix:** define one authoritative movement source per canonical cash account, or deduplicate via durable confirmed lineage. Do not add both ledgers blindly. Share one cash-evidence implementation between the cockpit and cash service so their balances cannot diverge. 6. **Same-day model-to-official corrections are omitted or compared against the wrong model** - `src/jarvis_finance/services/modelled_wealth.py:340-360` - The marker lookup requires `model_day < official_day`. If a daily model was stored and an official report later arrived for the same valuation date, no marker is emitted. - **Runtime counterexample:** same-day model `520` followed by official `525` returned `correction_markers=[]`. - **Fix:** select the latest model logically preceding the official snapshot using valuation date plus capture/lineage ordering. Prefer the same-day model when it was captured before the official import; otherwise use the latest earlier day. Preserve capture timestamps in `_latest_model_rows()`/`_official_rows()` to support this. ### Medium 7. **Cash movements change the chart data but do not make the chart visible** - `src/jarvis_finance/services/modelled_wealth.py:460-474`, `:614` - `event_dates` includes investment valuations and cash snapshot dates, but not budget/canonical movement dates. - **Runtime counterexample:** cash moved from CHF 100 to CHF 110 across three generated points, but `chart_visible` remained `False`. - **Fix:** include confirmed account-specific cash movement dates in `event_dates`, or compute visibility from distinct generated points with real evidence/value changes. 8. **Unknown-account output leaks raw private account names** - Raw names enter at `src/jarvis_finance/services/modelled_wealth.py:378-390` and are returned at `:560-572`. - Other cockpit code already uses `safe_account_label`; this new path bypasses it. Real account names may contain owner names or account-number suffixes. - The unkeyed hash at `:568-569` is also stable/linkable when account IDs are guessable. - **Fix:** apply the existing safe-label projection and expose an opaque response-local key or keyed pseudonym. Never return canonical account IDs or raw names. 9. **Cash provenance dates claim the request day rather than the latest actual movement day** - `src/jarvis_finance/services/modelled_wealth.py:117-119`, `:153` - Any post-anchor movement sets `source_date=as_of`, even when the last movement occurred days earlier. - **Fix:** query and return `MAX(transaction_date/trade_date)` across the selected authoritative movement source; keep quality `carried` after that actual date. ## Positive observations - PostFinance’s official total is not directly added to same-day depot plus settlement cash: the official event overwrites the model event at `modelled_wealth.py:274-279`. - Forward carry uses only events at or before the requested day (`modelled_wealth.py:363-375`); no current quote is retroactively applied by this read model. - The model builder itself is read-only and deterministic in the exercised tests. - Missing PostFinance reports do not inherently gate the model chart when complete stored depot and settlement-cash daily rows exist. ## Verification - Targeted suite: **31 passed** - `git diff --check`: **passed** - Added adversarial in-memory probes confirmed the crypto source mismatch, false mixed-date confirmation, missing same-day correction marker, and cash-chart visibility defect. - **Files modified/created:** none. - Initial `.venv/bin/python` command was unavailable; system Python/pytest succeeded.