# Sprint 3 – Reconciliation & Snapshot Foundation

**Branch:** `sprint3/reconciliation-snapshot-foundation`

**Base:** `92c2345f6cc006bb731bbf6f5c0c24a76505ae56`
**Scope:** additive, local, read-only data-quality slice; no performance calculation.

## Delivered contract

### Read-only reconciliation

`GET /api/portfolio/reconciliation-snapshot` exposes local, non-mutating quality information:

- **Account reconciliation:** compares only an existing `csv_anchor_balance` with an existing `manual_balance`/`reconciliation` snapshot for the *same account*, *same currency* and *same `as_of` date*.
- **States:** `reconciled`, `difference`, `partial`, `not_comparable`, `stale`, `unavailable`.
- **No invented zero:** absent ledger or comparison values remain `null`; no correction, import, confirmation or booking is performed.
- **Difference convention:** `ledger − comparison`, represented as an unrounded Decimal string so sub-unit and 3-decimal differences remain visible.
- **Freshness:** derives solely from stored source `as_of`/balance dates (`fresh`, `stale`, `partial`, `unavailable`, `unknown`). Mixed available/unavailable sources are `partial` rather than hidden by `stale`.
- **Import receipt:** `last_successful_import` uses stored transaction `created_at` for CSV-origin records when present; `trade_date` remains source `as_of`.

### Snapshot/FX metadata

Existing local `account_value_snapshots` and `fx_rates` are surfaced as metadata only:

- preserved `as_of` and `received_at` fields;
- source-labelled valuation/FX metadata with availability/freshness, issue codes and estimate flag;
- no provider request, recalculation, valuation or persistence is triggered.

### API safety / redaction

The endpoint is GET-only. It emits a deterministic opaque account identifier; account labels are redacted if account-, wallet- or path/file-like content is detected; snapshot/provider sources use an allowlist and otherwise become `local`. It does not expose raw account IDs, full IBANs, wallet addresses, paths or source filenames.

### UI

`PortfolioPage` requests the new view separately from its established portfolio data. A reconciliation failure leaves loaded portfolio sections visible and renders a partial-data state. Reconciliation outcome (e.g. `Differenz vorhanden`) and data freshness are separate visible/ARIA-labelled statuses. Existing `DataQualityBadge` semantics (`ok`, `warning`, `critical`) remain compatible while adding freshness states.

## Explicit exclusions

- no performance or return calculation;
- no new market/FX/import/account source;
- no schema migration, correction booking, write endpoint, provider call or trading/order action;
- no change to Sprint-0 write guards, Sprint-1 transfer pairing or Sprint-2 navigation shell.

## Verification

### Passed

- `PYTHONPATH=src python3 -m pytest -q tests/unit/test_reconciliation_snapshot_foundation.py` → **10 passed** after review fixes.
- `PYTHONPATH=src python3 -m pytest -q` → **604 passed**.
- `npm ci --no-audit --no-fund` → **306 lockfile-pinned packages installed**; `package.json` and `package-lock.json` SHA-256 values and Git diff stayed unchanged.
- Focused frontend: `DataQualityBadge`, `PortfolioPage`, `ReadOnlySurfaces` → **14 passed**.
- Full frontend Vitest suite → **40 files / 134 tests passed**. Existing Vue router-injection warnings occurred in legacy test mounts; they did not fail the suite.
- `npm run typecheck` → passed.
- `npm run build` → passed. Vite reported the known non-blocking >500 kB chunk warning.
- `PYTHONPATH=src python3 -m compileall -q src tests` → passed.
- Changed-file Ruff check → passed.
- `git diff --check` → passed.

### Responsive UAT (synthetic local API data; browser/CDP)

The Sprint-3 Vite worktree server was inspected at three responsive viewports with synthetic local read-only responses (no production data):

- Desktop/laptop **1440×900**: no horizontal overflow; reconciliation result, freshness, partial quality state, disabled Research, Budget review and Transfers were all present.
- iPad landscape **1024×768**: same assertions passed; no horizontal overflow.
- Smartphone **390×844**: same assertions passed; no horizontal overflow. Visual inspection confirmed the amber **"Differenz vorhanden"** result badge is visibly distinct from green **"Aktuell"** freshness and the stale FX badge remains readable.

## Frontend lint

No frontend linter script is configured in `frontend/package.json`; no substitute linter was introduced.

## Review evidence

- One constrained Spark worker: `gpt-5.3-codex-spark`, profile `code-worker`, model resolved, 20 tool calls; only `DataQualityBadge.vue` plus its test. Exact separate reasoning allocation is not technically verifiable.
- One narrow Sol review: session `20260723_144013_b7103e`, actual `gpt-5.6-sol`, 8 direct tool calls (9 including nested read); configured high reasoning target is not independently verifiable. Terra addressed its currency, Decimal, redaction, freshness, import-timestamp and distinct UI-status findings and reran the affected backend checks.

## Follow-up

No Sprint 4 work is started. The known Vite chunk-size warning remains intentionally deferred to a dedicated build/performance scope. This sprint requires no migration or runtime-data change.
