# Sprint 12 – TrueWealth verified snapshot architecture

This is the implementation contract to apply after a canonical TrueWealth source file passes the source gate in `sprint12-truewealth-source-gate.md`. It deliberately does not define an unverified file layout.

## Boundaries

FinanceManager records, reconciles and displays TrueWealth source snapshots. TrueWealth continues to manage and rebalance the portfolio. The integration does not create orders, recommendations, reconstructed transactions, cost basis, performance metrics, policy signals, benchmark signals or ETF look-through.

PostFinance positions, quantities, prices, mappings and Sprint-11 provenance are separate and immutable from this workflow.

## Implemented schema migration

Migration 44 introduces five source-specific tables rather than overloading transaction-ledger or PostFinance tables.

### `truewealth_portfolios`

- stable internal portfolio ID;
- source portfolio reference stored in a redacted or hashed form where necessary;
- user-safe portfolio label;
- portfolio kind only when evidenced (`free_assets`, `pillar_3a`, `child`, otherwise null);
- source/base currency when evidenced;
- active flag and timestamps.

No implicit merge is allowed. Ambiguous account/portfolio identity blocks Confirm.

### `truewealth_import_batches`

Immutable Confirm history:

- batch ID;
- source file type;
- full SHA-256 file digest;
- sanitized filename label or filename digest;
- parser ID and parser version tied to the verified real export format;
- provenance (`truewealth_customer_export` or other explicitly verified source class);
- imported/confirmed timestamp and audit ID;
- source snapshot date and portfolio count;
- status restricted to confirmed batches.

The full source document remains outside the repository in an owner-only runtime directory. Git contains only synthetic/anonymized fixtures preserving the verified format.

A unique constraint on source SHA-256 makes re-importing the identical file idempotent. The filename is never identity: the same filename with different content creates a new preview and is decided by digest plus source semantics.

### `truewealth_snapshots`

Immutable snapshot history:

- snapshot ID, portfolio ID and import batch ID;
- authoritative source snapshot date;
- source total and source-total currency, nullable when absent;
- summed evidenced position values and their common currency, nullable when not safely summable;
- explicitly reported cash/other values only when present;
- reconciliation difference, tolerance/version and status;
- position count, completeness status, reason codes and creation/import timestamp.

Official snapshot history is immutable. The current account total is selected from active official and manual valuations using the precedence rules below; the latest official snapshot independently drives position details.

### `truewealth_snapshot_positions`

Immutable source positions:

- snapshot position ID and parent snapshot ID;
- source row reference safe for audit;
- instrument name, ISIN, ticker, asset type;
- quantity;
- price/position currency;
- source price and source position value;
- value currency;
- source evidence flags and nullable unknown fields.

No instrument or field may be estimated. ETF constituents are stored only if the official source contains actual look-through data and clearly links it to the portfolio snapshot.

### `truewealth_snapshot_cash`

Immutable provider-reported currency cash rows store the source currency amount, nullable evidenced source FX rate and CHF value. They are components of the same portfolio snapshot and are never added again as independent bank accounts.

Update/delete triggers enforce immutability for confirmed batches, snapshots, positions and cash rows.

## Import state machine

1. **Select/upload**
   - accept only the source type whose real format has been verified;
   - enforce a bounded file size;
   - compute SHA-256 from original bytes;
   - keep Preview side-effect free; archive only during Confirm outside Git with mode `0600` in an owner-only directory;
   - never log raw rows, account IDs, holdings or amounts.
2. **Parse and validate**
   - parser ID/version is explicit;
   - Swiss dates, decimal separators, thousands separators and currencies are deterministic;
   - missing values remain null;
   - portfolio boundaries and snapshot dates must be unambiguous;
   - contradictions produce a blocked Preview, not partial confirmation.
3. **Preview**
   - side-effect-free regarding portfolio/batch/snapshot/position/audit tables;
   - show sanitized source type, file digest prefix, portfolio identity, snapshot date, positions, quantities, currencies, totals, completeness and reconciliation;
   - duplicate digest is reported as already imported.
