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

from sqlite3 import Connection

def create_postfinance_ledger_import_v1(conn: Connection) -> None:
    """Create immutable PostFinance source, ledger, snapshot and cost-basis tables."""
    position_columns = {row[1] for row in conn.execute("PRAGMA table_info(positions_snapshot)")}
    if "source_type" not in position_columns:
        conn.execute("ALTER TABLE positions_snapshot ADD COLUMN source_type TEXT")
    if "source_reference" not in position_columns:
        conn.execute("ALTER TABLE positions_snapshot ADD COLUMN source_reference TEXT")
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS postfinance_account_roles (
            role TEXT PRIMARY KEY CHECK(role IN ('efinance','etrading_depot','etrading_cash')),
            account_id TEXT NOT NULL REFERENCES accounts(account_id),
            source_reference_hash TEXT,
            created_at TEXT NOT NULL,
            UNIQUE(account_id, role)
        );
        CREATE TABLE IF NOT EXISTS postfinance_import_batches (
            batch_id TEXT PRIMARY KEY,
            bundle_sha256 TEXT NOT NULL UNIQUE CHECK(length(bundle_sha256)=64),
            zip_sha256 TEXT NOT NULL CHECK(length(zip_sha256)=64),
            overview_sha256 TEXT NOT NULL CHECK(length(overview_sha256)=64),
            parser_id TEXT NOT NULL,
            parser_version TEXT NOT NULL,
            db_revision TEXT NOT NULL,
            archive_reference TEXT NOT NULL,
            snapshot_at 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 TABLE IF NOT EXISTS postfinance_documents (
            document_hash TEXT PRIMARY KEY CHECK(length(document_hash)=64),
            filename_hash TEXT NOT NULL CHECK(length(filename_hash)=64),
            semantic_identity TEXT NOT NULL UNIQUE CHECK(length(semantic_identity)=64),
            semantic_reference_hash TEXT NOT NULL CHECK(length(semantic_reference_hash)=64),
            document_type TEXT NOT NULL,
            document_date TEXT,
            account_reference_hash TEXT,
            account_role TEXT NOT NULL CHECK(account_role IN ('efinance','etrading_depot','etrading_cash')),
            page_count INTEGER NOT NULL CHECK(page_count > 0),
            archive_reference TEXT NOT NULL,
            parser_version TEXT NOT NULL,
            created_at TEXT NOT NULL
        );
        CREATE TABLE IF NOT EXISTS postfinance_batch_documents (
            batch_id TEXT NOT NULL REFERENCES postfinance_import_batches(batch_id),
            document_hash TEXT NOT NULL REFERENCES postfinance_documents(document_hash),
            PRIMARY KEY(batch_id, document_hash)
        );
        CREATE TABLE IF NOT EXISTS postfinance_snapshots (
            snapshot_id TEXT PRIMARY KEY,
            batch_id TEXT NOT NULL UNIQUE REFERENCES postfinance_import_batches(batch_id),
            account_id TEXT NOT NULL REFERENCES accounts(account_id),
            snapshot_at TEXT NOT NULL,
            total_chf TEXT NOT NULL,
            securities_chf TEXT NOT NULL,
            cash_chf TEXT NOT NULL,
            stocks_chf TEXT NOT NULL,
            etfs_chf TEXT NOT NULL,
            component_total_chf TEXT NOT NULL,
            difference_chf TEXT NOT NULL,
            tolerance_chf TEXT NOT NULL,
            position_count INTEGER NOT NULL,
            cash_count INTEGER NOT NULL,
            open_orders INTEGER NOT NULL,
            reconciliation_status TEXT NOT NULL CHECK(reconciliation_status IN ('matched','within_tolerance')),
            created_at TEXT NOT NULL
        );
        CREATE TABLE IF NOT EXISTS postfinance_snapshot_positions (
            snapshot_position_id TEXT PRIMARY KEY,
            snapshot_id TEXT NOT NULL REFERENCES postfinance_snapshots(snapshot_id),
            source_row_reference TEXT NOT NULL,
            instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),
            asset_class TEXT NOT NULL CHECK(asset_class IN ('stock','etf')),
        row = conn.execute(
            "SELECT batch_id,audit_id FROM postfinance_import_batches WHERE bundle_sha256=?",
            (preview["bundle_sha256"],),
        ).fetchone()
        return {
            "status": "confirmed",
            "batch_id": row["batch_id"],
            "audit_id": row["audit_id"],
            "idempotent": True,
        }
    accounts = _resolve_accounts(conn, request)
    zip_raw, overview_raw = _source_bytes(request)
    bundle = parse_postfinance_bundle(zip_raw, overview_raw)  # mandatory byte reparse on confirm
    if (
        bundle.bundle_hash != preview["bundle_sha256"]
        or _revision(conn, accounts) != preview["db_revision"]
    ):
        raise ValueError("PostFinance source bytes or database revision changed after preview")
    position_map = _match_instruments(conn, bundle, accounts["etrading_depot"])
    event_instruments = {
        event.event_fingerprint: _event_instrument(event, bundle, position_map, conn)
        for event in bundle.events
    }
    archive_refs: list[tuple[str, bool]] = []
    zip_ref, created = _archive(zip_raw, bundle.zip_hash, "zip")
    archive_refs.append((zip_ref, created))
    overview_ref, created = _archive(overview_raw, bundle.overview_hash, "pdf")
    archive_refs.append((overview_ref, created))
    for document in bundle.documents:
        reference, created = _archive(document.raw, document.document_hash, "pdf")
        archive_refs.append((reference, created))
    now = _now()
    batch_id = _id("pfbatch")
    snapshot_id = _id("pfsnapshot")
    try:
        conn.execute("BEGIN IMMEDIATE")
        if _revision(conn, accounts) != preview["db_revision"]:
            raise ValueError("PostFinance database revision changed before persistence")
        _ensure_efinance_account(conn, accounts, now)
        for role, account_id in accounts.items():
            conn.execute(
                "INSERT OR IGNORE INTO postfinance_account_roles(role,account_id,source_reference_hash,created_at) VALUES(?,?,NULL,?)",
                (role, account_id, now),
            )
            mapped = conn.execute(
                "SELECT account_id FROM postfinance_account_roles WHERE role=?", (role,)
            ).fetchone()
            if not mapped or mapped["account_id"] != account_id:
                raise ValueError(
                    "PostFinance account-role mapping conflicts with confirmed mapping"
                )
        audit_id = _audit(conn, batch_id, bundle, now)
        conn.execute(
            """INSERT INTO postfinance_import_batches(batch_id,bundle_sha256,zip_sha256,overview_sha256,parser_id,parser_version,db_revision,archive_reference,snapshot_at,status,audit_id,confirmed_at,confirmed_by) VALUES(?,?,?,?,?,?,?,?,?,'confirmed',?,?,'user')""",
            (
                batch_id,
                bundle.bundle_hash,
                bundle.zip_hash,
                bundle.overview_hash,
                PARSER_ID,
                PARSER_VERSION,
                preview["db_revision"],
                zip_ref,
                bundle.snapshot.valuation_at,
                audit_id,
                now,
            ),
        )
        for document in bundle.documents:
            reference = f"postfinance/archive/{document.document_hash}.pdf"
            ref_hash = _hash(document.semantic_reference)
from __future__ import annotations

import json
from collections.abc import Mapping
from decimal import Decimal
from sqlite3 import Connection

from fastapi import HTTPException

from jarvis_finance.api.schemas.positions import EquityPortfolioSummary, EquityPosition
from jarvis_finance.dashboard import data as dashboard_data
from jarvis_finance.services.api_helpers import optional_decimal_text

def _asset_class_label(value: object) -> str:
    return "ETF" if str(value or "").lower() == "etf" else "Aktie"

def _account_label(row: dict[str, str]) -> str:
    platform = str(row.get("platform") or "").strip()
    account = str(row.get("account_name") or "").strip()
    if "postfinance" in platform.lower():
        if not account or "manual" in account.lower():
            return "PostFinance E-Trading"
        return account if "postfinance" in account.lower() or "e-trading" in account.lower() else f"PostFinance · {account}"
    return account or platform

def _latest_analysis(conn: Connection) -> tuple[dict[str, object] | None, dict[tuple[str, str], dict[str, object]], dict[str, str]]:
    row = conn.execute(
        """SELECT pas.*,mdr.completed_at,mdr.missing_instruments_json
             FROM portfolio_analysis_snapshots pas
             JOIN market_data_runs mdr ON mdr.run_id=pas.run_id
            ORDER BY pas.as_of DESC,pas.created_at DESC LIMIT 1"""
    ).fetchone()
    if not row:
        return None, {}, {}
    try:
        payload = json.loads(row["summary_json"] or "{}")
    except json.JSONDecodeError:
        payload = {}
    values = {
        (str(item.get("account_id") or ""), str(item.get("instrument_id") or "")): item
        for item in payload.get("positions", [])
        if isinstance(item, dict)
    }
    try:
        missing_rows = json.loads(row["missing_instruments_json"] or "[]")
    except json.JSONDecodeError:
        missing_rows = []
    missing = {
        str(item.get("instrument_id") or ""): str(item.get("reason_code") or "price_missing")
        for item in missing_rows
        if isinstance(item, dict)
    }
    return dict(row), values, missing

def _status_reason(code: str) -> str:
    labels = {
        "fmp_endpoint_restricted": "Der primäre Provider beschränkt diesen historischen Kurs im aktuellen Tarif.",
        "plan_restricted": "Der Provider beschränkt diesen historischen Kurs im aktuellen Tarif.",
        "fmp_rate_limited": "Das Provider-Limit wurde erreicht. Der Lauf kann kontrolliert fortgesetzt werden.",
        "rate_limited": "Das Provider-Limit wurde erreicht. Der Lauf kann kontrolliert fortgesetzt werden.",
        "currency_mismatch": "Die Kurswährung stimmt nicht mit der bestätigten Instrumentwährung überein.",
        "exchange_mismatch": "Der Handelsplatz des Provider-Ergebnisses ist nicht bestätigt.",
        "future_price_rejected": "Der gelieferte Kurs liegt nach dem zulässigen Börsenstichtag.",
        "stale_price": "Der letzte bestätigte Kurs ist für den Börsenstichtag zu alt und wird nicht bewertet.",
        "mapping_required": "Die Provider-Zuordnung ist noch nicht eindeutig bestätigt.",
        "fx_rate_missing": "Für die CHF-Bewertung fehlt ein bestätigter FX-Kurs.",
    }
    return labels.get(code, "Für den Börsenstichtag konnte noch kein bestätigter Kurs geladen werden.")

def _position_status(row: Mapping[str, object]) -> str:
    """Compatibility status when no audited market-analysis snapshot exists yet."""

    if "quantity" in row and Decimal(str(row.get("quantity") or "0")) <= 0:
        return "Einstand unvollständig"
    market_value = row.get("market_value_chf")
    warnings = str(row.get("quality_warnings") or "")
    if market_value not in {None, ""}:
        if "missing_cost_basis" in warnings or "missing_fx" in warnings:
            return "Einstand unvollständig"
        return "Bewertet"
    if str(row.get("price_status") or "") != "ok":
        return "Kurs fehlt"
    if str(row.get("fx_status") or "") == "missing_fx":
        return "FX fehlt für Bewertung"
    return "Bewertung unvollständig"

def _mapping_details(conn: Connection, instrument_id: str) -> dict[str, str | None]:
    row = conn.execute(
        """SELECT provider,provider_symbol,provider_market,upper(COALESCE(trading_currency,currency,'')) currency
             FROM instrument_price_mappings
            WHERE instrument_id=? AND mapping_status='mapped'
            ORDER BY updated_at DESC,mapping_id LIMIT 1""",
        (instrument_id,),
    ).fetchone()
    return dict(row) if row else {"provider": None, "provider_symbol": None, "provider_market": None, "currency": None}

def list_equity_positions(conn: Connection) -> list[EquityPosition]:
    source_rows = [
        row for row in dashboard_data.get_positions(conn)
        if row.get("asset_class", "").lower() in {"stock", "equity", "etf"}
        and row.get("instrument_status", "").lower() != "inactive"
    ]
    analysis, values, missing = _latest_analysis(conn)
    effective_values: dict[tuple[str, str], Decimal | None] = {}
    for row in source_rows:
        key = (str(row.get("account_id") or ""), str(row.get("instrument_id") or ""))
        candidate = values.get(key)
        row_is_audited = row.get("valuation_source") in {
            "postfinance_official_import",
            "audited_market_run",
        }
        candidate_is_newer = bool(
            analysis
            and candidate
            and str(analysis.get("as_of") or "") > str(row.get("valuation_as_of") or row.get("price_date") or "")
            and candidate.get("quality_status") == "fresh"
        )
        raw = (
            candidate.get("value_chf")
            if candidate_is_newer and candidate
            else row.get("market_value_chf") if analysis is None or row_is_audited else None
        )
        effective_values[key] = Decimal(str(raw)) if raw not in {None, ""} else None
    complete = bool(source_rows) and all(value is not None for value in effective_values.values())
    total = sum((value or Decimal("0") for value in effective_values.values()), Decimal("0"))
    positions: list[EquityPosition] = []
    for row in source_rows:
        account_id = str(row.get("account_id") or "")
        instrument_id = str(row.get("instrument_id") or "")
        snapshot_value = values.get((account_id, instrument_id))
        value = (
            snapshot_value
            if analysis
            and snapshot_value
            and snapshot_value.get("quality_status") == "fresh"
            and str(analysis.get("as_of") or "") > str(row.get("valuation_as_of") or row.get("price_date") or "")
            else None
        )
        row_fallback_allowed = analysis is None or row.get("valuation_source") in {
            "postfinance_official_import",
            "audited_market_run",
        }
        mapping = _mapping_details(conn, instrument_id)
        raw_market_value = (
            value.get("value_chf")
            if value
            else row.get("market_value_chf") if row_fallback_allowed else None
        )
        if Decimal(str(row.get("quantity") or "0")) <= 0:
            raw_market_value = None
        market_value = Decimal(str(raw_market_value)) if raw_market_value not in {None, ""} else None
        share = (market_value / total * Decimal("100")) if complete and market_value is not None and total > 0 else None
        code = "valued" if market_value is not None else ("stale_price" if snapshot_value else missing.get(instrument_id, "price_missing"))


Output:
Chunk ID: 994859
Wall time: 2.1748 seconds
Process exited with code 0
Original token count: 25
Output:
........                                                                 [100%]
8 passed in 1.93s


nl -ba src/jarvis_finance/dashboard/data.py | sed -n '550,690p'; nl -ba src/jarvis_finance/services/portfolio_aggregation.py | sed -n '80,260p'; nl -ba src/jarvis_finance/services/postfinance_service.py | sed -n '870,930p'; nl -ba src/jarvis_finance/services/equity_service.py | sed -n '108,205p'


Output:
Chunk ID: abe509
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 6562
Output:
   550	            "currency": position.currency,
   551	            "amount_original": position.amount,
   552	            "amount_chf": position.used_value_chf,
   553	            "quality_status": "ok" if position.status != "Abgleich offen" else "partial",
   554	        }
   555	        for position in list_cash_positions(conn)
   556	    ]
   557
   558
   559	def get_positions(conn: Connection) -> list[dict[str, str]]:
   560	    result = calculate_positions(conn, persist_alerts=False)
   561	    official = latest_official_postfinance_positions(conn)
   562	    post_snapshot_deltas = post_snapshot_postfinance_quantity_deltas(conn, official)
   563	    audited = latest_audited_market_values(conn)
   564	    roles = postfinance_account_roles(conn)
   565	    trading_cash_id = roles.get("etrading_cash")
   566	    official_instruments = {key[1] for key in official}
   567	    for key in list(result.positions):
   568	        if key[0] == trading_cash_id and key[1] in official_instruments:
   569	            # Instrument-bearing settlement events live on trading cash, while the
   570	            # authoritative holding belongs to the distinct depot role.
   571	            result.positions.pop(key)
   572	    for key, override in official.items():
   573	        result.positions.setdefault(
   574	            key, Position(account_id=override.account_id, instrument_id=override.instrument_id)
   575	        )
   576	    rows: list[dict[str, str]] = []
   577	    for (account_id, instrument_id), pos in sorted(result.positions.items()):
   578	        row = conn.execute(
   579	            """
   580	            SELECT a.account_name, p.name AS platform, i.name, i.ticker, i.isin, i.asset_class, i.currency,
   581	                   i.hedge_status, i.instrument_status, i.valuation_policy, i.corporate_action_status,
   582	                   mp.close AS latest_price, mp.currency AS latest_price_currency, mp.price_date AS latest_price_date,
   583	                   mp.provider AS latest_price_provider
   584	            FROM accounts a JOIN platforms p ON p.platform_id=a.platform_id
   585	            JOIN instruments i ON i.instrument_id=?
   586	            LEFT JOIN market_prices mp ON mp.market_price_id = (
   587	                SELECT market_price_id FROM market_prices
   588	                WHERE instrument_id=i.instrument_id AND quality_status IN ('fresh','ok')
   589	                ORDER BY price_date DESC, created_at DESC LIMIT 1
   590	            )
   591	            WHERE a.account_id=?
   592	            """,
   593	            (instrument_id, account_id),
   594	        ).fetchone()
   595	        override = official.get((account_id, instrument_id))
   596	        effective_quantity = (
   597	            override.quantity + post_snapshot_deltas.get(instrument_id, Decimal("0"))
   598	            if override
   599	            else pos.quantity
   600	        )
   601	        if override and effective_quantity <= 0:
   602	            continue
   603	        market_value = audited.get((account_id, instrument_id))
   604	        use_market_run = bool(
   605	            market_value
   606	            and (not override or market_value.as_of > override.snapshot_date)
   607	        )
   608	        if override and not use_market_run:
   609	            quantity = effective_quantity
   610	            market_price = override.market_price_original
   611	            market_value_chf = (
   612	                override.market_value_chf * effective_quantity / override.quantity
   613	                if override.quantity
   614	                else Decimal("0")
   615	            )
   616	            price_currency = override.price_currency
   617	            price_date = override.valuation_at
   618	            price_provider = "PostFinance official snapshot"
   619	            valuation_source = "postfinance_official_import"
   620	        elif use_market_run and market_value:
   621	            quantity = effective_quantity
   622	            market_price = market_value.close
   623	            market_value_chf = (
   624	                market_value.value_chf * effective_quantity / market_value.quantity
   625	                if market_value.quantity
   626	                else market_value.value_chf
   627	            )
   628	            price_currency = market_value.currency
   629	            price_date = market_value.as_of
   630	            price_provider = market_value.provider
   631	            valuation_source = "audited_market_run"
   632	        else:
   633	            quantity = pos.quantity
   634	            market_price = pos.market_price_original
   635	            market_value_chf = pos.market_value_chf
   636	            price_currency = row["latest_price_currency"] if row else ""
   637	            price_date = date_text(row["latest_price_date"] if row else "")
   638	            price_provider = row["latest_price_provider"] if row else ""
   639	            valuation_source = "canonical_ledger_market"
   640	        rows.append({
   641	            "account_id": account_id,
   642	            "instrument_id": instrument_id,
   643	            "platform": row["platform"] if row else "",
   644	            "account_name": row["account_name"] if row else account_id,
   645	            "asset_class": row["asset_class"] if row else "",
   646	            "name": row["name"] if row else instrument_id,
   647	            "ticker": row["ticker"] if row else "",
   648	            "isin": row["isin"] if row else "",
   649	            "quantity": dec_text(quantity),
   650	            "cost_basis_chf": dec_text(pos.cost_basis_chf, "0.01"),
   651	            "market_price_original": dec_text(market_price, "0.01") if market_price is not None else "",
   652	            "price_currency": price_currency,
   653	            "price_date": price_date,
   654	            "price_provider": price_provider,
   655	            "market_value_chf": dec_text(market_value_chf, "0.01") if market_value_chf is not None else "",
   656	            "price_status": "ok" if market_price is not None else "missing_market_price",
   657	            "valuation_source": valuation_source,
   658	            "valuation_as_of": price_date,
   659	            "provider_symbol_status": "mapped" if conn.execute("SELECT 1 FROM instrument_price_mappings WHERE instrument_id=? AND mapping_status IN ('mapped','confirmed','user_confirmed') AND provider_symbol IS NOT NULL LIMIT 1", (instrument_id,)).fetchone() else "missing_provider_symbol",
   660	            "hedge_status": row["hedge_status"] if row else pos.hedge_status,
   661	            "instrument_status": row["instrument_status"] if row else pos.instrument_status,
   662	            "valuation_policy": row["valuation_policy"] if row else pos.valuation_policy,
   663	            "fx_status": "ok" if "missing_fx" not in pos.quality_warnings else "missing_fx",
   664	            "market_price_status": "ok" if market_price is not None else "missing_market_price",
   665	            "valuation_timestamp_status": pos.valuation_timestamp_status,
   666	            "corporate_action_status": row["corporate_action_status"] if row else pos.corporate_action_status,
   667	            "valuation_status": pos.valuation_status,
   668	            "total_return_chf": dec_text(pos.total_return_chf, "0.01") if pos.total_return_chf is not None else "",
   669	            "total_return_status": "complete" if pos.total_return_chf is not None else "incomplete",
   670	            "quality_warnings": ",".join(pos.quality_warnings),
   671	            "data_quality_status": pos.data_quality_status,
   672	        })
   673	    return rows
   674
   675
   676	def get_portfolio_positions_user(conn: Connection) -> list[dict[str, str]]:
   677	    rows: list[dict[str, str]] = []
   678	    for row in get_positions(conn):
   679	        if row.get("asset_class", "").lower() not in {"stock", "equity", "etf"}:
   680	            continue
   681	        rows.append({
   682	            "name": row.get("name", ""),
   683	            "ticker": row.get("ticker", ""),
   684	            "isin": row.get("isin", ""),
   685	            "depot": row.get("account_name", ""),
   686	            "assettyp": "ETF" if row.get("asset_class", "").lower() == "etf" else "Aktie",
   687	            "menge": row.get("quantity", ""),
   688	            "kurs": row.get("market_price_original") or "Preis noch nicht verfügbar",
   689	            "kurswaehrung": row.get("price_currency", ""),
   690	            "marktwert_chf": chf_text(row.get("market_value_chf")) if row.get("market_value_chf") else "nicht bewertet",
    80	        return []
    81	    raw_total = sum(values, Decimal("0"))
    82	    if raw_total <= 0 or target < 0:
    83	        raise ValueError("Official portfolio components cannot be reconciled")
    84	    allocated = [_money(value * target / raw_total) for value in values]
    85	    residual = _money(target - sum(allocated, Decimal("0")))
    86	    if residual:
    87	        largest = max(range(len(values)), key=values.__getitem__)
    88	        allocated[largest] = _money(allocated[largest] + residual)
    89	    if sum(allocated, Decimal("0")) != _money(target):
    90	        raise ValueError("Official portfolio component allocation does not reconcile")
    91	    return allocated
    92
    93
    94	def latest_official_postfinance_cash(
    95	    conn: Connection,
    96	    *,
    97	    as_of: str | None = None,
    98	    data_cutoff: str | None = None,
    99	) -> list[OfficialCashComponent]:
   100	    roles = postfinance_account_roles(conn)
   101	    account_id = roles.get("etrading_cash")
   102	    if not account_id:
   103	        return []
   104	    conditions: list[str] = []
   105	    params: list[str] = []
   106	    if as_of:
   107	        conditions.append("date(snapshot_at)<=date(?)")
   108	        params.append(as_of)
   109	    if data_cutoff:
   110	        conditions.append("datetime(created_at)<=datetime(?)")
   111	        params.append(data_cutoff)
   112	    where = f"WHERE {' AND '.join(conditions)}" if conditions else ""
   113	    snapshot = conn.execute(
   114	        f"""SELECT snapshot_id,snapshot_at,cash_chf
   115	            FROM postfinance_snapshots {where}
   116	            ORDER BY snapshot_at DESC,datetime(created_at) DESC LIMIT 1""",
   117	        params,
   118	    ).fetchone()
   119	    if not snapshot:
   120	        return []
   121	    rows = conn.execute(
   122	        """SELECT currency,amount_original,fx_rate_to_chf
   123	           FROM postfinance_snapshot_cash WHERE snapshot_id=? ORDER BY currency""",
   124	        (snapshot["snapshot_id"],),
   125	    ).fetchall()
   126	    raw_values = [
   127	        _decimal(row["amount_original"]) * _decimal(row["fx_rate_to_chf"])
   128	        for row in rows
   129	    ]
   130	    target = _money(_decimal(snapshot["cash_chf"]))
   131	    allocated = _allocate_pro_rata(raw_values, target)
   132	    valuation_at = str(snapshot["snapshot_at"])
   133	    return [
   134	        OfficialCashComponent(
   135	            account_id=account_id,
   136	            currency=str(row["currency"]),
   137	            amount_original=_decimal(row["amount_original"]),
   138	            source_fx_rate_to_chf=_decimal(row["fx_rate_to_chf"]),
   139	            amount_chf=value,
   140	            snapshot_date=valuation_at[:10],
   141	            valuation_at=valuation_at,
   142	        )
   143	        for row, value in zip(rows, allocated, strict=True)
   144	    ]
   145
   146
   147	def latest_official_postfinance_positions(
   148	    conn: Connection,
   149	    *,
   150	    as_of: str | None = None,
   151	    data_cutoff: str | None = None,
   152	) -> dict[tuple[str, str], OfficialPositionOverride]:
   153	    roles = postfinance_account_roles(conn)
   154	    account_id = roles.get("etrading_depot")
   155	    if not account_id:
   156	        return {}
   157	    conditions: list[str] = []
   158	    params: list[str] = []
   159	    if as_of:
   160	        conditions.append("date(snapshot_at)<=date(?)")
   161	        params.append(as_of)
   162	    if data_cutoff:
   163	        conditions.append("datetime(created_at)<=datetime(?)")
   164	        params.append(data_cutoff)
   165	    where = f"WHERE {' AND '.join(conditions)}" if conditions else ""
   166	    snapshot = conn.execute(
   167	        f"""SELECT snapshot_id,snapshot_at,stocks_chf,etfs_chf
   168	            FROM postfinance_snapshots {where}
   169	            ORDER BY snapshot_at DESC,datetime(created_at) DESC LIMIT 1""",
   170	        params,
   171	    ).fetchone()
   172	    if not snapshot:
   173	        return {}
   174	    rows = conn.execute(
   175	        """SELECT instrument_id,asset_class,quantity,price_original,price_currency,value_chf
   176	           FROM postfinance_snapshot_positions
   177	           WHERE snapshot_id=? ORDER BY asset_class,instrument_id""",
   178	        (snapshot["snapshot_id"],),
   179	    ).fetchall()
   180	    targets = {
   181	        "stock": _money(_decimal(snapshot["stocks_chf"])),
   182	        "etf": _money(_decimal(snapshot["etfs_chf"])),
   183	    }
   184	    allocated_by_instrument: dict[str, Decimal] = {}
   185	    for asset_class, target in targets.items():
   186	        members = [row for row in rows if str(row["asset_class"]).lower() == asset_class]
   187	        allocated = _allocate_pro_rata(
   188	            [_decimal(row["value_chf"]) for row in members], target
   189	        )
   190	        allocated_by_instrument.update(
   191	            {
   192	                str(row["instrument_id"]): value
   193	                for row, value in zip(members, allocated, strict=True)
   194	            }
   195	        )
   196	    valuation_at = str(snapshot["snapshot_at"])
   197	    return {
   198	        (account_id, str(row["instrument_id"])): OfficialPositionOverride(
   199	            account_id=account_id,
   200	            instrument_id=str(row["instrument_id"]),
   201	            asset_class=str(row["asset_class"]).lower(),
   202	            quantity=_decimal(row["quantity"]),
   203	            market_price_original=_decimal(row["price_original"]),
   204	            price_currency=str(row["price_currency"]),
   205	            market_value_chf=allocated_by_instrument[str(row["instrument_id"])],
   206	            snapshot_date=valuation_at[:10],
   207	            valuation_at=valuation_at,
   208	        )
   209	        for row in rows
   210	    }
   211
   212
   213	def post_snapshot_postfinance_quantity_deltas(
   214	    conn: Connection,
   215	    positions: dict[tuple[str, str], OfficialPositionOverride],
   216	    *,
   217	    as_of: str | None = None,
   218	) -> dict[str, Decimal]:
   219	    """Replay confirmed buys/sells after the official snapshot baseline."""
   220
   221	    if not positions:
   222	        return {}
   223	    roles = postfinance_account_roles(conn)
   224	    account_ids = [
   225	        account_id
   226	        for role in ("etrading_depot", "etrading_cash")
   227	        if (account_id := roles.get(role))
   228	    ]
   229	    if not account_ids:
   230	        return {}
   231	    snapshot_date = min(item.snapshot_date for item in positions.values())
   232	    effective_as_of = as_of or "9999-12-31"
   233	    placeholders = ",".join("?" for _ in account_ids)
   234	    rows = conn.execute(
   235	        f"""SELECT instrument_id,lower(transaction_type) transaction_type,quantity
   236	            FROM transactions
   237	            WHERE account_id IN ({placeholders})
   238	              AND instrument_id IS NOT NULL
   239	              AND COALESCE(is_confirmed,0)=1
   240	              AND COALESCE(is_voided,0)=0
   241	              AND lower(transaction_type) IN ('buy','sell','partial_sell','full_sell')
   242	              AND trade_date>? AND trade_date<=?""",
   243	        (*account_ids, snapshot_date, effective_as_of),
   244	    ).fetchall()
   245	    deltas: dict[str, Decimal] = {}
   246	    for row in rows:
   247	        instrument_id = str(row["instrument_id"])
   248	        quantity = abs(_decimal(row["quantity"]))
   249	        if str(row["transaction_type"]) in {"sell", "partial_sell", "full_sell"}:
   250	            quantity = -quantity
   251	        deltas[instrument_id] = deltas.get(instrument_id, Decimal("0")) + quantity
   252	    return deltas
   253
   254
   255	def latest_audited_market_values(
   256	    conn: Connection,
   257	) -> dict[tuple[str, str], AuditedMarketValue]:
   258	    """Return the latest immutable market-analysis values without writing on GET."""
   259
   260	    row = conn.execute(
   870	    latest = conn.execute(
   871	        """SELECT snapshot_id,snapshot_at,total_chf,securities_chf,cash_chf,stocks_chf,etfs_chf,
   872	                  position_count,cash_count,open_orders,difference_chf,reconciliation_status,created_at
   873	           FROM postfinance_snapshots ORDER BY snapshot_at DESC,created_at DESC LIMIT 1"""
   874	    ).fetchone()
   875	    if not latest:
   876	        return {
   877	            "latest_snapshot": None,
   878	            "positions": [],
   879	            "cash": [],
   880	            "event_counts": {},
   881	            "imports": [],
   882	        }
   883	    official_position_values = {
   884	        override.instrument_id: override.market_value_chf
   885	        for override in latest_official_postfinance_positions(conn).values()
   886	    }
   887	    positions = [
   888	        {
   889	            "row_key": _hash("position", row["instrument_name"], row["quantity"])[:16],
   890	            "instrument_name": row["instrument_name"],
   891	            "ticker": row["ticker"],
   892	            "asset_class": row["asset_class"],
   893	            "quantity": row["quantity"],
   894	            "provider_average_cost_original": row["provider_average_cost_original"],
   895	            "provider_cost_total_original": row["provider_cost_total_original"],
   896	            "price_original": row["price_original"],
   897	            "price_currency": row["price_currency"],
   898	            "value_chf": _money(official_position_values.get(row["instrument_id"], Decimal(str(row["value_chf"])))),
   899	            "weight_pct": row["weight_pct"],
   900	            "computed_cost_basis_original": row["computed_cost_basis_original"],
   901	            "computed_cost_basis_status": row["computed_cost_basis_status"],
   902	        }
   903	        for row in conn.execute(
   904	            """SELECT p.instrument_id,p.asset_class,p.quantity,p.provider_average_cost_original,
   905	                      p.provider_cost_total_original,p.price_original,p.price_currency,p.value_chf,
   906	                      p.weight_pct,p.computed_cost_basis_original,p.computed_cost_basis_status,
   907	                      i.name AS instrument_name,i.ticker
   908	               FROM postfinance_snapshot_positions p
   909	               JOIN instruments i ON i.instrument_id=p.instrument_id
   910	               WHERE p.snapshot_id=? ORDER BY CAST(p.value_chf AS REAL) DESC""",
   911	            (latest["snapshot_id"],),
   912	        ).fetchall()
   913	    ]
   914	    cash = [
   915	        {
   916	            "row_key": _hash("cash", component.currency)[:16],
   917	            "currency": component.currency,
   918	            "amount_original": _text(component.amount_original),
   919	            "fx_rate_to_chf": _text(component.source_fx_rate_to_chf),
   920	            "value_chf": _money(component.amount_chf),
   921	        }
   922	        for component in latest_official_postfinance_cash(conn)
   923	    ]
   924	    events = {
   925	        str(row["event_type"]): int(row["c"])
   926	        for row in conn.execute(
   927	            "SELECT event_type,COUNT(*) c FROM postfinance_ledger_events GROUP BY event_type ORDER BY event_type"
   928	        ).fetchall()
   929	    }
   930	    imports = [
   108	        and row.get("instrument_status", "").lower() != "inactive"
   109	    ]
   110	    analysis, values, missing = _latest_analysis(conn)
   111	    effective_values: dict[tuple[str, str], Decimal | None] = {}
   112	    for row in source_rows:
   113	        key = (str(row.get("account_id") or ""), str(row.get("instrument_id") or ""))
   114	        candidate = values.get(key)
   115	        row_is_audited = row.get("valuation_source") in {
   116	            "postfinance_official_import",
   117	            "audited_market_run",
   118	        }
   119	        candidate_is_newer = bool(
   120	            analysis
   121	            and candidate
   122	            and str(analysis.get("as_of") or "") > str(row.get("valuation_as_of") or row.get("price_date") or "")
   123	            and candidate.get("quality_status") == "fresh"
   124	        )
   125	        raw = (
   126	            candidate.get("value_chf")
   127	            if candidate_is_newer and candidate
   128	            else row.get("market_value_chf") if analysis is None or row_is_audited else None
   129	        )
   130	        effective_values[key] = Decimal(str(raw)) if raw not in {None, ""} else None
   131	    complete = bool(source_rows) and all(value is not None for value in effective_values.values())
   132	    total = sum((value or Decimal("0") for value in effective_values.values()), Decimal("0"))
   133	    positions: list[EquityPosition] = []
   134	    for row in source_rows:
   135	        account_id = str(row.get("account_id") or "")
   136	        instrument_id = str(row.get("instrument_id") or "")
   137	        snapshot_value = values.get((account_id, instrument_id))
   138	        value = (
   139	            snapshot_value
   140	            if analysis
   141	            and snapshot_value
   142	            and snapshot_value.get("quality_status") == "fresh"
   143	            and str(analysis.get("as_of") or "") > str(row.get("valuation_as_of") or row.get("price_date") or "")
   144	            else None
   145	        )
   146	        row_fallback_allowed = analysis is None or row.get("valuation_source") in {
   147	            "postfinance_official_import",
   148	            "audited_market_run",
   149	        }
   150	        mapping = _mapping_details(conn, instrument_id)
   151	        raw_market_value = (
   152	            value.get("value_chf")
   153	            if value
   154	            else row.get("market_value_chf") if row_fallback_allowed else None
   155	        )
   156	        if Decimal(str(row.get("quantity") or "0")) <= 0:
   157	            raw_market_value = None
   158	        market_value = Decimal(str(raw_market_value)) if raw_market_value not in {None, ""} else None
   159	        share = (market_value / total * Decimal("100")) if complete and market_value is not None and total > 0 else None
   160	        code = "valued" if market_value is not None else ("stale_price" if snapshot_value else missing.get(instrument_id, "price_missing"))
   161	        status = "Bewertet" if market_value is not None else ("Kurs veraltet" if snapshot_value else _position_status(row))
   162	        provenance = value.get("price_input_provenance") if value else None
   163	        provenance_provider = provenance.get("provider") if isinstance(provenance, dict) else None
   164	        positions.append(EquityPosition(
   165	            position_id=f"{account_id}:{instrument_id}",
   166	            instrument_id=instrument_id,
   167	            name=str(row.get("name") or ""),
   168	            ticker=str(row.get("ticker") or ""),
   169	            isin=str(row.get("isin") or ""),
   170	            account=_account_label(row),
   171	            asset_class=_asset_class_label(row.get("asset_class")),
   172	            quantity=optional_decimal_text(row.get("quantity")) or "0",
   173	            currency=str(value.get("currency") if value else mapping.get("currency") or row.get("price_currency") or row.get("currency") or ""),
   174	            price=(
   175	                optional_decimal_text(value.get("close"))
   176	                if value
   177	                else optional_decimal_text(row.get("market_price_original") or row.get("price"))
   178	                if row_fallback_allowed
   179	                else None
   180	            ),
   181	            market_value_chf=optional_decimal_text(market_value, 2) if market_value is not None else None,
   182	            portfolio_share_pct=optional_decimal_text(share, 2) if share is not None else None,
   183	            price_date=(
   184	                str(analysis.get("as_of"))
   185	                if value and analysis
   186	                else str(row.get("valuation_as_of") or row.get("price_date") or "") or None
   187	                if row_fallback_allowed
   188	                else str(analysis.get("as_of")) if analysis else None
   189	            ),
   190	            provider=str((value.get("provider") if value else None) or provenance_provider or row.get("price_provider") or mapping.get("provider") or ""),
   191	            provider_symbol=str(mapping.get("provider_symbol") or ""),
   192	            exchange=str(mapping.get("provider_market") or ""),
   193	            price_currency=str(value.get("currency") if value else mapping.get("currency") or row.get("price_currency") or row.get("currency") or ""),
   194	            data_status="fresh" if market_value is not None else "missing",
   195	            status_code=code,
   196	            status_reason=None if market_value is not None else _status_reason(code),
   197	            status=status,
   198	        ))
   199	    return sorted(positions, key=lambda item: Decimal(item.market_value_chf or "-1"), reverse=True)
   200
   201
   202	def get_equity_summary(conn: Connection) -> EquityPortfolioSummary:
   203	    positions = list_equity_positions(conn)
   204	    valued = [item for item in positions if item.market_value_chf is not None]
   205	    partial = sum((Decimal(item.market_value_chf or "0") for item in valued), Decimal("0"))


sed -n '1,260p' src/jarvis_finance/ledger/cash.py && grep -n "def get_cash_summary" -n src/jarvis_finance/services/cash_service.py && sed -n '260,380p' src/jarvis_finance/services/cash_service.py


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

from dataclasses import dataclass, field
from decimal import Decimal
from sqlite3 import Connection

from jarvis_finance.audit.log import record_audit_event
from jarvis_finance.imports.common import stable_id, utc_now
from jarvis_finance.quality.alerts import create_alert

@dataclass
class CashBalance:
    account_id: str
    currency: str
    amount_original: Decimal = Decimal("0")
    amount_chf: Decimal | None = Decimal("0")
    quality_status: str = "ok"

@dataclass
class CashCalculationResult:
    balances: dict[tuple[str, str], CashBalance] = field(default_factory=dict)
    negative_cash_warnings: list[str] = field(default_factory=list)

def _d(value: object, default: str = "0") -> Decimal:
    if value is None or str(value).strip() == "":
        return Decimal(default)
    return Decimal(str(value))

def _signed_cash_delta(row) -> tuple[Decimal, Decimal | None]:
    t = row["transaction_type"]
    gross = _d(row["gross_amount_original"])
    net = _d(row["net_amount_original"]) if row["net_amount_original"] is not None else None
    fee = _d(row["fee_original"])
    tax = _d(row["tax_original"])
    fx = _d(row["fx_rate_to_chf"], "1") if row["fx_rate_to_chf"] is not None else None

    if t == "initial_cash_snapshot":
        original = net if net is not None else gross
    elif t == "cash_deposit":
        original = net if net is not None else gross
    elif t == "cash_withdrawal":
        original = -(net if net is not None else gross)
    elif t == "buy":
        original = -(net if net is not None else gross + fee + tax)
    elif t in {"partial_sell", "full_sell"}:
        original = net if net is not None else gross - fee - tax
    elif t in {"dividend", "etf_distribution"}:
        original = net if net is not None else gross - tax - fee
    elif t in {"fee", "tax"}:
        original = -(net if net is not None else gross or fee or tax)
    elif t == "fx_conversion":
        original = net if net is not None else gross
    elif t == "manual_cash_correction":
        original = net if net is not None else gross
    else:
        original = Decimal("0")
    chf = original * fx if fx is not None else None
    return original, chf

def calculate_cash_balances(
    conn: Connection, *, as_of_date: str | None = None, persist_alerts: bool = True
) -> CashCalculationResult:
    result = CashCalculationResult()
    params: tuple[object, ...] = ()
    where = "WHERE COALESCE(is_voided, 0) = 0"
    if as_of_date:
        where += " AND trade_date <= ?"
        params = (as_of_date,)
    rows = conn.execute(
        f"""
        SELECT * FROM transactions
        {where}
        ORDER BY account_id, currency_original, trade_date, created_at, transaction_id
        """,
        params,
    ).fetchall()
    for row in rows:
        account_id = row["account_id"]
        currency = row["currency_original"]
        key = (account_id, currency)
        balance = result.balances.setdefault(key, CashBalance(account_id, currency))
        original, chf = _signed_cash_delta(row)
        balance.amount_original += original
        if chf is None:
            balance.amount_chf = None
            balance.quality_status = "incomplete"
        elif balance.amount_chf is not None:
            balance.amount_chf += chf
    for key, balance in result.balances.items():
        if balance.amount_original < 0:
            msg = f"Negative cash balance for account={balance.account_id} currency={balance.currency}: {balance.amount_original}"
            result.negative_cash_warnings.append(msg)
            if persist_alerts:
                create_alert(
                    conn,
                    priority="warnung",
                    category="cash",
                    entity_type="account",
                    entity_id=balance.account_id,
                    rule_id="negative_cash",
                    message=msg,
                    evidence={"currency": balance.currency, "amount_original": str(balance.amount_original)},
                )
    if persist_alerts:
        conn.commit()
    return result

def apply_manual_cash_correction(
    conn: Connection,
    *,
    account_id: str,
    currency: str,
    amount: Decimal,
    note: str,
    trade_date: str | None = None,
) -> str:
    if not note.strip():
        raise ValueError("manual cash correction requires a note")
    now = utc_now()
    trade_date = trade_date or now[:10]
    transaction_id = stable_id("cashcorr", account_id, currency, trade_date, amount, note)
    fx_rate = Decimal("1") if currency.upper() == "CHF" else None
    conn.execute(
        """
        INSERT INTO transactions(
            transaction_id, transaction_type, account_id, trade_date, gross_amount_original,
            net_amount_original, currency_original, fx_rate_to_chf, fx_status,
            gross_amount_chf, net_amount_chf, source_type, is_confirmed,
            quality_status, notes, created_at
        ) VALUES (?, 'manual_cash_correction', ?, ?, ?, ?, ?, ?, ?, ?, ?, 'manual', 1, ?, ?, ?)
        """,
        (
            transaction_id, account_id, trade_date, str(amount), str(amount), currency.upper(),
            str(fx_rate) if fx_rate is not None else None,
            "ok" if fx_rate is not None else "missing",
            str(amount * fx_rate) if fx_rate is not None else None,
            str(amount * fx_rate) if fx_rate is not None else None,
            "ok" if fx_rate is not None else "incomplete",
            note,
            now,
        ),
    )
    audit_id = record_audit_event(
        conn,
        source="manual_cash_correction",
        action="manual_cash_correction",
        entity_type="transaction",
        entity_id=transaction_id,
        new_values={"account_id": account_id, "currency": currency.upper(), "amount": str(amount)},
        user_text_note=note,
        confirmed=True,
        created_by="system",
    )
    conn.commit()
    return audit_id
265:def get_cash_summary(conn: Connection) -> CashSummary:
            )
        )
    return positions

def get_cash_summary(conn: Connection) -> CashSummary:
    # Read paths must never seed or refresh accounts; setup is an explicit Confirm action.
    positions = list_cash_positions(conn)
    total = sum(Decimal(p.used_value_chf) for p in positions)
    last_dates = [p.last_manual_reconciliation for p in positions if p.last_manual_reconciliation]
    return CashSummary(
        base_currency="CHF",
        cash_chf=decimal_text(total, 2),
        manual_accounts_count=sum(1 for p in positions if p.balance_mode == "manual"),
        csv_calculated_accounts_count=sum(1 for p in positions if p.balance_mode == "csv_calculated"),
        open_reconciliation_count=sum(1 for p in positions if p.status == "Abgleich offen"),
        last_reconciliation_at=max(last_dates) if last_dates else None,
        positions=positions,
    )

def preview_cash_snapshot(conn: Connection, request: CashSnapshotPreviewRequest) -> PreviewResponse:
    row = conn.execute("SELECT account_id, account_type FROM accounts WHERE account_id=?", (request.account_id,)).fetchone()
    if not row or row["account_type"] != "cash":
        raise HTTPException(status_code=404, detail="Cash account not found")
    if request.snapshot_type not in {"manual_balance", "csv_anchor_balance", "calculated_balance", "reconciliation"}:
        raise HTTPException(status_code=422, detail="Invalid snapshot_type")
    amount = _decimal(request.amount_chf, "amount_chf")
    return PreviewResponse(preview_id=_uuid("preview"), asset_class="cash", summary=f"{request.snapshot_type} CHF per {request.balance_date}", amount_chf=decimal_text(amount, 2), fx_status="not_needed", warnings=[])

def confirm_cash_snapshot(conn: Connection, request: CashSnapshotConfirmRequest) -> ConfirmResponse:
    if not request.confirm:
        raise HTTPException(status_code=400, detail="Explicit confirm required")
    preview_cash_snapshot(conn, request)
    amount = _decimal(request.amount_chf, "amount_chf")
    snapshot_id = _uuid("cashsnap")
    now = _now()
    source = "manual" if request.snapshot_type in {"manual_balance", "reconciliation"} else "csv_import" if request.snapshot_type == "csv_anchor_balance" else "system_calculated"
    audit_id = _audit(conn, action=f"cash_{request.snapshot_type}_confirm", entity_type="cash_account", entity_id=request.account_id, payload=request.model_dump(), note=request.note)
    conn.execute(
        """
        INSERT INTO cash_account_snapshots(snapshot_id, account_id, snapshot_type, balance_date, amount_original, currency, amount_chf, source, note, created_at, created_by, audit_id)
        VALUES (?, ?, ?, ?, ?, 'CHF', ?, ?, ?, ?, 'user', ?)
        """,
        (snapshot_id, request.account_id, request.snapshot_type, request.balance_date, decimal_text(amount, 2), decimal_text(amount, 2), source, request.note, now, audit_id),
    )
    conn.commit()
    return ConfirmResponse(status="confirmed", entity_id=snapshot_id, audit_id=audit_id, message="Kontostand gespeichert · Audit geschrieben")

def get_cash_position_detail(conn: Connection, position_id: str) -> CashPositionDetail:
    position = next((p for p in get_cash_summary(conn).positions if p.id == position_id), None)
    if not position:
        raise HTTPException(status_code=404, detail="Cash position not found")
    account_id = position.account_id
    rows = conn.execute(
        """
        SELECT snapshot_id, snapshot_type, balance_date, amount_chf, source, COALESCE(note,'') AS note, audit_id
        FROM cash_account_snapshots
        WHERE account_id=?
        ORDER BY balance_date DESC, created_at DESC
        LIMIT 30
        """,
        (account_id,),
    ).fetchall()
    history: list[CashSnapshot] = []
    previous: Decimal | None = None
    for row in reversed(rows):
        current = Decimal(str(row["amount_chf"]))
        diff = None if previous is None else decimal_text(current - previous, 2)
        previous = current
        history.append(CashSnapshot(snapshot_id=row["snapshot_id"], snapshot_type=row["snapshot_type"], balance_date=row["balance_date"], amount_chf=decimal_text(current, 2), source=row["source"], note=row["note"], difference_to_previous_chf=diff, audit_hint="Audit vorhanden" if row["audit_id"] else None))
    history = list(reversed(history))
    return CashPositionDetail(**position.model_dump(), last_change=position.last_manual_reconciliation, history=history, available_actions=[ActionItem(label="Einzahlung", enabled=True), ActionItem(label="Auszahlung", enabled=True), ActionItem(label="Kontostand aktualisieren", enabled=True), ActionItem(label="CSV-Anfangsstand setzen", enabled=True), ActionItem(label="Abgleich bestätigen", enabled=True), ActionItem(label="Verlauf anzeigen", enabled=True)])


sed -n '1,260p' src/jarvis_finance/ledger/positions.py


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

from dataclasses import dataclass, field
from datetime import datetime, timezone
from decimal import Decimal, ROUND_HALF_UP
from sqlite3 import Connection

from jarvis_finance.audit.log import record_audit_event
from jarvis_finance.imports.common import stable_id, utc_now
from jarvis_finance.quality.alerts import create_alert
from jarvis_finance.fx.rates import latest_fx_rate

from .cost_basis import WeightedAverageLot

MONEY = Decimal("0.01")

def _d(value: object, default: str = "0") -> Decimal:
    if value is None or str(value).strip() == "":
        return Decimal(default)
    return Decimal(str(value))

def _money(value: Decimal | None) -> Decimal | None:
    if value is None:
        return None
    return value.quantize(MONEY, rounding=ROUND_HALF_UP)

@dataclass
class Position:
    account_id: str
    instrument_id: str
    quantity: Decimal = Decimal("0")
    cost_basis_original: Decimal = Decimal("0")
    cost_basis_chf: Decimal = Decimal("0")
    average_cost_original: Decimal | None = None
    average_cost_chf: Decimal | None = None
    realized_pnl_chf: Decimal = Decimal("0")
    income_chf: Decimal = Decimal("0")
    fees_chf: Decimal = Decimal("0")
    taxes_chf: Decimal = Decimal("0")
    market_price_original: Decimal | None = None
    market_value_original: Decimal | None = None
    market_value_chf: Decimal | None = None
    unrealized_pnl_chf: Decimal | None = None
    total_return_chf: Decimal | None = None
    data_quality_status: str = "ok"
    valuation_status: str = "not_valuable"
    valuation_timestamp_status: str = "missing_timestamp"
    hedge_status: str = "unknown"
    instrument_status: str = "unknown"
    valuation_policy: str = "live_price"
    corporate_action_status: str = "not_checked"
    quality_warnings: list[str] = field(default_factory=list)

@dataclass
class PositionCalculationResult:
    positions: dict[tuple[str, str], Position] = field(default_factory=dict)
    as_of_date: str | None = None

def _latest_market_price(conn: Connection, instrument_id: str, as_of_date: str | None):
    if as_of_date:
        return conn.execute(
            """
            SELECT close, currency, price_timestamp, price_date, corporate_action_status FROM market_prices
            WHERE instrument_id=? AND price_date <= ? AND quality_status IN ('fresh','ok')
            ORDER BY price_date DESC, created_at DESC LIMIT 1
            """,
            (instrument_id, as_of_date),
        ).fetchone()
    return conn.execute(
        """
        SELECT close, currency, price_timestamp, price_date, corporate_action_status FROM market_prices
        WHERE instrument_id=? AND quality_status IN ('fresh','ok')
        ORDER BY price_date DESC, created_at DESC LIMIT 1
        """,
        (instrument_id,),
    ).fetchone()

def _add_warning(
    conn: Connection,
    pos: Position,
    rule_id: str,
    message: str,
    *,
    persist: bool = True,
) -> None:
    if rule_id not in pos.quality_warnings:
        pos.quality_warnings.append(rule_id)
    pos.data_quality_status = "incomplete"
    if persist:
        create_alert(
            conn,
            priority="kritisch" if rule_id == "missing_fx" else "warnung",
            category="ledger",
            entity_type="instrument",
            entity_id=pos.instrument_id,
            rule_id=rule_id,
            message=message,
            evidence={"account_id": pos.account_id, "instrument_id": pos.instrument_id},
        )

def _timestamp_status(price_ts: str | None, fx_ts: str | None, *, max_delta_hours: int = 24) -> str:
    if not price_ts or not fx_ts:
        return "missing_timestamp"
    try:
        p = datetime.fromisoformat(price_ts.replace("Z", "+00:00"))
        f = datetime.fromisoformat(fx_ts.replace("Z", "+00:00"))
        if p.tzinfo is None:
            p = p.replace(tzinfo=timezone.utc)
        if f.tzinfo is None:
            f = f.replace(tzinfo=timezone.utc)
    except ValueError:
        return "missing_timestamp"
    delta_hours = abs((p - f).total_seconds()) / 3600
    if delta_hours <= 1:
        return "aligned"
    if delta_hours <= max_delta_hours:
        return "acceptable"
    return "stale_mismatch"

def _instrument_metadata(conn: Connection, instrument_id: str) -> dict[str, object]:
    row = conn.execute("SELECT * FROM instruments WHERE instrument_id=?", (instrument_id,)).fetchone()
    if not row:
        return {"hedge_status": "unknown", "instrument_status": "unknown", "valuation_policy": "live_price", "corporate_action_status": "not_checked"}
    return dict(row)

def calculate_positions(
    conn: Connection,
    *,
    as_of_date: str | None = None,
    max_price_fx_time_delta_hours: int = 24,
    persist_alerts: bool = True,
) -> PositionCalculationResult:
    def add_warning(pos: Position, rule_id: str, message: str) -> None:
        _add_warning(conn, pos, rule_id, message, persist=persist_alerts)

    result = PositionCalculationResult(as_of_date=as_of_date)
    lots: dict[tuple[str, str], WeightedAverageLot] = {}
    params: tuple[object, ...] = ()
    where = "WHERE instrument_id IS NOT NULL AND COALESCE(is_voided, 0) = 0"
    if as_of_date:
        where += " AND trade_date <= ?"
        params = (as_of_date,)
    rows = conn.execute(
        f"""
        SELECT * FROM transactions
        {where}
        ORDER BY account_id, instrument_id, trade_date, created_at, transaction_id
        """,
        params,
    ).fetchall()
    for row in rows:
        key = (row["account_id"], row["instrument_id"])
        pos = result.positions.setdefault(key, Position(row["account_id"], row["instrument_id"]))
        lot = lots.setdefault(key, WeightedAverageLot())
        t = row["transaction_type"]
        qty = _d(row["quantity"])
        gross = _d(row["gross_amount_original"])
        fee = _d(row["fee_original"])
        tax = _d(row["tax_original"])
        fx = _d(row["fx_rate_to_chf"], "1") if row["fx_rate_to_chf"] is not None else None
        if fx is None or row["fx_status"] == "missing":
            add_warning(pos, "missing_fx", "Historic FX is missing; precise CHF total return cannot be calculated.")
        if t in {"initial_position_snapshot", "buy"}:
            if qty > 0:
                cost_original = gross + fee
                cost_chf = (gross + fee) * fx if fx is not None else Decimal("0")
                lot.buy(qty, cost_original, cost_chf)
                pos.fees_chf += fee * fx if fx is not None else Decimal("0")
                pos.taxes_chf += tax * fx if fx is not None else Decimal("0")
        elif t in {"partial_sell", "full_sell"}:
            if qty > 0:
                proceeds_chf = (gross - fee - tax) * fx if fx is not None else Decimal("0")
                lot.sell(qty, proceeds_chf)
                pos.fees_chf += fee * fx if fx is not None else Decimal("0")
                pos.taxes_chf += tax * fx if fx is not None else Decimal("0")
        elif t in {"dividend", "etf_distribution"}:
            income_original = _d(row["net_amount_original"]) if row["net_amount_original"] is not None else gross - fee - tax
            if fx is not None:
                pos.income_chf += income_original * fx
                pos.fees_chf += fee * fx
                pos.taxes_chf += tax * fx
        if t == "initial_position_snapshot":
            add_warning(pos, "snapshot_only", "Initial snapshot lacks full transaction history; performance is incomplete.")
            if gross == 0 or fx is None:
                add_warning(pos, "cost_basis_uncertain", "Cost basis is uncertain for this initial snapshot.")
    for key, lot in lots.items():
        pos = result.positions[key]
        meta = _instrument_metadata(conn, pos.instrument_id)
        pos.hedge_status = str(meta.get("hedge_status") or "unknown")
        pos.instrument_status = str(meta.get("instrument_status") or "unknown")
        pos.valuation_policy = str(meta.get("valuation_policy") or "live_price")
        pos.corporate_action_status = str(meta.get("corporate_action_status") or "not_checked")
        if pos.hedge_status == "unknown":
            add_warning(pos, "hedge_status_unknown", "Currency hedge status is unknown; FX attribution is incomplete.")
        if pos.instrument_status in {"delisted", "suspended", "merged", "inactive"} and pos.valuation_policy in {"live_price", "exclude_from_auto_price_update"}:
            add_warning(pos, "delisted_or_suspended", "Instrument status excludes or limits automatic valuation.")
        if pos.corporate_action_status in {"suspected", "confirmed"} or meta.get("split_or_corporate_action_review_required"):
            add_warning(pos, "corporate_action_suspected", "Corporate action review is required before high-confidence valuation.")
        pos.quantity = lot.quantity.normalize()
        pos.cost_basis_original = _money(lot.cost_basis_original) or Decimal("0")
        pos.cost_basis_chf = _money(lot.cost_basis_chf) or Decimal("0")
        pos.average_cost_original = lot.average_cost_original
        pos.average_cost_chf = lot.average_cost_chf
        pos.realized_pnl_chf = _money(lot.realized_pnl_chf) or Decimal("0")
        pos.income_chf = _money(pos.income_chf) or Decimal("0")
        pos.fees_chf = _money(pos.fees_chf) or Decimal("0")
        pos.taxes_chf = _money(pos.taxes_chf) or Decimal("0")
        market = _latest_market_price(conn, pos.instrument_id, as_of_date)
        if market is None:
            add_warning(pos, "missing_market_price", "Market price is missing; unrealized P&L cannot be calculated.")
        else:
            pos.market_price_original = _d(market["close"])
            pos.market_value_original = _money(pos.quantity * pos.market_price_original)
            # MVP: use only local FX data; dashboards never call providers during render.
            if market["currency"] == "CHF":
                pos.market_value_chf = pos.market_value_original
                pos.valuation_timestamp_status = "aligned" if market["price_timestamp"] or market["price_date"] else "missing_timestamp"
                pos.unrealized_pnl_chf = _money(pos.market_value_chf - pos.cost_basis_chf)
            else:
                fx_row = latest_fx_rate(conn, base_currency=market["currency"], quote_currency="CHF", rate_date=as_of_date)
                if fx_row:
                    market_fx = _d(fx_row["rate"])
                    pos.valuation_timestamp_status = _timestamp_status(market["price_timestamp"] or market["price_date"], fx_row["rate_timestamp"] or fx_row["rate_date"], max_delta_hours=max_price_fx_time_delta_hours)
                    if pos.valuation_timestamp_status == "stale_mismatch":
                        add_warning(pos, "stale_valuation", "Price and FX timestamps are outside the configured valuation tolerance.")
                    pos.market_value_chf = _money(pos.market_value_original * market_fx) if pos.market_value_original is not None else None
                    if pos.market_value_chf is not None:
                        pos.unrealized_pnl_chf = _money(pos.market_value_chf - pos.cost_basis_chf)
                    if pos.hedge_status == "hedged" and (meta.get("hedged_to_currency") or "").upper() == "CHF":
                        add_warning(pos, "currency_hedged_fx_attribution", "CHF-hedged instrument: free FX P&L attribution is methodically restricted.")
                else:
                    add_warning(pos, "missing_fx", "Market FX is missing; CHF valuation is incomplete.")
            if market["corporate_action_status"] in {"suspected", "confirmed"}:
                add_warning(pos, "corporate_action_suspected", "Market price may cross a corporate action; manual review required.")
        if pos.market_value_chf is not None and "missing_fx" not in pos.quality_warnings and "cost_basis_uncertain" not in pos.quality_warnings and "snapshot_only" not in pos.quality_warnings:
            pos.total_return_chf = _money((pos.unrealized_pnl_chf or Decimal("0")) + pos.realized_pnl_chf + pos.income_chf - pos.fees_chf - pos.taxes_chf)
        else:
            pos.total_return_chf = None
        blocking = {"missing_market_price", "missing_fx", "hedge_status_unknown", "stale_valuation", "corporate_action_suspected", "delisted_or_suspended"}
        if pos.quantity == 0 or pos.instrument_status in {"delisted", "suspended", "merged", "inactive"} and pos.valuation_policy == "exclude_from_auto_price_update":
            pos.valuation_status = "not_valuable"
        elif pos.market_value_chf is not None and not (blocking & set(pos.quality_warnings)):
            pos.valuation_status = "valuable"
        elif pos.quantity != 0:
            pos.valuation_status = "partially_valuable"
        else:
            pos.valuation_status = "not_valuable"
    if persist_alerts:
        conn.commit()
    return result


Output:
Chunk ID: 8c9be6
Wall time: 30.0014 seconds
Process running with session ID 48413
Original token count: 7
Output:
...........................


Output:
Chunk ID: b738a1
Wall time: 30.0010 seconds
Process running with session ID 48413
Original token count: 0
Output:


Output:
Chunk ID: 40151f
Wall time: 30.0008 seconds
Process running with session ID 48413
Original token count: 0
Output:


Output:
Chunk ID: 57822d
Wall time: 30.0015 seconds
Process running with session ID 48413
Original token count: 0
Output:


Output: write_stdin failed: stdin is closed for this session; rerun exec_command with tty=true to keep stdin open


sed -n '1,180p' tests/test_postfinance_sprint13.py


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

import base64
import json
from dataclasses import replace
from decimal import Decimal
from hashlib import sha256

import pytest

from jarvis_finance.dashboard.data import (
    get_cash_overview,
    get_command_center_summary,
    get_positions,
)
from jarvis_finance.imports.postfinance_documents import (
    PFBundle,
    PFDocument,
    PFEvent,
    PFSnapshot,
    PFSnapshotCash,
    PFSnapshotPosition,
)
from jarvis_finance.services import postfinance_service as service
from jarvis_finance.services.cash_service import get_cash_position_detail, get_cash_summary
from jarvis_finance.services.equity_service import get_equity_summary
from jarvis_finance.services.portfolio_aggregation import (
    latest_official_postfinance_cash,
    latest_official_postfinance_positions,
)
from jarvis_finance.services.portfolio_analytics import confirmed_canonical_positions
from jarvis_finance.services.portfolio_data import build_portfolio_reconciliation
from jarvis_finance.services.portfolio_service import get_overview
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations

DEPOT = "account_pf_depot_test"
CASH = "account_pf_trading_cash_test"

def make_conn():
    conn = connect_memory()
    apply_migrations(conn)
    conn.execute(
        "INSERT INTO platforms(platform_id,name,platform_type,country,default_currency,is_active,created_at) VALUES('pf','PostFinance','bank','CH','CHF',1,'2026-01-01')"
    )
    conn.execute(
        "INSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,performance_included,is_active,created_at,portfolio_bucket) VALUES(?,'pf','PostFinance E-Trading','brokerage','CHF',1,1,'2026-01-01','postfinance')",
        (DEPOT,),
    )
    conn.execute(
        "INSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,performance_included,is_active,created_at,portfolio_bucket) VALUES(?,'pf','PostFinance Cash-Konto','cash','CHF',1,1,'2026-01-01','cash')",
        (CASH,),
    )
    for index in range(22):
        instrument = f"instrument_pf_{index:02d}"
        label = f"Synthetic Asset {index + 1:02d}"
        conn.execute(
            "INSERT INTO instruments(instrument_id,name,ticker,isin,asset_class,currency,is_active,created_at) VALUES(?,?,?,?,?,?,1,?)",
            (
                instrument,
                label,
                f"S{index:02d}",
                f"CH{index:010d}",
                "equity" if index < 11 else "etf",
                "CHF",
                "2026-01-01",
            ),
        )
        conn.execute(
            "INSERT INTO positions_snapshot(position_snapshot_id,snapshot_date,account_id,platform_id,instrument_id,quantity,average_cost_original,cost_basis_original,market_price_original,market_value_original,market_fx_rate_to_chf,market_value_chf,category,data_quality_status,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
            (
                f"baseline_{index}",
                "2026-06-30",
                DEPOT,
                "pf",
                instrument,
                "1",
                "50",
                "50",
                "90",
                "90",
                "1",
                "90",
                "equity" if index < 11 else "etf",
                "complete",
                "2026-06-30T00:00:00Z",
            ),
        )
    conn.commit()
    return conn

def bundle(
    zip_raw: bytes = b"synthetic-zip", overview_raw: bytes = b"%PDF-synthetic-overview"
) -> PFBundle:
    zip_hash = sha256(zip_raw).hexdigest()
    overview_hash = sha256(overview_raw).hexdigest()
    doc_raw = b"%PDF-synthetic-trade"
    document = PFDocument(
        sha256(doc_raw).hexdigest(),
        sha256(b"trade.pdf").hexdigest(),
        "trade_confirmation",
        "synthetic-reference",
        sha256(b"semantic").hexdigest(),
        "2026-07-01",
        sha256(b"account").hexdigest(),
        "etrading_cash",
        1,
        doc_raw,
        "synthetic",
    )
    event = PFEvent(
        sha256(b"event").hexdigest(),
        document.document_hash,
        document.semantic_reference,
        "buy",
        "etrading_cash",
        "2026-07-01",
        "2026-07-03",
        "out",
        "Synthetic Asset 01",
        "CH0000000000",
        Decimal("1"),
        Decimal("50"),
        Decimal("50"),
        Decimal("1"),
        Decimal("0"),
        Decimal("51"),
        "CHF",
        Decimal("1"),
        None,
        "complete",
        (),
    )
    transfer_out = PFEvent(
        sha256(b"transfer-out").hexdigest(),
        document.document_hash,
        "transfer-reference",
        "internal_transfer",
        "efinance",
        "2026-07-02",
        "2026-07-02",
        "out",
        None,
        None,
        None,
        None,
        Decimal("10"),
        Decimal("0"),
        Decimal("0"),
        Decimal("10"),
        "CHF",
        Decimal("1"),
        "synthetic-transfer-group",
        "partial",
        ("internal_transfer_not_income_or_expense",),
    )
    transfer_in = PFEvent(
        sha256(b"transfer-in").hexdigest(),
        document.document_hash,
        "transfer-reference",
        "internal_transfer",
        "etrading_cash",
        "2026-07-02",
        "2026-07-02",
        "in",
        None,
        None,
        None,
        None,
        Decimal("10"),
        Decimal("0"),
        Decimal("0"),
        Decimal("10"),
        "CHF",
        Decimal("1"),
        "synthetic-transfer-group",
        "partial",
        ("internal_transfer_not_income_or_expense",),


grep -n "cash_account_snapshots\|account_value_snapshots\|portfolio_valuation_snapshots" -n src/jarvis_finance/services/postfinance_service.py | head -50


Output:
Chunk ID: cf0b8a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 87
Output:
163:        "account_value_snapshots",
825:            "INSERT INTO account_value_snapshots(snapshot_id,account_id,valuation_date,total_value_chf,currency,source_type,quality_status,notes,created_at,valuation_at,source_reference,is_active) VALUES(?,?,?,?,'CHF','postfinance_official_import','ok','Official PostFinance account overview',?,?,?,1)",


sed -n '780,840p' src/jarvis_finance/services/postfinance_service.py


Output:
Chunk ID: 68d753
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 706
Output:
                    _text(position.provider_average_cost_original),
                    _text(position.provider_cost_total_original),
                    _text(position.market_price_original),
                    _text(position.quantity * position.market_price_original),
                    None,
                    _money(position.market_value_chf),
                    _text(position.weight_pct),
                    position.asset_class,
                    "verified",
                    "postfinance_official_import",
                    snapshot_id,
                    now,
                    accounts["etrading_depot"],
                ),
            )
        for cash in snapshot.cash:
            conn.execute(
                "INSERT INTO postfinance_snapshot_cash(snapshot_cash_id,snapshot_id,currency,amount_original,fx_rate_to_chf,value_chf,created_at) VALUES(?,?,?,?,?,?,?)",
                (
                    _id("pfcash"),
                    snapshot_id,
                    cash.currency,
                    _text(cash.amount_original),
                    _text(cash.fx_rate_to_chf),
                    _money(cash.source_value_chf),
                    now,
                ),
            )
            conn.execute(
                "INSERT INTO cash_balances(cash_balance_id,account_id,balance_date,currency,amount_original,fx_rate_to_chf,amount_chf,source_type,quality_status,notes,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)",
                (
                    _id("cash"),
                    accounts["etrading_cash"],
                    snapshot.valuation_at[:10],
                    cash.currency,
                    _text(cash.amount_original),
                    _text(cash.fx_rate_to_chf),
                    _money(cash.source_value_chf),
                    "postfinance_official_import",
                    "verified",
                    "Official PostFinance account overview",
                    now,
                ),
            )
        conn.execute(
            "INSERT INTO account_value_snapshots(snapshot_id,account_id,valuation_date,total_value_chf,currency,source_type,quality_status,notes,created_at,valuation_at,source_reference,is_active) VALUES(?,?,?,?,'CHF','postfinance_official_import','ok','Official PostFinance account overview',?,?,?,1)",
            (
                _id("acctval"),
                accounts["etrading_depot"],
                snapshot.valuation_at[:10],
                _money(snapshot.total_value_chf),
                now,
                snapshot.valuation_at,
                snapshot_id,
            ),
        )
        conn.commit()
    except IntegrityError as exc:
        conn.rollback()
        duplicate = conn.execute(
            "SELECT batch_id,audit_id FROM postfinance_import_batches WHERE bundle_sha256=?",


sed -n '1,240p' docs/sprint13-global-portfolio-aggregation-hotfix.md


Output:
Chunk ID: 62be69
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 911
Output:
# Sprint 13 hotfix: global portfolio aggregation

## Scope

This hotfix changes the shared portfolio read model only. It does not rewrite or delete official source bytes, import batches, ledger events, snapshots, cost-basis lots, canonical projections, or historical manual values.

## Root cause

The PostFinance overview contains currency rows that combine a true currency cash amount and rounded FX rate with a CHF column representing the whole portfolio's currency exposure. Sprint 13 projected that exposure column as cash. The two projected values therefore summed to the official account total instead of the official cash subtotal.

A second inclusion collision existed in equity: instrument-bearing cash-settlement ledger rows could create shadow holdings on the trading-cash role, while the official holdings belong to the separate depot role. An older market-analysis snapshot could also override a newer official holdings snapshot.

## Contract

- Account totals from `postfinance_official_import` are controls, never additive holdings.
- Cash is resolved from the explicit `etrading_cash` role, original currency amounts, rounded source FX, and the signed-off cash subtotal.
- Rounded component CHF values are deterministically reconciled pro rata to the official subtotal; no adjustment asset is created.
- Holdings are resolved from the explicit `etrading_depot` role and official position snapshot.
- Cash-settlement ledger rows remain available for audit and cost basis but do not create holdings on the cash role when the official depot snapshot covers the instrument.
- A released market-analysis value replaces an official source value only when its `as_of` is newer. The effective valuation timestamp remains visible.
- Historical manual placeholders and immutable canonical rows remain present but are superseded by provenance-aware read-model selection.
- TrueWealth and non-PostFinance aggregation rules remain unchanged.

## Shared consumers

The shared cash resolver feeds the cash API and dashboard/Command Center cash overview. The role-aware official position resolver feeds dashboard positions, equity APIs, overview/advisor allocations, and future portfolio-analysis runs. The PostFinance summary uses the same reconciled source components.

## Delayed review findings reverified at the merge base

All three findings were rerun read-only against exact commit `17ad9ad9eba002b74ef18664fa1a008d926bf5fd` before applying the hotfix:

1. Same-day conflicts in `positions_snapshot` and `cash_balances`: **stale/already fixed**. The preview conflict test passed and confirm was not required to discover the conflict.
2. Immutable canonical projections in `transactions`, `positions_snapshot`, and `cash_balances`: **stale/already fixed**. Update and delete trigger assertions passed for all three tables.
3. `/api/postfinance/summary` privacy allowlist: **stale/already fixed**. The summary contract test passed and exposes no account, batch, snapshot, instrument, ISIN, document, customer, or account-number identifiers.

Evidence: the three exact base-commit tests passed (`3 passed`). The findings must be rerun again with the complete suite and final review at the hotfix HEAD.

## Regression coverage

The synthetic production-shape contract includes 22 positions, two currency cash rows, a misleading account-total-sized exposure projection, a historical manual placeholder, a separate E-Finance role, internal transfers, TrueWealth, and both older and newer market-analysis snapshots. It verifies no double counting, read-only GET behavior, historical preservation, privacy, and repeated-import idempotency.


nl -ba src/jarvis_finance/services/cash_service.py | sed -n '175,255p'


Output:
Chunk ID: 8b5a08
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1162
Output:
   175
   176
   177	def list_cash_positions(conn: Connection) -> list[CashPosition]:
   178	    ledger = calculate_cash_balances(conn, persist_alerts=False)
   179	    rows = conn.execute(
   180	        """
   181	        SELECT a.account_id, a.account_name, a.account_type, a.currency, COALESCE(a.balance_mode,'manual') AS balance_mode, p.name AS platform
   182	        FROM accounts a JOIN platforms p ON p.platform_id=a.platform_id
   183	        WHERE a.is_active=1
   184	          AND (
   185	              a.account_type='cash'
   186	              OR a.account_id IN (
   187	                  SELECT account_id FROM postfinance_account_roles
   188	                  WHERE role='etrading_cash'
   189	              )
   190	          )
   191	        ORDER BY p.name, a.account_name
   192	        """
   193	    ).fetchall()
   194	    positions: list[CashPosition] = []
   195	    official_components = latest_official_postfinance_cash(conn)
   196	    official_by_account: dict[str, list] = {}
   197	    for component in official_components:
   198	        official_by_account.setdefault(component.account_id, []).append(component)
   199	    for row in rows:
   200	        account_id = row["account_id"]
   201	        if account_id in official_by_account:
   202	            for component in official_by_account[account_id]:
   203	                positions.append(
   204	                    CashPosition(
   205	                        id=f"{account_id}:{component.currency}",
   206	                        account_id=account_id,
   207	                        platform=row["platform"],
   208	                        account_label=row["account_name"],
   209	                        account_type=row["account_type"],
   210	                        currency=component.currency,
   211	                        balance_mode="official_components",
   212	                        amount=decimal_text(component.amount_original, 2),
   213	                        amount_chf=decimal_text(component.amount_chf, 2),
   214	                        calculated_balance_chf=decimal_text(component.amount_chf, 2),
   215	                        manual_balance_chf=None,
   216	                        csv_anchor_balance_chf=None,
   217	                        used_value_chf=decimal_text(component.amount_chf, 2),
   218	                        difference_chf="0.00",
   219	                        last_manual_reconciliation=component.snapshot_date,
   220	                        last_imported_booking=_last_imported_booking(conn, account_id),
   221	                        status="Offiziell abgeglichen",
   222	                    )
   223	                )
   224	            continue
   225	        manual = _latest_snapshot(conn, account_id, "manual_balance")
   226	        anchor = _latest_snapshot(conn, account_id, "csv_anchor_balance")
   227	        calculated = _calculated_balance(conn, account_id)
   228	        manual_amount = Decimal(str(manual["amount_chf"])) if manual else None
   229	        mode = row["balance_mode"] or "manual"
   230	        used = manual_amount if mode == "manual" and manual_amount is not None else calculated
   231	        ledger_balance = ledger.balances.get((account_id, str(row["currency"])))
   232	        amount_original = (
   233	            ledger_balance.amount_original
   234	            if ledger_balance is not None
   235	            else used if str(row["currency"]).upper() == "CHF" else Decimal("0")
   236	        )
   237	        difference = (manual_amount - calculated) if manual_amount is not None else Decimal("0")
   238	        status = "Aktuell"
   239	        if manual_amount is not None and difference != 0:
   240	            status = "Abgleich offen"
   241	        positions.append(
   242	            CashPosition(
   243	                id=f"{account_id}:{row['currency']}",
   244	                account_id=account_id,
   245	                platform=row["platform"],
   246	                account_label=row["account_name"],
   247	                account_type=row["account_type"],
   248	                currency=row["currency"],
   249	                balance_mode=mode,
   250	                amount=decimal_text(amount_original, 2),
   251	                amount_chf=decimal_text(used, 2),
   252	                calculated_balance_chf=decimal_text(calculated, 2),
   253	                manual_balance_chf=decimal_text(manual_amount, 2) if manual_amount is not None else None,
   254	                csv_anchor_balance_chf=decimal_text(anchor["amount_chf"], 2) if anchor else None,
   255	                used_value_chf=decimal_text(used, 2),


[P2] Preserve non-default cash currencies

For a non-PostFinance cash account that has ledger transactions in a currency different from `accounts.currency` (or multiple currencies), this lookup only emits the account's default currency row. The previous `get_cash_overview()` used `calculate_cash_balances()` and returned every `(account_id, currency)` balance, so USD/EUR sub-balances on a CHF cash account now disappear from the dashboard/API totals instead of remaining unchanged for non-PostFinance accounts.

[P2] Replay PostFinance cash deltas after snapshots

When the PostFinance trading-cash account has any confirmed cash-affecting transaction after the official snapshot (for example a buy/sell/dividend before the next import), this branch returns only the snapshot components and then `continue`s, so those ledger cash deltas are excluded from cash totals. The position resolver does replay post-snapshot security quantity deltas, so the portfolio can show updated holdings with stale cash until another official snapshot is imported.

The patch fixes the official PostFinance projection path but introduces cash aggregation regressions: non-default currency balances can be dropped, and PostFinance cash is not advanced for post-snapshot ledger movements while holdings are. These affect reported portfolio totals in realistic inputs.