## Audit outcome: **not Sprint 11 acceptance-ready** ### Important findings 1. **Portfolio/equity totals are not data-honest** - `/api/overview` always returns numeric strings; the schema cannot express unknown or partial totals: `src/jarvis_finance/api/schemas/overview.py:8-18`. - Missing TrueWealth snapshots become `0`, not `null`: `src/jarvis_finance/services/portfolio_service.py:12-25`. - PostFinance equity is calculated by summing transaction `gross_amount_chf`, rather than current position valuations; transaction types are not constrained: `portfolio_service.py:28-43`. - Overall total then adds these forced values as though complete: `portfolio_service.py:46-66`. - Existing tests lock in this historical-cost behavior even when a different current market price exists: `tests/unit/test_fastapi_user_dashboard_skeleton.py:32-62`. - UI compounds this by treating absent overview values and missing row valuations as zero: - `EquityPage.vue:163-168` - `PortfolioPage.vue:44-49` - **Required seam:** make overview amounts nullable and add explicit completeness/coverage metadata, e.g. `status`, `known_value_chf`, valued/total counts, missing reason codes, and `as_of`. 2. **Zero and unknown are conflated** - A calculated market value of exactly zero is serialized as `null`: `src/jarvis_finance/services/equity_service.py:48-61`. - `_position_status()` also uses zero market value as evidence of incomplete cost basis: `equity_service.py:21-36`. - UI defaults missing TrueWealth to `"0.00"` and tests snapshot presence with `Number(value) > 0`, so a legitimate zero snapshot is reported as absent: `EquityPage.vue:26,165`. - **Required seam:** preserve zero as `"0.00"`; determine missingness from explicit valuation fields/statuses, never numeric truthiness. 3. **Equity list contract lacks the fields needed for honest UX** - `EquityPosition` exposes only one free-text `status`, one `account` string, and nullable value: `api/schemas/positions.py:82-109`; mirrored at `frontend/src/api/types.ts:15,36`. - It lacks platform/account IDs and labels, valuation status/reason codes, price/FX freshness and `as_of`, and coverage metadata. - Backend drops the available platform and uses only `account_name`: `equity_service.py:50-61`, although the source row has both platform and account at `dashboard/data.py:576-605`. - Detail route has no declared response model, weakening OpenAPI/serialization contract enforcement: `api/routers/positions.py:73-75`. - **Required seam:** introduce an explicit Sprint 11 equity collection response envelope and typed detail response. 4. **Required search/filter/sort UX is absent** - Current UI provides one unlabeled free-text `Filter` input: `EquityPage.vue:30-34`. - It searches only asset class, account, status, name, and ticker: `EquityPage.vue:168`; ISIN is omitted. - No account/type/status filters, no sort state, and table headers are static: `EquityPage.vue:43-47,161`. - Current tests cover only row separation, mobile ticker/quantity, and explicit chart loading: `EquityPageHotfix.test.ts:30-58`. - **Required seam:** add canonical search plus account, asset-type and valuation-status controls; deterministic sortable columns; result count and clear/reset state; tests for combinations and null ordering. 5. **Responsive detail behavior does not meet desktop/iPad/mobile contract** - `DetailDrawer` is a fixed bottom sheet only below `sm`; at `sm` and larger it becomes ordinary static content: `frontend/src/components/DetailDrawer.vue:1-7`. - Consequently it is: - not a desktop right-side drawer, - not an iPad sheet, - inline below all Equity, TrueWealth, and Cash sections on larger screens. - It also lacks dialog semantics, backdrop, Escape handling, focus management/restoration, and an accessible close label. - Existing responsive tests only assert mobile cards and hidden table; they do not verify drawer geometry or interaction: `MobileUx.test.ts:80-90`. - **Required seam:** replace/adapt `DetailDrawer` with three explicit breakpoint modes and add viewport/browser acceptance tests. 6. **No portfolio-completeness status line** - `LocalDataStatus` reports backend/runtime connectivity and a hard-coded provider claim, not valuation completeness: `LocalDataStatus.vue:2-14`. - The three data-quality boxes are disconnected heuristics and provide neither denominators nor partial-total semantics: `EquityPage.vue:24-28,166-167`. - `lastLoadedAt` is browser request time, not valuation `as_of`: `EquityPage.vue:184-185`. - **Required seam:** API-provided status such as “8/10 positions valued · known subtotal CHF … · 2 excluded · valuation as of …”, rendered consistently above KPIs. ### Criterion currently satisfied - **No provider calls during initial EquityPage render:** initial load and detail reads are local API/DB reads: `EquityPage.vue:178-208`. - Quote, FX, and candle provider work is user-triggered: `EquityPage.vue:79-81,197-204`. - Cached quote/chart reads are local: `market_service.py:146-159`. - Candle provider access occurs only when the explicit request uses `refresh=true`: `market_service.py:237-256`. - Backend tests explicitly cover this boundary: `tests/unit/test_market_quotes_detail_charts_v1.py:79-116` and `test_vue_dashboard_v12_acceptance.py:107-127`. ### Verification - Backend focused suite: **33 passed**. - Frontend tests could not start because this read-only worktree has no `frontend/node_modules` (`vite` and `@vitejs/plugin-vue` unresolved). - Worktree remained clean; **no files created or modified**.