4. **Confirm**
   - require a matching Preview identity bound to the current database revision and explicit confirmation;
   - decode the submitted original bytes again and verify the full digest;
   - re-parse with the same parser version;
   - execute portfolio mapping, batch, snapshot, positions, account valuation and audit in one SQLite transaction;
   - any failure rolls back the whole transaction.
5. **Audit and idempotency**
   - one immutable audit event per confirmed batch;
   - repeated identical file returns the existing batch/snapshot without new rows;
   - same filename with changed bytes is a distinct candidate determined by SHA-256 and must pass validation again.

## Reconciliation and double-count protection

- source total and sum of position values are distinct fields;
- the portfolio read model uses the confirmed source total when present, otherwise the safely summable evidenced positions total;
- it never adds source total and positions total together;
- reconciliation difference is `source_total - positions_sum` only when currencies and values are comparable;
- roundings, cash and other components are displayed explicitly rather than hidden;
- a legacy manual TrueWealth total is excluded once a verified imported snapshot is current for the same portfolio, but remains in immutable history;
- ambiguous mapping between legacy placeholder and imported portfolio blocks Confirm instead of silently merging or counting both.

## Read-only API contract

Implemented routes under `/api/truewealth`:

- `GET /summary` – current value, official position details, cash rows, reconciliation and history;
- `POST /imports/preview` – read-only Preview boundary for an uploaded verified format;
- `POST /imports/confirm` – atomic official-source write boundary;
- `POST /manual-values/preview` and `/confirm` – provisional total-value workflow;
- `POST /manual-values/{id}/status` – confirmed reversible activation/deactivation.

All GET routes are database-only and must not call market providers, TrueWealth or Google Drive.

## UI contract

The existing `/portfolio/truewealth` route should be converted from a manual-total-only page after verified import support exists. It should reuse responsive cards/table patterns and show:

- confirmed source total;
- actual snapshot date and last successful import;
- separate portfolio identities;
- actual position count and completeness;
- source-total versus position-sum reconciliation;
- immutable snapshot history;
- positions with only evidenced fields;
- explicit unknown/unreconciled warnings;
- anonymizable monetary values.

No horizontal overflow is allowed at laptop, iPad or mobile widths. Normal rendering remains provider-free.

## Required verification after the source gate opens

Use a synthetic/anonymized fixture preserving the exact verified export structure. Cover Swiss locale parsing, multiple portfolios, immutable history, digest idempotency, changed-content detection, atomic rollback, nullable unknowns, no estimated rows/look-through, double-count prevention, legacy-placeholder exclusion, PostFinance sentinels, side-effect-free GETs, provider-free rendering and responsive layouts. Then run the full backend/frontend/typecheck/build/migration/verify/Ruff/compileall/Safety/diff gates and final diff review before PR and production work.

## Confirmed implementation record

Migration 44 implements the confirmed `Freie Anlagen` mapping without modifying either legacy placeholder account. It adds the source-specific portfolio, batch, snapshot, position and per-currency cash tables, plus source-link and activation metadata on account value snapshots. Confirmed source batches, official snapshots, positions and cash rows are update/delete protected. Full source bytes are archived outside Git under a SHA-256 filename with directory mode `0700` and file mode `0600`.

The verified parser is `truewealth_tax_statement` version 1 and accepts only bounded, non-encrypted PDFs with the expected TrueWealth tax-statement text layer. It parses Swiss dates and number formatting, preserves the whole-CHF source total independently from component totals, and blocks differences above CHF 1.00. Same-date, different-digest official sources are conflicts and cannot be confirmed silently.

### Manual provisional total values

`POST /api/truewealth/manual-values/preview` and `/confirm` create append-only `truewealth_manual_provisional` valuations on the same confirmed account. The valuation amount, timestamp and note are immutable; a separate confirmed status action may deactivate or reactivate the row without deletion.

Current-value precedence is deterministic:

1. newest valuation date wins;
2. on the same date, an official import wins over a manual provisional value;
3. a newer manual provisional value wins over an older official import;
4. inactive manual values are excluded from all current-value and performance selection paths.

The latest official positions always remain independently labelled with their own import date. If a newer manual total is current, allocation/performance usability is false and the UI states that no difference was distributed to positions or invented as cash. The prior manual value remains stored and visible but is older than the confirmed official valuation.
