Output:
Chunk ID: efe1c3
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1319
Output:
   210	            fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
   211	            with os.fdopen(fd, "wb") as handle:
   212	                handle.write(raw)
   213	                handle.flush()
   214	                os.fsync(handle.fileno())
   215	            os.replace(tmp, target)
   216	            target.chmod(0o600)
   217	            created = True
   218	        finally:
   219	            if tmp.exists():
   220	                tmp.unlink()
   221	    return f"truewealth/archive/{digest}.pdf", created
   222
   223
   224	def _remove_new_archive(reference: str) -> None:
   225	    root = Path(os.environ.get("JARVIS_FINANCE_RUNTIME_DIR", str(DEFAULT_RUNTIME_DIR))).expanduser()
   226	    target = root / "imports" / reference
   227	    try:
   228	        target.unlink(missing_ok=True)
   229	    except OSError:
   230	        pass
   231
   232
   233	def _insert_audit(
   234	    conn: Connection,
   235	    *,
   236	    action: str,
   237	    entity_type: str,
   238	    entity_id: str,
   239	    details: dict[str, Any],
   240	    note: str = "",
   241	    quality: str = "ok",
   242	) -> str:
   243	    audit_id = _id("audit")
   244	    now = _now()
   245	    conn.execute(
   246	        """INSERT INTO audit_log(
   247	             audit_id,timestamp,source,action,entity_type,entity_id,old_values_json,new_values_json,
   248	             user_text_note,confirmed,confirmation_timestamp,auto_parsed,parse_confidence,created_by,
   249	             quality_status,created_at)
   250	           VALUES(?,?,'truewealth_sprint12',?,?,?,NULL,?,?,1,?,0,NULL,'user',?,?)""",
src/jarvis_finance/api/main.py:10:from jarvis_finance.api.routers import budget, cash, crypto, crypto_trader, equity, health, market, overview, positions, reports, system, truewealth
src/jarvis_finance/api/main.py:24:    "/api/truewealth/imports/preview",
src/jarvis_finance/api/main.py:25:    "/api/truewealth/manual-values/preview",
src/jarvis_finance/api/main.py:76:    for router in (health.router, overview.router, crypto.router, crypto_trader.router, equity.router, cash.router, reports.router, positions.router, budget.router, system.router, market.router, truewealth.router):
src/jarvis_finance/api/routers/truewealth.py:6:from jarvis_finance.api.schemas.truewealth import (
src/jarvis_finance/api/routers/truewealth.py:13:from jarvis_finance.services.truewealth_service import (
src/jarvis_finance/api/routers/truewealth.py:14:    confirm_manual_truewealth_value,
src/jarvis_finance/api/routers/truewealth.py:15:    confirm_truewealth_import,
src/jarvis_finance/api/routers/truewealth.py:16:    get_truewealth_summary,
src/jarvis_finance/api/routers/truewealth.py:17:    preview_manual_truewealth_value,
src/jarvis_finance/api/routers/truewealth.py:18:    preview_truewealth_import,
src/jarvis_finance/api/routers/truewealth.py:19:    set_manual_truewealth_value_active,
src/jarvis_finance/api/routers/truewealth.py:22:router = APIRouter(prefix="/truewealth", tags=["truewealth"])
src/jarvis_finance/api/routers/truewealth.py:34:    return _run(get_truewealth_summary, conn)
src/jarvis_finance/api/routers/truewealth.py:39:    return _run(preview_truewealth_import, conn, request.model_dump())
src/jarvis_finance/api/routers/truewealth.py:44:    return _run(confirm_truewealth_import, conn, request.model_dump())
src/jarvis_finance/api/routers/truewealth.py:49:    return _run(preview_manual_truewealth_value, conn, request.model_dump())
src/jarvis_finance/api/routers/truewealth.py:54:    return _run(confirm_manual_truewealth_value, conn, request.model_dump())
src/jarvis_finance/api/routers/truewealth.py:63:    return _run(set_manual_truewealth_value_active, conn, snapshot_id, request.model_dump())
frontend/src/api/truewealth.ts:111:export const getTrueWealthSummary = (refresh = false) => apiGet<TrueWealthSummary>('/api/truewealth/summary', { refresh })
frontend/src/api/truewealth.ts:112:export const previewTrueWealthImport = (payload: TrueWealthImportPayload) => apiPost<TrueWealthImportPreview>('/api/truewealth/imports/preview', payload)
frontend/src/api/truewealth.ts:113:export const confirmTrueWealthImport = (payload: TrueWealthImportPayload & Pick<TrueWealthImportPreview, 'preview_id' | 'confirmation_id'> & { confirm: true }) => apiPost<{ status: string; batch_id: string; snapshot_id?: string; audit_id: string; idempotent: boolean }>('/api/truewealth/imports/confirm', payload)
frontend/src/api/truewealth.ts:114:export const previewManualTrueWealthValue = (payload: ManualValuePayload) => apiPost<ManualValuePreview>('/api/truewealth/manual-values/preview', payload)
frontend/src/api/truewealth.ts:115:export const confirmManualTrueWealthValue = (payload: ManualValuePayload & Pick<ManualValuePreview, 'preview_id' | 'confirmation_id'> & { confirm: true }) => apiPost<{ status: string; snapshot_id: string; audit_id: string }>('/api/truewealth/manual-values/confirm', payload)
frontend/src/api/truewealth.ts:116:export const setManualTrueWealthValueStatus = (snapshotId: string, active: boolean, note: string) => apiPost<{ status: string; snapshot_id: string; audit_id: string; idempotent: boolean }>(`/api/truewealth/manual-values/${encodeURIComponent(snapshotId)}/status`, { active, note, confirm: true })


nl -ba src/jarvis_finance/services/truewealth_service.py | sed -n '260,285p'; python -m py_compile src/jarvis_finance/services/truewealth_service.py 2>&1 || true


Output:
Chunk ID: e72f09
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 427
Output:
   260	    if request.get("preview_id") != preview["preview_id"] or request.get("confirmation_id") != preview["confirmation_id"]:
   261	        raise ValueError("TrueWealth import preview is stale or changed")
   262	    if preview["duplicate"]:
   263	        row = conn.execute(
   264	            "SELECT batch_id,audit_id FROM truewealth_import_batches WHERE file_sha256=?",
   265	            (preview["file_sha256"],),
   266	        ).fetchone()
   267	        return {"status": "confirmed", "batch_id": row["batch_id"], "audit_id": row["audit_id"], "idempotent": True}
   268	    if preview["conflict"]:
   269	        raise ValueError("A different official TrueWealth source already exists for this snapshot date")
   270	    account_id = _validate_account(conn, str(request.get("account_id") or "").strip() or None)
   271
   272	    raw = _decode_pdf(str(request.get("content_base64") or ""))
   273	    statement = parse_truewealth_tax_statement(raw)
   274	    archive_reference, archive_created = _archive_pdf(raw, statement.file_sha256)
   275	    now = _now()
   276	    batch_id = _id("twbatch")
   277	    snapshot_id = _id("twsnapshot")
   278	    account_snapshot_id = _id("acctval")
   279	    difference = statement.source_total_chf - statement.components_total_chf
   280	    try:
   281	        conn.execute("BEGIN IMMEDIATE")
   282	        _validate_account(conn, account_id)
   283	        existing_portfolios = conn.execute(
   284	            "SELECT portfolio_id,account_id FROM truewealth_portfolios WHERE is_active=1"
   285	        ).fetchall()
[Errno 30] Read-only file system: 'src/jarvis_finance/services/__pycache__/truewealth_service.cpython-311.pyc.135923184205872'


grep -n "truewealth" -n src/jarvis_finance/storage/migrations.py | head -80 && sed -n '320,540p' src/jarvis_finance/storage/migrations.py && sed -n '1,320p' src/jarvis_finance/imports/truewealth_tax_statement.py


Output:
Chunk ID: 45c5b6
Wall time: 0.0001 seconds
Process exited with code 0
Original token count: 5850
Output:
10:MIGRATION_NAME = "044_truewealth_verified_snapshot_v1"
1928:def _create_truewealth_verified_snapshot_v1(conn: Connection) -> None:
1942:        CREATE TABLE IF NOT EXISTS truewealth_portfolios (
1954:        CREATE TABLE IF NOT EXISTS truewealth_import_batches (
1956:            portfolio_id TEXT NOT NULL REFERENCES truewealth_portfolios(portfolio_id),
1963:            provenance TEXT NOT NULL CHECK(provenance='truewealth_customer_export'),
1972:        CREATE INDEX IF NOT EXISTS idx_truewealth_batches_portfolio_date
1973:            ON truewealth_import_batches(portfolio_id, snapshot_date DESC, confirmed_at DESC);
1975:        CREATE TABLE IF NOT EXISTS truewealth_snapshots (
1977:            portfolio_id TEXT NOT NULL REFERENCES truewealth_portfolios(portfolio_id),
1979:            batch_id TEXT NOT NULL UNIQUE REFERENCES truewealth_import_batches(batch_id),
1996:        CREATE INDEX IF NOT EXISTS idx_truewealth_snapshot_date
1997:          ON truewealth_snapshots(portfolio_id, snapshot_date DESC);
1998:        CREATE UNIQUE INDEX IF NOT EXISTS ux_truewealth_snapshot_portfolio_date
1999:          ON truewealth_snapshots(portfolio_id, snapshot_date);
2000:        CREATE TABLE IF NOT EXISTS truewealth_snapshot_positions (
2002:            snapshot_id TEXT NOT NULL REFERENCES truewealth_snapshots(snapshot_id),
2017:        CREATE INDEX IF NOT EXISTS idx_truewealth_positions_snapshot
2018:            ON truewealth_snapshot_positions(snapshot_id, isin);
2020:        CREATE TABLE IF NOT EXISTS truewealth_snapshot_cash (
2022:            snapshot_id TEXT NOT NULL REFERENCES truewealth_snapshots(snapshot_id),
2034:        CREATE INDEX IF NOT EXISTS idx_truewealth_cash_snapshot
2035:            ON truewealth_snapshot_cash(snapshot_id, currency);
2037:        CREATE UNIQUE INDEX IF NOT EXISTS ux_account_value_truewealth_official_reference
2039:            WHERE source_type='truewealth_official_import' AND source_reference IS NOT NULL;
2041:        CREATE TRIGGER IF NOT EXISTS truewealth_account_values_no_delete
2043:        WHEN OLD.source_type IN ('truewealth_official_import','truewealth_manual_provisional')
2044:        BEGIN SELECT RAISE(ABORT, 'truewealth account valuations cannot be deleted'); END;
2045:        CREATE TRIGGER IF NOT EXISTS truewealth_official_account_values_immutable
2047:        WHEN OLD.source_type='truewealth_official_import'
2048:        BEGIN SELECT RAISE(ABORT, 'official truewealth account valuations are immutable'); END;
2049:        CREATE TRIGGER IF NOT EXISTS truewealth_manual_value_fields_immutable
2051:        WHEN OLD.source_type='truewealth_manual_provisional' AND (
2059:        BEGIN SELECT RAISE(ABORT, 'manual truewealth valuation fields are immutable'); END;
2061:        CREATE TRIGGER IF NOT EXISTS truewealth_batches_immutable_update
2062:        BEFORE UPDATE ON truewealth_import_batches
2063:        BEGIN SELECT RAISE(ABORT, 'truewealth import batches are immutable'); END;
2064:        CREATE TRIGGER IF NOT EXISTS truewealth_batches_no_delete
2065:        BEFORE DELETE ON truewealth_import_batches
2066:        BEGIN SELECT RAISE(ABORT, 'truewealth import batches cannot be deleted'); END;
2067:        CREATE TRIGGER IF NOT EXISTS truewealth_snapshots_immutable_update
2068:        BEFORE UPDATE ON truewealth_snapshots
2069:        BEGIN SELECT RAISE(ABORT, 'truewealth snapshots are immutable'); END;
2070:        CREATE TRIGGER IF NOT EXISTS truewealth_snapshots_no_delete
2071:        BEFORE DELETE ON truewealth_snapshots
2072:        BEGIN SELECT RAISE(ABORT, 'truewealth snapshots cannot be deleted'); END;
2073:        CREATE TRIGGER IF NOT EXISTS truewealth_positions_immutable_update
2074:        BEFORE UPDATE ON truewealth_snapshot_positions
2075:        BEGIN SELECT RAISE(ABORT, 'truewealth positions are immutable'); END;
2076:        CREATE TRIGGER IF NOT EXISTS truewealth_positions_no_delete
2077:        BEFORE DELETE ON truewealth_snapshot_positions
2078:        BEGIN SELECT RAISE(ABORT, 'truewealth positions cannot be deleted'); END;
2079:        CREATE TRIGGER IF NOT EXISTS truewealth_cash_immutable_update
2080:        BEFORE UPDATE ON truewealth_snapshot_cash
2081:        BEGIN SELECT RAISE(ABORT, 'truewealth cash is immutable'); END;
2082:        CREATE TRIGGER IF NOT EXISTS truewealth_cash_no_delete
2083:        BEFORE DELETE ON truewealth_snapshot_cash
2084:        BEGIN SELECT RAISE(ABORT, 'truewealth cash cannot be deleted'); END;
2085:        CREATE TRIGGER IF NOT EXISTS truewealth_audit_immutable_update
2086:        BEFORE UPDATE ON audit_log WHEN OLD.entity_type='truewealth_import_batch'
2087:        BEGIN SELECT RAISE(ABORT, 'truewealth import audit is immutable'); END;
2088:        CREATE TRIGGER IF NOT EXISTS truewealth_audit_no_delete
2089:        BEFORE DELETE ON audit_log WHEN OLD.entity_type='truewealth_import_batch'
2090:        BEGIN SELECT RAISE(ABORT, 'truewealth import audit cannot be deleted'); END;
2125:    _create_truewealth_verified_snapshot_v1(conn)
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS instrument_price_mappings (
            mapping_id TEXT PRIMARY KEY,
            instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),
            isin TEXT,
            ticker TEXT,
            exchange TEXT,
            currency TEXT,
            provider TEXT NOT NULL,
            provider_symbol TEXT,
            provider_market TEXT,
            mapping_status TEXT NOT NULL DEFAULT 'needs_manual_review',
            confidence TEXT,
            notes TEXT,
            created_at TEXT NOT NULL,
            updated_at TEXT,
            UNIQUE(instrument_id, provider)
        );
        CREATE INDEX IF NOT EXISTS idx_instrument_price_mappings_status ON instrument_price_mappings(mapping_status);
        CREATE INDEX IF NOT EXISTS idx_instrument_price_mappings_provider_symbol ON instrument_price_mappings(provider, provider_symbol);
        """
    )
    _add_missing_columns(conn, "market_prices", {
        "provider_market": "TEXT",
        "error_message": "TEXT",
        "corporate_action_status": "TEXT NOT NULL DEFAULT 'not_checked'",
    })
    _add_missing_columns(conn, "fx_rates", {
        "error_status": "TEXT",
        "error_message": "TEXT",
    })
    _add_missing_columns(conn, "instrument_price_mappings", {
        "hedge_status": "TEXT NOT NULL DEFAULT 'unknown'",
        "is_currency_hedged": "INTEGER NOT NULL DEFAULT 0",
        "hedged_to_currency": "TEXT",
        "instrument_status": "TEXT NOT NULL DEFAULT 'unknown'",
        "valuation_policy": "TEXT NOT NULL DEFAULT 'live_price'",
        "trading_currency": "TEXT",
        "base_exposure_currency": "TEXT",
        "source_symbol": "TEXT",
        "source_venue": "TEXT NOT NULL DEFAULT 'unknown'",
        "source_currency": "TEXT",
    })
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS instrument_catalog_entries (
            catalog_entry_id TEXT PRIMARY KEY,
            asset_class TEXT NOT NULL,
            name TEXT NOT NULL,
            normalized_name TEXT NOT NULL,
            isin TEXT,
            ticker TEXT,
            exchange TEXT,
            trading_currency TEXT,
            instrument_currency TEXT,
            provider TEXT,
            provider_symbol TEXT,
            provider_market TEXT,
            country TEXT,
            sector TEXT,
            issuer TEXT,
            fund_type TEXT,
            is_currency_hedged INTEGER,
            hedged_to_currency TEXT,
            hedge_status TEXT NOT NULL DEFAULT 'unknown',
            instrument_status TEXT NOT NULL DEFAULT 'unknown',
            valuation_policy TEXT NOT NULL DEFAULT 'live_price',
            source TEXT NOT NULL DEFAULT 'manual',
            source_confidence TEXT NOT NULL DEFAULT 'low',
            last_verified_at TEXT,
            notes TEXT,
            last_price TEXT,
            price_currency TEXT,
            price_date TEXT,
            price_source TEXT,
            exchange_name TEXT,
            mic TEXT,
            security_type TEXT,
            created_at TEXT NOT NULL,
            updated_at TEXT
        );
        CREATE INDEX IF NOT EXISTS idx_catalog_isin ON instrument_catalog_entries(isin);
        CREATE INDEX IF NOT EXISTS idx_catalog_ticker_exchange ON instrument_catalog_entries(ticker, exchange, trading_currency);
        CREATE INDEX IF NOT EXISTS idx_catalog_provider_symbol ON instrument_catalog_entries(provider, provider_symbol);
        CREATE INDEX IF NOT EXISTS idx_catalog_normalized_name ON instrument_catalog_entries(normalized_name);
        """
    )
    _add_missing_columns(conn, "instrument_catalog_entries", CATALOG_OPTIONAL_COLUMNS)
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS instrument_price_mapping_candidates (
            candidate_id TEXT PRIMARY KEY,
            instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),
            isin TEXT,
            candidate_provider TEXT NOT NULL,
            candidate_provider_symbol TEXT NOT NULL,
            candidate_exchange TEXT,
            candidate_currency TEXT,
            candidate_name TEXT,
            candidate_asset_class TEXT,
            candidate_is_hedged INTEGER,
            candidate_hedged_to_currency TEXT,
            candidate_hedge_status TEXT NOT NULL DEFAULT 'unknown',
            candidate_instrument_status TEXT NOT NULL DEFAULT 'unknown',
            candidate_valuation_policy TEXT NOT NULL DEFAULT 'live_price',
            ranking_score INTEGER NOT NULL DEFAULT 0,
            ranking_reason TEXT,
            risk_flags TEXT,
            recommended_action TEXT NOT NULL DEFAULT 'needs_manual_review',
            confidence TEXT NOT NULL DEFAULT 'low',
            evidence_source TEXT,
            evidence_note TEXT,
            review_status TEXT NOT NULL DEFAULT 'proposed',
            created_at TEXT NOT NULL,
            updated_at TEXT
        );
        CREATE INDEX IF NOT EXISTS idx_ipmc_instrument ON instrument_price_mapping_candidates(instrument_id);
        CREATE INDEX IF NOT EXISTS idx_ipmc_review_status ON instrument_price_mapping_candidates(review_status);
        CREATE INDEX IF NOT EXISTS idx_ipmc_confidence ON instrument_price_mapping_candidates(confidence);
        """
    )
    _add_missing_columns(conn, "instrument_price_mapping_candidates", {
        "candidate_asset_class": "TEXT",
        "candidate_hedge_status": "TEXT NOT NULL DEFAULT 'unknown'",
        "candidate_valuation_policy": "TEXT NOT NULL DEFAULT 'live_price'",
        "ranking_score": "INTEGER NOT NULL DEFAULT 0",
        "ranking_reason": "TEXT",
        "risk_flags": "TEXT",
        "recommended_action": "TEXT NOT NULL DEFAULT 'needs_manual_review'",
    })
    conn.execute("CREATE INDEX IF NOT EXISTS idx_ipmc_ranking_score ON instrument_price_mapping_candidates(ranking_score)")
    conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_market_prices_unique_provider_date ON market_prices(instrument_id, price_date, provider)")
    conn.execute("CREATE INDEX IF NOT EXISTS idx_market_prices_instrument_date ON market_prices(instrument_id, price_date)")
    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)")
    conn.execute("CREATE INDEX IF NOT EXISTS idx_fx_rates_pair_date ON fx_rates(base_currency, quote_currency, rate_date)")

def _create_postfinance_baseline_mapping_audit_v1(conn: Connection) -> None:
    """Keep source trading labels separate from provider valuation lines."""

    _add_missing_columns(conn, "instrument_price_mappings", {
        "source_symbol": "TEXT",
        "source_venue": "TEXT NOT NULL DEFAULT 'unknown'",
        "source_currency": "TEXT",
    })
    duplicates = conn.execute(
        """SELECT UPPER(isin) FROM instruments
           WHERE isin IS NOT NULL AND trim(isin)!=''
           GROUP BY UPPER(isin) HAVING COUNT(*)>1 LIMIT 1"""
    ).fetchone()
    if duplicates:
        raise ValueError("duplicate canonical instrument ISIN prevents migration 43")
    conn.execute("DROP INDEX IF EXISTS idx_instruments_unique_isin")
    conn.execute(
        """CREATE UNIQUE INDEX idx_instruments_unique_isin
           ON instruments(UPPER(isin)) WHERE isin IS NOT NULL AND trim(isin)!=''"""
    )

def _create_instrument_import_candidates(conn: Connection) -> None:
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS instrument_import_candidates (
            candidate_id TEXT PRIMARY KEY,
            source_file TEXT NOT NULL,
            platform TEXT NOT NULL,
            account TEXT,
            source_row_number TEXT,
            raw_name TEXT,
            raw_ticker TEXT,
            raw_isin TEXT,
            raw_currency TEXT,
            raw_quantity TEXT,
            raw_asset_class TEXT,
            extracted_by TEXT NOT NULL DEFAULT 'deterministic_parser',
            extraction_confidence TEXT NOT NULL DEFAULT 'low',
            proposed_name TEXT,
            proposed_ticker TEXT,
            proposed_isin TEXT,
            proposed_exchange TEXT,
            proposed_currency TEXT,
            proposed_asset_class TEXT,
            provider TEXT,
            provider_symbol TEXT,
            mapping_status TEXT NOT NULL DEFAULT 'needs_manual_review',
            review_note TEXT,
            created_at TEXT NOT NULL,
            updated_at TEXT
        );
        CREATE INDEX IF NOT EXISTS idx_instrument_import_candidates_status ON instrument_import_candidates(mapping_status);
        CREATE INDEX IF NOT EXISTS idx_instrument_import_candidates_platform ON instrument_import_candidates(platform);
        CREATE INDEX IF NOT EXISTS idx_instrument_import_candidates_isin ON instrument_import_candidates(proposed_isin, raw_isin);
        """
    )
    _add_missing_columns(conn, "instrument_import_candidates", {
        "account": "TEXT",
        "provider": "TEXT",
        "provider_symbol": "TEXT",
        "review_note": "TEXT",
    })

def _create_budget_phase1_tables(conn: Connection) -> None:
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS budget_accounts (
            budget_account_id TEXT PRIMARY KEY,
            linked_account_id TEXT REFERENCES accounts(account_id),
            name TEXT NOT NULL,
            account_type TEXT NOT NULL CHECK(account_type IN ('checking','credit_card','cash','savings','investment_cash','virtual','reserve','other')),
            currency TEXT NOT NULL,
            is_active INTEGER NOT NULL DEFAULT 1,
            archived_at TEXT,
            notes TEXT,
            created_at TEXT NOT NULL,
            updated_at TEXT
        );
        CREATE INDEX IF NOT EXISTS idx_budget_accounts_linked_account ON budget_accounts(linked_account_id);
        CREATE INDEX IF NOT EXISTS idx_budget_accounts_active ON budget_accounts(is_active);
from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal, InvalidOperation
from hashlib import sha256
from io import BytesIO
import re

PARSER_ID = "truewealth_tax_statement"
PARSER_VERSION = "1"
MAX_PDF_BYTES = 5 * 1024 * 1024
_DATE = r"(?P<day>\d{2})\.(?P<month>\d{2})\.(?P<year>\d{4})"
_DATE_PLAIN = r"\d{2}\.\d{2}\.\d{4}"
_ISIN = re.compile(r"^\s*\d+\s+([A-Z]{2}[A-Z0-9]{9}[0-9])\s+-\s+(.+?)\s*$")
_POSITION = re.compile(
    rf"^\s*{_DATE}\s+Bestand\s*/\s*Steuerwert\s*/\s*Ertrag\s+"
    r"([\d’'.,]+)\s+([A-Z]{3})\s+([\d’'.,]+)\s+([\d’'.,]+)(?:\s|$)"
)
_CASH_LABEL = re.compile(r"^\s*(CHF|EUR|GBP|USD)\s+\(")
_CASH_VALUE = re.compile(
    rf"^\s*{_DATE}\s+Steuerwert\s*/\s*Ertrag\s+([A-Z]{{3}})\s+(.+)$"
)

@dataclass(frozen=True)
class TrueWealthPosition:
    source_ref: str
    name: str
    isin: str
    quantity: Decimal
    currency: str
    source_price: Decimal
    source_value_chf: Decimal

@dataclass(frozen=True)
class TrueWealthCash:
    source_ref: str
    currency: str
    amount_original: Decimal
    fx_rate_to_chf: Decimal | None
    value_chf: Decimal

@dataclass(frozen=True)
class TrueWealthStatement:
    statement_date: str
    period_from: str
    period_to: str
    source_total_chf: Decimal
    positions: tuple[TrueWealthPosition, ...]
    cash: tuple[TrueWealthCash, ...]
    file_sha256: str
    page_count: int

    @property
    def securities_total_chf(self) -> Decimal:
        return sum((row.source_value_chf for row in self.positions), Decimal("0"))

    @property
    def cash_total_chf(self) -> Decimal:
        return sum((row.value_chf for row in self.cash), Decimal("0"))

    @property
    def components_total_chf(self) -> Decimal:
        return self.securities_total_chf + self.cash_total_chf

def _iso(day: str, month: str, year: str) -> str:
    return f"{year}-{month}-{day}"

def _decimal(value: str) -> Decimal:
    normalized = value.replace("’", "").replace("'", "").replace(" ", "")
    if normalized.count(",") == 1 and "." not in normalized:
        normalized = normalized.replace(",", ".")
    try:
        return Decimal(normalized)
    except InvalidOperation as exc:
        raise ValueError("TrueWealth statement contains an invalid numeric value") from exc

def _extract_pdf_text(raw: bytes) -> tuple[str, int]:
    if not raw or len(raw) > MAX_PDF_BYTES or not raw.startswith(b"%PDF-"):
        raise ValueError("TrueWealth source must be a bounded PDF")
    try:
        from pypdf import PdfReader

        reader = PdfReader(BytesIO(raw), strict=True)
        if reader.is_encrypted:
            raise ValueError("Encrypted TrueWealth PDFs are not supported")
        pages = [page.extract_text(extraction_mode="layout") or "" for page in reader.pages]
    except ValueError:
        raise
    except Exception as exc:
        raise ValueError("TrueWealth PDF could not be read") from exc
    if not pages or not all(page.strip() for page in pages[:6]):
        raise ValueError("TrueWealth PDF does not contain the expected text layer")
    return "\n\f\n".join(pages), len(pages)

def parse_truewealth_text(text: str, *, file_sha256: str = "synthetic", page_count: int = 7) -> TrueWealthStatement:
    if "True Wealth AG" not in text or "Steuerauszug in CHF" not in text:
        raise ValueError("PDF is not an evidenced TrueWealth tax statement")
    date_match = re.search(rf"Steuerauszug\s+in\s+CHF\s+{_DATE}", text)
    period_match = re.search(rf"Periode\s+{_DATE_PLAIN}\s*-\s*{_DATE_PLAIN}", text)
    if not date_match or not period_match:
        raise ValueError("TrueWealth statement date or period is missing")
    statement_date = _iso(date_match.group("day"), date_match.group("month"), date_match.group("year"))
    period_dates = re.findall(r"\d{2}\.\d{2}\.\d{4}", period_match.group(0))
    period_from = "-".join(reversed(period_dates[0].split(".")))
    period_to = "-".join(reversed(period_dates[1].split(".")))
    if statement_date != period_to:
        raise ValueError("TrueWealth statement date conflicts with the period end")

    total_match = re.search(
        rf"Total\s+Steuerwert\s+der[\s\S]{{0,500}}?Werte\s+am\s+{re.escape(period_dates[1])}[\s\S]{{0,350}}?\n\s*([\d’']+(?:[.,]\d+)?)\s+",
        text,
    )
    if not total_match:
        raise ValueError("TrueWealth source total is missing")
    source_total = _decimal(total_match.group(1))

    positions: list[TrueWealthPosition] = []
    cash: list[TrueWealthCash] = []
    current_isin: str | None = None
    current_name: list[str] = []
    current_cash_currency: str | None = None
    for raw_line in text.splitlines():
        line = raw_line.rstrip()
        cash_label = _CASH_LABEL.match(line)
        if cash_label:
            current_cash_currency = cash_label.group(1)
            continue
        cash_value = _CASH_VALUE.match(line)
        if cash_value and current_cash_currency:
            currency = cash_value.group(4)
            if currency != current_cash_currency:
                raise ValueError("TrueWealth cash currency is inconsistent")
            numbers = re.findall(r"[\d’'.,]+", cash_value.group(5))
            required = 2 if currency == "CHF" else 3
            if len(numbers) < required:
                raise ValueError("TrueWealth cash value is incomplete")
            amount = _decimal(numbers[0])
            optional_fx = None if currency == "CHF" else _decimal(numbers[1])
            value_chf = _decimal(numbers[1] if currency == "CHF" else numbers[2])
            cash.append(
                TrueWealthCash(
                    source_ref=f"cash:{currency}",
                    currency=currency,
                    amount_original=amount,
                    fx_rate_to_chf=optional_fx,
                    value_chf=value_chf,
                )
            )
            current_cash_currency = None
            continue

        start = _ISIN.match(line)
        if start:
            current_isin = start.group(1)
            current_name = [start.group(2).strip()]
            continue
        if current_isin and line.strip() and not re.match(r"^\s*\d{2}\.\d{2}\.\d{4}", line):
            if not any(token in line for token in ("True Wealth AG", "Kunde", "Kdnr.", "Periode", "Erstellt am", "Kanton", "Valoren-Nr.", "Datum", "ISIN", "Depot ", "USA", "Werte mit Anrechnung")):
                current_name.append(line.strip())
            continue
        position = _POSITION.match(line)
        if position and current_isin:
            row_date = _iso(position.group("day"), position.group("month"), position.group("year"))
            if row_date != statement_date:
                raise ValueError("TrueWealth position date conflicts with statement date")
            positions.append(
                TrueWealthPosition(
                    source_ref=f"position:{current_isin}",
                    name=" ".join(current_name),
                    isin=current_isin,
                    quantity=_decimal(position.group(4)),
                    currency=position.group(5),
                    source_price=_decimal(position.group(6)),
                    source_value_chf=_decimal(position.group(7)),
                )
            )
            current_isin = None
            current_name = []

    if len(positions) < 1 or len({row.isin for row in positions}) != len(positions):
        raise ValueError("TrueWealth positions are missing or duplicated")
    if not cash:
        raise ValueError("TrueWealth cash rows are missing")
    if any(row.currency != "CHF" for row in positions):
        raise ValueError("TrueWealth tax-statement position values must be reported in CHF")
    difference = source_total - (sum((row.source_value_chf for row in positions), Decimal("0")) + sum((row.value_chf for row in cash), Decimal("0")))
    if abs(difference) > Decimal("1.00"):
        raise ValueError("TrueWealth components do not reconcile to the source total")
    return TrueWealthStatement(
        statement_date=statement_date,
        period_from=period_from,
        period_to=period_to,
        source_total_chf=source_total,
        positions=tuple(positions),
        cash=tuple(cash),
        file_sha256=file_sha256,
        page_count=page_count,
    )

def parse_truewealth_tax_statement(raw: bytes) -> TrueWealthStatement:
    text, pages = _extract_pdf_text(raw)
    return parse_truewealth_text(text, file_sha256=sha256(raw).hexdigest(), page_count=pages)


sed -n '1,260p' frontend/src/pages/TrueWealthPage.vue && sed -n '260,620p' frontend/src/pages/TrueWealthPage.vue && sed -n '1,200p' src/jarvis_finance/api/routers/truewealth.py && sed -n '1,260p' src/jarvis_finance/api/schemas/truewealth.py


Output:
Chunk ID: 41c502
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 5174
Output:
<template>

    <LoadActionBar
      testid="refresh-truewealth"
      title="TrueWealth · Freie Anlagen"
      description="Offizielle Portfolio-Snapshots und provisorische manuelle Gesamtwerte mit klar getrenntem Datenstand."
      :loading="loading"
      @load="loadData(true)"
    />
    <LocalDataStatus :backend-ok="backendOk" :runtime-ok="runtimeOk" :loading="loading" :error="error" :last-loaded-at="lastLoadedAt" />

Freie Anlagen

Aktueller TrueWealth-Gesamtwert CHF

{{ summary?.current_label ?? 'Noch kein Wert verfügbar' }}

        <MoneyValue :value="summary?.current_value_chf ?? '0.00'" />

        Manuell / provisorisch. {{ summary.positions_label }}. Die Differenz wird weder Positionen noch Cash zugerechnet.

        Gesamtwert und Positionsdetails haben unterschiedliche Stichtage. Allokation, Gewinn und Rendite sind für diesen Mischstand deaktiviert.

        <button data-testid="truewealth-update-button" type="button" class="rounded-lg border border-cyan-300 bg-cyan-50 px-3 py-2 font-semibold text-cyan-800" @click="showManualForm = !showManualForm">Gesamtwert manuell aktualisieren</button>
        <button data-testid="truewealth-import-button" type="button" class="rounded-lg border border-slate-300 bg-white px-3 py-2 font-semibold text-slate-800" @click="showImportForm = !showImportForm">Offiziellen Auszug importieren</button>
        Zur Portfolio-Übersicht

Gesamtwert manuell aktualisieren

Erstellt einen neuen historischen, provisorischen Datensatz. Offizielle Positionen bleiben unverändert.

        Gesamtwert CHF<input data-testid="truewealth-amount-input" v-model="manualForm.total_value_chf" class="mt-1 w-full rounded-lg border border-slate-300 bg-white p-2" inputmode="decimal" placeholder="Betrag in CHF" @input="manualPreview = null" />
        Bewertungszeitpunkt<input data-testid="truewealth-date-input" v-model="manualForm.valuation_at" class="mt-1 w-full rounded-lg border border-slate-300 bg-white p-2" type="datetime-local" @input="manualPreview = null" />
        Notiz optional<input data-testid="truewealth-note-input" v-model="manualForm.note" class="mt-1 w-full rounded-lg border border-slate-300 bg-white p-2" maxlength="240" placeholder="z. B. Stand im Kundenportal" @input="manualPreview = null" />

        <button data-testid="truewealth-preview-button" class="rounded-lg bg-slate-900 px-3 py-2 font-semibold text-white disabled:opacity-50" :disabled="actionLoading || !manualForm.total_value_chf || !manualForm.valuation_at" @click="doManualPreview">Preview</button>
        <button v-if="manualPreview" data-testid="truewealth-confirm-button" class="rounded-lg bg-emerald-600 px-3 py-2 font-semibold text-white disabled:opacity-50" :disabled="actionLoading" @click="doManualConfirm">Confirm</button>

Preview: CHF {{ money(manualPreview.total_value_chf) }} · {{ formatDateTime(manualPreview.valuation_at) }}

Manuell / provisorisch. Positionsdetails werden nicht verändert.

Offiziellen TrueWealth-Auszug importieren

Nur unveränderte TrueWealth-Steuerauszüge als PDF. Der Import wird zuerst vollständig geprüft und angezeigt.

      <input data-testid="truewealth-file-input" class="mt-3 block w-full rounded-lg border border-slate-300 bg-white p-2 text-sm" type="file" accept="application/pdf,.pdf" @change="onFileSelected" />

        <button data-testid="truewealth-import-preview-button" class="rounded-lg bg-slate-900 px-3 py-2 font-semibold text-white disabled:opacity-50" :disabled="actionLoading || !importPayload" @click="doImportPreview">Import-Preview</button>
        <button v-if="importPreview" data-testid="truewealth-import-confirm-button" class="rounded-lg bg-emerald-600 px-3 py-2 font-semibold text-white disabled:opacity-50" :disabled="actionLoading || importPreview.reconciliation_status === 'mismatch' || importPreview.conflict" @click="doImportConfirm">Import bestätigen</button>

Stichtag

{{ formatDate(importPreview.snapshot_date) }}

Gesamtwert

CHF {{ money(importPreview.source_total_chf) }}

Inhalt

{{ importPreview.positions.length }} Positionen · {{ importPreview.cash.length }} Cashkonten

Reconciliation

{{ importPreview.reconciliation_status }} · CHF {{ money(importPreview.reconciliation_difference_chf) }}

Identische Quelle bereits bestätigt. Ein erneuter Confirm ist idempotent.

Konflikt: Für diesen Stichtag existiert bereits eine andere offizielle Quelle. Confirm ist gesperrt.

{{ message }}

{{ actionError }}

Positionsdetails

{{ summary?.positions_label }}

{{ summary?.positions.length ?? 0 }} Positionen

{{ position.instrument_name }}

{{ position.isin }}

Menge
{{ position.quantity }}Quellwert
CHF {{ money(position.source_value_chf) }}

Noch keine offiziellen Positionen importiert.

{{ cash.currency }} {{ cash.amount_original }} · CHF {{ money(cash.source_value_chf) }}

Bewertungshistorie

Manuelle und offizielle Werte bleiben historisch und auditierbar erhalten.

CHF {{ money(item.total_value_chf) }}

{{ formatDateTime(item.valuation_at) }}

{{ sourceLabel(item.source_type) }}

{{ item.notes }}

Deaktiviert · historisch erhalten

          <button v-if="item.source_type === 'truewealth_manual_provisional'" class="mt-3 rounded-lg border border-slate-300 px-2 py-1 text-xs font-semibold" :data-testid="`truewealth-history-status-${item.snapshot_id}`" @click="toggleManualStatus(item)">{{ item.is_active ? 'Provisorischen Wert deaktivieren' : 'Wert reaktivieren' }}</button>

Importhistorie

Offizieller Snapshot {{ formatDate(item.snapshot_date) }}

{{ item.position_count }} Positionen · {{ item.cash_count }} Cashkonten · CHF {{ money(item.source_total_chf) }}

Parser {{ item.parser_id }} v{{ item.parser_version }} · {{ item.reconciliation_status }}

Noch keine bestätigten offiziellen Importe.

</template>

from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException

from jarvis_finance.api.dependencies import get_db
from jarvis_finance.api.schemas.truewealth import (
    TrueWealthImportConfirmRequest,
    TrueWealthImportPreviewRequest,
    TrueWealthManualConfirmRequest,
    TrueWealthManualPreviewRequest,
    TrueWealthManualStatusRequest,
)
from jarvis_finance.services.truewealth_service import (
    confirm_manual_truewealth_value,
    confirm_truewealth_import,
    get_truewealth_summary,
    preview_manual_truewealth_value,
    preview_truewealth_import,
    set_manual_truewealth_value_active,
)

router = APIRouter(prefix="/truewealth", tags=["truewealth"])

def _run(callable_, *args):
    try:
        return callable_(*args)
    except ValueError as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc

@router.get("/summary")
def summary(conn=Depends(get_db)):
    return _run(get_truewealth_summary, conn)

@router.post("/imports/preview")
def import_preview(request: TrueWealthImportPreviewRequest, conn=Depends(get_db)):
    return _run(preview_truewealth_import, conn, request.model_dump())

@router.post("/imports/confirm")
def import_confirm(request: TrueWealthImportConfirmRequest, conn=Depends(get_db)):
    return _run(confirm_truewealth_import, conn, request.model_dump())

@router.post("/manual-values/preview")
def manual_preview(request: TrueWealthManualPreviewRequest, conn=Depends(get_db)):
    return _run(preview_manual_truewealth_value, conn, request.model_dump())

@router.post("/manual-values/confirm")
def manual_confirm(request: TrueWealthManualConfirmRequest, conn=Depends(get_db)):
    return _run(confirm_manual_truewealth_value, conn, request.model_dump())

@router.post("/manual-values/{snapshot_id}/status")
def manual_status(
    snapshot_id: str,
    request: TrueWealthManualStatusRequest,
    conn=Depends(get_db),
):
    return _run(set_manual_truewealth_value_active, conn, snapshot_id, request.model_dump())
from __future__ import annotations

from pydantic import BaseModel, ConfigDict, Field

class TrueWealthImportPreviewRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")
    account_id: str | None = Field(default=None, min_length=1, max_length=120)
    file_name: str = Field(min_length=1, max_length=200)
    content_base64: str = Field(min_length=8, max_length=8_000_000)

class TrueWealthImportConfirmRequest(TrueWealthImportPreviewRequest):
    preview_id: str = Field(min_length=10, max_length=80)
    confirmation_id: str = Field(min_length=10, max_length=80)
    confirm: bool

class TrueWealthManualPreviewRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")
    total_value_chf: str = Field(min_length=1, max_length=40)
    valuation_at: str = Field(min_length=10, max_length=40)
    note: str = Field(default="", max_length=240)

class TrueWealthManualConfirmRequest(TrueWealthManualPreviewRequest):
    preview_id: str = Field(min_length=10, max_length=80)
    confirmation_id: str = Field(min_length=10, max_length=80)
    confirm: bool

class TrueWealthManualStatusRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")
    active: bool
    note: str = Field(default="", max_length=240)
    confirm: bool


git diff -- src/jarvis_finance/api/main.py src/jarvis_finance/services/portfolio_data.py src/jarvis_finance/services/portfolio_service.py src/jarvis_finance/services/portfolio_performance.py src/jarvis_finance/services/reconciliation_snapshot.py scripts/ci_portfolio_phase3_gate.py pyproject.toml requirements-ci.txt tests/unit/test_schema.py


Output:
Chunk ID: 1a0734
Wall time: 0.0001 seconds
Process exited with code 0
Original token count: 2198
Output:
diff --git a/pyproject.toml b/pyproject.toml
index ea33360..a3f1f33 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,7 +7,7 @@ name = "jarvis-finance"
 version = "0.1.0"
 description = "JARVIS Finance System MVP - code, docs and synthetic fixtures only"
 requires-python = ">=3.11"
-dependencies = []
+dependencies = ["pypdf==6.14.2"]

 [project.optional-dependencies]
 dev = ["pytest>=8", "ruff>=0.5"]
diff --git a/requirements-ci.txt b/requirements-ci.txt
index 6f593ae..375d951 100644
--- a/requirements-ci.txt
+++ b/requirements-ci.txt
@@ -5,6 +5,7 @@ openpyxl==3.1.5
 pandas==3.0.3
 plotly==6.7.0
 pydantic==2.13.4
+pypdf==6.14.2
 pytest==9.0.2
 ruff==0.15.13
 streamlit==1.57.0
diff --git a/scripts/ci_portfolio_phase3_gate.py b/scripts/ci_portfolio_phase3_gate.py
index 22ab947..b91f367 100644
--- a/scripts/ci_portfolio_phase3_gate.py
+++ b/scripts/ci_portfolio_phase3_gate.py
@@ -15,7 +15,7 @@ from jarvis_finance.config.settings import load_settings
 from jarvis_finance.storage.migrations import apply_migrations, get_schema_version

 SPRINT5_COMMIT = "480d7a1b72a950e864c4bb021d3f58af8f8c15f9"
-EXPECTED_SCHEMA = 43
+EXPECTED_SCHEMA = 44

 def _assert_tmp_path(path: Path) -> Path:
@@ -159,8 +159,8 @@ def main() -> None:
     sprint5.close()

     print("phase3_migration_gate=PASS")
-    print("empty_schema=43 integrity=ok ingestion_and_analysis_rows=0")
-    print(f"sprint5_schema=40_to_43 integrity=ok seed_digest={digest_before}")
+    print(f"empty_schema={EXPECTED_SCHEMA} integrity=ok ingestion_and_analysis_rows=0")
+    print(f"sprint5_schema=40_to_{EXPECTED_SCHEMA} integrity=ok seed_digest={digest_before}")
     print("settings_guard=PASS production_paths_rejected=true")

diff --git a/src/jarvis_finance/api/main.py b/src/jarvis_finance/api/main.py
index 069007f..5336423 100644
--- a/src/jarvis_finance/api/main.py
+++ b/src/jarvis_finance/api/main.py
@@ -7,7 +7,7 @@ from fastapi import FastAPI, Request
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import JSONResponse

-from jarvis_finance.api.routers import budget, cash, crypto, crypto_trader, equity, health, market, overview, positions, reports, system
+from jarvis_finance.api.routers import budget, cash, crypto, crypto_trader, equity, health, market, overview, positions, reports, system, truewealth
 from jarvis_finance.api.security import WRITE_METHODS, is_write_request_allowed, resolve_write_mode

 LOCAL_ORIGINS = [
@@ -18,7 +18,12 @@ LOCAL_ORIGINS = [
     "http://localhost:8503",
     "http://127.0.0.1:8503",
 ]
-READ_ONLY_POST_PATHS = {"/api/portfolio/ingestion/preview", "/api/portfolio/policy/preview"}
+READ_ONLY_POST_PATHS = {
+    "/api/portfolio/ingestion/preview",
+    "/api/portfolio/policy/preview",
+    "/api/truewealth/imports/preview",
+    "/api/truewealth/manual-values/preview",
+}

 def _is_safe_local_origin(origin: str) -> bool:
@@ -68,7 +73,7 @@ def create_app(*, write_mode: str | None = None) -> FastAPI:
         return await call_next(request)

     app.state.write_mode = active_write_mode
-    for router in (health.router, overview.router, crypto.router, crypto_trader.router, equity.router, cash.router, reports.router, positions.router, budget.router, system.router, market.router):
+    for router in (health.router, overview.router, crypto.router, crypto_trader.router, equity.router, cash.router, reports.router, positions.router, budget.router, system.router, market.router, truewealth.router):
         app.include_router(router, prefix="/api")
     return app

diff --git a/src/jarvis_finance/services/portfolio_data.py b/src/jarvis_finance/services/portfolio_data.py
index 230f4cb..7a5762e 100644
--- a/src/jarvis_finance/services/portfolio_data.py
+++ b/src/jarvis_finance/services/portfolio_data.py
@@ -1596,8 +1596,11 @@ def build_portfolio_reconciliation(
         cash_value = _stored_decimal(cash["amount_chf"], "Cashbestand") if cash else None
         cash_date = str(cash["balance_date"]) if cash else None
         legacy = conn.execute(
-            """SELECT total_value_chf,valuation_date,source_type FROM account_value_snapshots WHERE account_id=? AND valuation_date<=? AND created_at<=?
-               ORDER BY valuation_date DESC,created_at DESC,snapshot_id DESC LIMIT 1""", (account, as_of, cutoff)
+            """SELECT total_value_chf,valuation_date,source_type FROM account_value_snapshots
+               WHERE account_id=? AND valuation_date<=? AND created_at<=? AND COALESCE(is_active,1)=1
+               ORDER BY valuation_date DESC,
+                        CASE source_type WHEN 'truewealth_official_import' THEN 3 WHEN 'truewealth_manual_provisional' THEN 2 ELSE 1 END DESC,
+                        COALESCE(valuation_at,created_at) DESC,created_at DESC,snapshot_id DESC LIMIT 1""", (account, as_of, cutoff)
         ).fetchone()
         reported_total = _stored_decimal(legacy["total_value_chf"], "Kontototal") if legacy else None
         total_date = str(legacy["valuation_date"]) if legacy else None
diff --git a/src/jarvis_finance/services/portfolio_performance.py b/src/jarvis_finance/services/portfolio_performance.py
index 3539bd8..10a4744 100644
--- a/src/jarvis_finance/services/portfolio_performance.py
+++ b/src/jarvis_finance/services/portfolio_performance.py
@@ -228,13 +228,15 @@ def _load_valuations(
           SELECT s.*,
                  ROW_NUMBER() OVER (
                    PARTITION BY account_id, valuation_date
-                   ORDER BY created_at DESC, snapshot_id DESC
+                   ORDER BY CASE source_type WHEN 'truewealth_official_import' THEN 3 WHEN 'truewealth_manual_provisional' THEN 2 ELSE 1 END DESC,
+                            COALESCE(valuation_at,created_at) DESC, created_at DESC, snapshot_id DESC
                  ) AS rn
           FROM account_value_snapshots s
           WHERE account_id IN ({placeholders})
             AND valuation_date BETWEEN ? AND ?
             AND created_at <= ?
-            AND updated_at IS NULL
+            AND COALESCE(is_active,1)=1
+            AND source_type <> 'truewealth_manual_provisional'
         )
         SELECT * FROM ranked WHERE rn=1 ORDER BY valuation_date, account_id
         """,
diff --git a/src/jarvis_finance/services/portfolio_service.py b/src/jarvis_finance/services/portfolio_service.py
index 74344d1..b270e8c 100644
--- a/src/jarvis_finance/services/portfolio_service.py
+++ b/src/jarvis_finance/services/portfolio_service.py
@@ -18,8 +18,11 @@ def _latest_truewealth_value(conn: Connection) -> Decimal:
         JOIN accounts a ON a.account_id = avs.account_id
         JOIN platforms p ON p.platform_id = a.platform_id
         WHERE a.is_active = 1
+          AND COALESCE(avs.is_active,1) = 1
           AND (lower(p.name || ' ' || a.account_name) LIKE '%true%wealth%' OR lower(a.account_type) IN ('managed_portfolio','robo_portfolio'))
-        ORDER BY avs.valuation_date DESC, avs.created_at DESC
+        ORDER BY avs.valuation_date DESC,
+                 CASE avs.source_type WHEN 'truewealth_official_import' THEN 3 WHEN 'truewealth_manual_provisional' THEN 2 ELSE 1 END DESC,
+                 COALESCE(avs.valuation_at,avs.created_at) DESC, avs.created_at DESC
         LIMIT 1
         """
     ).fetchone()
diff --git a/src/jarvis_finance/services/reconciliation_snapshot.py b/src/jarvis_finance/services/reconciliation_snapshot.py
index 086b2cb..8a76ece 100644
--- a/src/jarvis_finance/services/reconciliation_snapshot.py
+++ b/src/jarvis_finance/services/reconciliation_snapshot.py
@@ -201,7 +201,10 @@ def list_snapshot_metadata(conn: Connection, *, now: datetime | None = None) ->
         conn,
         """SELECT account_id, valuation_date, created_at, source_type, currency, total_value_chf, quality_status
            FROM account_value_snapshots
-           ORDER BY account_id, valuation_date DESC, created_at DESC, snapshot_id DESC""",
+           WHERE COALESCE(is_active,1)=1
+           ORDER BY account_id, valuation_date DESC,
+                    CASE source_type WHEN 'truewealth_official_import' THEN 3 WHEN 'truewealth_manual_provisional' THEN 2 ELSE 1 END DESC,
+                    COALESCE(valuation_at,created_at) DESC, created_at DESC, snapshot_id DESC""",
     )
     latest_accounts: set[str] = set()
     for row in valuations:
diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py
index 76683ed..f899370 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) == 43
+    assert get_schema_version(conn) == 44

 def test_decimal_sensitive_columns_use_text_affinity() -> None:


sed -n '1880,2135p' src/jarvis_finance/storage/migrations.py && grep -R "CREATE TABLE IF NOT EXISTS account_value_snapshots\|account_value_snapshots" -n src/jarvis_finance/storage/schema.py src/jarvis_finance/storage/migrations.py | head -80


Output:
Chunk ID: f43d69
Wall time: 0.0001 seconds
Process exited with code 0
Original token count: 3627
Output:
            source_as_of TEXT NOT NULL,
            fetched_at TEXT NOT NULL,
            quality_status TEXT NOT NULL,
            reason_codes_json TEXT NOT NULL DEFAULT '[]'
                CHECK(json_valid(reason_codes_json) AND json_type(reason_codes_json)='array'),
            UNIQUE(run_id, policy_id, benchmark_reference, as_of)
        );
        CREATE INDEX IF NOT EXISTS idx_benchmark_snapshots_policy_time
            ON benchmark_snapshots(policy_id, benchmark_reference, as_of, fetched_at);

        CREATE TABLE IF NOT EXISTS portfolio_analysis_snapshots (
            analysis_snapshot_id TEXT PRIMARY KEY,
            run_id TEXT NOT NULL UNIQUE REFERENCES market_data_runs(run_id),
            as_of TEXT NOT NULL,
            base_currency TEXT NOT NULL DEFAULT 'CHF',
            total_value_chf TEXT,
            price_coverage_pct TEXT NOT NULL,
            fx_coverage_pct TEXT NOT NULL,
            benchmark_coverage_pct TEXT NOT NULL,
            quality_status TEXT NOT NULL CHECK(quality_status IN ('complete','partial','unavailable')),
            reason_codes_json TEXT NOT NULL DEFAULT '[]'
                CHECK(json_valid(reason_codes_json) AND json_type(reason_codes_json)='array'),
            summary_json TEXT NOT NULL DEFAULT '{}'
                CHECK(json_valid(summary_json) AND json_type(summary_json)='object'),
            created_at TEXT NOT NULL
        );
        CREATE INDEX IF NOT EXISTS idx_portfolio_analysis_snapshots_time
            ON portfolio_analysis_snapshots(as_of DESC, created_at DESC);

        CREATE TRIGGER IF NOT EXISTS market_data_runs_no_delete
        BEFORE DELETE ON market_data_runs
        BEGIN SELECT RAISE(ABORT, 'market data runs cannot be deleted'); END;
        CREATE TRIGGER IF NOT EXISTS benchmark_snapshots_immutable
        BEFORE UPDATE ON benchmark_snapshots
        BEGIN SELECT RAISE(ABORT, 'benchmark snapshots are immutable'); END;
        CREATE TRIGGER IF NOT EXISTS benchmark_snapshots_no_delete
        BEFORE DELETE ON benchmark_snapshots
        BEGIN SELECT RAISE(ABORT, 'benchmark snapshots cannot be deleted'); END;
        CREATE TRIGGER IF NOT EXISTS portfolio_analysis_snapshots_immutable
        BEFORE UPDATE ON portfolio_analysis_snapshots
        BEGIN SELECT RAISE(ABORT, 'portfolio analysis snapshots are immutable'); END;
        CREATE TRIGGER IF NOT EXISTS portfolio_analysis_snapshots_no_delete
        BEFORE DELETE ON portfolio_analysis_snapshots
        BEGIN SELECT RAISE(ABORT, 'portfolio analysis snapshots cannot be deleted'); END;
        """
    )

def _create_truewealth_verified_snapshot_v1(conn: Connection) -> None:
    _add_missing_columns(
        conn,
        "account_value_snapshots",
        {
            "valuation_at": "TEXT",
            "source_reference": "TEXT",
            "is_active": "INTEGER NOT NULL DEFAULT 1",
            "deactivated_at": "TEXT",
            "deactivation_reason": "TEXT",
        },
    )
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS truewealth_portfolios (
            portfolio_id TEXT PRIMARY KEY,
            account_id TEXT NOT NULL UNIQUE REFERENCES accounts(account_id),
            source_reference_hash TEXT NOT NULL,
            label TEXT NOT NULL,
            portfolio_kind TEXT CHECK(portfolio_kind IN ('free_assets','pillar_3a','child','other')),
            base_currency TEXT NOT NULL DEFAULT 'CHF',
            is_active INTEGER NOT NULL DEFAULT 1 CHECK(is_active IN (0,1)),
            created_at TEXT NOT NULL,
            updated_at TEXT
        );

        CREATE TABLE IF NOT EXISTS truewealth_import_batches (
            batch_id TEXT PRIMARY KEY,
            portfolio_id TEXT NOT NULL REFERENCES truewealth_portfolios(portfolio_id),
            account_id TEXT NOT NULL REFERENCES accounts(account_id),
            file_sha256 TEXT NOT NULL UNIQUE CHECK(length(file_sha256)=64),
            filename_sha256 TEXT NOT NULL CHECK(length(filename_sha256)=64),
            source_file_type TEXT NOT NULL CHECK(source_file_type='application/pdf'),
            parser_id TEXT NOT NULL,
            parser_version TEXT NOT NULL,
            provenance TEXT NOT NULL CHECK(provenance='truewealth_customer_export'),
            snapshot_date TEXT NOT NULL,
            page_count INTEGER NOT NULL CHECK(page_count > 0),
            archive_reference TEXT NOT NULL,
            status TEXT NOT NULL CHECK(status='confirmed'),
            audit_id TEXT NOT NULL,
            confirmed_at TEXT NOT NULL,
            confirmed_by TEXT NOT NULL DEFAULT 'user'
        );
        CREATE INDEX IF NOT EXISTS idx_truewealth_batches_portfolio_date
            ON truewealth_import_batches(portfolio_id, snapshot_date DESC, confirmed_at DESC);

        CREATE TABLE IF NOT EXISTS truewealth_snapshots (
            snapshot_id TEXT PRIMARY KEY,
            portfolio_id TEXT NOT NULL REFERENCES truewealth_portfolios(portfolio_id),
            account_id TEXT NOT NULL REFERENCES accounts(account_id),
            batch_id TEXT NOT NULL UNIQUE REFERENCES truewealth_import_batches(batch_id),
            snapshot_date TEXT NOT NULL,
            source_total_chf TEXT NOT NULL,
            securities_total_chf TEXT NOT NULL,
            cash_total_chf TEXT NOT NULL,
            components_total_chf TEXT NOT NULL,
            reconciliation_difference_chf TEXT NOT NULL,
            reconciliation_tolerance_chf TEXT NOT NULL DEFAULT '1.00',
            reconciliation_status TEXT NOT NULL CHECK(reconciliation_status IN ('matched','within_tolerance','mismatch')),
            position_count INTEGER NOT NULL,
            cash_count INTEGER NOT NULL,
            completeness_status TEXT NOT NULL CHECK(completeness_status IN ('complete','partial','blocked')),
            reason_codes_json TEXT NOT NULL DEFAULT '[]'
                CHECK(json_valid(reason_codes_json) AND json_type(reason_codes_json)='array'),
            created_at TEXT NOT NULL,
            UNIQUE(portfolio_id, snapshot_date, batch_id)
        );
        CREATE INDEX IF NOT EXISTS idx_truewealth_snapshot_date
          ON truewealth_snapshots(portfolio_id, snapshot_date DESC);
        CREATE UNIQUE INDEX IF NOT EXISTS ux_truewealth_snapshot_portfolio_date
          ON truewealth_snapshots(portfolio_id, snapshot_date);
        CREATE TABLE IF NOT EXISTS truewealth_snapshot_positions (
            snapshot_position_id TEXT PRIMARY KEY,
            snapshot_id TEXT NOT NULL REFERENCES truewealth_snapshots(snapshot_id),
            source_row_reference TEXT NOT NULL,
            instrument_name TEXT NOT NULL,
            isin TEXT NOT NULL,
            asset_type TEXT,
            quantity TEXT NOT NULL,
            price_currency TEXT NOT NULL,
            source_price TEXT NOT NULL,
            source_value_chf TEXT NOT NULL,
            source_evidence_json TEXT NOT NULL DEFAULT '{}'
                CHECK(json_valid(source_evidence_json) AND json_type(source_evidence_json)='object'),
            created_at TEXT NOT NULL,
            UNIQUE(snapshot_id, source_row_reference),
            UNIQUE(snapshot_id, isin)
        );
        CREATE INDEX IF NOT EXISTS idx_truewealth_positions_snapshot
            ON truewealth_snapshot_positions(snapshot_id, isin);

        CREATE TABLE IF NOT EXISTS truewealth_snapshot_cash (
            snapshot_cash_id TEXT PRIMARY KEY,
            snapshot_id TEXT NOT NULL REFERENCES truewealth_snapshots(snapshot_id),
            source_row_reference TEXT NOT NULL,
            currency TEXT NOT NULL,
            amount_original TEXT NOT NULL,
            fx_rate_to_chf TEXT,
            source_value_chf TEXT NOT NULL,
            source_evidence_json TEXT NOT NULL DEFAULT '{}'
                CHECK(json_valid(source_evidence_json) AND json_type(source_evidence_json)='object'),
            created_at TEXT NOT NULL,
            UNIQUE(snapshot_id, source_row_reference),
            UNIQUE(snapshot_id, currency)
        );
        CREATE INDEX IF NOT EXISTS idx_truewealth_cash_snapshot
            ON truewealth_snapshot_cash(snapshot_id, currency);

        CREATE UNIQUE INDEX IF NOT EXISTS ux_account_value_truewealth_official_reference
            ON account_value_snapshots(source_reference)
            WHERE source_type='truewealth_official_import' AND source_reference IS NOT NULL;

        CREATE TRIGGER IF NOT EXISTS truewealth_account_values_no_delete
        BEFORE DELETE ON account_value_snapshots
        WHEN OLD.source_type IN ('truewealth_official_import','truewealth_manual_provisional')
        BEGIN SELECT RAISE(ABORT, 'truewealth account valuations cannot be deleted'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_official_account_values_immutable
        BEFORE UPDATE ON account_value_snapshots
        WHEN OLD.source_type='truewealth_official_import'
        BEGIN SELECT RAISE(ABORT, 'official truewealth account valuations are immutable'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_manual_value_fields_immutable
        BEFORE UPDATE ON account_value_snapshots
        WHEN OLD.source_type='truewealth_manual_provisional' AND (
            NEW.snapshot_id IS NOT OLD.snapshot_id OR NEW.account_id IS NOT OLD.account_id OR
            NEW.valuation_date IS NOT OLD.valuation_date OR NEW.total_value_chf IS NOT OLD.total_value_chf OR
            NEW.currency IS NOT OLD.currency OR NEW.source_type IS NOT OLD.source_type OR
            NEW.quality_status IS NOT OLD.quality_status OR NEW.notes IS NOT OLD.notes OR
            NEW.created_at IS NOT OLD.created_at OR NEW.valuation_at IS NOT OLD.valuation_at OR
            NEW.source_reference IS NOT OLD.source_reference
        )
        BEGIN SELECT RAISE(ABORT, 'manual truewealth valuation fields are immutable'); END;

        CREATE TRIGGER IF NOT EXISTS truewealth_batches_immutable_update
        BEFORE UPDATE ON truewealth_import_batches
        BEGIN SELECT RAISE(ABORT, 'truewealth import batches are immutable'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_batches_no_delete
        BEFORE DELETE ON truewealth_import_batches
        BEGIN SELECT RAISE(ABORT, 'truewealth import batches cannot be deleted'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_snapshots_immutable_update
        BEFORE UPDATE ON truewealth_snapshots
        BEGIN SELECT RAISE(ABORT, 'truewealth snapshots are immutable'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_snapshots_no_delete
        BEFORE DELETE ON truewealth_snapshots
        BEGIN SELECT RAISE(ABORT, 'truewealth snapshots cannot be deleted'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_positions_immutable_update
        BEFORE UPDATE ON truewealth_snapshot_positions
        BEGIN SELECT RAISE(ABORT, 'truewealth positions are immutable'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_positions_no_delete
        BEFORE DELETE ON truewealth_snapshot_positions
        BEGIN SELECT RAISE(ABORT, 'truewealth positions cannot be deleted'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_cash_immutable_update
        BEFORE UPDATE ON truewealth_snapshot_cash
        BEGIN SELECT RAISE(ABORT, 'truewealth cash is immutable'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_cash_no_delete
        BEFORE DELETE ON truewealth_snapshot_cash
        BEGIN SELECT RAISE(ABORT, 'truewealth cash cannot be deleted'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_audit_immutable_update
        BEFORE UPDATE ON audit_log WHEN OLD.entity_type='truewealth_import_batch'
        BEGIN SELECT RAISE(ABORT, 'truewealth import audit is immutable'); END;
        CREATE TRIGGER IF NOT EXISTS truewealth_audit_no_delete
        BEFORE DELETE ON audit_log WHEN OLD.entity_type='truewealth_import_batch'
        BEGIN SELECT RAISE(ABORT, 'truewealth import audit cannot be deleted'); END;
        """
    )

def _apply_compat_migrations(conn: Connection) -> None:
    _add_missing_instrument_columns(conn)
    for table, text_columns in TEXT_AFFINITY_COLUMNS.items():
        _rebuild_table_with_text_columns(conn, table, text_columns)
    _create_broker_bank_mapping_tables(conn)
    _create_broker_import_review_items(conn)
    _create_broker_import_execution_plans(conn)
    _add_transaction_void_columns(conn)
    _create_fx_market_data_tables(conn)
    _create_market_quote_chart_tables(conn)
    _create_account_value_snapshot_tables(conn)
    _create_cash_account_snapshot_tables(conn)
    _create_instrument_import_candidates(conn)
    _create_budget_phase1_tables(conn)
    _create_budget_phase11_tables(conn)
    _create_budget_phase14_tables(conn)
    _create_budget_phase15_tables(conn)
    _create_budget_phase18_tables(conn)
    _create_budget_phase19_tables(conn)
    _create_budget_import_production_v1_tables(conn)
    _create_budget_categories_ux_fix_tables(conn)
    _create_budget_planning_forecast_v1_tables(conn)
    _create_budget_fixed_costs_subscriptions_v1_tables(conn)
    _create_budget_monthly_import_rule_learning_v1_tables(conn)
    _create_transfer_pairing_v2_tables(conn)
    _create_portfolio_policy_tables(conn)
    _create_portfolio_performance_tables(conn)
    _create_portfolio_ingestion_reconciliation_tables(conn)
    _create_daily_market_analytics_tables(conn)
    _create_postfinance_baseline_mapping_audit_v1(conn)
    _create_truewealth_verified_snapshot_v1(conn)
    _create_grocery_optimizer_v1_tables(conn)
    _add_grocery_price_provider_v1_columns(conn)
    _create_grocery_matching_learning_v2_tables(conn)

def apply_migrations(conn: Connection) -> None:
    conn.executescript(INITIAL_SCHEMA_SQL)
    existing_initial = conn.execute("SELECT 1 FROM schema_migrations WHERE version = 1").fetchone()
    if not existing_initial:
        conn.execute(
src/jarvis_finance/storage/migrations.py:1523:        CREATE TABLE IF NOT EXISTS account_value_snapshots (
src/jarvis_finance/storage/migrations.py:1535:        CREATE INDEX IF NOT EXISTS idx_account_value_snapshots_account_date ON account_value_snapshots(account_id, valuation_date, created_at);
src/jarvis_finance/storage/migrations.py:1931:        "account_value_snapshots",
src/jarvis_finance/storage/migrations.py:2038:            ON account_value_snapshots(source_reference)
src/jarvis_finance/storage/migrations.py:2042:        BEFORE DELETE ON account_value_snapshots
src/jarvis_finance/storage/migrations.py:2046:        BEFORE UPDATE ON account_value_snapshots
src/jarvis_finance/storage/migrations.py:2050:        BEFORE UPDATE ON account_value_snapshots


PYTHONDONTWRITEBYTECODE=1 python -m pytest tests/test_truewealth_sprint12.py -q


Output:
Chunk ID: d166ab
Wall time: 0.0500 seconds
Process exited with code 1
Original token count: 1697
Output:
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pytest/__main__.py", line 9, in <module>
    raise SystemExit(pytest.console_main())
                     ^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/config/__init__.py", line 223, in console_main
    code = main()
           ^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/config/__init__.py", line 193, in main
    config = _prepareconfig(new_args, plugins)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/config/__init__.py", line 361, in _prepareconfig
    config: Config = pluginmanager.hook.pytest_cmdline_parse(
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pluggy/_hooks.py", line 512, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pluggy/_callers.py", line 167, in _multicall
    raise exception
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pluggy/_callers.py", line 139, in _multicall
    teardown.throw(exception)
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/helpconfig.py", line 124, in pytest_cmdline_parse
    config = yield
             ^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pluggy/_callers.py", line 121, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/config/__init__.py", line 1186, in pytest_cmdline_parse
    self.parse(args)
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/config/__init__.py", line 1556, in parse
    self.hook.pytest_load_initial_conftests(
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pluggy/_hooks.py", line 512, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pluggy/_callers.py", line 167, in _multicall
    raise exception
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pluggy/_callers.py", line 139, in _multicall
    teardown.throw(exception)
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/warnings.py", line 128, in pytest_load_initial_conftests
    return (yield)
            ^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/pluggy/_callers.py", line 116, in _multicall
    next(function_gen)  # first yield
    ^^^^^^^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/capture.py", line 170, in pytest_load_initial_conftests
    capman.start_global_capturing()
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/capture.py", line 773, in start_global_capturing
    self._global_capturing = _get_multicapture(self._method)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/capture.py", line 714, in _get_multicapture
    return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2))
                                              ^^^^^^^^^^^^
  File "/home/agent/.hermes/worktrees/FinanceManager-sprint9-portfolio-basis-policy/.venv/lib/python3.11/site-packages/_pytest/capture.py", line 493, in __init__
    TemporaryFile(buffering=0),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/tempfile.py", line 628, in TemporaryFile
    prefix, suffix, dir, output_type = _sanitize_params(prefix, suffix, dir)
                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/tempfile.py", line 127, in _sanitize_params
    dir = gettempdir()
          ^^^^^^^^^^^^
  File "/home/agent/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/tempfile.py", line 316, in gettempdir
    return _os.fsdecode(_gettempdir())
                        ^^^^^^^^^^^^^
  File "/home/agent/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/tempfile.py", line 309, in _gettempdir
    tempdir = _get_default_tempdir()
              ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/agent/.local/share/uv/python/cpython-3.11.15-linux-x86_64-gnu/lib/python3.11/tempfile.py", line 224, in _get_default_tempdir
    raise FileNotFoundError(_errno.ENOENT,
FileNotFoundError: [Errno 2] No usable temporary directory found in ['/tmp', '/var/tmp', '/usr/tmp', '/home/agent/.hermes/worktrees/FinanceManager-sprint12-truewealth-verified-import']


sed -n '1,140p' src/jarvis_finance/storage/database.py && grep -R "confirmAccountValue\|account_value_snapshots" -n src/jarvis_finance/api/routers src/jarvis_finance/services | head -80


Output:
Chunk ID: cb7c1a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 852
Output:
from __future__ import annotations

import sqlite3
from pathlib import Path

def connect(db_path: str | Path) -> sqlite3.Connection:
    if str(db_path) == ":memory:":
        conn = sqlite3.connect(":memory:", check_same_thread=False)
    else:
        path = Path(db_path).expanduser()
        path.parent.mkdir(parents=True, exist_ok=True)
        conn = sqlite3.connect(path, check_same_thread=False)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys = ON")
    return conn

def connect_memory() -> sqlite3.Connection:
    conn = sqlite3.connect(":memory:", check_same_thread=False)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys = ON")
    return conn
grep: src/jarvis_finance/services/__pycache__/portfolio_performance.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_data.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/reconciliation_snapshot.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/manual_entry_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/truewealth_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/truewealth_service.cpython-312.pyc: binary file matches
src/jarvis_finance/services/manual_entry_service.py:1097:        INSERT INTO account_value_snapshots(snapshot_id, account_id, valuation_date, total_value_chf, currency, source_type, quality_status, notes, created_at)
src/jarvis_finance/services/portfolio_data.py:214:        f"""SELECT * FROM account_value_snapshots
src/jarvis_finance/services/portfolio_data.py:1195:            """INSERT INTO account_value_snapshots(
src/jarvis_finance/services/portfolio_data.py:1370:        ("legacy_account_values", "Bestehende Account-Totalwerte", "account_value_snapshots", True, False, True, ["Account-Totalwert"]),
src/jarvis_finance/services/portfolio_data.py:1599:            """SELECT total_value_chf,valuation_date,source_type FROM account_value_snapshots
src/jarvis_finance/services/reconciliation_snapshot.py:203:           FROM account_value_snapshots
src/jarvis_finance/services/portfolio_performance.py:234:          FROM account_value_snapshots s
src/jarvis_finance/services/portfolio_service.py:17:        FROM account_value_snapshots avs
src/jarvis_finance/services/truewealth_service.py:69:                 SELECT 1 FROM account_value_snapshots v
src/jarvis_finance/services/truewealth_service.py:107:           FROM account_value_snapshots WHERE account_id=?""",
src/jarvis_finance/services/truewealth_service.py:394:            """INSERT INTO account_value_snapshots(
src/jarvis_finance/services/truewealth_service.py:486:            """INSERT INTO account_value_snapshots(
src/jarvis_finance/services/truewealth_service.py:523:        """SELECT snapshot_id,is_active FROM account_value_snapshots
src/jarvis_finance/services/truewealth_service.py:540:            """UPDATE account_value_snapshots SET is_active=?,deactivated_at=?,deactivation_reason=?,updated_at=?
src/jarvis_finance/services/truewealth_service.py:565:           FROM account_value_snapshots
src/jarvis_finance/services/truewealth_service.py:608:           FROM account_value_snapshots WHERE account_id=?


sed -n '1510,1545p' src/jarvis_finance/storage/migrations.py && grep -n "source_type" -n src/jarvis_finance/storage/schema.py | head -30


Output:
Chunk ID: 49347f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3436
Output:
            source_quality TEXT NOT NULL DEFAULT 'fresh',
            fetched_at TEXT NOT NULL,
            UNIQUE(asset_id, provider, provider_symbol, timestamp, interval, currency)
        );
        CREATE INDEX IF NOT EXISTS idx_crypto_price_points_asset_time ON crypto_price_points(asset_id, currency, timestamp);
        """
    )
    _add_missing_columns(conn, "crypto_assets", {"binance_symbol": "TEXT", "binance_mapping_status": "TEXT NOT NULL DEFAULT 'missing'"})

def _create_account_value_snapshot_tables(conn: Connection) -> None:
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS account_value_snapshots (
            snapshot_id TEXT PRIMARY KEY,
            account_id TEXT NOT NULL REFERENCES accounts(account_id),
            valuation_date TEXT NOT NULL,
            total_value_chf TEXT NOT NULL,
            currency TEXT NOT NULL DEFAULT 'CHF',
            source_type TEXT NOT NULL DEFAULT 'manual_total_value',
            quality_status TEXT NOT NULL DEFAULT 'ok',
            notes TEXT,
            created_at TEXT NOT NULL,
            updated_at TEXT
        );
        CREATE INDEX IF NOT EXISTS idx_account_value_snapshots_account_date ON account_value_snapshots(account_id, valuation_date, created_at);
        """
    )

def _create_cash_account_snapshot_tables(conn: Connection) -> None:
    _add_missing_columns(conn, "accounts", {
        "balance_mode": "TEXT NOT NULL DEFAULT 'manual'",
        "portfolio_bucket": "TEXT NOT NULL DEFAULT 'cash'",
    })
    conn.executescript(
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"


sed -n '2135,2195p' src/jarvis_finance/storage/migrations.py


Output:
Chunk ID: dc2683
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 158
Output:
        conn.execute(
            "INSERT INTO schema_migrations(version, name, applied_at, checksum) VALUES (?, ?, ?, ?)",
            (1, "001_initial_schema", utc_now(), checksum_sql(INITIAL_SCHEMA_SQL)),
        )
    _apply_compat_migrations(conn)
    existing = conn.execute("SELECT 1 FROM schema_migrations WHERE version = ?", (MIGRATION_VERSION,)).fetchone()
    if not existing:
        conn.execute(
            "INSERT INTO schema_migrations(version, name, applied_at, checksum) VALUES (?, ?, ?, ?)",
            (MIGRATION_VERSION, MIGRATION_NAME, utc_now(), checksum_sql(MIGRATION_NAME)),
        )
    conn.commit()


sed -n '1,180p' frontend/src/api/truewealth.ts && grep -R "function apiGet\|export const apiGet" -n frontend/src/api | head -20


Output:
Chunk ID: 1c4bbf
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1093
Output:
import { apiGet, apiPost } from './client'

export type TrueWealthPosition = {
  snapshot_position_id: string
  instrument_name: string
  isin: string
  asset_type: string | null
  quantity: string
  price_currency: string
  source_price: string
  source_value_chf: string
}

export type TrueWealthCash = {
  snapshot_cash_id: string
  currency: string
  amount_original: string
  fx_rate_to_chf: string | null
  source_value_chf: string
}

export type TrueWealthHistory = {
  snapshot_id: string
  total_value_chf: string
  valuation_date: string
  valuation_at: string
  source_type: string
  quality_status: string
  notes: string | null
  is_active: number
  deactivated_at: string | null
  deactivation_reason: string | null
}

export type TrueWealthImportHistory = {
  batch_id: string
  snapshot_id: string
  snapshot_date: string
  parser_id: string
  parser_version: string
  confirmed_at: string
  status: string
  position_count: number
  cash_count: number
  source_total_chf: string
  securities_total_chf: string
  cash_total_chf: string
  components_total_chf: string
  reconciliation_difference_chf: string
  reconciliation_status: string
  completeness_status: string
}

export type TrueWealthSummary = {
  portfolio_label: string
  current_value_chf: string | null
  current_valuation_at: string | null
  current_source_type: string
  current_is_manual_provisional: boolean
  current_label: string
  positions_snapshot_date: string | null
  positions_label: string
  mixed_as_of: boolean
  allocation_performance_usable: boolean
  positions: TrueWealthPosition[]
  cash: TrueWealthCash[]
  history: TrueWealthHistory[]
  imports: TrueWealthImportHistory[]
  latest_official: Record<string, unknown> | null
}

export type TrueWealthImportPayload = {
  file_name: string
  content_base64: string
}

export type TrueWealthImportPreview = TrueWealthImportPayload & {
  preview_id: string
  confirmation_id: string
  portfolio_label: string
  snapshot_date: string
  period_from: string
  period_to: string
  file_sha256: string
  page_count: number
  source_total_chf: string
  securities_total_chf: string
  cash_total_chf: string
  components_total_chf: string
  reconciliation_difference_chf: string
  reconciliation_status: string
  positions: Array<{ name: string; isin: string; quantity: string; currency: string; source_price: string; source_value_chf: string }>
  cash: Array<{ currency: string; amount_original: string; fx_rate_to_chf: string | null; source_value_chf: string }>
  duplicate: boolean
  existing_batch_id: string | null
  conflict: boolean
  conflicting_batch_id: string | null
  warnings: string[]
}

export type ManualValuePayload = { total_value_chf: string; valuation_at: string; note: string }
export type ManualValuePreview = ManualValuePayload & {
  preview_id: string
  confirmation_id: string
  valuation_date: string
  source_type: string
  status: string
  warnings: string[]
}

export const getTrueWealthSummary = (refresh = false) => apiGet<TrueWealthSummary>('/api/truewealth/summary', { refresh })
export const previewTrueWealthImport = (payload: TrueWealthImportPayload) => apiPost<TrueWealthImportPreview>('/api/truewealth/imports/preview', payload)
export const confirmTrueWealthImport = (payload: TrueWealthImportPayload & Pick<TrueWealthImportPreview, 'preview_id' | 'confirmation_id'> & { confirm: true }) => apiPost<{ status: string; batch_id: string; snapshot_id?: string; audit_id: string; idempotent: boolean }>('/api/truewealth/imports/confirm', payload)
export const previewManualTrueWealthValue = (payload: ManualValuePayload) => apiPost<ManualValuePreview>('/api/truewealth/manual-values/preview', payload)
export const confirmManualTrueWealthValue = (payload: ManualValuePayload & Pick<ManualValuePreview, 'preview_id' | 'confirmation_id'> & { confirm: true }) => apiPost<{ status: string; snapshot_id: string; audit_id: string }>('/api/truewealth/manual-values/confirm', payload)
export const setManualTrueWealthValueStatus = (snapshotId: string, active: boolean, note: string) => apiPost<{ status: string; snapshot_id: string; audit_id: string; idempotent: boolean }>(`/api/truewealth/manual-values/${encodeURIComponent(snapshotId)}/status`, { active, note, confirm: true })
frontend/src/api/client.ts:14:export async function apiGet<T>(path: string, options: { refresh?: boolean } = {}): Promise<T> {


sed -n '1,60p' frontend/src/api/client.ts


Output:
Chunk ID: abd921
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 406
Output:
const DEFAULT_API_BASE_URL = ''

export const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL as string | undefined) ?? DEFAULT_API_BASE_URL

const sessionCache = new Map<string, unknown>()

export class ApiError extends Error {
  constructor(message: string, public readonly status?: number) {
    super(message)
    this.name = 'ApiError'
  }
}

export async function apiGet<T>(path: string, options: { refresh?: boolean } = {}): Promise<T> {
  const url = `${API_BASE_URL}${path}`
  if (!options.refresh && sessionCache.has(url)) {
    return sessionCache.get(url) as T
  }
  let response: Response
  try {
    response = await fetch(url, { headers: { Accept: 'application/json' } })
  } catch {
    throw new ApiError('Backend nicht erreichbar')
  }
  if (!response.ok) {
    throw new ApiError(response.status === 404 ? 'Runtime-DB nicht verfügbar' : `Lokale API meldet Fehler ${response.status}`, response.status)
  }
  const data = (await response.json()) as T
  sessionCache.set(url, data)
  return data
}

export async function apiPost<T>(path: string, body: unknown): Promise<T> {
  const url = `${API_BASE_URL}${path}`
  let response: Response
  try {
    response = await fetch(url, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
  } catch {
    throw new ApiError('Backend nicht erreichbar')
  }
  if (!response.ok) {
    throw new ApiError(`Lokale API meldet Fehler ${response.status}`, response.status)
  }
  clearApiCache()
  return (await response.json()) as T
}

export function clearApiCache() {
  sessionCache.clear()
}


grep -R "get_current_truewealth_value\|_latest_truewealth_value" -n src tests | head -50 && git diff -- tests/unit/test_portfolio_data_ingestion_reconciliation.py


Output:
Chunk ID: 9d53e2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 383
Output:
grep: src/jarvis_finance/services/__pycache__/portfolio_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/truewealth_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/truewealth_service.cpython-312.pyc: binary file matches
src/jarvis_finance/services/portfolio_service.py:13:def _latest_truewealth_value(conn: Connection) -> Decimal:
src/jarvis_finance/services/portfolio_service.py:52:    truewealth = _latest_truewealth_value(conn)
src/jarvis_finance/services/truewealth_service.py:576:def get_current_truewealth_value(conn: Connection) -> Decimal:
diff --git a/tests/unit/test_portfolio_data_ingestion_reconciliation.py b/tests/unit/test_portfolio_data_ingestion_reconciliation.py
index 4837566..28a5ca5 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 == 43
-    assert conn.execute("SELECT MAX(version) FROM schema_migrations").fetchone()[0] == 43
+    assert MIGRATION_VERSION == 44
+    assert conn.execute("SELECT MAX(version) FROM schema_migrations").fetchone()[0] == 44
     assert {row[1] for row in conn.execute("PRAGMA table_info(instrument_price_mappings)")} >= {
         "source_symbol", "source_venue", "source_currency",
     }


python - <<'PY'
import ast, pathlib
files=['src/jarvis_finance/api/routers/truewealth.py','src/jarvis_finance/api/schemas/truewealth.py','src/jarvis_finance/imports/truewealth_tax_statement.py','src/jarvis_finance/services/truewealth_service.py','src/jarvis_finance/storage/migrations.py','src/jarvis_finance/api/main.py']
for f in files:
    ast.parse(pathlib.Path(f).read_text(), filename=f)
    print('ok',f)
PY


Output:
Chunk ID: 1bbf73
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 72
Output:
ok src/jarvis_finance/api/routers/truewealth.py
ok src/jarvis_finance/api/schemas/truewealth.py
ok src/jarvis_finance/imports/truewealth_tax_statement.py
ok src/jarvis_finance/services/truewealth_service.py
ok src/jarvis_finance/storage/migrations.py
ok src/jarvis_finance/api/main.py


PYTHONDONTWRITEBYTECODE=1 python - <<'PY'
from tests.test_truewealth_sprint12 import make_conn, statement, confirm_import, import_request
from jarvis_finance.services import truewealth_service as service
from hashlib import sha256
raw=b'%PDF-x'
conn=make_conn()
service.parse_truewealth_tax_statement=lambda value: statement(value)
service._archive_pdf=lambda raw,digest: (f'truewealth/archive/{digest}.pdf', False)
print(service.preview_truewealth_import(conn, import_request(raw))['source_total_chf'])
print(confirm_import(conn, raw))
print(service.get_truewealth_summary(conn)['current_value_chf'])
PY


Output:
Chunk ID: f7c4ce
Wall time: 0.0387 seconds
Process exited with code 0
Original token count: 50
Output:
12000.00
{'status': 'confirmed', 'batch_id': 'twbatch_6e7689d69bcd44cab58f', 'snapshot_id': 'twsnapshot_edd9af7482ad4018b05c', 'audit_id': 'audit_2851002828ef43208b35', 'idempotent': False}
12000.00