Output:
Chunk ID: 9263b3
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 6769
Output:
src/jarvis_finance/storage/postfinance_schema.py:7:    """Create immutable PostFinance source, ledger, snapshot and cost-basis tables."""
src/jarvis_finance/storage/postfinance_schema.py:153:            WHERE source_type='postfinance_official_import' AND source_reference IS NOT NULL;
src/jarvis_finance/storage/postfinance_schema.py:155:        CREATE TRIGGER IF NOT EXISTS postfinance_batches_immutable BEFORE UPDATE ON postfinance_import_batches
src/jarvis_finance/storage/postfinance_schema.py:156:        BEGIN SELECT RAISE(ABORT, 'postfinance import batches are immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:157:        CREATE TRIGGER IF NOT EXISTS postfinance_batches_no_delete BEFORE DELETE ON postfinance_import_batches
src/jarvis_finance/storage/postfinance_schema.py:159:        CREATE TRIGGER IF NOT EXISTS postfinance_documents_immutable BEFORE UPDATE ON postfinance_documents
src/jarvis_finance/storage/postfinance_schema.py:160:        BEGIN SELECT RAISE(ABORT, 'postfinance documents are immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:161:        CREATE TRIGGER IF NOT EXISTS postfinance_documents_no_delete BEFORE DELETE ON postfinance_documents
src/jarvis_finance/storage/postfinance_schema.py:163:        CREATE TRIGGER IF NOT EXISTS postfinance_batch_documents_immutable BEFORE UPDATE ON postfinance_batch_documents
src/jarvis_finance/storage/postfinance_schema.py:164:        BEGIN SELECT RAISE(ABORT, 'postfinance batch document links are immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:165:        CREATE TRIGGER IF NOT EXISTS postfinance_batch_documents_no_delete BEFORE DELETE ON postfinance_batch_documents
src/jarvis_finance/storage/postfinance_schema.py:167:        CREATE TRIGGER IF NOT EXISTS postfinance_snapshots_immutable BEFORE UPDATE ON postfinance_snapshots
src/jarvis_finance/storage/postfinance_schema.py:168:        BEGIN SELECT RAISE(ABORT, 'postfinance snapshots are immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:169:        CREATE TRIGGER IF NOT EXISTS postfinance_snapshots_no_delete BEFORE DELETE ON postfinance_snapshots
src/jarvis_finance/storage/postfinance_schema.py:171:        CREATE TRIGGER IF NOT EXISTS postfinance_snapshot_positions_immutable BEFORE UPDATE ON postfinance_snapshot_positions
src/jarvis_finance/storage/postfinance_schema.py:172:        BEGIN SELECT RAISE(ABORT, 'postfinance snapshot positions are immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:173:        CREATE TRIGGER IF NOT EXISTS postfinance_snapshot_positions_no_delete BEFORE DELETE ON postfinance_snapshot_positions
src/jarvis_finance/storage/postfinance_schema.py:175:        CREATE TRIGGER IF NOT EXISTS postfinance_snapshot_cash_immutable BEFORE UPDATE ON postfinance_snapshot_cash
src/jarvis_finance/storage/postfinance_schema.py:176:        BEGIN SELECT RAISE(ABORT, 'postfinance snapshot cash is immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:177:        CREATE TRIGGER IF NOT EXISTS postfinance_snapshot_cash_no_delete BEFORE DELETE ON postfinance_snapshot_cash
src/jarvis_finance/storage/postfinance_schema.py:179:        CREATE TRIGGER IF NOT EXISTS postfinance_events_immutable BEFORE UPDATE ON postfinance_ledger_events
src/jarvis_finance/storage/postfinance_schema.py:180:        BEGIN SELECT RAISE(ABORT, 'postfinance ledger events are immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:181:        CREATE TRIGGER IF NOT EXISTS postfinance_events_no_delete BEFORE DELETE ON postfinance_ledger_events
src/jarvis_finance/storage/postfinance_schema.py:183:        CREATE TRIGGER IF NOT EXISTS postfinance_event_components_immutable BEFORE UPDATE ON postfinance_event_components
src/jarvis_finance/storage/postfinance_schema.py:184:        BEGIN SELECT RAISE(ABORT, 'postfinance event components are immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:185:        CREATE TRIGGER IF NOT EXISTS postfinance_event_components_no_delete BEFORE DELETE ON postfinance_event_components
src/jarvis_finance/storage/postfinance_schema.py:187:        CREATE TRIGGER IF NOT EXISTS postfinance_cost_basis_immutable BEFORE UPDATE ON postfinance_cost_basis_lots
src/jarvis_finance/storage/postfinance_schema.py:188:        BEGIN SELECT RAISE(ABORT, 'postfinance cost basis is immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:189:        CREATE TRIGGER IF NOT EXISTS postfinance_cost_basis_no_delete BEFORE DELETE ON postfinance_cost_basis_lots
src/jarvis_finance/storage/postfinance_schema.py:191:        CREATE TRIGGER IF NOT EXISTS postfinance_official_account_values_immutable BEFORE UPDATE ON account_value_snapshots
src/jarvis_finance/storage/postfinance_schema.py:192:        WHEN OLD.source_type='postfinance_official_import'
src/jarvis_finance/storage/postfinance_schema.py:193:        BEGIN SELECT RAISE(ABORT, 'official postfinance account valuations are immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:194:        CREATE TRIGGER IF NOT EXISTS postfinance_official_account_values_no_delete BEFORE DELETE ON account_value_snapshots
src/jarvis_finance/storage/postfinance_schema.py:195:        WHEN OLD.source_type='postfinance_official_import'
src/jarvis_finance/storage/postfinance_schema.py:197:        CREATE TRIGGER IF NOT EXISTS postfinance_audit_immutable_update
src/jarvis_finance/storage/postfinance_schema.py:199:        BEGIN SELECT RAISE(ABORT, 'postfinance import audit is immutable'); END;
src/jarvis_finance/storage/postfinance_schema.py:200:        CREATE TRIGGER IF NOT EXISTS postfinance_audit_no_delete
src/jarvis_finance/storage/schema.py:1:INITIAL_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS schema_migrations (\n    version INTEGER PRIMARY KEY,\n    name TEXT NOT NULL,\n    applied_at TEXT NOT NULL,\n    checksum TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS platforms (\n    platform_id TEXT PRIMARY KEY,\n    name TEXT NOT NULL UNIQUE,\n    platform_type TEXT NOT NULL,\n    country TEXT,\n    default_currency TEXT NOT NULL DEFAULT 'CHF',\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS accounts (\n    account_id TEXT PRIMARY KEY,\n    platform_id TEXT NOT NULL REFERENCES platforms(platform_id),\n    account_name TEXT NOT NULL,\n    account_type TEXT NOT NULL,\n    currency TEXT NOT NULL DEFAULT 'CHF',\n    performance_included INTEGER NOT NULL DEFAULT 1,\n    is_health_reserve INTEGER NOT NULL DEFAULT 0,\n    target_cash_min_chf NUMERIC,\n    target_cash_max_chf NUMERIC,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT,\n    UNIQUE(platform_id, account_name)\n);\n\nCREATE TABLE IF NOT EXISTS instruments (\n    instrument_id TEXT PRIMARY KEY,\n    asset_class TEXT NOT NULL,\n    name TEXT NOT NULL,\n    ticker TEXT,\n    isin TEXT,\n    exchange TEXT,\n    currency TEXT NOT NULL,\n    country TEXT,\n    sector TEXT,\n    industry TEXT,\n    provider_symbol TEXT,\n    data_provider_primary TEXT,\n    position_category TEXT,\n    ter TEXT,\n    distribution_policy TEXT,\n    index_name TEXT,\n    fund_domicile TEXT,\n    benchmark TEXT,\n    data_provider_fallback TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS transactions (\n    transaction_id TEXT PRIMARY KEY,\n    transaction_type TEXT NOT NULL,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    instrument_id TEXT REFERENCES instruments(instrument_id),\n    trade_date TEXT NOT NULL,\n    settlement_date TEXT,\n    quantity TEXT,\n    price_original TEXT,\n    gross_amount_original TEXT,\n    fee_original TEXT DEFAULT '0',\n    tax_original TEXT DEFAULT '0',\n    net_amount_original TEXT,\n    currency_original TEXT NOT NULL,\n    fx_rate_to_chf TEXT,\n    fx_source TEXT,\n    fx_status TEXT NOT NULL DEFAULT 'ok',\n    gross_amount_chf TEXT,\n    fee_chf TEXT,\n    tax_chf TEXT,\n    net_amount_chf TEXT,\n    source_type TEXT NOT NULL,\n    source_id TEXT,\n    external_transaction_id TEXT,\n    row_hash TEXT,\n    is_confirmed INTEGER NOT NULL DEFAULT 1,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS positions_snapshot (\n    position_snapshot_id TEXT PRIMARY KEY,\n    snapshot_date TEXT NOT NULL,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    platform_id TEXT NOT NULL REFERENCES platforms(platform_id),\n    instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),\n    quantity NUMERIC NOT NULL,\n    average_cost_original NUMERIC,\n    cost_basis_original NUMERIC,\n    cost_basis_chf NUMERIC,\n    market_price_original NUMERIC,\n    market_value_original NUMERIC,\n    market_fx_rate_to_chf NUMERIC,\n    market_value_chf NUMERIC,\n    unrealized_price_pnl_chf NUMERIC,\n    unrealized_fx_pnl_chf NUMERIC,\n    realized_pnl_chf NUMERIC,\n    income_chf NUMERIC,\n    fees_chf NUMERIC,\n    taxes_chf NUMERIC,\n    total_return_chf NUMERIC,\n    portfolio_weight_pct NUMERIC,\n    category TEXT,\n    data_quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL,\n    UNIQUE(snapshot_date, account_id, instrument_id)\n);\n\nCREATE TABLE IF NOT EXISTS cash_balances (\n    cash_balance_id TEXT PRIMARY KEY,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    balance_date TEXT NOT NULL,\n    currency TEXT NOT NULL,\n    amount_original NUMERIC NOT NULL,\n    fx_rate_to_chf NUMERIC,\n    amount_chf NUMERIC,\n    source_type TEXT NOT NULL,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    UNIQUE(account_id, balance_date, currency, source_type)\n);\n\nCREATE TABLE IF NOT EXISTS fx_rates (\n    fx_rate_id TEXT PRIMARY KEY,\n    base_currency TEXT NOT NULL,\n    quote_currency TEXT NOT NULL DEFAULT 'CHF',\n    rate_date TEXT NOT NULL,\n    rate_timestamp TEXT,\n    rate NUMERIC NOT NULL,\n    provider TEXT NOT NULL,\n    rate_type TEXT NOT NULL,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL,\n    UNIQUE(base_currency, quote_currency, rate_date, provider, rate_type)\n);\n\nCREATE TABLE IF NOT EXISTS market_prices (\n    market_price_id TEXT PRIMARY KEY,\n    instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),\n    price_date TEXT NOT NULL,\n    price_timestamp TEXT,\n    open NUMERIC,\n    high NUMERIC,\n    low NUMERIC,\n    close NUMERIC NOT NULL,\n    adjusted_close NUMERIC,\n    currency TEXT NOT NULL,\n    provider TEXT NOT NULL,\n    provider_symbol TEXT,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL,\n    UNIQUE(instrument_id, price_date, provider)\n);\n\nCREATE TABLE IF NOT EXISTS crypto_wallets (\n    wallet_id TEXT PRIMARY KEY,\n    wallet_name TEXT NOT NULL UNIQUE,\n    wallet_type TEXT NOT NULL,\n    platform_provider TEXT,\n    network_chain TEXT,\n    wallet_address TEXT,\n    owner TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    last_verified_at TEXT,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS crypto_assets (\n    asset_id TEXT PRIMARY KEY,\n    coin_name TEXT NOT NULL,\n    symbol TEXT NOT NULL,\n    coingecko_id TEXT UNIQUE,\n    network_chain_default TEXT,\n    is_stablecoin INTEGER NOT NULL DEFAULT 0,\n    price_provider_primary TEXT NOT NULL DEFAULT 'CoinGecko',\n    price_provider_fallback TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS crypto_holdings (\n    crypto_holding_id TEXT PRIMARY KEY,\n    asset_id TEXT NOT NULL REFERENCES crypto_assets(asset_id),\n    wallet_id TEXT NOT NULL REFERENCES crypto_wallets(wallet_id),\n    quantity TEXT NOT NULL,\n    acquisition_source TEXT,\n    last_verified_at TEXT,\n    verification_status TEXT NOT NULL DEFAULT 'unverified',\n    legacy_snapshot_value_original TEXT,\n    legacy_snapshot_value_chf TEXT,\n    legacy_snapshot_currency TEXT,\n    legacy_snapshot_date TEXT,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT,\n    UNIQUE(asset_id, wallet_id)\n);\n\nCREATE TABLE IF NOT EXISTS crypto_transactions (\n    crypto_transaction_id TEXT PRIMARY KEY,\n    transaction_id TEXT REFERENCES transactions(transaction_id),\n    transaction_type TEXT NOT NULL,\n    asset_id TEXT NOT NULL REFERENCES crypto_assets(asset_id),\n    quantity TEXT NOT NULL,\n    price_original TEXT,\n    currency_original TEXT,\n    gross_amount_original TEXT,\n    fee_quantity TEXT,\n    fee_original TEXT,\n    fee_currency TEXT,\n    fx_rate_to_chf TEXT,\n    fx_source TEXT,\n    amount_chf TEXT,\n    from_wallet_id TEXT REFERENCES crypto_wallets(wallet_id),\n    to_wallet_id TEXT REFERENCES crypto_wallets(wallet_id),\n    transaction_datetime TEXT NOT NULL,\n    tx_hash TEXT,\n    source TEXT NOT NULL,\n    confirmation_status TEXT NOT NULL DEFAULT 'confirmed',\n    parse_confidence NUMERIC,\n    original_input_text TEXT,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS crypto_prices (\n    crypto_price_id TEXT PRIMARY KEY,\n    asset_id TEXT NOT NULL REFERENCES crypto_assets(asset_id),\n    coingecko_id TEXT,\n    price_currency TEXT NOT NULL,\n    price TEXT NOT NULL,\n    provider TEXT NOT NULL DEFAULT 'CoinGecko',\n    provider_timestamp TEXT,\n    fetched_at TEXT NOT NULL,\n    quality_status TEXT NOT NULL DEFAULT 'fresh',\n    error_message TEXT\n);\n\nCREATE TABLE IF NOT EXISTS watchlist (\n    watchlist_id TEXT PRIMARY KEY,\n    instrument_id TEXT REFERENCES instruments(instrument_id),\n    crypto_asset_id TEXT REFERENCES crypto_assets(asset_id),\n    name TEXT NOT NULL,\n    asset_class TEXT NOT NULL,\n    reason TEXT NOT NULL,\n    target_entry_price NUMERIC,\n    target_entry_currency TEXT,\n    desired_position_size_chf NUMERIC,\n    desired_weight_pct NUMERIC,\n    trigger_rules TEXT,\n    risk_notes TEXT,\n    investment_case TEXT,\n    bear_case TEXT,\n    sources TEXT,\n    status TEXT NOT NULL DEFAULT 'active',\n    next_review_date TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS reports (\n    report_id TEXT PRIMARY KEY,\n    report_type TEXT NOT NULL,\n    title TEXT NOT NULL,\n    period_start TEXT,\n    period_end TEXT,\n    generated_at TEXT NOT NULL,\n    file_path TEXT,\n    format TEXT NOT NULL,\n    data_quality_status TEXT NOT NULL DEFAULT 'ok',\n    summary_json TEXT,\n    created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS alerts (\n    alert_id TEXT PRIMARY KEY,\n    priority TEXT NOT NULL,\n    category TEXT NOT NULL,\n    entity_type TEXT,\n    entity_id TEXT,\n    rule_id TEXT,\n    message TEXT NOT NULL,\n    evidence_json TEXT,\n    status TEXT NOT NULL DEFAULT 'active',\n    created_at TEXT NOT NULL,\n    resolved_at TEXT,\n    muted_until TEXT,\n    last_seen_at TEXT,\n    occurrence_count INTEGER NOT NULL DEFAULT 1,\n    fingerprint TEXT,\n    dedup_key TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_alerts_dedup ON alerts(dedup_key, status);\n\nCREATE TABLE IF NOT EXISTS audit_log (\n    audit_id TEXT PRIMARY KEY,\n    timestamp TEXT NOT NULL,\n    source TEXT NOT NULL,\n    action TEXT NOT NULL,\n    entity_type TEXT NOT NULL,\n    entity_id TEXT NOT NULL,\n    old_values_json TEXT,\n    new_values_json TEXT,\n    user_text_note TEXT,\n    original_input_text TEXT,\n    confirmed INTEGER NOT NULL DEFAULT 1,\n    confirmation_timestamp TEXT,\n    auto_parsed INTEGER NOT NULL DEFAULT 0,\n    parse_confidence NUMERIC,\n    created_by TEXT NOT NULL DEFAULT 'system',\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS import_sessions (\n    import_session_id TEXT PRIMARY KEY,\n    import_type TEXT NOT NULL,\n    source_filename TEXT NOT NULL,\n    source_hash TEXT,\n    started_at TEXT NOT NULL,\n    finished_at TEXT,\n    status TEXT NOT NULL,\n    rows_total INTEGER DEFAULT 0,\n    rows_imported INTEGER DEFAULT 0,\n    rows_failed INTEGER DEFAULT 0,\n    errors_json TEXT,\n    notes TEXT\n);\n\nCREATE TABLE IF NOT EXISTS decision_journal (\n    decision_id TEXT PRIMARY KEY,\n    decision_date TEXT NOT NULL,\n    decision_type TEXT NOT NULL,\n    entity_type TEXT,\n    entity_id TEXT,\n    system_recommendation TEXT,\n    human_decision TEXT NOT NULL,\n    rationale TEXT NOT NULL,\n    investment_case TEXT,\n    risks TEXT,\n    exit_rule TEXT,\n    alternatives_considered TEXT,\n    sources TEXT,\n    review_date TEXT,\n    outcome_status TEXT,\n    outcome_return_chf NUMERIC,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_accounts_platform_id ON accounts(platform_id);\nCREATE INDEX IF NOT EXISTS idx_transactions_account_date ON transactions(account_id, trade_date);\nCREATE INDEX IF NOT EXISTS idx_transactions_instrument_date ON transactions(instrument_id, trade_date);\nCREATE INDEX IF NOT EXISTS idx_transactions_type ON transactions(transaction_type);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_external_id ON transactions(external_transaction_id) WHERE external_transaction_id IS NOT NULL AND external_transaction_id != '';\nCREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_row_hash ON transactions(row_hash) WHERE row_hash IS NOT NULL AND row_hash != '';\nCREATE INDEX IF NOT EXISTS idx_crypto_transactions_asset_datetime ON crypto_transactions(asset_id, transaction_datetime);\nCREATE INDEX IF NOT EXISTS idx_alerts_priority_status ON alerts(priority, status);\nCREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id);\n"
src/jarvis_finance/storage/migrations.py:583:            source_type TEXT NOT NULL CHECK(source_type IN ('manual','import_candidate_later','system')),
src/jarvis_finance/storage/migrations.py:652:            source_type TEXT NOT NULL DEFAULT 'manual' CHECK(source_type IN ('manual','excel_seed_dry_run','system')),
src/jarvis_finance/storage/migrations.py:746:            source_type TEXT NOT NULL DEFAULT 'csv_seed',
src/jarvis_finance/storage/migrations.py:782:                source_type TEXT NOT NULL DEFAULT 'csv_seed',
src/jarvis_finance/storage/migrations.py:808:                transaction_candidate_id, source_file_label, source_row_or_range, source_type,
src/jarvis_finance/storage/migrations.py:813:            SELECT transaction_candidate_id, source_file_label, source_row_or_range, source_type,
src/jarvis_finance/storage/migrations.py:873:            source_type TEXT,
src/jarvis_finance/storage/migrations.py:883:        CREATE INDEX IF NOT EXISTS idx_budget_import_rules_active ON budget_import_rules(is_active, source_type);
src/jarvis_finance/storage/migrations.py:899:    if "source_type IN ('manual','import_candidate_later','system')" in sql:
src/jarvis_finance/storage/migrations.py:919:                source_type TEXT NOT NULL CHECK(source_type IN ('manual','import_candidate','import_candidate_later','system')),
src/jarvis_finance/storage/migrations.py:929:                amount_chf, fx_status, category_id, status, source_type, notes, created_at, updated_at,
src/jarvis_finance/storage/migrations.py:934:                amount_chf, fx_status, category_id, status, source_type, notes, created_at, updated_at,
src/jarvis_finance/storage/migrations.py:950:                source_type TEXT NOT NULL DEFAULT 'csv_seed',
src/jarvis_finance/storage/migrations.py:980:                transaction_candidate_id, source_file_label, source_row_or_range, source_type,
src/jarvis_finance/storage/migrations.py:988:            SELECT transaction_candidate_id, source_file_label, source_row_or_range, source_type,
src/jarvis_finance/storage/migrations.py:1087:            source_type TEXT,
src/jarvis_finance/storage/migrations.py:1094:        CREATE INDEX IF NOT EXISTS idx_budget_review_rules_active ON budget_review_rules(is_active, source_type);
src/jarvis_finance/storage/migrations.py:1133:            source_type TEXT,
src/jarvis_finance/storage/migrations.py:1315:            source_type TEXT NOT NULL DEFAULT 'migros_receipt',
src/jarvis_finance/storage/migrations.py:1524:        CREATE TABLE IF NOT EXISTS account_value_snapshots (
src/jarvis_finance/storage/migrations.py:1530:            source_type TEXT NOT NULL DEFAULT 'manual_total_value',
src/jarvis_finance/storage/migrations.py:1636:    CREATE TRIGGER IF NOT EXISTS portfolio_policies_content_immutable
src/jarvis_finance/storage/migrations.py:1656:    BEGIN SELECT RAISE(ABORT, 'portfolio policy content is immutable'); END;
src/jarvis_finance/storage/migrations.py:1657:    CREATE TRIGGER IF NOT EXISTS portfolio_policies_no_delete
src/jarvis_finance/storage/migrations.py:1660:    CREATE TRIGGER IF NOT EXISTS portfolio_policy_allocations_immutable_update
src/jarvis_finance/storage/migrations.py:1662:    BEGIN SELECT RAISE(ABORT, 'portfolio policy allocations are immutable'); END;
src/jarvis_finance/storage/migrations.py:1663:    CREATE TRIGGER IF NOT EXISTS portfolio_policy_allocations_no_delete
src/jarvis_finance/storage/migrations.py:1677:        CREATE TRIGGER IF NOT EXISTS portfolio_policy_request_identity_immutable
src/jarvis_finance/storage/migrations.py:1681:        BEGIN SELECT RAISE(ABORT, 'portfolio policy request identity is immutable'); END;
src/jarvis_finance/storage/migrations.py:1742:        CREATE TRIGGER IF NOT EXISTS portfolio_valuation_snapshots_immutable
src/jarvis_finance/storage/migrations.py:1744:        BEGIN SELECT RAISE(ABORT, 'portfolio valuation snapshots are immutable'); END;
src/jarvis_finance/storage/migrations.py:1754:        CREATE TRIGGER IF NOT EXISTS portfolio_valuation_snapshots_no_delete
src/jarvis_finance/storage/migrations.py:1762:    """Add immutable ingestion audit history; previews and reconciliation remain projections."""
src/jarvis_finance/storage/migrations.py:1809:        CREATE TRIGGER IF NOT EXISTS portfolio_ingestion_batches_immutable_update
src/jarvis_finance/storage/migrations.py:1811:        BEGIN SELECT RAISE(ABORT, 'portfolio ingestion batches are immutable'); END;
src/jarvis_finance/storage/migrations.py:1812:        CREATE TRIGGER IF NOT EXISTS portfolio_ingestion_batches_no_delete
src/jarvis_finance/storage/migrations.py:1815:        CREATE TRIGGER IF NOT EXISTS portfolio_ingestion_items_immutable_update
src/jarvis_finance/storage/migrations.py:1817:        BEGIN SELECT RAISE(ABORT, 'portfolio ingestion items are immutable'); END;
src/jarvis_finance/storage/migrations.py:1818:        CREATE TRIGGER IF NOT EXISTS portfolio_ingestion_items_no_delete
src/jarvis_finance/storage/migrations.py:1821:        CREATE TRIGGER IF NOT EXISTS portfolio_ingestion_audit_immutable_update
src/jarvis_finance/storage/migrations.py:1823:        BEGIN SELECT RAISE(ABORT, 'portfolio ingestion audit is immutable'); END;
src/jarvis_finance/storage/migrations.py:1824:        CREATE TRIGGER IF NOT EXISTS portfolio_ingestion_audit_no_delete
src/jarvis_finance/storage/migrations.py:1910:        CREATE TRIGGER IF NOT EXISTS market_data_runs_no_delete
src/jarvis_finance/storage/migrations.py:1913:        CREATE TRIGGER IF NOT EXISTS benchmark_snapshots_immutable
src/jarvis_finance/storage/migrations.py:1915:        BEGIN SELECT RAISE(ABORT, 'benchmark snapshots are immutable'); END;
src/jarvis_finance/storage/migrations.py:1916:        CREATE TRIGGER IF NOT EXISTS benchmark_snapshots_no_delete
src/jarvis_finance/storage/migrations.py:1919:        CREATE TRIGGER IF NOT EXISTS portfolio_analysis_snapshots_immutable
src/jarvis_finance/storage/migrations.py:1921:        BEGIN SELECT RAISE(ABORT, 'portfolio analysis snapshots are immutable'); END;
src/jarvis_finance/storage/migrations.py:1922:        CREATE TRIGGER IF NOT EXISTS portfolio_analysis_snapshots_no_delete
src/jarvis_finance/storage/migrations.py:2040:            WHERE source_type='truewealth_official_import' AND source_reference IS NOT NULL;
src/jarvis_finance/storage/migrations.py:2042:        CREATE TRIGGER IF NOT EXISTS truewealth_account_values_no_delete
src/jarvis_finance/storage/migrations.py:2044:        WHEN OLD.source_type IN ('truewealth_official_import','truewealth_manual_provisional')
src/jarvis_finance/storage/migrations.py:2046:        CREATE TRIGGER IF NOT EXISTS truewealth_official_account_values_immutable
src/jarvis_finance/storage/migrations.py:2048:        WHEN OLD.source_type='truewealth_official_import'
src/jarvis_finance/storage/migrations.py:2049:        BEGIN SELECT RAISE(ABORT, 'official truewealth account valuations are immutable'); END;
src/jarvis_finance/storage/migrations.py:2050:        CREATE TRIGGER IF NOT EXISTS truewealth_manual_value_fields_immutable
src/jarvis_finance/storage/migrations.py:2052:        WHEN OLD.source_type='truewealth_manual_provisional' AND (
src/jarvis_finance/storage/migrations.py:2055:            NEW.currency IS NOT OLD.currency OR NEW.source_type IS NOT OLD.source_type OR
src/jarvis_finance/storage/migrations.py:2060:        BEGIN SELECT RAISE(ABORT, 'manual truewealth valuation fields are immutable'); END;
src/jarvis_finance/storage/migrations.py:2062:        CREATE TRIGGER IF NOT EXISTS truewealth_batches_immutable_update
src/jarvis_finance/storage/migrations.py:2064:        BEGIN SELECT RAISE(ABORT, 'truewealth import batches are immutable'); END;
src/jarvis_finance/storage/migrations.py:2065:        CREATE TRIGGER IF NOT EXISTS truewealth_batches_no_delete
src/jarvis_finance/storage/migrations.py:2068:        CREATE TRIGGER IF NOT EXISTS truewealth_snapshots_immutable_update
src/jarvis_finance/storage/migrations.py:2070:        BEGIN SELECT RAISE(ABORT, 'truewealth snapshots are immutable'); END;
src/jarvis_finance/storage/migrations.py:2071:        CREATE TRIGGER IF NOT EXISTS truewealth_snapshots_no_delete
src/jarvis_finance/storage/migrations.py:2074:        CREATE TRIGGER IF NOT EXISTS truewealth_positions_immutable_update
src/jarvis_finance/storage/migrations.py:2076:        BEGIN SELECT RAISE(ABORT, 'truewealth positions are immutable'); END;
src/jarvis_finance/storage/migrations.py:2077:        CREATE TRIGGER IF NOT EXISTS truewealth_positions_no_delete
src/jarvis_finance/storage/migrations.py:2080:        CREATE TRIGGER IF NOT EXISTS truewealth_cash_immutable_update
src/jarvis_finance/storage/migrations.py:2082:        BEGIN SELECT RAISE(ABORT, 'truewealth cash is immutable'); END;
src/jarvis_finance/storage/migrations.py:2083:        CREATE TRIGGER IF NOT EXISTS truewealth_cash_no_delete
src/jarvis_finance/storage/migrations.py:2086:        CREATE TRIGGER IF NOT EXISTS truewealth_audit_immutable_update
src/jarvis_finance/storage/migrations.py:2088:        BEGIN SELECT RAISE(ABORT, 'truewealth import audit is immutable'); END;
src/jarvis_finance/storage/migrations.py:2089:        CREATE TRIGGER IF NOT EXISTS truewealth_audit_no_delete


Output:
Chunk ID: 3a93b8
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 800
Output:
# Sprint 13 — PostFinance verified document, ledger and cost-basis import

## Scope

Sprint 13 adds a local-only, audited PostFinance import for the verified document ZIP and the separate account/portfolio overview. Real customer sources, raw extracted text, customer/account identifiers, complete source hashes, security identifiers and transaction details are not part of Git.

## Safety contract

- ZIP traversal, symlink, encrypted-entry, file-count, file-size, compression-size, extension and PDF gates run before parsing.
- Preview decodes, hashes and parses both original sources fully and performs no writes.
- Confirm requires explicit confirmation, decodes, hashes and parses original bytes again, verifies preview identity and database revision, then persists the batch atomically.
- Source ZIP, overview and contained PDFs are archived outside Git with `0700` directories and `0600` files.
- Documents are immutable and identified by original hash plus semantic business identity. Same bytes are idempotent; same business identity with different bytes is blocked.
- Batch, document links, snapshot, cash, positions, ledger events, components, cost-basis lots, canonical portfolio projections and audit are committed together.
- Confirm failure rolls back the database and removes newly created archive files.

## Financial model

The model keeps three source roles separate:

1. E-Finance cash account;
2. E-Trading depot;
3. E-Trading multi-currency cash attached to the depot.

Supported economic events are buys, sells, dividends, withholding taxes, fees, interest, internal transfers, FX metadata and non-cash corporate actions. Internal transfer legs share a transfer group and use canonical `activity_kind=internal_transfer`; they are not external income or expense.

Provider-reported average acquisition values and totals are stored exactly as snapshot facts. Calculated cost basis is derived only from imported acquisition/disposal lots and associated documented charges. If the source bundle does not prove the complete opening history, calculated basis remains `partial` instead of being inferred from market value.

The official account overview is persisted as a snapshot. It does not create synthetic trades. Latest positions, currency cash and the official account-value snapshot are projected once into the existing portfolio read model, while source-specific immutable tables retain provenance.

## API and UI

- `POST /api/v1/postfinance/imports/preview`
- `POST /api/v1/postfinance/imports/confirm`
- `GET /api/v1/postfinance/summary`
- responsive page: `/portfolio/postfinance`

The page shows official totals, reconciliation, position-level provider versus calculated basis, currency cash, event counts and import history. Confirm is disabled for source conflicts or failed reconciliation.

## Verification

CI uses synthetic fixtures only. Coverage includes ZIP safety, complete snapshot reconciliation, preview side-effect freedom, confirm reparse, immutable rows, atomic persistence, rollback/archive behavior, account-role separation, canonical internal-transfer classification, cost-basis separation, conflict blocking and bundle/document/event idempotency.


Output:
Chunk ID: 42bc0d
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 345
Output:
diff --git a/tests/unit/test_portfolio_data_ingestion_reconciliation.py b/tests/unit/test_portfolio_data_ingestion_reconciliation.py
index 28a5ca5..3d1cd76 100644
--- a/tests/unit/test_portfolio_data_ingestion_reconciliation.py
+++ b/tests/unit/test_portfolio_data_ingestion_reconciliation.py
@@ -115,8 +115,8 @@ def test_migrations_41_to_43_are_additive_and_ingestion_history_is_immutable(
     tmp_path: Path, monkeypatch: pytest.MonkeyPatch
 ) -> None:
     conn = database()
-    assert MIGRATION_VERSION == 44
-    assert conn.execute("SELECT MAX(version) FROM schema_migrations").fetchone()[0] == 44
+    assert MIGRATION_VERSION == 45
+    assert conn.execute("SELECT MAX(version) FROM schema_migrations").fetchone()[0] == 45
     assert {row[1] for row in conn.execute("PRAGMA table_info(instrument_price_mappings)")} >= {
         "source_symbol", "source_venue", "source_currency",
     }
diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py
index f899370..bcd06d3 100644
--- a/tests/unit/test_schema.py
+++ b/tests/unit/test_schema.py
@@ -15,7 +15,7 @@ def test_db_schema_can_be_created() -> None:
 def test_schema_version_recorded() -> None:
     conn = connect_memory()
     apply_migrations(conn)
-    assert get_schema_version(conn) == 44
+    assert get_schema_version(conn) == 45

 def test_decimal_sensitive_columns_use_text_affinity() -> None:


One important distinction is emerging: the service blocks a second PostFinance batch for the same snapshot date at preview, but I haven’t yet found a preview guard for collisions with existing canonical `positions_snapshot` or `cash_balances` rows on that same date. I’m going to confirm that against the table uniqueness rules and then run verification.


nl -ba src/jarvis_finance/services/postfinance_service.py | sed -n '220,520p'


nl -ba src/jarvis_finance/services/postfinance_service.py | sed -n '640,860p'


nl -ba src/jarvis_finance/storage/schema.py | sed -n '1,1p'


nl -ba src/jarvis_finance/storage/postfinance_schema.py | sed -n '145,205p'


Output:
Chunk ID: 537040
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3659
Output:
   220	        if len(candidates) != 1:
   221	            raise ValueError(
   222	                "PostFinance snapshot position cannot be mapped unambiguously to the confirmed depot baseline"
   223	            )
   224	        instrument_id = next(iter(candidates))
   225	        mapping[position.row_reference] = instrument_id
   226	        used.add(instrument_id)
   227	    if len(mapping) != 22 or len(used) != 22:
   228	        raise ValueError("PostFinance snapshot-to-depot mapping is incomplete")
   229	    # Trade and income ISINs independently verify that no document points to another instrument.
   230	    for event in bundle.events:
   231	        if event.isin and event.isin.upper() not in by_isin:
   232	            raise ValueError(
   233	                "PostFinance ledger document references an instrument outside the confirmed depot"
   234	            )
   235	    return mapping
   236
   237
   238	def _event_instrument(
   239	    event: PFEvent, bundle: PFBundle, position_map: dict[str, str], conn: Connection
   240	) -> str | None:
   241	    if event.isin:
   242	        row = conn.execute(
   243	            "SELECT instrument_id FROM instruments WHERE upper(isin)=upper(?) AND is_active=1",
   244	            (event.isin,),
   245	        ).fetchall()
   246	        if len(row) != 1:
   247	            raise ValueError("PostFinance ledger ISIN mapping is ambiguous")
   248	        return str(row[0]["instrument_id"])
   249	    if event.instrument_name:
   250	        key = normalize_label(event.instrument_name)
   251	        for position in bundle.snapshot.positions:
   252	            if (
   253	                position.normalized_label == key
   254	                or key in position.normalized_label
   255	                or position.normalized_label in key
   256	            ):
   257	                return position_map[position.row_reference]
   258	    return None
   259
   260
   261	def _preview_payload(
   262	    bundle: PFBundle, accounts: dict[str, str], mapping: dict[str, str]
   263	) -> dict[str, Any]:
   264	    snapshot = bundle.snapshot
   265	    component_total = snapshot.securities_total_chf + snapshot.cash_total_chf
   266	    difference = snapshot.total_value_chf - component_total
   267	    event_counts: dict[str, int] = defaultdict(int)
   268	    for event in bundle.events:
   269	        event_counts[event.event_type] += 1
   270	    quality_counts: dict[str, int] = defaultdict(int)
   271	    for event in bundle.events:
   272	        quality_counts[event.quality_status] += 1
   273	    return {
   274	        "bundle_sha256": bundle.bundle_hash,
   275	        "zip_sha256": bundle.zip_hash,
   276	        "overview_sha256": bundle.overview_hash,
   277	        "parser_id": PARSER_ID,
   278	        "parser_version": PARSER_VERSION,
   279	        "snapshot_at": snapshot.valuation_at,
   280	        "document_count": len(bundle.documents) + 1,
   281	        "zip_document_count": len(bundle.documents),
   282	        "page_count": sum(doc.page_count for doc in bundle.documents) + snapshot.page_count,
   283	        "position_count": len(snapshot.positions),
   284	        "cash_count": len(snapshot.cash),
   285	        "event_count": len(bundle.events),
   286	        "event_counts": dict(sorted(event_counts.items())),
   287	        "quality_counts": dict(sorted(quality_counts.items())),
   288	        "transfer_group_count": len(bundle.transfer_groups),
   289	        "total_chf": _money(snapshot.total_value_chf),
   290	        "securities_chf": _money(snapshot.securities_total_chf),
   291	        "cash_chf": _money(snapshot.cash_total_chf),
   292	        "stocks_chf": _money(snapshot.stock_total_chf),
   293	        "etfs_chf": _money(snapshot.etf_total_chf),
   294	        "component_total_chf": _money(component_total),
   295	        "reconciliation_difference_chf": _money(difference),
   296	        "reconciliation_status": "matched" if difference == 0 else "within_tolerance",
   297	        "open_orders": snapshot.open_orders,
   298	        "account_roles_resolved": sorted(accounts),
   299	        "mapped_position_count": len(mapping),
   300	        "warnings": [
   301	            "Calculated cost basis remains partial where no documented acquisition exists in this source bundle"
   302	        ],
   303	    }
   304
   305
   306	def preview_postfinance_import(conn: Connection, request: dict[str, Any]) -> dict[str, Any]:
   307	    accounts = _resolve_accounts(conn, request)
   308	    zip_raw, overview_raw = _source_bytes(request)
   309	    bundle = parse_postfinance_bundle(zip_raw, overview_raw)
   310	    mapping = _match_instruments(conn, bundle, accounts["etrading_depot"])
   311	    payload = _preview_payload(bundle, accounts, mapping)
   312	    revision = _revision(conn, accounts)
   313	    fingerprint = _hash(payload, revision)
   314	    duplicate = conn.execute(
   315	        "SELECT batch_id FROM postfinance_import_batches WHERE bundle_sha256=?",
   316	        (bundle.bundle_hash,),
   317	    ).fetchone()
   318	    conflict = conn.execute(
   319	        "SELECT batch_id FROM postfinance_import_batches WHERE substr(snapshot_at,1,10)=? AND bundle_sha256<>?",
   320	        (bundle.snapshot.valuation_at[:10], bundle.bundle_hash),
   321	    ).fetchone()
   322	    semantic_conflicts = conn.execute(
   323	        """SELECT COUNT(*) FROM postfinance_documents d
   324	           WHERE EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value=d.semantic_identity)
   325	             AND d.document_hash NOT IN (SELECT value FROM json_each(?))""",
   326	        (
   327	            json.dumps([d.semantic_identity for d in bundle.documents]),
   328	            json.dumps([d.document_hash for d in bundle.documents]),
   329	        ),
   330	    ).fetchone()[0]
   331	    if semantic_conflicts:
   332	        conflict = conflict or {"batch_id": "semantic_document_conflict"}
   333	    if conflict:
   334	        payload["warnings"].append(
   335	            "A different immutable PostFinance source has the same business identity"
   336	        )
   337	    payload.update(
   338	        {
   339	            "preview_id": f"pfpreview_{fingerprint[:24]}",
   340	            "confirmation_id": f"pfconfirm_{fingerprint[24:48]}",
   341	            "db_revision": revision,
   342	            "duplicate": bool(duplicate),
   343	            "existing_batch_id": str(duplicate["batch_id"]) if duplicate else None,
   344	            "conflict": bool(conflict),
   345	            "conflicting_batch_id": str(conflict["batch_id"]) if conflict else None,
   346	        }
   347	    )
   348	    return payload
   349
   350
   351	def _archive(raw: bytes, digest: str, suffix: str) -> tuple[str, bool]:
   352	    root = Path(os.environ.get("JARVIS_FINANCE_RUNTIME_DIR", str(DEFAULT_RUNTIME_DIR))).expanduser()
   353	    folder = root / "imports" / "postfinance" / "archive"
   354	    folder.mkdir(mode=0o700, parents=True, exist_ok=True)
   355	    folder.chmod(0o700)
   356	    target = folder / f"{digest}.{suffix}"
   357	    if target.exists():
   358	        if sha256(target.read_bytes()).hexdigest() != digest:
   359	            raise ValueError("Archived PostFinance source hash mismatch")
   360	        return f"postfinance/archive/{target.name}", False
   361	    tmp = folder / f".{digest}.{uuid4().hex}.tmp"
   362	    fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
   363	    try:
   364	        with os.fdopen(fd, "wb") as handle:
   365	            handle.write(raw)
   366	            handle.flush()
   367	            os.fsync(handle.fileno())
   368	        os.replace(tmp, target)
   369	        target.chmod(0o600)
   370	    finally:
   371	        tmp.unlink(missing_ok=True)
   372	    return f"postfinance/archive/{target.name}", True
   373
   374
   375	def _remove_archive(reference: str) -> None:
   376	    root = Path(os.environ.get("JARVIS_FINANCE_RUNTIME_DIR", str(DEFAULT_RUNTIME_DIR))).expanduser()
   377	    (root / "imports" / reference).unlink(missing_ok=True)
   378
   379
   380	def _audit(conn: Connection, batch_id: str, bundle: PFBundle, now: str) -> str:
   381	    audit_id = _id("audit")
   382	    details = {
   383	        "bundle_sha256": bundle.bundle_hash,
   384	        "parser_id": PARSER_ID,
   385	        "parser_version": PARSER_VERSION,
   386	        "document_count": len(bundle.documents) + 1,
   387	        "event_count": len(bundle.events),
   388	        "snapshot_at": bundle.snapshot.valuation_at,
   389	    }
   390	    conn.execute(
   391	        """INSERT INTO audit_log(audit_id,timestamp,source,action,entity_type,entity_id,old_values_json,new_values_json,user_text_note,confirmed,confirmation_timestamp,auto_parsed,parse_confidence,created_by,quality_status,created_at)
   392	           VALUES(?,?,'postfinance_sprint13','postfinance_import_confirm','postfinance_import_batch',?,NULL,?,'Verified official source bundle',1,?,0,NULL,'user','ok',?)""",
   393	        (audit_id, now, batch_id, json.dumps(details, sort_keys=True), now, now),
   394	    )
   395	    return audit_id
   396
   397
   398	def _canonical_transaction(
   399	    conn: Connection,
   400	    event_id: str,
   401	    event: PFEvent,
   402	    account_id: str,
   403	    instrument_id: str | None,
   404	    now: str,
   405	) -> None:
   406	    if event.event_type in {"split", "corporate_action"}:
   407	        return
   408	    tx_type = {
   409	        "buy": "buy",
   410	        "sell": "sell",
   411	        "dividend": "dividend",
   412	        "interest": "interest",
   413	        "fee": "fee",
   414	        "internal_transfer": "transfer",
   415	        "fx": "fx",
   416	    }[event.event_type]
   417	    activity = (
   418	        "internal_transfer"
   419	        if event.event_type == "internal_transfer"
   420	        else "external_cashflow"
   421	        if event.event_type in {"dividend", "interest", "fee"}
   422	        else "trade"
   423	    )
   424	    columns = (
   425	        "transaction_id,transaction_type,account_id,instrument_id,trade_date,settlement_date,quantity,"
   426	        "price_original,gross_amount_original,fee_original,tax_original,net_amount_original,currency_original,"
   427	        "fx_rate_to_chf,fx_source,fx_status,gross_amount_chf,fee_chf,tax_chf,net_amount_chf,source_type,source_id,"
   428	        "external_transaction_id,row_hash,is_confirmed,quality_status,notes,created_at,updated_at,is_voided,"
   429	        "activity_kind,booking_date,base_currency,internal_transfer_group_id,source_reference"
   430	    )
   431	    values = (
   432	        event_id,
   433	        tx_type,
   434	        account_id,
   435	        instrument_id,
   436	        event.occurred_on,
   437	        event.settlement_on,
   438	        _text(event.quantity),
   439	        _text(event.price_original),
   440	        _text(event.gross_original),
   441	        _text(event.fee_original) or "0",
   442	        _text(event.tax_original) or "0",
   443	        _text(event.net_original),
   444	        event.currency,
   445	        _text(event.fx_rate_to_chf),
   446	        None,
   447	        "unavailable",
   448	        None,
   449	        None,
   450	        None,
   451	        None,
   452	        "postfinance_official_import",
   453	        event_id,
   454	        event.event_fingerprint,
   455	        event.event_fingerprint,
   456	        1,
   457	        event.quality_status,
   458	        "Verified PostFinance document event",
   459	        now,
   460	        None,
   461	        0,
   462	        activity,
   463	        event.settlement_on or event.occurred_on,
   464	        "CHF",
   465	        event.internal_transfer_group,
   466	        _hash(event.semantic_reference),
   467	    )
   468	    conn.execute(
   469	        f"INSERT INTO transactions({columns}) VALUES({','.join('?' for _ in values)})", values
   470	    )
   471
   472
   473	def confirm_postfinance_import(conn: Connection, request: dict[str, Any]) -> dict[str, Any]:
   474	    if request.get("confirm") is not True:
   475	        raise ValueError("Explicit PostFinance import confirmation is required")
   476	    preview = preview_postfinance_import(conn, request)
   477	    if (
   478	        request.get("preview_id") != preview["preview_id"]
   479	        or request.get("confirmation_id") != preview["confirmation_id"]
   480	    ):
   481	        raise ValueError("PostFinance import preview is stale or changed")
   482	    if preview["conflict"]:
   483	        raise ValueError("PostFinance source conflicts with existing immutable business identity")
   484	    if preview["duplicate"]:
   485	        row = conn.execute(
   486	            "SELECT batch_id,audit_id FROM postfinance_import_batches WHERE bundle_sha256=?",
   487	            (preview["bundle_sha256"],),
   488	        ).fetchone()
   489	        return {
   490	            "status": "confirmed",
   491	            "batch_id": row["batch_id"],
   492	            "audit_id": row["audit_id"],
   493	            "idempotent": True,
   494	        }
   495	    accounts = _resolve_accounts(conn, request)
   496	    zip_raw, overview_raw = _source_bytes(request)
   497	    bundle = parse_postfinance_bundle(zip_raw, overview_raw)  # mandatory byte reparse on confirm
   498	    if (
   499	        bundle.bundle_hash != preview["bundle_sha256"]
   500	        or _revision(conn, accounts) != preview["db_revision"]
   501	    ):
   502	        raise ValueError("PostFinance source bytes or database revision changed after preview")
   503	    position_map = _match_instruments(conn, bundle, accounts["etrading_depot"])
   504	    event_instruments = {
   505	        event.event_fingerprint: _event_instrument(event, bundle, position_map, conn)
   506	        for event in bundle.events
   507	    }
   508	    archive_refs: list[tuple[str, bool]] = []
   509	    zip_ref, created = _archive(zip_raw, bundle.zip_hash, "zip")
   510	    archive_refs.append((zip_ref, created))
   511	    overview_ref, created = _archive(overview_raw, bundle.overview_hash, "pdf")
   512	    archive_refs.append((overview_ref, created))
   513	    for document in bundle.documents:
   514	        reference, created = _archive(document.raw, document.document_hash, "pdf")
   515	        archive_refs.append((reference, created))
   516	    now = _now()
   517	    batch_id = _id("pfbatch")
   518	    snapshot_id = _id("pfsnapshot")
   519	    try:
   520	        conn.execute("BEGIN IMMEDIATE")


Output:
Chunk ID: 14f865
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2971
Output:
   640	            for kind, amount in (
   641	                ("gross", event.gross_original),
   642	                ("fee", event.fee_original),
   643	                ("tax", event.tax_original),
   644	                ("net", event.net_original),
   645	            ):
   646	                if amount is not None and event.currency:
   647	                    conn.execute(
   648	                        "INSERT INTO postfinance_event_components(component_id,event_id,component_type,amount_original,currency,provenance_document_hash,created_at) VALUES(?,?,?,?,?,?,?)",
   649	                        (
   650	                            _id("pfcomponent"),
   651	                            event_id,
   652	                            kind,
   653	                            _text(amount),
   654	                            event.currency,
   655	                            event.document_hash,
   656	                            now,
   657	                        ),
   658	                    )
   659	            _canonical_transaction(conn, event_id, event, account_id, instrument_id, now)
   660	        lots: dict[str, list[dict[str, Any]]] = defaultdict(list)
   661	        for event in sorted(
   662	            bundle.events,
   663	            key=lambda item: (
   664	                item.occurred_on,
   665	                0 if item.event_type == "buy" else 1,
   666	                item.event_fingerprint,
   667	            ),
   668	        ):
   669	            instrument_id = event_instruments[event.event_fingerprint]
   670	            if (
   671	                event.event_type == "buy"
   672	                and instrument_id
   673	                and event.quantity
   674	                and event.price_original
   675	                and event.currency
   676	            ):
   677	                lots[instrument_id].append({"event": event, "remaining": event.quantity})
   678	            elif event.event_type == "sell" and instrument_id and event.quantity:
   679	                to_consume = event.quantity
   680	                for lot in lots[instrument_id]:
   681	                    if to_consume <= 0:
   682	                        break
   683	                    consumed = min(lot["remaining"], to_consume)
   684	                    lot["remaining"] -= consumed
   685	                    to_consume -= consumed
   686	        for instrument_id, instrument_lots in lots.items():
   687	            for lot in instrument_lots:
   688	                event = lot["event"]
   689	                conn.execute(
   690	                    """INSERT INTO postfinance_cost_basis_lots(lot_id,event_id,instrument_id,acquired_on,quantity_acquired,quantity_remaining,unit_cost_original,fees_original,currency,method,provenance_document_hash,created_at) VALUES(?,?,?,?,?,?,?,?,?,'documented_trade_fifo',?,?)""",
   691	                    (
   692	                        _id("pflot"),
   693	                        f"pfevent_{event.event_fingerprint[:24]}",
   694	                        instrument_id,
   695	                        event.occurred_on,
   696	                        _text(event.quantity),
   697	                        _text(lot["remaining"]),
   698	                        _text(event.price_original),
   699	                        _text(event.fee_original) or "0",
   700	                        event.currency,
   701	                        event.document_hash,
   702	                        now,
   703	                    ),
   704	                )
   705	        for position in snapshot.positions:
   706	            instrument_id = position_map[position.row_reference]
   707	            matched_lots = lots.get(instrument_id, [])
   708	            quantity = sum((lot["remaining"] for lot in matched_lots), Decimal("0"))
   709	            complete = (
   710	                bool(matched_lots)
   711	                and quantity == position.quantity
   712	                and len({lot["event"].currency for lot in matched_lots}) == 1
   713	            )
   714	            calculated = (
   715	                sum(
   716	                    (
   717	                        lot["remaining"] * (lot["event"].price_original or 0)
   718	                        + (lot["event"].fee_original + lot["event"].tax_original)
   719	                        * (lot["remaining"] / lot["event"].quantity)
   720	                        for lot in matched_lots
   721	                        if lot["event"].quantity
   722	                    ),
   723	                    Decimal("0"),
   724	                )
   725	                if complete
   726	                else None
   727	            )
   728	            status = "complete" if complete else "partial" if matched_lots else "unavailable"
   729	            conn.execute(
   730	                """INSERT INTO postfinance_snapshot_positions(snapshot_position_id,snapshot_id,source_row_reference,instrument_id,asset_class,quantity,provider_average_cost_original,provider_cost_total_original,price_original,price_currency,value_chf,weight_pct,computed_cost_basis_original,computed_cost_basis_status,provenance_json,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
   731	                (
   732	                    _id("pfposition"),
   733	                    snapshot_id,
   734	                    position.row_reference,
   735	                    instrument_id,
   736	                    position.asset_class,
   737	                    _text(position.quantity),
   738	                    _text(position.provider_average_cost_original),
   739	                    _text(position.provider_cost_total_original),
   740	                    _text(position.market_price_original),
   741	                    position.price_currency,
   742	                    _money(position.market_value_chf),
   743	                    _text(position.weight_pct),
   744	                    _text(calculated),
   745	                    status,
   746	                    json.dumps(
   747	                        {
   748	                            "provider": "official_account_overview",
   749	                            "calculated": "documented_trade_fifo",
   750	                            "estimated": False,
   751	                        },
   752	                        sort_keys=True,
   753	                    ),
   754	                    now,
   755	                ),
   756	            )
   757	            conn.execute(
   758	                """INSERT INTO positions_snapshot(position_snapshot_id,snapshot_date,account_id,platform_id,instrument_id,quantity,average_cost_original,cost_basis_original,market_price_original,market_value_original,market_fx_rate_to_chf,market_value_chf,portfolio_weight_pct,category,data_quality_status,created_at) SELECT ?,?,?,a.platform_id,?,?,?,?,?,?,?,?,?,?,?,? FROM accounts a WHERE a.account_id=?""",
   759	                (
   760	                    _id("position"),
   761	                    snapshot.valuation_at[:10],
   762	                    accounts["etrading_depot"],
   763	                    instrument_id,
   764	                    _text(position.quantity),
   765	                    _text(position.provider_average_cost_original),
   766	                    _text(position.provider_cost_total_original),
   767	                    _text(position.market_price_original),
   768	                    _text(position.quantity * position.market_price_original),
   769	                    None,
   770	                    _money(position.market_value_chf),
   771	                    _text(position.weight_pct),
   772	                    position.asset_class,
   773	                    "verified",
   774	                    now,
   775	                    accounts["etrading_depot"],
   776	                ),
   777	            )
   778	        for cash in snapshot.cash:
   779	            conn.execute(
   780	                "INSERT INTO postfinance_snapshot_cash(snapshot_cash_id,snapshot_id,currency,amount_original,fx_rate_to_chf,value_chf,created_at) VALUES(?,?,?,?,?,?,?)",
   781	                (
   782	                    _id("pfcash"),
   783	                    snapshot_id,
   784	                    cash.currency,
   785	                    _text(cash.amount_original),
   786	                    _text(cash.fx_rate_to_chf),
   787	                    _money(cash.source_value_chf),
   788	                    now,
   789	                ),
   790	            )
   791	            conn.execute(
   792	                "INSERT INTO cash_balances(cash_balance_id,account_id,balance_date,currency,amount_original,fx_rate_to_chf,amount_chf,source_type,quality_status,notes,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)",
   793	                (
   794	                    _id("cash"),
   795	                    accounts["etrading_cash"],
   796	                    snapshot.valuation_at[:10],
   797	                    cash.currency,
   798	                    _text(cash.amount_original),
   799	                    _text(cash.fx_rate_to_chf),
   800	                    _money(cash.source_value_chf),
   801	                    "postfinance_official_import",
   802	                    "verified",
   803	                    "Official PostFinance account overview",
   804	                    now,
   805	                ),
   806	            )
   807	        conn.execute(
   808	            "INSERT INTO account_value_snapshots(snapshot_id,account_id,valuation_date,total_value_chf,currency,source_type,quality_status,notes,created_at,valuation_at,source_reference,is_active) VALUES(?,?,?,?,'CHF','postfinance_official_import','ok','Official PostFinance account overview',?,?,?,1)",
   809	            (
   810	                _id("acctval"),
   811	                accounts["etrading_depot"],
   812	                snapshot.valuation_at[:10],
   813	                _money(snapshot.total_value_chf),
   814	                now,
   815	                snapshot.valuation_at,
   816	                snapshot_id,
   817	            ),
   818	        )
   819	        conn.commit()
   820	    except IntegrityError as exc:
   821	        conn.rollback()
   822	        duplicate = conn.execute(
   823	            "SELECT batch_id,audit_id FROM postfinance_import_batches WHERE bundle_sha256=?",
   824	            (bundle.bundle_hash,),
   825	        ).fetchone()
   826	        if duplicate:
   827	            return {
   828	                "status": "confirmed",
   829	                "batch_id": duplicate["batch_id"],
   830	                "audit_id": duplicate["audit_id"],
   831	                "idempotent": True,
   832	            }
   833	        for ref, created in archive_refs:
   834	            if created:
   835	                _remove_archive(ref)
   836	        raise ValueError("PostFinance import conflicted with immutable data") from exc
   837	    except Exception:
   838	        conn.rollback()
   839	        for ref, created in archive_refs:
   840	            if created:
   841	                _remove_archive(ref)
   842	        raise
   843	    return {
   844	        "status": "confirmed",
   845	        "batch_id": batch_id,
   846	        "snapshot_id": snapshot_id,
   847	        "audit_id": audit_id,
   848	        "idempotent": False,
   849	    }
   850
   851
   852	def get_postfinance_summary(conn: Connection) -> dict[str, Any]:
   853	    latest = conn.execute(
   854	        """SELECT snapshot_id,snapshot_at,total_chf,securities_chf,cash_chf,stocks_chf,etfs_chf,
   855	                  position_count,cash_count,open_orders,difference_chf,reconciliation_status,created_at
   856	           FROM postfinance_snapshots ORDER BY snapshot_at DESC,created_at DESC LIMIT 1"""
   857	    ).fetchone()
   858	    if not latest:
   859	        return {
   860	            "latest_snapshot": None,


Output:
Chunk ID: 086d90
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3053
Output:
Total output lines: 1

     1	INITIAL_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS schema_migrations (\n    version INTEGER PRIMARY KEY,\n    name TEXT NOT NULL,\n    applied_at TEXT NOT NULL,\n    checksum TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS platforms (\n    platform_id TEXT PRIMARY KEY,\n    name TEXT NOT NULL UNIQUE,\n    platform_type TEXT NOT NULL,\n    country TEXT,\n    default_currency TEXT NOT NULL DEFAULT 'CHF',\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS accounts (\n    account_id TEXT PRIMARY KEY,\n    platform_id TEXT NOT NULL REFERENCES platforms(platform_id),\n    account_name TEXT NOT NULL,\n    account_type TEXT NOT NULL,\n    currency TEXT NOT NULL DEFAULT 'CHF',\n    performance_included INTEGER NOT NULL DEFAULT 1,\n    is_health_reserve INTEGER NOT NULL DEFAULT 0,\n    target_cash_min_chf NUMERIC,\n    target_cash_max_chf NUMERIC,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT,\n    UNIQUE(platform_id, account_name)\n);\n\nCREATE TABLE IF NOT EXISTS instruments (\n    instrument_id TEXT PRIMARY KEY,\n    asset_class TEXT NOT NULL,\n    name TEXT NOT NULL,\n    ticker TEXT,\n    isin TEXT,\n    exchange TEXT,\n    currency TEXT NOT NULL,\n    country TEXT,\n    sector TEXT,\n    industry TEXT,\n    provider_symbol TEXT,\n    data_provider_primary TEXT,\n    position_category TEXT,\n    ter TEXT,\n    distribution_policy TEXT,\n    index_name TEXT,\n    fund_domicile TEXT,\n    benchmark TEXT,\n    data_provider_fallback TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS transactions (\n    transaction_id TEXT PRIMARY KEY,\n    transaction_type TEXT NOT NULL,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    instrument_id TEXT REFERENCES instruments(instrument_id),\n    trade_date TEXT NOT NULL,\n    settlement_date TEXT,\n    quantity TEXT,\n    price_original TEXT,\n    gross_amount_original TEXT,\n    fee_original TEXT DEFAULT '0',\n    tax_original TEXT DEFAULT '0',\n    net_amount_original TEXT,\n    currency_original TEXT NOT NULL,\n    fx_rate_to_chf TEXT,\n    fx_source TEXT,\n    fx_status TEXT NOT NULL DEFAULT 'ok',\n    gross_amount_chf TEXT,\n    fee_chf TEXT,\n    tax_chf TEXT,\n    net_amount_chf TEXT,\n    source_type TEXT NOT NULL,\n    source_id TEXT,\n    external_transaction_id TEXT,\n    row_hash TEXT,\n    is_confirmed INTEGER NOT NULL DEFAULT 1,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS positions_snapshot (\n    position_snapshot_id TEXT PRIMARY KEY,\n    snapshot_date TEXT NOT NULL,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    platform_id TEXT NOT NULL REFERENCES platforms(platform_id),\n    instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),\n    quantity NUMERIC NOT NULL,\n    average_cost_original NUMERIC,\n    cost_basis_original NUMERIC,\n    cost_basis_chf NUMERIC,\n    market_price_original NUMERIC,\n    market_value_original NUMERIC,\n    market_fx_rate_to_chf NUMERIC,\n    market_value_chf NUMERIC,\n    unrealized_price_pnl_chf NUMERIC,\n    unrealized_fx_pnl_chf NUMERIC,\n    realized_pnl_chf NUMERIC,\n    income_chf NUMERIC,\n    fees_chf NUMERIC,\n    taxes_chf NUMERIC,\n    total_return_chf NUMERIC,\n    portfolio_weight_pct NUMERIC,\n    category TEXT,\n    data_quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL,\n    UNIQUE(snapshot_date, account_id, instrument_id)\n);\n\nCREATE TABLE IF NOT EXISTS cash_balances (\n    cash_balance_id TEXT PRIMARY KEY,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    balance_date TEXT NOT NULL,\n    currency TEXT NOT NULL,\n    amount_original NUMERIC NOT NULL,\n    fx_rate_to_chf NUMER…1053 tokens truncated…_id TEXT REFERENCES crypto_assets(asset_id),\n    name TEXT NOT NULL,\n    asset_class TEXT NOT NULL,\n    reason TEXT NOT NULL,\n    target_entry_price NUMERIC,\n    target_entry_currency TEXT,\n    desired_position_size_chf NUMERIC,\n    desired_weight_pct NUMERIC,\n    trigger_rules TEXT,\n    risk_notes TEXT,\n    investment_case TEXT,\n    bear_case TEXT,\n    sources TEXT,\n    status TEXT NOT NULL DEFAULT 'active',\n    next_review_date TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS reports (\n    report_id TEXT PRIMARY KEY,\n    report_type TEXT NOT NULL,\n    title TEXT NOT NULL,\n    period_start TEXT,\n    period_end TEXT,\n    generated_at TEXT NOT NULL,\n    file_path TEXT,\n    format TEXT NOT NULL,\n    data_quality_status TEXT NOT NULL DEFAULT 'ok',\n    summary_json TEXT,\n    created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS alerts (\n    alert_id TEXT PRIMARY KEY,\n    priority TEXT NOT NULL,\n    category TEXT NOT NULL,\n    entity_type TEXT,\n    entity_id TEXT,\n    rule_id TEXT,\n    message TEXT NOT NULL,\n    evidence_json TEXT,\n    status TEXT NOT NULL DEFAULT 'active',\n    created_at TEXT NOT NULL,\n    resolved_at TEXT,\n    muted_until TEXT,\n    last_seen_at TEXT,\n    occurrence_count INTEGER NOT NULL DEFAULT 1,\n    fingerprint TEXT,\n    dedup_key TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_alerts_dedup ON alerts(dedup_key, status);\n\nCREATE TABLE IF NOT EXISTS audit_log (\n    audit_id TEXT PRIMARY KEY,\n    timestamp TEXT NOT NULL,\n    source TEXT NOT NULL,\n    action TEXT NOT NULL,\n    entity_type TEXT NOT NULL,\n    entity_id TEXT NOT NULL,\n    old_values_json TEXT,\n    new_values_json TEXT,\n    user_text_note TEXT,\n    original_input_text TEXT,\n    confirmed INTEGER NOT NULL DEFAULT 1,\n    confirmation_timestamp TEXT,\n    auto_parsed INTEGER NOT NULL DEFAULT 0,\n    parse_confidence NUMERIC,\n    created_by TEXT NOT NULL DEFAULT 'system',\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS import_sessions (\n    import_session_id TEXT PRIMARY KEY,\n    import_type TEXT NOT NULL,\n    source_filename TEXT NOT NULL,\n    source_hash TEXT,\n    started_at TEXT NOT NULL,\n    finished_at TEXT,\n    status TEXT NOT NULL,\n    rows_total INTEGER DEFAULT 0,\n    rows_imported INTEGER DEFAULT 0,\n    rows_failed INTEGER DEFAULT 0,\n    errors_json TEXT,\n    notes TEXT\n);\n\nCREATE TABLE IF NOT EXISTS decision_journal (\n    decision_id TEXT PRIMARY KEY,\n    decision_date TEXT NOT NULL,\n    decision_type TEXT NOT NULL,\n    entity_type TEXT,\n    entity_id TEXT,\n    system_recommendation TEXT,\n    human_decision TEXT NOT NULL,\n    rationale TEXT NOT NULL,\n    investment_case TEXT,\n    risks TEXT,\n    exit_rule TEXT,\n    alternatives_considered TEXT,\n    sources TEXT,\n    review_date TEXT,\n    outcome_status TEXT,\n    outcome_return_chf NUMERIC,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_accounts_platform_id ON accounts(platform_id);\nCREATE INDEX IF NOT EXISTS idx_transactions_account_date ON transactions(account_id, trade_date);\nCREATE INDEX IF NOT EXISTS idx_transactions_instrument_date ON transactions(instrument_id, trade_date);\nCREATE INDEX IF NOT EXISTS idx_transactions_type ON transactions(transaction_type);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_external_id ON transactions(external_transaction_id) WHERE external_transaction_id IS NOT NULL AND external_transaction_id != '';\nCREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_row_hash ON transactions(row_hash) WHERE row_hash IS NOT NULL AND row_hash != '';\nCREATE INDEX IF NOT EXISTS idx_crypto_transactions_asset_datetime ON crypto_transactions(asset_id, transaction_datetime);\nCREATE INDEX IF NOT EXISTS idx_alerts_priority_status ON alerts(priority, status);\nCREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id);\n"


Output:
Chunk ID: e95c4b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1405
Output:
   145	            fees_original TEXT NOT NULL,
   146	            currency TEXT NOT NULL,
   147	            method TEXT NOT NULL CHECK(method='documented_trade_fifo'),
   148	            provenance_document_hash TEXT NOT NULL REFERENCES postfinance_documents(document_hash),
   149	            created_at TEXT NOT NULL
   150	        );
   151	        CREATE UNIQUE INDEX IF NOT EXISTS ux_account_value_postfinance_official_reference
   152	            ON account_value_snapshots(source_reference)
   153	            WHERE source_type='postfinance_official_import' AND source_reference IS NOT NULL;
   154
   155	        CREATE TRIGGER IF NOT EXISTS postfinance_batches_immutable BEFORE UPDATE ON postfinance_import_batches
   156	        BEGIN SELECT RAISE(ABORT, 'postfinance import batches are immutable'); END;
   157	        CREATE TRIGGER IF NOT EXISTS postfinance_batches_no_delete BEFORE DELETE ON postfinance_import_batches
   158	        BEGIN SELECT RAISE(ABORT, 'postfinance import batches cannot be deleted'); END;
   159	        CREATE TRIGGER IF NOT EXISTS postfinance_documents_immutable BEFORE UPDATE ON postfinance_documents
   160	        BEGIN SELECT RAISE(ABORT, 'postfinance documents are immutable'); END;
   161	        CREATE TRIGGER IF NOT EXISTS postfinance_documents_no_delete BEFORE DELETE ON postfinance_documents
   162	        BEGIN SELECT RAISE(ABORT, 'postfinance documents cannot be deleted'); END;
   163	        CREATE TRIGGER IF NOT EXISTS postfinance_batch_documents_immutable BEFORE UPDATE ON postfinance_batch_documents
   164	        BEGIN SELECT RAISE(ABORT, 'postfinance batch document links are immutable'); END;
   165	        CREATE TRIGGER IF NOT EXISTS postfinance_batch_documents_no_delete BEFORE DELETE ON postfinance_batch_documents
   166	        BEGIN SELECT RAISE(ABORT, 'postfinance batch document links cannot be deleted'); END;
   167	        CREATE TRIGGER IF NOT EXISTS postfinance_snapshots_immutable BEFORE UPDATE ON postfinance_snapshots
   168	        BEGIN SELECT RAISE(ABORT, 'postfinance snapshots are immutable'); END;
   169	        CREATE TRIGGER IF NOT EXISTS postfinance_snapshots_no_delete BEFORE DELETE ON postfinance_snapshots
   170	        BEGIN SELECT RAISE(ABORT, 'postfinance snapshots cannot be deleted'); END;
   171	        CREATE TRIGGER IF NOT EXISTS postfinance_snapshot_positions_immutable BEFORE UPDATE ON postfinance_snapshot_positions
   172	        BEGIN SELECT RAISE(ABORT, 'postfinance snapshot positions are immutable'); END;
   173	        CREATE TRIGGER IF NOT EXISTS postfinance_snapshot_positions_no_delete BEFORE DELETE ON postfinance_snapshot_positions
   174	        BEGIN SELECT RAISE(ABORT, 'postfinance snapshot positions cannot be deleted'); END;
   175	        CREATE TRIGGER IF NOT EXISTS postfinance_snapshot_cash_immutable BEFORE UPDATE ON postfinance_snapshot_cash
   176	        BEGIN SELECT RAISE(ABORT, 'postfinance snapshot cash is immutable'); END;
   177	        CREATE TRIGGER IF NOT EXISTS postfinance_snapshot_cash_no_delete BEFORE DELETE ON postfinance_snapshot_cash
   178	        BEGIN SELECT RAISE(ABORT, 'postfinance snapshot cash cannot be deleted'); END;
   179	        CREATE TRIGGER IF NOT EXISTS postfinance_events_immutable BEFORE UPDATE ON postfinance_ledger_events
   180	        BEGIN SELECT RAISE(ABORT, 'postfinance ledger events are immutable'); END;
   181	        CREATE TRIGGER IF NOT EXISTS postfinance_events_no_delete BEFORE DELETE ON postfinance_ledger_events
   182	        BEGIN SELECT RAISE(ABORT, 'postfinance ledger events cannot be deleted'); END;
   183	        CREATE TRIGGER IF NOT EXISTS postfinance_event_components_immutable BEFORE UPDATE ON postfinance_event_components
   184	        BEGIN SELECT RAISE(ABORT, 'postfinance event components are immutable'); END;
   185	        CREATE TRIGGER IF NOT EXISTS postfinance_event_components_no_delete BEFORE DELETE ON postfinance_event_components
   186	        BEGIN SELECT RAISE(ABORT, 'postfinance event components cannot be deleted'); END;
   187	        CREATE TRIGGER IF NOT EXISTS postfinance_cost_basis_immutable BEFORE UPDATE ON postfinance_cost_basis_lots
   188	        BEGIN SELECT RAISE(ABORT, 'postfinance cost basis is immutable'); END;
   189	        CREATE TRIGGER IF NOT EXISTS postfinance_cost_basis_no_delete BEFORE DELETE ON postfinance_cost_basis_lots
   190	        BEGIN SELECT RAISE(ABORT, 'postfinance cost basis cannot be deleted'); END;
   191	        CREATE TRIGGER IF NOT EXISTS postfinance_official_account_values_immutable BEFORE UPDATE ON account_value_snapshots
   192	        WHEN OLD.source_type='postfinance_official_import'
   193	        BEGIN SELECT RAISE(ABORT, 'official postfinance account valuations are immutable'); END;
   194	        CREATE TRIGGER IF NOT EXISTS postfinance_official_account_values_no_delete BEFORE DELETE ON account_value_snapshots
   195	        WHEN OLD.source_type='postfinance_official_import'
   196	        BEGIN SELECT RAISE(ABORT, 'official postfinance account valuations cannot be deleted'); END;
   197	        CREATE TRIGGER IF NOT EXISTS postfinance_audit_immutable_update
   198	        BEFORE UPDATE ON audit_log WHEN OLD.entity_type='postfinance_import_batch'
   199	        BEGIN SELECT RAISE(ABORT, 'postfinance import audit is immutable'); END;
   200	        CREATE TRIGGER IF NOT EXISTS postfinance_audit_no_delete
   201	        BEFORE DELETE ON audit_log WHEN OLD.entity_type='postfinance_import_batch'
   202	        BEGIN SELECT RAISE(ABORT, 'postfinance import audit cannot be deleted'); END;
   203	        """
   204	    )


I’ve got enough evidence for one likely blocker, but I’ll still run the repository verification commands now so the report includes the current test state. No files will be modified.


Output:
Chunk ID: 9a9c44
Wall time: 0.0000 seconds
Process exited with code 2
Original token count: 34
Output:
.venv/bin/python -m compileall src tests
make: .venv/bin/python: No such file or directory
make: *** [Makefile:9: compile] Error 127


Output:
Chunk ID: 65763b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 0
Output:


nl -ba src/jarvis_finance/storage/migrations.py | sed -n '1518,1545p'


rg -n "CREATE UNIQUE INDEX|UNIQUE\(" src/jarvis_finance/storage/migrations.py src/jarvis_finance/storage/schema.py | head -100


Output:
Chunk ID: 345ce8
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 344
Output:
  1518	    _add_missing_columns(conn, "crypto_assets", {"binance_symbol": "TEXT", "binance_mapping_status": "TEXT NOT NULL DEFAULT 'missing'"})
  1519
  1520
  1521	def _create_account_value_snapshot_tables(conn: Connection) -> None:
  1522	    conn.executescript(
  1523	        """
  1524	        CREATE TABLE IF NOT EXISTS account_value_snapshots (
  1525	            snapshot_id TEXT PRIMARY KEY,
  1526	            account_id TEXT NOT NULL REFERENCES accounts(account_id),
  1527	            valuation_date TEXT NOT NULL,
  1528	            total_value_chf TEXT NOT NULL,
  1529	            currency TEXT NOT NULL DEFAULT 'CHF',
  1530	            source_type TEXT NOT NULL DEFAULT 'manual_total_value',
  1531	            quality_status TEXT NOT NULL DEFAULT 'ok',
  1532	            notes TEXT,
  1533	            created_at TEXT NOT NULL,
  1534	            updated_at TEXT
  1535	        );
  1536	        CREATE INDEX IF NOT EXISTS idx_account_value_snapshots_account_date ON account_value_snapshots(account_id, valuation_date, created_at);
  1537	        """
  1538	    )
  1539
  1540
  1541	def _create_cash_account_snapshot_tables(conn: Connection) -> None:
  1542	    _add_missing_columns(conn, "accounts", {
  1543	        "balance_mode": "TEXT NOT NULL DEFAULT 'manual'",
  1544	        "portfolio_bucket": "TEXT NOT NULL DEFAULT 'cash'",
  1545	    })


Output:
Chunk ID: bc215f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 4048
Output:
src/jarvis_finance/storage/schema.py:1:INITIAL_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS schema_migrations (\n    version INTEGER PRIMARY KEY,\n    name TEXT NOT NULL,\n    applied_at TEXT NOT NULL,\n    checksum TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS platforms (\n    platform_id TEXT PRIMARY KEY,\n    name TEXT NOT NULL UNIQUE,\n    platform_type TEXT NOT NULL,\n    country TEXT,\n    default_currency TEXT NOT NULL DEFAULT 'CHF',\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS accounts (\n    account_id TEXT PRIMARY KEY,\n    platform_id TEXT NOT NULL REFERENCES platforms(platform_id),\n    account_name TEXT NOT NULL,\n    account_type TEXT NOT NULL,\n    currency TEXT NOT NULL DEFAULT 'CHF',\n    performance_included INTEGER NOT NULL DEFAULT 1,\n    is_health_reserve INTEGER NOT NULL DEFAULT 0,\n    target_cash_min_chf NUMERIC,\n    target_cash_max_chf NUMERIC,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT,\n    UNIQUE(platform_id, account_name)\n);\n\nCREATE TABLE IF NOT EXISTS instruments (\n    instrument_id TEXT PRIMARY KEY,\n    asset_class TEXT NOT NULL,\n    name TEXT NOT NULL,\n    ticker TEXT,\n    isin TEXT,\n    exchange TEXT,\n    currency TEXT NOT NULL,\n    country TEXT,\n    sector TEXT,\n    industry TEXT,\n    provider_symbol TEXT,\n    data_provider_primary TEXT,\n    position_category TEXT,\n    ter TEXT,\n    distribution_policy TEXT,\n    index_name TEXT,\n    fund_domicile TEXT,\n    benchmark TEXT,\n    data_provider_fallback TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS transactions (\n    transaction_id TEXT PRIMARY KEY,\n    transaction_type TEXT NOT NULL,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    instrument_id TEXT REFERENCES instruments(instrument_id),\n    trade_date TEXT NOT NULL,\n    settlement_date TEXT,\n    quantity TEXT,\n    price_original TEXT,\n    gross_amount_original TEXT,\n    fee_original TEXT DEFAULT '0',\n    tax_original TEXT DEFAULT '0',\n    net_amount_original TEXT,\n    currency_original TEXT NOT NULL,\n    fx_rate_to_chf TEXT,\n    fx_source TEXT,\n    fx_status TEXT NOT NULL DEFAULT 'ok',\n    gross_amount_chf TEXT,\n    fee_chf TEXT,\n    tax_chf TEXT,\n    net_amount_chf TEXT,\n    source_type TEXT NOT NULL,\n    source_id TEXT,\n    external_transaction_id TEXT,\n    row_hash TEXT,\n    is_confirmed INTEGER NOT NULL DEFAULT 1,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS positions_snapshot (\n    position_snapshot_id TEXT PRIMARY KEY,\n    snapshot_date TEXT NOT NULL,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    platform_id TEXT NOT NULL REFERENCES platforms(platform_id),\n    instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),\n    quantity NUMERIC NOT NULL,\n    average_cost_original NUMERIC,\n    cost_basis_original NUMERIC,\n    cost_basis_chf NUMERIC,\n    market_price_original NUMERIC,\n    market_value_original NUMERIC,\n    market_fx_rate_to_chf NUMERIC,\n    market_value_chf NUMERIC,\n    unrealized_price_pnl_chf NUMERIC,\n    unrealized_fx_pnl_chf NUMERIC,\n    realized_pnl_chf NUMERIC,\n    income_chf NUMERIC,\n    fees_chf NUMERIC,\n    taxes_chf NUMERIC,\n    total_return_chf NUMERIC,\n    portfolio_weight_pct NUMERIC,\n    category TEXT,\n    data_quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL,\n    UNIQUE(snapshot_date, account_id, instrument_id)\n);\n\nCREATE TABLE IF NOT EXISTS cash_balances (\n    cash_balance_id TEXT PRIMARY KEY,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    balance_date TEXT NOT NULL,\n    currency TEXT NOT NULL,\n    amount_original NUMERIC NOT NULL,\n    fx_rate_to_chf NUMERIC,\n    amount_chf NUMERIC,\n    source_type TEXT NOT NULL,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    UNIQUE(account_id, balance_date, currency, source_type)\n);\n\nCREATE TABLE IF NOT EXISTS fx_rates (\n    fx_rate_id TEXT PRIMARY KEY,\n    base_currency TEXT NOT NULL,\n    quote_currency TEXT NOT NULL DEFAULT 'CHF',\n    rate_date TEXT NOT NULL,\n    rate_timestamp TEXT,\n    rate NUMERIC NOT NULL,\n    provider TEXT NOT NULL,\n    rate_type TEXT NOT NULL,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL,\n    UNIQUE(base_currency, quote_currency, rate_date, provider, rate_type)\n);\n\nCREATE TABLE IF NOT EXISTS market_prices (\n    market_price_id TEXT PRIMARY KEY,\n    instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),\n    price_date TEXT NOT NULL,\n    price_timestamp TEXT,\n    open NUMERIC,\n    high NUMERIC,\n    low NUMERIC,\n    close NUMERIC NOT NULL,\n    adjusted_close NUMERIC,\n    currency TEXT NOT NULL,\n    provider TEXT NOT NULL,\n    provider_symbol TEXT,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL,\n    UNIQUE(instrument_id, price_date, provider)\n);\n\nCREATE TABLE IF NOT EXISTS crypto_wallets (\n    wallet_id TEXT PRIMARY KEY,\n    wallet_name TEXT NOT NULL UNIQUE,\n    wallet_type TEXT NOT NULL,\n    platform_provider TEXT,\n    network_chain TEXT,\n    wallet_address TEXT,\n    owner TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    last_verified_at TEXT,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS crypto_assets (\n    asset_id TEXT PRIMARY KEY,\n    coin_name TEXT NOT NULL,\n    symbol TEXT NOT NULL,\n    coingecko_id TEXT UNIQUE,\n    network_chain_default TEXT,\n    is_stablecoin INTEGER NOT NULL DEFAULT 0,\n    price_provider_primary TEXT NOT NULL DEFAULT 'CoinGecko',\n    price_provider_fallback TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS crypto_holdings (\n    crypto_holding_id TEXT PRIMARY KEY,\n    asset_id TEXT NOT NULL REFERENCES crypto_assets(asset_id),\n    wallet_id TEXT NOT NULL REFERENCES crypto_wallets(wallet_id),\n    quantity TEXT NOT NULL,\n    acquisition_source TEXT,\n    last_verified_at TEXT,\n    verification_status TEXT NOT NULL DEFAULT 'unverified',\n    legacy_snapshot_value_original TEXT,\n    legacy_snapshot_value_chf TEXT,\n    legacy_snapshot_currency TEXT,\n    legacy_snapshot_date TEXT,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT,\n    UNIQUE(asset_id, wallet_id)\n);\n\nCREATE TABLE IF NOT EXISTS crypto_transactions (\n    crypto_transaction_id TEXT PRIMARY KEY,\n    transaction_id TEXT REFERENCES transactions(transaction_id),\n    transaction_type TEXT NOT NULL,\n    asset_id TEXT NOT NULL REFERENCES crypto_assets(asset_id),\n    quantity TEXT NOT NULL,\n    price_original TEXT,\n    currency_original TEXT,\n    gross_amount_original TEXT,\n    fee_quantity TEXT,\n    fee_original TEXT,\n    fee_currency TEXT,\n    fx_rate_to_chf TEXT,\n    fx_source TEXT,\n    amount_chf TEXT,\n    from_wallet_id TEXT REFERENCES crypto_wallets(wallet_id),\n    to_wallet_id TEXT REFERENCES crypto_wallets(wallet_id),\n    transaction_datetime TEXT NOT NULL,\n    tx_hash TEXT,\n    source TEXT NOT NULL,\n    confirmation_status TEXT NOT NULL DEFAULT 'confirmed',\n    parse_confidence NUMERIC,\n    original_input_text TEXT,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS crypto_prices (\n    crypto_price_id TEXT PRIMARY KEY,\n    asset_id TEXT NOT NULL REFERENCES crypto_assets(asset_id),\n    coingecko_id TEXT,\n    price_currency TEXT NOT NULL,\n    price TEXT NOT NULL,\n    provider TEXT NOT NULL DEFAULT 'CoinGecko',\n    provider_timestamp TEXT,\n    fetched_at TEXT NOT NULL,\n    quality_status TEXT NOT NULL DEFAULT 'fresh',\n    error_message TEXT\n);\n\nCREATE TABLE IF NOT EXISTS watchlist (\n    watchlist_id TEXT PRIMARY KEY,\n    instrument_id TEXT REFERENCES instruments(instrument_id),\n    crypto_asset_id TEXT REFERENCES crypto_assets(asset_id),\n    name TEXT NOT NULL,\n    asset_class TEXT NOT NULL,\n    reason TEXT NOT NULL,\n    target_entry_price NUMERIC,\n    target_entry_currency TEXT,\n    desired_position_size_chf NUMERIC,\n    desired_weight_pct NUMERIC,\n    trigger_rules TEXT,\n    risk_notes TEXT,\n    investment_case TEXT,\n    bear_case TEXT,\n    sources TEXT,\n    status TEXT NOT NULL DEFAULT 'active',\n    next_review_date TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS reports (\n    report_id TEXT PRIMARY KEY,\n    report_type TEXT NOT NULL,\n    title TEXT NOT NULL,\n    period_start TEXT,\n    period_end TEXT,\n    generated_at TEXT NOT NULL,\n    file_path TEXT,\n    format TEXT NOT NULL,\n    data_quality_status TEXT NOT NULL DEFAULT 'ok',\n    summary_json TEXT,\n    created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS alerts (\n    alert_id TEXT PRIMARY KEY,\n    priority TEXT NOT NULL,\n    category TEXT NOT NULL,\n    entity_type TEXT,\n    entity_id TEXT,\n    rule_id TEXT,\n    message TEXT NOT NULL,\n    evidence_json TEXT,\n    status TEXT NOT NULL DEFAULT 'active',\n    created_at TEXT NOT NULL,\n    resolved_at TEXT,\n    muted_until TEXT,\n    last_seen_at TEXT,\n    occurrence_count INTEGER NOT NULL DEFAULT 1,\n    fingerprint TEXT,\n    dedup_key TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_alerts_dedup ON alerts(dedup_key, status);\n\nCREATE TABLE IF NOT EXISTS audit_log (\n    audit_id TEXT PRIMARY KEY,\n    timestamp TEXT NOT NULL,\n    source TEXT NOT NULL,\n    action TEXT NOT NULL,\n    entity_type TEXT NOT NULL,\n    entity_id TEXT NOT NULL,\n    old_values_json TEXT,\n    new_values_json TEXT,\n    user_text_note TEXT,\n    original_input_text TEXT,\n    confirmed INTEGER NOT NULL DEFAULT 1,\n    confirmation_timestamp TEXT,\n    auto_parsed INTEGER NOT NULL DEFAULT 0,\n    parse_confidence NUMERIC,\n    created_by TEXT NOT NULL DEFAULT 'system',\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS import_sessions (\n    import_session_id TEXT PRIMARY KEY,\n    import_type TEXT NOT NULL,\n    source_filename TEXT NOT NULL,\n    source_hash TEXT,\n    started_at TEXT NOT NULL,\n    finished_at TEXT,\n    status TEXT NOT NULL,\n    rows_total INTEGER DEFAULT 0,\n    rows_imported INTEGER DEFAULT 0,\n    rows_failed INTEGER DEFAULT 0,\n    errors_json TEXT,\n    notes TEXT\n);\n\nCREATE TABLE IF NOT EXISTS decision_journal (\n    decision_id TEXT PRIMARY KEY,\n    decision_date TEXT NOT NULL,\n    decision_type TEXT NOT NULL,\n    entity_type TEXT,\n    entity_id TEXT,\n    system_recommendation TEXT,\n    human_decision TEXT NOT NULL,\n    rationale TEXT NOT NULL,\n    investment_case TEXT,\n    risks TEXT,\n    exit_rule TEXT,\n    alternatives_considered TEXT,\n    sources TEXT,\n    review_date TEXT,\n    outcome_status TEXT,\n    outcome_return_chf NUMERIC,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_accounts_platform_id ON accounts(platform_id);\nCREATE INDEX IF NOT EXISTS idx_transactions_account_date ON transactions(account_id, trade_date);\nCREATE INDEX IF NOT EXISTS idx_transactions_instrument_date ON transactions(instrument_id, trade_date);\nCREATE INDEX IF NOT EXISTS idx_transactions_type ON transactions(transaction_type);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_external_id ON transactions(external_transaction_id) WHERE external_transaction_id IS NOT NULL AND external_transaction_id != '';\nCREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_row_hash ON transactions(row_hash) WHERE row_hash IS NOT NULL AND row_hash != '';\nCREATE INDEX IF NOT EXISTS idx_crypto_transactions_asset_datetime ON crypto_transactions(asset_id, transaction_datetime);\nCREATE INDEX IF NOT EXISTS idx_alerts_priority_status ON alerts(priority, status);\nCREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id);\n"
src/jarvis_finance/storage/migrations.py:109:        conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_crypto_holdings_asset_wallet ON crypto_holdings(asset_id, wallet_id)")
src/jarvis_finance/storage/migrations.py:302:        CREATE UNIQUE INDEX IF NOT EXISTS idx_broker_execution_plan_review_item ON broker_import_execution_plans(review_item_id);
src/jarvis_finance/storage/migrations.py:303:        CREATE UNIQUE INDEX IF NOT EXISTS idx_broker_execution_plan_source_hash ON broker_import_execution_plans(source_row_hash, target_account_id, target_instrument_id, transaction_type);
src/jarvis_finance/storage/migrations.py:338:            UNIQUE(instrument_id, provider)
src/jarvis_finance/storage/migrations.py:453:    conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_market_prices_unique_provider_date ON market_prices(instrument_id, price_date, provider)")
src/jarvis_finance/storage/migrations.py:455:    conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_fx_rates_unique_provider_date ON fx_rates(base_currency, quote_currency, rate_date, provider, rate_type)")
src/jarvis_finance/storage/migrations.py:476:        """CREATE UNIQUE INDEX idx_instruments_unique_isin
src/jarvis_finance/storage/migrations.py:1125:        CREATE UNIQUE INDEX IF NOT EXISTS idx_budget_merchants_normalized ON budget_merchants(normalized_name);
src/jarvis_finance/storage/migrations.py:1168:        CREATE UNIQUE INDEX IF NOT EXISTS idx_budget_category_baselines_unique ON budget_category_baselines(category_id, year, COALESCE(month, ''), baseline_type);
src/jarvis_finance/storage/migrations.py:1376:        CREATE UNIQUE INDEX IF NOT EXISTS idx_grocery_detail_cache_url ON grocery_product_details_cache(retailer, product_url);
src/jarvis_finance/storage/migrations.py:1479:            UNIQUE(instrument_id, provider, provider_symbol, timestamp, interval)
src/jarvis_finance/storage/migrations.py:1499:            UNIQUE(instrument_id, provider, provider_symbol, range_key, interval_key, timestamp)
src/jarvis_finance/storage/migrations.py:1513:            UNIQUE(asset_id, provider, provider_symbol, timestamp, interval, currency)
src/jarvis_finance/storage/migrations.py:1609:        CREATE UNIQUE INDEX IF NOT EXISTS idx_budget_transfer_pairs_confirmed_source
src/jarvis_finance/storage/migrations.py:1611:        CREATE UNIQUE INDEX IF NOT EXISTS idx_budget_transfer_pairs_confirmed_target
src/jarvis_finance/storage/migrations.py:1628:    CREATE UNIQUE INDEX IF NOT EXISTS idx_portfolio_policies_one_active ON portfolio_policies(is_active) WHERE is_active=1;
src/jarvis_finance/storage/migrations.py:1632:      UNIQUE(policy_id, asset_class)
src/jarvis_finance/storage/migrations.py:1674:        CREATE UNIQUE INDEX IF NOT EXISTS idx_portfolio_policies_confirmation_id
src/jarvis_finance/storage/migrations.py:1735:            UNIQUE(scope_kind, scope_id, valuation_at, snapshot_version)
src/jarvis_finance/storage/migrations.py:1802:            UNIQUE(batch_id, source_record_ref, record_kind)
src/jarvis_finance/storage/migrations.py:1862:            UNIQUE(source_key, as_of, input_fingerprint)
src/jarvis_finance/storage/migrations.py:1886:            UNIQUE(run_id, policy_id, benchmark_reference, as_of)
src/jarvis_finance/storage/migrations.py:1995:            UNIQUE(portfolio_id, snapshot_date, batch_id)
src/jarvis_finance/storage/migrations.py:1999:        CREATE UNIQUE INDEX IF NOT EXISTS ux_truewealth_snapshot_portfolio_date
src/jarvis_finance/storage/migrations.py:2015:            UNIQUE(snapshot_id, source_row_reference),
src/jarvis_finance/storage/migrations.py:2016:            UNIQUE(snapshot_id, isin)
src/jarvis_finance/storage/migrations.py:2032:            UNIQUE(snapshot_id, source_row_reference),
src/jarvis_finance/storage/migrations.py:2033:            UNIQUE(snapshot_id, currency)
src/jarvis_finance/storage/migrations.py:2038:        CREATE UNIQUE INDEX IF NOT EXISTS ux_account_value_truewealth_official_reference


`git diff --check` is clean. The mandated `make PYTHON=.venv/bin/python verify` cannot run in this checkout because `.venv/bin/python` does not exist, so I’m not going to substitute that silently in the final review.


nl -ba src/jarvis_finance/storage/postfinance_schema.py | sed -n '1,80p'


nl -ba src/jarvis_finance/services/postfinance_service.py | sed -n '35,130p'


nl -ba src/jarvis_finance/api/routers/postfinance.py | sed -n '1,80p'


nl -ba src/jarvis_finance/services/postfinance_service.py | sed -n '852,930p'


Output:
Chunk ID: 66a88f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1136
Output:
     1	from __future__ import annotations
     2
     3	from sqlite3 import Connection
     4
     5
     6	def create_postfinance_ledger_import_v1(conn: Connection) -> None:
     7	    """Create immutable PostFinance source, ledger, snapshot and cost-basis tables."""
     8	    conn.executescript(
     9	        """
    10	        CREATE TABLE IF NOT EXISTS postfinance_account_roles (
    11	            role TEXT PRIMARY KEY CHECK(role IN ('efinance','etrading_depot','etrading_cash')),
    12	            account_id TEXT NOT NULL REFERENCES accounts(account_id),
    13	            source_reference_hash TEXT,
    14	            created_at TEXT NOT NULL,
    15	            UNIQUE(account_id, role)
    16	        );
    17	        CREATE TABLE IF NOT EXISTS postfinance_import_batches (
    18	            batch_id TEXT PRIMARY KEY,
    19	            bundle_sha256 TEXT NOT NULL UNIQUE CHECK(length(bundle_sha256)=64),
    20	            zip_sha256 TEXT NOT NULL CHECK(length(zip_sha256)=64),
    21	            overview_sha256 TEXT NOT NULL CHECK(length(overview_sha256)=64),
    22	            parser_id TEXT NOT NULL,
    23	            parser_version TEXT NOT NULL,
    24	            db_revision TEXT NOT NULL,
    25	            archive_reference TEXT NOT NULL,
    26	            snapshot_at TEXT NOT NULL,
    27	            status TEXT NOT NULL CHECK(status='confirmed'),
    28	            audit_id TEXT NOT NULL,
    29	            confirmed_at TEXT NOT NULL,
    30	            confirmed_by TEXT NOT NULL DEFAULT 'user'
    31	        );
    32	        CREATE TABLE IF NOT EXISTS postfinance_documents (
    33	            document_hash TEXT PRIMARY KEY CHECK(length(document_hash)=64),
    34	            filename_hash TEXT NOT NULL CHECK(length(filename_hash)=64),
    35	            semantic_identity TEXT NOT NULL UNIQUE CHECK(length(semantic_identity)=64),
    36	            semantic_reference_hash TEXT NOT NULL CHECK(length(semantic_reference_hash)=64),
    37	            document_type TEXT NOT NULL,
    38	            document_date TEXT,
    39	            account_reference_hash TEXT,
    40	            account_role TEXT NOT NULL CHECK(account_role IN ('efinance','etrading_depot','etrading_cash')),
    41	            page_count INTEGER NOT NULL CHECK(page_count > 0),
    42	            archive_reference TEXT NOT NULL,
    43	            parser_version TEXT NOT NULL,
    44	            created_at TEXT NOT NULL
    45	        );
    46	        CREATE TABLE IF NOT EXISTS postfinance_batch_documents (
    47	            batch_id TEXT NOT NULL REFERENCES postfinance_import_batches(batch_id),
    48	            document_hash TEXT NOT NULL REFERENCES postfinance_documents(document_hash),
    49	            PRIMARY KEY(batch_id, document_hash)
    50	        );
    51	        CREATE TABLE IF NOT EXISTS postfinance_snapshots (
    52	            snapshot_id TEXT PRIMARY KEY,
    53	            batch_id TEXT NOT NULL UNIQUE REFERENCES postfinance_import_batches(batch_id),
    54	            account_id TEXT NOT NULL REFERENCES accounts(account_id),
    55	            snapshot_at TEXT NOT NULL,
    56	            total_chf TEXT NOT NULL,
    57	            securities_chf TEXT NOT NULL,
    58	            cash_chf TEXT NOT NULL,
    59	            stocks_chf TEXT NOT NULL,
    60	            etfs_chf TEXT NOT NULL,
    61	            component_total_chf TEXT NOT NULL,
    62	            difference_chf TEXT NOT NULL,
    63	            tolerance_chf TEXT NOT NULL,
    64	            position_count INTEGER NOT NULL,
    65	            cash_count INTEGER NOT NULL,
    66	            open_orders INTEGER NOT NULL,
    67	            reconciliation_status TEXT NOT NULL CHECK(reconciliation_status IN ('matched','within_tolerance')),
    68	            created_at TEXT NOT NULL
    69	        );
    70	        CREATE TABLE IF NOT EXISTS postfinance_snapshot_positions (
    71	            snapshot_position_id TEXT PRIMARY KEY,
    72	            snapshot_id TEXT NOT NULL REFERENCES postfinance_snapshots(snapshot_id),
    73	            source_row_reference TEXT NOT NULL,
    74	            instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),
    75	            asset_class TEXT NOT NULL CHECK(asset_class IN ('stock','etf')),
    76	            quantity TEXT NOT NULL,
    77	            provider_average_cost_original TEXT,
    78	            provider_cost_total_original TEXT,
    79	            price_original TEXT NOT NULL,
    80	            price_currency TEXT NOT NULL,