## Outcome Audited the Sprint-6 worktree at commit `480d7a1` read-only. Existing data is fragmented across canonical Sprint-5 tables, legacy snapshots, derived positions, crypto ledgers, and broker-review staging. ### Source matrix | Source | Exact implementation / tables | Supported content | Lineage / idempotency | Safest Sprint-6 projection | |---|---|---|---|---| | Generic transaction CSV | `imports/transactions_importer.py`; `transactions`, `import_sessions` | Buy, partial/full sell, initial position/cash snapshots; schema also accepts arbitrary transaction types | Strong row-level dedupe through unique `external_transaction_id` or normalized `row_hash`; transaction `source_type='csv'`, `source_id=filename`. Import batch has file SHA but no uniqueness or transaction→session FK | Keep `transactions` as target. Populate Sprint-5 `activity_kind`, dates, `source_reference`; preserve row hash. Map only recognized activities and flag snapshots/unknown types | | Broker DOCX/XLSX review | `imports/broker_parsers.py`, `broker_mapping.py`, `execution.py`; `broker_import_dry_runs`, `broker_import_review_items`, `broker_import_execution_plans` | PostFinance and True Wealth position/cash candidates; Raiffeisen cash plus aggregate total candidates. Productive execution supports **only** reviewed `initial_position_snapshot` | Execution is strongly deduped by reviewed item, source-row hash/target/type, transaction row hash and execution-plan ID. Dry-run file “hash” is only a hash of the filename string; repeated dry runs/review items are not deduped | Reuse reviewed execution lineage. Do not add parsers/providers. Numeric extraction is absent, so only already-ready execution plans can safely project to `transactions`; aggregate/total rows should become versioned account valuations only after explicit numeric confirmation | | Manual equity ledger | `equity/manage.py`, `services/manual_entry_service.py`; `transactions` | Buy, partial/full sell, dividend/distribution, fee, manual adjustment, initial snapshot; separate Vue sale writes type `sell` | Preview/confirm/audit exists, but UUID/time-based IDs mean request retry is not idempotent; generally no row hash or external reference | Normalize recognized rows into Sprint-5 activity fields in place; generate deterministic source fingerprint before any new projection | | Derived equity positions | `ledger/positions.py`; `positions_snapshot` | Quantity, WAC cost basis, market price/value, P&L and quality | `INSERT OR REPLACE` on deterministic `(date, account, instrument)` ID; mutable and lacks source lineage | Treat as derived input only. Project each account/instrument/date into immutable `portfolio_valuation_snapshots(scope_kind='instrument')`, freezing price/FX and quality reasons | | Manual managed-portfolio totals | `services/manual_entry_service.py:1081-1104`, `portfolio_service.py`; `account_value_snapshots` | CHF account total, primarily True Wealth / `robo_portfolio` / `managed_portfolio` | Append-only UUID in practice, but no uniqueness/idempotency; audit is linked only indirectly through payload | Highest-safety source for `portfolio_valuation_snapshots(scope_kind='account')`. Deterministic source fingerprint should include source table ID, account, date, value, currency, and creation timestamp | | Cash | `imports/cash_balances_importer.py`, `ledger/cash.py`, `services/cash_service.py`; `transactions`, `cash_balances`, `cash_account_snapshots` | Initial snapshots, deposits, withdrawals, corrections, CSV anchors, manual/reconciliation balances | CSV initial snapshot uses deterministic account/date/currency ID but stores no row hash; changed amounts can be silently treated as existing. Manual snapshots use UUIDs and allow duplicates | Activities go to `transactions`; confirmed/anchor balance snapshots can project to immutable account valuations. Never sum `cash_balances` rows as snapshots without first distinguishing movement vs balance semantics | | Crypto | `imports/crypto_holdings_importer.py`, `crypto_transactions_importer.py`, `crypto/holdings.py`, `crypto/transactions.py`; `crypto_holdings`, `crypto_transactions`, linked `transactions`, `crypto_prices`, `crypto_price_points` | Initial holdings; buy, sell, transfer, fee; current wallet/asset quantities | Holdings overwrite unique wallet/asset state and lose history. Transaction importer dedupes only non-empty `tx_hash`; its row hash is only intra-file, and generated IDs/timestamps are time-based. Buy/sell create linked fiat ledger rows | Do not auto-project holdings until wallets have explicit canonical account mapping. Existing linked fiat `transactions` may be normalized. Crypto valuation needs immutable wallet/account bridge plus frozen crypto price and FX | | Equity prices | `market_data/prices.py`; `market_prices`, `equity_price_points` | Latest/daily prices and timestamped points | `market_prices` upserts and overwrites `(instrument,date,provider)`; `equity_price_points` has timestamp/provider uniqueness | Read existing local prices only; freeze selected price ID/provider/timestamp into valuation lineage. No new provider engine | | FX | `fx/rates.py`, `fx/overrides.py`; `fx_rates` | CHF conversion rates, unavailable states, manual overrides | Upsert overwrites pair/date/provider/type; manual overrides are audited, ordinary provider updates are not immutable | Resolve from stored historical rate only and copy the exact rate/provider/date/direction into each immutable valuation. Missing FX must produce `unavailable`, never an implicit `1` | | Sprint-5 canonical valuation | `storage/migrations.py:1687-1730`, `services/portfolio_performance.py`; `portfolio_valuation_snapshots` | Account and instrument valuations, versions, supersession, quality/reasons | Immutable update/delete triggers; unique scope/scope-id/time/version; explicit source reference | Correct final valuation target | | Import batches | `import_sessions` plus broker dry-run/execution tables | CSV run totals/errors/file hash; broker review lifecycle | No common batch model or universal target lineage | Add one projection batch/item layer referencing source fingerprints and canonical target IDs; do not replace existing importer engines | ### Key gaps and risks - Sprint-5 `ACTIVITY_MAP` in `services/portfolio_performance.py:26-47` does **not** recognize `initial_position_snapshot`, `initial_cash_snapshot`, `manual_adjustment`, or cash corrections. They currently become unsupported performance activities. - Transaction vocabularies diverge: Vue writes `sell`, while legacy position calculation only processes `partial_sell`/`full_sell`. - No production writer currently targets `portfolio_valuation_snapshots`; tests insert it directly. Legacy `account_value_snapshots` is only a read fallback. - Broker parsers detect only the **presence** of quantity/value fields, not numeric values. Raiffeisen aggregate rows are deliberately blocked. - No immutable price/FX revision lineage exists; both `market_prices` and `fx_rates` can be overwritten. - `accounts.account_type` has no database CHECK. Observed semantics include `brokerage`, `cash`, `robo_portfolio`, `managed_portfolio`, `reserve`, and `other`; CSV/manual account creation accepts additional free-form values. - Crypto wallets are not canonical accounts, preventing safe account-scoped performance projection. - `positions_snapshot` is mutable derived state and must not be treated as canonical history. - Existing `import_sessions` records a batch but cannot prove which canonical rows came from it. ### Recommended projection order 1. `account_value_snapshots` → immutable account valuations. 2. Confirmed cash snapshots/anchors → immutable cash-account valuations. 3. Existing normalized transaction rows → Sprint-5 activity fields, with strict vocabulary mapping and blocked unsupported rows. 4. `positions_snapshot` + exact local price/FX → immutable instrument valuations. 5. Broker execution plans → canonical initial-snapshot transactions using existing lineage. 6. Crypto only after explicit wallet→account mapping. ## Workspace state - **Files created/modified by me:** none. - A concurrent uncommitted change appeared during the audit in `src/jarvis_finance/storage/migrations.py`: it adds `portfolio_ingestion_batches` and `portfolio_ingestion_items` plus immutability triggers. It was not authored by this audit. - No tests were run to avoid writing caches/artifacts in the read-only audit worktree. `git diff --check` passed.