Output:
Chunk ID: a243c4
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2700
Output:
"""Central role-aware portfolio aggregation for verified PostFinance projections.

Official account totals are reconciliation controls, never additive holdings. Source FX
rates and line values can be rounded independently, so component CHF values are allocated
deterministically to the signed-off source subtotal without inventing an extra position.
"""

from __future__ import annotations

import json
from dataclasses import dataclass
from decimal import ROUND_HALF_UP, Decimal
from sqlite3 import Connection

MONEY = Decimal("0.01")

@dataclass(frozen=True)
class OfficialCashComponent:
    account_id: str
    currency: str
    amount_original: Decimal
    source_fx_rate_to_chf: Decimal
    amount_chf: Decimal
    snapshot_date: str
    valuation_at: str
    reconciliation_method: str = "official_total_pro_rata_rounded_fx_v1"

@dataclass(frozen=True)
class OfficialPositionOverride:
    account_id: str
    instrument_id: str
    asset_class: str
    quantity: Decimal
    market_price_original: Decimal
    price_currency: str
    market_value_chf: Decimal
    snapshot_date: str
    valuation_at: str
    reconciliation_method: str = "official_asset_class_residual_v1"

@dataclass(frozen=True)
class AuditedMarketValue:
    account_id: str
    instrument_id: str
    value_chf: Decimal
    close: Decimal | None
    currency: str
    as_of: str
    completed_at: str
    provider: str
    quantity: Decimal | None = None

def _decimal(value: object) -> Decimal:
    return Decimal(str(value))

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

def postfinance_account_roles(conn: Connection) -> dict[str, str]:
    """Return canonical PostFinance role mappings without name-based inference."""

    return {
        str(row["role"]): str(row["account_id"])
        for row in conn.execute(
            "SELECT role,account_id FROM postfinance_account_roles ORDER BY role"
        ).fetchall()
    }

def _allocate_pro_rata(values: list[Decimal], target: Decimal) -> list[Decimal]:
    """Reconcile rounded source components to an authoritative signed-off subtotal."""

    if not values:
        return []
    raw_total = sum(values, Decimal("0"))
    if raw_total <= 0 or target < 0:
        raise ValueError("Official portfolio components cannot be reconciled")
    allocated = [_money(value * target / raw_total) for value in values]
    residual = _money(target - sum(allocated, Decimal("0")))
    if residual:
        largest = max(range(len(values)), key=values.__getitem__)
        allocated[largest] = _money(allocated[largest] + residual)
    if sum(allocated, Decimal("0")) != _money(target):
        raise ValueError("Official portfolio component allocation does not reconcile")
    return allocated

def latest_official_postfinance_cash(
    conn: Connection,
    *,
    as_of: str | None = None,
    data_cutoff: str | None = None,
) -> list[OfficialCashComponent]:
    roles = postfinance_account_roles(conn)
    account_id = roles.get("etrading_cash")
    if not account_id:
        return []
    conditions: list[str] = []
    params: list[str] = []
    if as_of:
        conditions.append("date(snapshot_at)<=date(?)")
        params.append(as_of)
    if data_cutoff:
        conditions.append("datetime(created_at)<=datetime(?)")
        params.append(data_cutoff)
    where = f"WHERE {' AND '.join(conditions)}" if conditions else ""
    snapshot = conn.execute(
        f"""SELECT snapshot_id,snapshot_at,cash_chf
            FROM postfinance_snapshots {where}
            ORDER BY snapshot_at DESC,datetime(created_at) DESC LIMIT 1""",
        params,
    ).fetchone()
    if not snapshot:
        return []
    rows = conn.execute(
        """SELECT currency,amount_original,fx_rate_to_chf
           FROM postfinance_snapshot_cash WHERE snapshot_id=? ORDER BY currency""",
        (snapshot["snapshot_id"],),
    ).fetchall()
    raw_values = [
        _decimal(row["amount_original"]) * _decimal(row["fx_rate_to_chf"])
        for row in rows
    ]
    target = _money(_decimal(snapshot["cash_chf"]))
    allocated = _allocate_pro_rata(raw_values, target)
    valuation_at = str(snapshot["snapshot_at"])
    return [
        OfficialCashComponent(
            account_id=account_id,
            currency=str(row["currency"]),
            amount_original=_decimal(row["amount_original"]),
            source_fx_rate_to_chf=_decimal(row["fx_rate_to_chf"]),
            amount_chf=value,
            snapshot_date=valuation_at[:10],
            valuation_at=valuation_at,
        )
        for row, value in zip(rows, allocated, strict=True)
    ]

def latest_official_postfinance_positions(
    conn: Connection,
    *,
    as_of: str | None = None,
    data_cutoff: str | None = None,
) -> dict[tuple[str, str], OfficialPositionOverride]:
    roles = postfinance_account_roles(conn)
    account_id = roles.get("etrading_depot")
    if not account_id:
        return {}
    conditions: list[str] = []
    params: list[str] = []
    if as_of:
        conditions.append("date(snapshot_at)<=date(?)")
        params.append(as_of)
    if data_cutoff:
        conditions.append("datetime(created_at)<=datetime(?)")
        params.append(data_cutoff)
    where = f"WHERE {' AND '.join(conditions)}" if conditions else ""
    snapshot = conn.execute(
        f"""SELECT snapshot_id,snapshot_at,stocks_chf,etfs_chf
            FROM postfinance_snapshots {where}
            ORDER BY snapshot_at DESC,datetime(created_at) DESC LIMIT 1""",
        params,
    ).fetchone()
    if not snapshot:
        return {}
    rows = conn.execute(
        """SELECT instrument_id,asset_class,quantity,price_original,price_currency,value_chf
           FROM postfinance_snapshot_positions
           WHERE snapshot_id=? ORDER BY asset_class,instrument_id""",
        (snapshot["snapshot_id"],),
    ).fetchall()
    targets = {
        "stock": _money(_decimal(snapshot["stocks_chf"])),
        "etf": _money(_decimal(snapshot["etfs_chf"])),
    }
    allocated_by_instrument: dict[str, Decimal] = {}
    for asset_class, target in targets.items():
        members = [row for row in rows if str(row["asset_class"]).lower() == asset_class]
        allocated = _allocate_pro_rata(
            [_decimal(row["value_chf"]) for row in members], target
        )
        allocated_by_instrument.update(
            {
                str(row["instrument_id"]): value
                for row, value in zip(members, allocated, strict=True)
            }
        )
    valuation_at = str(snapshot["snapshot_at"])
    return {
        (account_id, str(row["instrument_id"])): OfficialPositionOverride(
            account_id=account_id,
            instrument_id=str(row["instrument_id"]),
            asset_class=str(row["asset_class"]).lower(),
            quantity=_decimal(row["quantity"]),
            market_price_original=_decimal(row["price_original"]),
            price_currency=str(row["price_currency"]),
            market_value_chf=allocated_by_instrument[str(row["instrument_id"])],
            snapshot_date=valuation_at[:10],
            valuation_at=valuation_at,
        )
        for row in rows
    }

def post_snapshot_postfinance_quantity_deltas(
    conn: Connection,
    positions: dict[tuple[str, str], OfficialPositionOverride],
    *,
    as_of: str | None = None,
) -> dict[str, Decimal]:
    """Replay confirmed buys/sells after the official snapshot baseline."""

    if not positions:
        return {}
    roles = postfinance_account_roles(conn)
    account_ids = [
        account_id
        for role in ("etrading_depot", "etrading_cash")
        if (account_id := roles.get(role))
    ]
    if not account_ids:
        return {}
    snapshot_date = min(item.snapshot_date for item in positions.values())
    effective_as_of = as_of or "9999-12-31"
    placeholders = ",".join("?" for _ in account_ids)
    rows = conn.execute(
        f"""SELECT instrument_id,lower(transaction_type) transaction_type,quantity
            FROM transactions
            WHERE account_id IN ({placeholders})
              AND instrument_id IS NOT NULL
              AND COALESCE(is_confirmed,0)=1
              AND COALESCE(is_voided,0)=0
              AND lower(transaction_type) IN ('buy','sell','partial_sell','full_sell')
              AND trade_date>? AND trade_date<=?""",
        (*account_ids, snapshot_date, effective_as_of),
    ).fetchall()
    deltas: dict[str, Decimal] = {}
    for row in rows:
        instrument_id = str(row["instrument_id"])
        quantity = abs(_decimal(row["quantity"]))
        if str(row["transaction_type"]) in {"sell", "partial_sell", "full_sell"}:
            quantity = -quantity
        deltas[instrument_id] = deltas.get(instrument_id, Decimal("0")) + quantity
    return deltas

def latest_audited_market_values(
    conn: Connection,
) -> dict[tuple[str, str], AuditedMarketValue]:
    """Return the latest immutable market-analysis values without writing on GET."""

    row = conn.execute(
        """SELECT pas.as_of,pas.summary_json,mdr.completed_at
           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 {}
    try:
        payload = json.loads(row["summary_json"] or "{}")
    except json.JSONDecodeError:
        return {}
    values: dict[tuple[str, str], AuditedMarketValue] = {}
    for item in payload.get("positions", []):
        if not isinstance(item, dict) or item.get("quality_status") != "fresh":
            continue
        account_id = str(item.get("account_id") or "")
        instrument_id = str(item.get("instrument_id") or "")
        if not account_id or not instrument_id or item.get("value_chf") in {None, ""}:
            continue
        provenance = item.get("price_input_provenance")
        provider = (
            str(item.get("provider") or "")
            or (str(provenance.get("provider") or "") if isinstance(provenance, dict) else "")
        )
        values[(account_id, instrument_id)] = AuditedMarketValue(
            account_id=account_id,
            instrument_id=instrument_id,
            value_chf=_decimal(item["value_chf"]),
            close=_decimal(item["close"]) if item.get("close") not in {None, ""} else None,
            currency=str(item.get("currency") or ""),
            as_of=str(row["as_of"]),
            completed_at=str(row["completed_at"]),
            provider=provider,
            quantity=(
                _decimal(item["quantity"])
                if item.get("quantity") not in {None, ""}
                else None
            ),
        )
    return values
grep: src/jarvis_finance/storage/migrations: No such file or directory


ls src/jarvis_finance/storage && grep -R "postfinance_account_roles\|postfinance_snapshots" -n src/jarvis_finance | head -100


Output:
Chunk ID: 83b896
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1120
Output:
__init__.py
__pycache__
database.py
migrations.py
postfinance_schema.py
repositories.py
schema.py
units_of_work.py
grep: src/jarvis_finance/storage/__pycache__/postfinance_schema.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/__pycache__/data.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/dashboard/__pycache__/data.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/postfinance_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__/cash_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_aggregation.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_analytics.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_aggregation.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_analytics.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/postfinance_service.cpython-312.pyc: binary file matches
src/jarvis_finance/storage/postfinance_schema.py:15:        CREATE TABLE IF NOT EXISTS postfinance_account_roles (
src/jarvis_finance/storage/postfinance_schema.py:56:        CREATE TABLE IF NOT EXISTS postfinance_snapshots (
src/jarvis_finance/storage/postfinance_schema.py:77:            snapshot_id TEXT NOT NULL REFERENCES postfinance_snapshots(snapshot_id),
src/jarvis_finance/storage/postfinance_schema.py:97:            snapshot_id TEXT NOT NULL REFERENCES postfinance_snapshots(snapshot_id),
src/jarvis_finance/storage/postfinance_schema.py:172:        CREATE TRIGGER IF NOT EXISTS postfinance_snapshots_immutable BEFORE UPDATE ON postfinance_snapshots
src/jarvis_finance/storage/postfinance_schema.py:174:        CREATE TRIGGER IF NOT EXISTS postfinance_snapshots_no_delete BEFORE DELETE ON postfinance_snapshots
src/jarvis_finance/dashboard/data.py:16:    postfinance_account_roles,
src/jarvis_finance/dashboard/data.py:564:    roles = postfinance_account_roles(conn)
src/jarvis_finance/services/portfolio_aggregation.py:65:def postfinance_account_roles(conn: Connection) -> dict[str, str]:
src/jarvis_finance/services/portfolio_aggregation.py:71:            "SELECT role,account_id FROM postfinance_account_roles ORDER BY role"
src/jarvis_finance/services/portfolio_aggregation.py:100:    roles = postfinance_account_roles(conn)
src/jarvis_finance/services/portfolio_aggregation.py:115:            FROM postfinance_snapshots {where}
src/jarvis_finance/services/portfolio_aggregation.py:153:    roles = postfinance_account_roles(conn)
src/jarvis_finance/services/portfolio_aggregation.py:168:            FROM postfinance_snapshots {where}
src/jarvis_finance/services/portfolio_aggregation.py:223:    roles = postfinance_account_roles(conn)
src/jarvis_finance/services/portfolio_data.py:34:    postfinance_account_roles,
src/jarvis_finance/services/portfolio_data.py:1599:    pf_roles = postfinance_account_roles(conn)
src/jarvis_finance/services/portfolio_analytics.py:31:    postfinance_account_roles,
src/jarvis_finance/services/portfolio_analytics.py:238:    trading_cash_id = postfinance_account_roles(conn).get("etrading_cash")
src/jarvis_finance/services/postfinance_service.py:541:                "INSERT OR IGNORE INTO postfinance_account_roles(role,account_id,source_reference_hash,created_at) VALUES(?,?,NULL,?)",
src/jarvis_finance/services/postfinance_service.py:545:                "SELECT account_id FROM postfinance_account_roles WHERE role=?", (role,)
src/jarvis_finance/services/postfinance_service.py:602:            """INSERT INTO postfinance_snapshots(snapshot_id,batch_id,account_id,snapshot_at,total_chf,securities_chf,cash_chf,stocks_chf,etfs_chf,component_total_chf,difference_chf,tolerance_chf,position_count,cash_count,open_orders,reconciliation_status,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,? ,?,?,?,?,?)""",
src/jarvis_finance/services/postfinance_service.py:873:           FROM postfinance_snapshots ORDER BY snapshot_at DESC,created_at DESC LIMIT 1"""
src/jarvis_finance/services/postfinance_service.py:937:               FROM postfinance_import_batches b JOIN postfinance_snapshots s ON s.batch_id=b.batch_id
src/jarvis_finance/services/cash_service.py:218:                  SELECT account_id FROM postfinance_account_roles


sed -n '1,130p' src/jarvis_finance/storage/postfinance_schema.py && sed -n '500,640p' src/jarvis_finance/services/postfinance_service.py


Output:
Chunk ID: 7987b9
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3505
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')),
            quantity TEXT NOT NULL,
            provider_average_cost_original TEXT,
            provider_cost_total_original TEXT,
            price_original TEXT NOT NULL,
            price_currency TEXT NOT NULL,
            value_chf TEXT NOT NULL,
            weight_pct TEXT NOT NULL,
            computed_cost_basis_original TEXT,
            computed_cost_basis_status TEXT NOT NULL CHECK(computed_cost_basis_status IN ('complete','partial','unavailable')),
            provenance_json TEXT NOT NULL CHECK(json_valid(provenance_json)),
            created_at TEXT NOT NULL,
            UNIQUE(snapshot_id, source_row_reference),
            UNIQUE(snapshot_id, instrument_id)
        );
        CREATE TABLE IF NOT EXISTS postfinance_snapshot_cash (
            snapshot_cash_id TEXT PRIMARY KEY,
            snapshot_id TEXT NOT NULL REFERENCES postfinance_snapshots(snapshot_id),
            currency TEXT NOT NULL,
            amount_original TEXT NOT NULL,
            fx_rate_to_chf TEXT NOT NULL,
            value_chf TEXT NOT NULL,
            created_at TEXT NOT NULL,
            UNIQUE(snapshot_id, currency)
        );
        CREATE TABLE IF NOT EXISTS postfinance_ledger_events (
            event_id TEXT PRIMARY KEY,
            event_fingerprint TEXT NOT NULL UNIQUE CHECK(length(event_fingerprint)=64),
            document_hash TEXT NOT NULL REFERENCES postfinance_documents(document_hash),
            semantic_reference_hash TEXT NOT NULL CHECK(length(semantic_reference_hash)=64),
            event_type TEXT NOT NULL CHECK(event_type IN ('buy','sell','dividend','interest','fee','internal_transfer','fx','split','corporate_action')),
            account_role TEXT NOT NULL CHECK(account_role IN ('efinance','etrading_depot','etrading_cash')),
            account_id TEXT NOT NULL REFERENCES accounts(account_id),
            occurred_on TEXT NOT NULL,
            settlement_on TEXT,
            direction TEXT NOT NULL CHECK(direction IN ('in','out','neutral')),
            instrument_id TEXT REFERENCES instruments(instrument_id),
            quantity TEXT,
            price_original TEXT,
            gross_original TEXT,
            fee_original TEXT NOT NULL DEFAULT '0',
            tax_original TEXT NOT NULL DEFAULT '0',
            net_original TEXT,
            currency TEXT,
            fx_rate_to_chf TEXT,
            internal_transfer_group TEXT,
            quality_status TEXT NOT NULL CHECK(quality_status IN ('complete','partial')),
            reason_codes_json TEXT NOT NULL CHECK(json_valid(reason_codes_json)),
            created_at TEXT NOT NULL
        );
        CREATE INDEX IF NOT EXISTS idx_postfinance_events_time ON postfinance_ledger_events(occurred_on, event_type);
        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)
            existing = conn.execute(
                "SELECT document_hash FROM postfinance_documents WHERE semantic_identity=?",
                (document.semantic_identity,),
            ).fetchone()
            if existing and existing["document_hash"] != document.document_hash:
                raise ValueError("PostFinance semantic document conflict")
            conn.execute(
                """INSERT OR IGNORE INTO postfinance_documents(document_hash,filename_hash,semantic_identity,semantic_reference_hash,document_type,document_date,account_reference_hash,account_role,page_count,archive_reference,parser_version,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)""",
                (
                    document.document_hash,
                    document.filename_hash,
                    document.semantic_identity,
                    ref_hash,
                    document.document_type,
                    document.document_date,
                    document.account_reference_hash,
                    document.account_role,
                    document.page_count,
                    reference,
                    PARSER_VERSION,
                    now,
                ),
            )
            conn.execute(
                "INSERT INTO postfinance_batch_documents(batch_id,document_hash) VALUES(?,?)",
                (batch_id, document.document_hash),
            )
        snapshot = bundle.snapshot
        component_total = snapshot.securities_total_chf + snapshot.cash_total_chf
        difference = snapshot.total_value_chf - component_total
        conn.execute(
            """INSERT INTO postfinance_snapshots(snapshot_id,batch_id,account_id,snapshot_at,total_chf,securities_chf,cash_chf,stocks_chf,etfs_chf,component_total_chf,difference_chf,tolerance_chf,position_count,cash_count,open_orders,reconciliation_status,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,? ,?,?,?,?,?)""",
            (
                snapshot_id,
                batch_id,
                accounts["etrading_depot"],
                snapshot.valuation_at,
                _money(snapshot.total_value_chf),
                _money(snapshot.securities_total_chf),
                _money(snapshot.cash_total_chf),
                _money(snapshot.stock_total_chf),
                _money(snapshot.etf_total_chf),
                _money(component_total),
                _money(difference),
                _money(TOLERANCE),
                len(snapshot.positions),
                len(snapshot.cash),
                snapshot.open_orders,
                "matched" if difference == 0 else "within_tolerance",
                now,
            ),
        )
        for event in bundle.events:
            instrument_id = event_instruments[event.event_fingerprint]
            event_id = f"pfevent_{event.event_fingerprint[:24]}"
            account_id = accounts[event.account_role]
            conn.execute(
                """INSERT INTO postfinance_ledger_events(event_id,event_fingerprint,document_hash,semantic_reference_hash,event_type,account_role,account_id,occurred_on,settlement_on,direction,instrument_id,quantity,price_original,gross_original,fee_original,tax_original,net_original,currency,fx_rate_to_chf,internal_transfer_group,quality_status,reason_codes_json,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
                (
                    event_id,
                    event.event_fingerprint,
                    event.document_hash,
                    _hash(event.semantic_reference),
                    event.event_type,
                    event.account_role,
                    account_id,
                    event.occurred_on,
                    event.settlement_on,
                    event.direction or "neutral",
                    instrument_id,


sed -n '1,130p' src/jarvis_finance/ledger/cash.py && grep -R "def get_command_center_summary\|cash_total_chf" -n src/jarvis_finance/dashboard/data.py src/jarvis_finance/services | head -80


Output:
Chunk ID: 5ac850
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1577
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(
        """
grep: src/jarvis_finance/services/__pycache__/postfinance_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__/postfinance_service.cpython-312.pyc: binary file matches
src/jarvis_finance/dashboard/data.py:703:        {"label": "Cash", "value_chf": summary["cash_total_chf"]},
src/jarvis_finance/dashboard/data.py:1170:def get_command_center_summary(conn: Connection) -> dict[str, str]:
src/jarvis_finance/dashboard/data.py:1193:        "cash_total_chf": dec_text(cash_total, "0.01"),
src/jarvis_finance/services/postfinance_service.py:269:    component_total = snapshot.securities_total_chf + snapshot.cash_total_chf
src/jarvis_finance/services/postfinance_service.py:295:        "cash_chf": _money(snapshot.cash_total_chf),
src/jarvis_finance/services/postfinance_service.py:599:        component_total = snapshot.securities_total_chf + snapshot.cash_total_chf
src/jarvis_finance/services/postfinance_service.py:610:                _money(snapshot.cash_total_chf),
src/jarvis_finance/services/truewealth_service.py:131:        "cash_total_chf": _decimal_text(statement.cash_total_chf),
src/jarvis_finance/services/truewealth_service.py:334:                 cash_total_chf,components_total_chf,reconciliation_difference_chf,reconciliation_tolerance_chf,
src/jarvis_finance/services/truewealth_service.py:345:                _decimal_text(statement.cash_total_chf),
src/jarvis_finance/services/truewealth_service.py:613:                  s.cash_total_chf,s.components_total_chf,s.reconciliation_difference_chf,s.reconciliation_status,


sed -n '650,740p' src/jarvis_finance/dashboard/data.py && sed -n '1140,1210p' src/jarvis_finance/dashboard/data.py


Output:
Chunk ID: 0ae611
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2415
Output:
            "cost_basis_chf": dec_text(pos.cost_basis_chf, "0.01"),
            "market_price_original": dec_text(market_price, "0.01") if market_price is not None else "",
            "price_currency": price_currency,
            "price_date": price_date,
            "price_provider": price_provider,
            "market_value_chf": dec_text(market_value_chf, "0.01") if market_value_chf is not None else "",
            "price_status": "ok" if market_price is not None else "missing_market_price",
            "valuation_source": valuation_source,
            "valuation_as_of": price_date,
            "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",
            "hedge_status": row["hedge_status"] if row else pos.hedge_status,
            "instrument_status": row["instrument_status"] if row else pos.instrument_status,
            "valuation_policy": row["valuation_policy"] if row else pos.valuation_policy,
            "fx_status": "ok" if "missing_fx" not in pos.quality_warnings else "missing_fx",
            "market_price_status": "ok" if market_price is not None else "missing_market_price",
            "valuation_timestamp_status": pos.valuation_timestamp_status,
            "corporate_action_status": row["corporate_action_status"] if row else pos.corporate_action_status,
            "valuation_status": pos.valuation_status,
            "total_return_chf": dec_text(pos.total_return_chf, "0.01") if pos.total_return_chf is not None else "",
            "total_return_status": "complete" if pos.total_return_chf is not None else "incomplete",
            "quality_warnings": ",".join(pos.quality_warnings),
            "data_quality_status": pos.data_quality_status,
        })
    return rows

def get_portfolio_positions_user(conn: Connection) -> list[dict[str, str]]:
    rows: list[dict[str, str]] = []
    for row in get_positions(conn):
        if row.get("asset_class", "").lower() not in {"stock", "equity", "etf"}:
            continue
        rows.append({
            "name": row.get("name", ""),
            "ticker": row.get("ticker", ""),
            "isin": row.get("isin", ""),
            "depot": row.get("account_name", ""),
            "assettyp": "ETF" if row.get("asset_class", "").lower() == "etf" else "Aktie",
            "menge": row.get("quantity", ""),
            "kurs": row.get("market_price_original") or "Preis noch nicht verfügbar",
            "kurswaehrung": row.get("price_currency", ""),
            "marktwert_chf": chf_text(row.get("market_value_chf")) if row.get("market_value_chf") else "nicht bewertet",
            "einstand_chf": chf_text(row.get("cost_basis_chf")) if row.get("cost_basis_chf") else "unvollständig",
            "p_l_chf": chf_text(row.get("total_return_chf")) if row.get("total_return_chf") else "nicht berechenbar",
            "status": translate_status(row.get("price_status") or row.get("data_quality_status")),
        })
    return rows

def get_portfolio_asset_class_chart(conn: Connection) -> list[dict[str, str]]:
    summary = get_command_center_summary(conn)
    return [
        {"label": "Crypto", "value_chf": summary["crypto_total_chf"]},
        {"label": "Aktien/ETF", "value_chf": summary["equity_etf_total_chf"]},
        {"label": "Cash", "value_chf": summary["cash_total_chf"]},
    ]

def get_platform_value_chart(conn: Connection) -> list[dict[str, str]]:
    totals: dict[str, Decimal] = {}
    for row in get_cash_overview(conn):
        totals[row["platform"]] = totals.get(row["platform"], Decimal("0")) + d(row.get("amount_chf"))
    for row in get_positions(conn):
        totals[row["platform"]] = totals.get(row["platform"], Decimal("0")) + d(row.get("market_value_chf"))
    crypto_total = d(get_crypto_summary_cards(conn)["total_value_chf"])
    if crypto_total:
        totals["Crypto Wallets"] = totals.get("Crypto Wallets", Decimal("0")) + crypto_total
    return [{"label": k, "value_chf": dec_text(v, "0.01")} for k, v in sorted(totals.items(), key=lambda item: item[1], reverse=True)]

def get_ledger_transactions(conn: Connection, limit: int = 200) -> list[dict[str, str]]:
    return rowdicts(conn.execute(
        """
        SELECT t.trade_date, p.name AS platform, a.account_name, t.transaction_type,
               i.name AS instrument, i.ticker, t.quantity, t.gross_amount_original,
               t.net_amount_original, t.currency_original, t.fx_status, t.quality_status,
               t.source_type, t.source_id
        FROM transactions t
        LEFT JOIN accounts a ON a.account_id=t.account_id
        LEFT JOIN platforms p ON p.platform_id=a.platform_id
        LEFT JOIN instruments i ON i.instrument_id=t.instrument_id
        ORDER BY t.trade_date DESC, t.created_at DESC LIMIT ?
        """, (limit,)
    ).fetchall())

def get_watchlist(conn: Connection) -> list[dict[str, str]]:
    return rowdicts(conn.execute("SELECT name, asset_class, reason, status, target_entry_price, target_entry_currency, investment_case, bear_case, next_review_date FROM watchlist ORDER BY name").fetchall())

def resolve_alert_entity_label(conn: Connection, entity_type: str | None, entity_id: str | None) -> str:
    if not entity_id:
    summary = get_command_center_summary(conn)
    if equity_etf_user_label(summary) == "noch nicht bewertet":
        hints.append("Aktien/ETF: Bewertung unvollständig")
    crypto = get_crypto_summary_cards(conn)
    if d(crypto.get("coins_without_price")) > 0:
        hints.append("Crypto-Preise fehlen für einzelne Coins")
    elif d(crypto.get("stale_price_count")) > 0:
        hints.append("Crypto-Preise teilweise veraltet")
    else:
        hints.append("Crypto-Preise aktuell")
    for alert in get_alert_cards(conn, limit=limit):
        text = alert["title"]
        if not any(marker in text for marker in ("asset_id", "wallet_id", "instrument-", "rule_id", "entity_id")):
            hints.append(text)
        if len(hints) >= limit:
            break
    return hints[:limit]

def compact_chart_rows(rows: list[dict[str, str]], *, limit: int = 10) -> list[dict[str, str]]:
    ordered = sorted(rows, key=lambda row: d(row.get("value_chf")), reverse=True)
    if len(ordered) <= limit:
        return ordered
    head = ordered[:limit]
    other = sum((d(row.get("value_chf")) for row in ordered[limit:]), Decimal("0"))
    if other > 0:
        head.append({"label": "Andere", "value_chf": dec_text(other, "0.01")})
    return head

def get_command_center_summary(conn: Connection) -> dict[str, str]:
    cash_rows = get_cash_overview(conn)
    crypto_summary = get_crypto_summary_cards(conn)
    positions = [r for r in get_positions(conn) if r.get("asset_class", "").lower() in {"stock", "equity", "etf"} and r.get("instrument_status", "").lower() != "inactive"]
    cash_total = sum(d(r["amount_chf"]) for r in cash_rows if r["amount_chf"])
    crypto_total = d(crypto_summary["total_value_chf"])
    stock_rows = [r for r in positions if r.get("asset_class", "").lower() in {"stock", "equity"}]
    etf_rows = [r for r in positions if r.get("asset_class", "").lower() == "etf"]
    stock_total = sum(d(r["market_value_chf"]) for r in stock_rows if r.get("market_value_chf"))
    etf_total = sum(d(r["market_value_chf"]) for r in etf_rows if r.get("market_value_chf"))
    equity_total = stock_total + etf_total
    missing_price_count = sum(1 for r in positions if not r.get("market_price_original"))
    missing_fx_count = sum(1 for r in positions if r.get("fx_status") == "missing_fx")
    incomplete_cost_count = sum(1 for r in positions if "cost_basis_uncertain" in (r.get("quality_warnings") or "") or not r.get("cost_basis_chf"))
    unvalued_count = missing_price_count + missing_fx_count
    latest_equity_price = max((r.get("price_date") or "" for r in positions), default="")
    alert_counts = conn.execute("SELECT priority, COUNT(*) AS c FROM alerts WHERE status='active' GROUP BY priority").fetchall()
    critical = sum(int(r["c"] or 0) for r in alert_counts if r["priority"] == "kritisch")
    quality = get_data_quality(conn)
    dq = "kritisch" if any(i["severity"] == "kritisch" and i["count"] != "0" for i in quality) else ("warnung" if any(i["severity"] == "warnung" and i["count"] != "0" for i in quality) else "ok")
    total = cash_total + crypto_total + equity_total
    return {
        "base_currency": "CHF",
        "cash_total_chf": dec_text(cash_total, "0.01"),
        "crypto_total_chf": dec_text(crypto_total, "0.01"),
        "stock_total_chf": dec_text(stock_total, "0.01"),
        "etf_total_chf": dec_text(etf_total, "0.01"),
        "equity_etf_total_chf": dec_text(equity_total, "0.01"),
        "equity_position_count": str(len(positions)),
        "unvalued_position_count": str(unvalued_count),
        "missing_price_count": str(missing_price_count),
        "missing_fx_count": str(missing_fx_count),
        "incomplete_cost_count": str(incomplete_cost_count),
        "total_portfolio_chf": dec_text(total, "0.01"),
        "total_value_chf": dec_text(total, "0.01"),
        "critical_alert_count": str(critical),
        "active_alerts": ", ".join(f"{r['priority']}: {r['c']}" for r in alert_counts) or "0",
        "data_quality_status": dq,
        "last_price_update_at": max([crypto_summary.get("last_price_update_at", ""), latest_equity_price]),
        "crypto_price_data_as_of": crypto_summary.get("last_price_update_at", ""),
        "equity_price_data_as_of": latest_equity_price,


.venv/bin/python -m pytest tests/test_postfinance_sprint13.py -q


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


grep -R "class CashPosition\|class Equity" -n src/jarvis_finance/api/schemas/positions.py src | head -60 && sed -n '1,110p' src/jarvis_finance/api/schemas/positions.py


Output:
Chunk ID: 4ae0c2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1152
Output:
src/jarvis_finance/api/schemas/positions.py:82:class EquityPosition(BaseModel):
src/jarvis_finance/api/schemas/positions.py:106:class EquityPortfolioSummary(BaseModel):
src/jarvis_finance/api/schemas/positions.py:121:class EquityPositionDetail(EquityPosition):
src/jarvis_finance/api/schemas/positions.py:148:class CashPosition(BaseModel):
src/jarvis_finance/api/schemas/positions.py:168:class CashPositionDetail(CashPosition):
src/jarvis_finance/api/schemas/positions.py:338:class EquitySellPreviewRequest(BaseModel):
src/jarvis_finance/api/schemas/positions.py:349:class EquitySellConfirmRequest(EquitySellPreviewRequest):
src/jarvis_finance/api/schemas/positions.py:354:class EquityDividendPreviewRequest(BaseModel):
src/jarvis_finance/api/schemas/positions.py:366:class EquityDividendConfirmRequest(EquityDividendPreviewRequest):
src/jarvis_finance/api/schemas/market.py:92:class EquityCandle(BaseModel):
src/jarvis_finance/api/schemas/market.py:101:class EquityCandlesResponse(BaseModel):
src/jarvis_finance/api/schemas/positions.py:82:class EquityPosition(BaseModel):
src/jarvis_finance/api/schemas/positions.py:106:class EquityPortfolioSummary(BaseModel):
src/jarvis_finance/api/schemas/positions.py:121:class EquityPositionDetail(EquityPosition):
src/jarvis_finance/api/schemas/positions.py:148:class CashPosition(BaseModel):
src/jarvis_finance/api/schemas/positions.py:168:class CashPositionDetail(CashPosition):
src/jarvis_finance/api/schemas/positions.py:338:class EquitySellPreviewRequest(BaseModel):
src/jarvis_finance/api/schemas/positions.py:349:class EquitySellConfirmRequest(EquitySellPreviewRequest):
src/jarvis_finance/api/schemas/positions.py:354:class EquityDividendPreviewRequest(BaseModel):
src/jarvis_finance/api/schemas/positions.py:366:class EquityDividendConfirmRequest(EquityDividendPreviewRequest):
src/jarvis_finance/market_data/prices.py:19:class EquityPriceQuote:
src/jarvis_finance/market_data/prices.py:55:class EquityPriceProvider(Protocol):
src/jarvis_finance/equity/manage.py:22:class EquityManageResult:
from __future__ import annotations

from pydantic import BaseModel

class ActionItem(BaseModel):
    label: str
    enabled: bool
    reason: str | None = None

class PricePoint(BaseModel):
    date: str
    value: str
    currency: str = "CHF"
    provider: str | None = None
    quality_status: str | None = None

class CryptoPosition(BaseModel):
    asset_id: str
    name: str
    symbol: str
    quantity_total: str
    price_chf: str | None = None
    market_value_chf: str | None = None
    portfolio_share_pct: str
    wallet_count: int
    price_status: str
    last_price_update: str | None = None
    coingecko_url: str | None = None

class WalletAllocation(BaseModel):
    wallet_id: str
    wallet: str
    quantity: str
    market_value_chf: str | None = None

class WalletCoin(BaseModel):
    asset_id: str
    name: str
    symbol: str
    quantity: str
    market_value_chf: str | None = None
    status: str

class CoinGeckoInfo(BaseModel):
    market_cap_rank: int | None = None
    market_cap: str | None = None
    volume_24h: str | None = None
    change_24h_pct: str | None = None
    change_7d_pct: str | None = None
    homepage: str | None = None
    image_url: str | None = None

class CryptoPositionDetail(CryptoPosition):
    wallet_allocations: list[WalletAllocation]
    coingecko_info: CoinGeckoInfo | None = None
    available_actions: list[ActionItem] = []
    price_history: list[PricePoint] = []
    read_only_note: str = "Live-Modus später; keine Provider-Abfrage beim Rendern."

class WalletSummary(BaseModel):
    wallet_id: str
    name: str
    wallet_type: str
    provider: str = ""
    coin_count: int
    market_value_chf: str | None = None
    status: str

class WalletDetail(WalletSummary):
    coins: list[WalletCoin]

class EquityPosition(BaseModel):
    position_id: str
    instrument_id: str | None = None
    name: str
    ticker: str = ""
    isin: str = ""
    account: str = ""
    asset_class: str
    quantity: str
    currency: str
    price: str | None = None
    market_value_chf: str | None = None
    portfolio_share_pct: str | None = None
    price_date: str | None = None
    provider: str | None = None
    provider_symbol: str | None = None
    exchange: str | None = None
    price_currency: str | None = None
    data_status: str = "missing"
    status_code: str = "price_missing"
    status_reason: str | None = None
    status: str

class EquityPortfolioSummary(BaseModel):
    total_positions: int
    valued_positions: int
    unvalued_positions: int
    coverage_complete: bool


sed -n '110,190p' src/jarvis_finance/api/schemas/positions.py


Output:
Chunk ID: 3b8a16
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 544
Output:
    coverage_complete: bool
    equity_value_chf: str | None = None
    valued_partial_chf: str
    value_label: str
    as_of: str | None = None
    last_successful_run: str | None = None
    status: str
    status_label: str
    unvalued_tickers: list[str] = []

class EquityPositionDetail(EquityPosition):
    fx_status: str = "not_needed"
    cost_basis_status: str = "unknown"
    day_change_chf: str | None = None
    last_trade_date: str | None = None
    provider: str | None = None
    source_url: str | None = None
    provider_symbol: str | None = None
    exchange: str | None = None
    price_currency: str | None = None
    fx_rate_to_chf: str | None = None
    data_source: str | None = None
    available_actions: list[ActionItem]
    price_history: list[PricePoint] = []

class CashSnapshot(BaseModel):
    snapshot_id: str
    snapshot_type: str
    balance_date: str
    amount_chf: str
    source: str
    note: str = ""
    difference_to_previous_chf: str | None = None
    audit_hint: str | None = None

class CashPosition(BaseModel):
    id: str
    account_id: str = ""
    platform: str
    account_label: str
    account_type: str = "cash"
    currency: str
    balance_mode: str = "manual"
    amount: str
    amount_chf: str | None = None
    calculated_balance_chf: str = "0.00"
    manual_balance_chf: str | None = None
    csv_anchor_balance_chf: str | None = None
    used_value_chf: str = "0.00"
    difference_chf: str = "0.00"
    last_manual_reconciliation: str | None = None
    last_imported_booking: str | None = None
    status: str

class CashPositionDetail(CashPosition):
    last_change: str | None = None
    history: list[CashSnapshot] = []
    available_actions: list[ActionItem]

class InstrumentSearchCandidate(BaseModel):
    candidate_id: str
    label: str
    asset_class: str
    isin: str = ""
    ticker: str = ""
    exchange: str = ""
    currency: str = ""
    provider: str = ""
    provider_symbol: str = ""
    exchange_name: str = ""
    mic: str = ""
    country: str = ""
    security_type: str = ""
    last_price: str | None = None
    price_currency: str | None = None
    price_date: str | None = None


Output:
Chunk ID: f51a90
Wall time: 30.0016 seconds
Process running with session ID 76287
Original token count: 7
Output:
...........................


Output:
Chunk ID: 40566e
Wall time: 30.0009 seconds
Process running with session ID 76287
Original token count: 0
Output:


Output:
Chunk ID: 21eb1f
Wall time: 30.0009 seconds
Process running with session ID 76287
Original token count: 0
Output:


sed -n '200,280p' src/jarvis_finance/services/portfolio_analytics.py && grep -R "confirmed_canonical_positions" -n src/jarvis_finance | grep -v pyc


Output:
Chunk ID: 9a0d98
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1090
Output:

def confirmed_canonical_positions(conn: Connection, *, as_of: str | None = None) -> list[ConfirmedPosition]:
    """Return only active, confirmed ledger positions. Preview and ingestion staging tables are never read."""

    rows = conn.execute(
        """
        SELECT t.account_id, t.instrument_id, i.isin, i.name, lower(i.asset_class) AS asset_class,
               upper(COALESCE(i.trading_currency, i.currency, '')) AS instrument_currency,
               t.transaction_type, t.quantity
        FROM transactions t
        JOIN instruments i ON i.instrument_id=t.instrument_id
        JOIN accounts a ON a.account_id=t.account_id
        WHERE t.instrument_id IS NOT NULL
          AND COALESCE(t.is_confirmed, 0)=1
          AND COALESCE(t.is_voided, 0)=0
          AND i.is_active=1 AND a.is_active=1
          AND COALESCE(i.instrument_status,'active') NOT IN ('inactive','delisted','suspended','merged')
          AND COALESCE(i.valuation_policy,'')!='exclude_from_auto_price_update'
          AND lower(i.asset_class) IN ('stock','equity','etf')
          AND lower(t.transaction_type) IN ('initial_position_snapshot','buy','sell','partial_sell','full_sell')
          AND t.trade_date<=?
        ORDER BY t.account_id, t.instrument_id, t.trade_date, t.created_at
        """,
        (as_of or datetime.now(timezone.utc).date().isoformat(),),
    ).fetchall()
    grouped: dict[tuple[str, str], dict[str, Any]] = {}
    for row in rows:
        key = (str(row["account_id"]), str(row["instrument_id"]))
        item = grouped.setdefault(key, dict(row) | {"quantity_total": ZERO})
        quantity = _decimal(row["quantity"])
        item["quantity_total"] += -abs(quantity) if str(row["transaction_type"]).lower() in SELL_TYPES else quantity
    official = latest_official_postfinance_positions(conn, as_of=as_of)
    post_snapshot_deltas = post_snapshot_postfinance_quantity_deltas(
        conn,
        official,
        as_of=as_of,
    )
    trading_cash_id = postfinance_account_roles(conn).get("etrading_cash")
    official_instruments = {key[1] for key in official}
    for key in list(grouped):
        if key[0] == trading_cash_id and key[1] in official_instruments:
            grouped.pop(key)
    for key, override in official.items():
        item = grouped.get(key)
        if item is None:
            instrument = conn.execute(
                """SELECT isin,name,lower(asset_class) asset_class,
                          upper(COALESCE(trading_currency,currency,'')) instrument_currency
                   FROM instruments WHERE instrument_id=? AND is_active=1""",
                (override.instrument_id,),
            ).fetchone()
            if not instrument:
                continue
            item = {
                "account_id": override.account_id,
                "instrument_id": override.instrument_id,
                "isin": instrument["isin"],
                "name": instrument["name"],
                "asset_class": instrument["asset_class"],
                "instrument_currency": instrument["instrument_currency"],
            }
            grouped[key] = item
        # The official snapshot is the baseline. Only confirmed trades after that
        # snapshot are replayed, so imported history is not counted twice.
        item["quantity_total"] = override.quantity + post_snapshot_deltas.get(
            override.instrument_id,
            ZERO,
        )
    return [
        ConfirmedPosition(
            account_id=item["account_id"],
            instrument_id=item["instrument_id"],
            isin=item["isin"],
            name=item["name"],
            asset_class=item["asset_class"],
            instrument_currency=item["instrument_currency"],
            quantity=item["quantity_total"],
        )
        for item in grouped.values()
        if item["quantity_total"] > ZERO
grep: src/jarvis_finance/services/__pycache__/portfolio_analytics.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_analytics.cpython-311.pyc: binary file matches
src/jarvis_finance/services/portfolio_analytics.py:202:def confirmed_canonical_positions(conn: Connection, *, as_of: str | None = None) -> list[ConfirmedPosition]:
src/jarvis_finance/services/portfolio_analytics.py:405:        positions = confirmed_canonical_positions(conn, as_of=effective.isoformat())


Output:
Chunk ID: 6c14f1
Wall time: 30.0005 seconds
Process running with session ID 76287
Original token count: 0
Output:


Output:
Chunk ID: 69d3b7
Wall time: 30.0017 seconds
Process running with session ID 76287
Original token count: 0
Output:


grep -n "source_value_chf\|PFSnapshotCash\|cash_total" -n src/jarvis_finance/imports/postfinance_documents.py | head -80 && sed -n '560,690p' src/jarvis_finance/imports/postfinance_documents.py


Output:
Chunk ID: f4afba
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1694
Output:
87:class PFSnapshotCash:
91:    source_value_chf: Decimal
100:    cash: tuple[PFSnapshotCash, ...]
102:    cash_total_chf: Decimal
122:    cash: list[PFSnapshotCash], cash_total_chf: Decimal
123:) -> list[PFSnapshotCash]:
135:    allocated = [(value * cash_total_chf / raw_total).quantize(money) for value in raw]
136:    residual = cash_total_chf.quantize(money) - sum(allocated, Decimal("0"))
140:    if sum(allocated, Decimal("0")) != cash_total_chf.quantize(money):
143:        replace(item, source_value_chf=value)
557:    cash: list[PFSnapshotCash] = []
560:    cash_total = securities_total = total = stock_total = etf_total = None
579:            cash.append(PFSnapshotCash(cols[0], amount, fx, value))
622:                cash_total, securities_total, total = values[0], values[1], values[2]
648:            cash_total = _number(m.group(1), required=True)
662:    if None in (cash_total, securities_total, total, stock_total, etf_total, open_orders):
665:        cash_total is not None
676:    if abs(securities_total + cash_total - total) > Decimal("0.05"):
680:    cash = _reconcile_cash_components(cash, cash_total)
688:        cash_total,
    cash_total = securities_total = total = stock_total = etf_total = None
    open_orders = None
    for line in text.splitlines():
        stripped = line.strip()
        if stripped == "Aktien":
            section = "stock"
            continue
        if stripped == "ETFs":
            section = "etf"
            continue
        if "Produkt" in stripped and "Anzahl" in stripped and "Einstandskurs" in stripped:
            in_positions = True
            continue
        cols = [item.strip() for item in re.split(r"\s{2,}", stripped) if item.strip()]
        if len(cols) == 7 and cols[0] in CURRENCIES and re.search(r"\d", cols[1]):
            fx = _number(cols[1], required=True)
            amount = _number(cols[2], required=True)
            value = _number(cols[5], required=True)
            assert fx is not None and amount is not None and value is not None
            cash.append(PFSnapshotCash(cols[0], amount, fx, value))
            continue
        if (
            in_positions
            and section
            and len(cols) == 9
            and re.search(r"\d", cols[1])
            and re.search(r"[A-Z]{3}", cols[5])
        ):
            quantity = _number(cols[1], required=True)
            average = _number(cols[2])
            provider_total = _number(cols[3])
            pm = re.search(r"([+\-−]?[0-9][0-9'’., ]*)\s*([A-Z]{3})", cols[5])
            if not pm:
                raise ValueError("PostFinance overview position lacks price/currency")
            price = _number(pm.group(1), required=True)
            currency = pm.group(2)
            value = _number(cols[7], required=True)
            weight = _number(cols[8])
            assert quantity is not None and price is not None and value is not None
            label = cols[0]
            positions.append(
                PFSnapshotPosition(
                    _hash("snapshot-row", len(positions) + 1, normalize_label(label))[:32],
                    label,
                    normalize_label(label),
                    section,
                    quantity,
                    average,
                    provider_total,
                    price,
                    currency,
                    value,
                    weight,
                )
            )
            continue
        if stripped.startswith("Gesamt CHF") and not in_positions:
            values = [
                _number(value, required=True)
                for value in re.findall(r"[+\-−]?[0-9][0-9'’.,]*", stripped)
            ]
            if len(values) >= 3:
                cash_total, securities_total, total = values[0], values[1], values[2]
            continue
        if stripped.startswith("Zwischensumme Aktien in CHF"):
            values = [
                _number(value, required=True)
                for value in re.findall(r"[+\-−]?[0-9][0-9'’.,]*", stripped)
            ]
            if len(values) >= 3:
                stock_total = values[2]
            continue
        if stripped.startswith("Zwischensumme ETFs in CHF"):
            values = [
                _number(value, required=True)
                for value in re.findall(r"[+\-−]?[0-9][0-9'’.,]*", stripped)
            ]
            if len(values) >= 3:
                etf_total = values[2]
            continue
        m = re.search(r"Offene Aufträge\s+([0-9]+)", stripped)
        if m:
            open_orders = int(m.group(1))
        m = re.search(r"Wert der Wertschriften\s+([0-9][0-9'’.,]*(?: [0-9]{3})*)", stripped)
        if m:
            securities_total = _number(m.group(1), required=True)
        m = re.search(r"Barguthaben\s+([0-9][0-9'’.,]*(?: [0-9]{3})*)", stripped)
        if m:
            cash_total = _number(m.group(1), required=True)
        m = re.search(r"Totalwert\s+([0-9][0-9'’.,]*(?: [0-9]{3})*)", stripped)
        if m and total is None:
            total = _number(m.group(1), required=True)
        m = re.search(r"Zwischensumme Aktien in CHF\s+([0-9][0-9'’.,]*(?: [0-9]{3})*)", stripped)
        if m:
            stock_total = _number(m.group(1), required=True)
        m = re.search(r"Zwischensumme ETFs in CHF\s+([0-9][0-9'’.,]*(?: [0-9]{3})*)", stripped)
        if m:
            etf_total = _number(m.group(1), required=True)
    if len(positions) != 22 or len(cash) != 2:
        raise ValueError(
            "PostFinance account overview does not contain the expected complete snapshot"
        )
    if None in (cash_total, securities_total, total, stock_total, etf_total, open_orders):
        raise ValueError("PostFinance account overview lacks required totals")
    assert (
        cash_total is not None
        and securities_total is not None
        and total is not None
        and stock_total is not None
        and etf_total is not None
        and open_orders is not None
    )
    if abs(sum((p.market_value_chf for p in positions), Decimal("0")) - securities_total) > Decimal(
        "0.05"
    ):
        raise ValueError("PostFinance position sum does not reconcile")
    if abs(securities_total + cash_total - total) > Decimal("0.05"):
        raise ValueError("PostFinance account total does not reconcile")
    if abs(stock_total + etf_total - securities_total) > Decimal("0.05"):
        raise ValueError("PostFinance asset-class totals do not reconcile")
    cash = _reconcile_cash_components(cash, cash_total)
    return PFSnapshot(
        sha256(raw).hexdigest(),
        page_count,
        valuation_at,
        tuple(positions),
        tuple(cash),
        securities_total,
        cash_total,
        total,
        stock_total,


Output:
Chunk ID: af615d
Wall time: 30.0010 seconds
Process running with session ID 76287
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


Output:
Chunk ID: 6c09f4
Wall time: 5.0010 seconds
Process running with session ID 76287
Original token count: 0
Output:


sed -n '1,120p' src/jarvis_finance/services/equity_service.py && sed -n '120,230p' src/jarvis_finance/services/equity_service.py


Output:
Chunk ID: 409215
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2919
Output:
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
            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"))
        status = "Bewertet" if market_value is not None else ("Kurs veraltet" if snapshot_value else _position_status(row))
        provenance = value.get("price_input_provenance") if value else None
        provenance_provider = provenance.get("provider") if isinstance(provenance, dict) else None
        positions.append(EquityPosition(
            position_id=f"{account_id}:{instrument_id}",
            instrument_id=instrument_id,
            name=str(row.get("name") or ""),
            ticker=str(row.get("ticker") or ""),
            isin=str(row.get("isin") or ""),
            account=_account_label(row),
            asset_class=_asset_class_label(row.get("asset_class")),
            quantity=optional_decimal_text(row.get("quantity")) or "0",
            currency=str(value.get("currency") if value else mapping.get("currency") or row.get("price_currency") or row.get("currency") or ""),
            price=(
                optional_decimal_text(value.get("close"))
                if value
                else optional_decimal_text(row.get("market_price_original") or row.get("price"))
                if row_fallback_allowed
                else None
            ),
            market_value_chf=optional_decimal_text(market_value, 2) if market_value is not None else None,
            portfolio_share_pct=optional_decimal_text(share, 2) if share is not None else None,
            price_date=(
                str(analysis.get("as_of"))
                if value and analysis
                else str(row.get("valuation_as_of") or row.get("price_date") or "") or None
                if row_fallback_allowed
                else str(analysis.get("as_of")) if analysis else None
            ),
            provider=str((value.get("provider") if value else None) or provenance_provider or row.get("price_provider") or mapping.get("provider") or ""),
            provider_symbol=str(mapping.get("provider_symbol") or ""),
            exchange=str(mapping.get("provider_market") or ""),
            price_currency=str(value.get("currency") if value else mapping.get("currency") or row.get("price_currency") or row.get("currency") or ""),
            data_status="fresh" if market_value is not None else "missing",
            status_code=code,
            status_reason=None if market_value is not None else _status_reason(code),
            status=status,
        ))
    return sorted(positions, key=lambda item: Decimal(item.market_value_chf or "-1"), reverse=True)

def get_equity_summary(conn: Connection) -> EquityPortfolioSummary:
    positions = list_equity_positions(conn)
    valued = [item for item in positions if item.market_value_chf is not None]
    partial = sum((Decimal(item.market_value_chf or "0") for item in valued), Decimal("0"))
    complete = bool(positions) and len(valued) == len(positions)
    analysis, _, _ = _latest_analysis(conn)
    status = "complete" if complete else ("partial" if positions else "unavailable")
    effective_as_of = max((item.price_date or "" for item in valued), default="") or None
    analysis_as_of = str(analysis.get("as_of") or "") if analysis else ""
    response_as_of = effective_as_of or analysis_as_of or None
    last_successful = (
        str(analysis.get("completed_at"))
        if analysis and (not effective_as_of or analysis_as_of >= str(effective_as_of)[:10])
        else effective_as_of
    )
    return EquityPortfolioSummary(
        total_positions=len(positions),
        valued_positions=len(valued),
        unvalued_positions=len(positions) - len(valued),
        coverage_complete=complete,
        equity_value_chf=optional_decimal_text(partial, 2) if complete else None,
        valued_partial_chf=optional_decimal_text(partial, 2) or "0.00",
        value_label="Aktien-/ETF-Gesamtwert" if complete else "Bewerteter Teilwert",
        as_of=response_as_of,
        last_successful_run=last_successful,
        status=status,
        status_label=f"{len(valued)}/{len(positions)} bewertet" if positions else "Keine Aktien-/ETF-Positionen",
        unvalued_tickers=[item.ticker for item in positions if item.market_value_chf is None],
    )


nl -ba src/jarvis_finance/services/equity_service.py | sed -n '118,135p'


Output:
Chunk ID: bda321
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 252
Output:
   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 "")


git show HEAD:src/jarvis_finance/services/equity_service.py | sed -n '100,180p'


Output:
Chunk ID: 70008a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1362
Output:
    ).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)
    use_cache_fallback = analysis is None
    expected_keys = {(str(row.get("account_id") or ""), str(row.get("instrument_id") or "")) for row in source_rows}
    complete = bool(analysis and analysis.get("quality_status") == "complete" and set(values) == expected_keys)
    total = sum((Decimal(str(item.get("value_chf") or "0")) for item in 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 snapshot_value and snapshot_value.get("quality_status") == "fresh" else None
        mapping = _mapping_details(conn, instrument_id)
        raw_market_value = value.get("value_chf") if value else (row.get("market_value_chf") if use_cache_fallback else None)
        if use_cache_fallback and 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"))
        status = "Bewertet" if value else ("Kurs veraltet" if snapshot_value else (_position_status(row) if use_cache_fallback else "Kurs fehlt"))
        provenance = value.get("price_input_provenance") if value else None
        provenance_provider = provenance.get("provider") if isinstance(provenance, dict) else None
        positions.append(EquityPosition(
            position_id=f"{account_id}:{instrument_id}",
            instrument_id=instrument_id,
            name=str(row.get("name") or ""),
            ticker=str(row.get("ticker") or ""),
            isin=str(row.get("isin") or ""),
            account=_account_label(row),
            asset_class=_asset_class_label(row.get("asset_class")),
            quantity=optional_decimal_text(row.get("quantity")) or "0",
            currency=str(value.get("currency") if value else mapping.get("currency") or row.get("price_currency") or row.get("currency") or ""),
            price=optional_decimal_text(value.get("close")) if value else (optional_decimal_text(row.get("market_price_original") or row.get("price")) if use_cache_fallback else None),
            market_value_chf=optional_decimal_text(market_value, 2) if market_value is not None else None,
            portfolio_share_pct=optional_decimal_text(share, 2) if share is not None else None,
            price_date=str(value.get("as_of")) if value else (str(analysis.get("as_of")) if analysis else None),
            provider=str((value.get("provider") if value else None) or provenance_provider or mapping.get("provider") or ""),
            provider_symbol=str(mapping.get("provider_symbol") or ""),
            exchange=str(mapping.get("provider_market") or ""),
            price_currency=str(value.get("currency") if value else mapping.get("currency") or row.get("price_currency") or row.get("currency") or ""),
            data_status="fresh" if market_value is not None else "missing",
            status_code=code,
            status_reason=None if market_value is not None else _status_reason(code),
            status=status,
        ))
    return sorted(positions, key=lambda item: Decimal(item.market_value_chf or "-1"), reverse=True)

def get_equity_summary(conn: Connection) -> EquityPortfolioSummary:
    positions = list_equity_positions(conn)
    valued = [item for item in positions if item.market_value_chf is not None]
    partial = sum((Decimal(item.market_value_chf or "0") for item in valued), Decimal("0"))
    complete = bool(positions) and len(valued) == len(positions)
    analysis, _, _ = _latest_analysis(conn)
    status = "complete" if complete else ("partial" if positions else "unavailable")
    return EquityPortfolioSummary(
        total_positions=len(positions),
        valued_positions=len(valued),
        unvalued_positions=len(positions) - len(valued),
        coverage_complete=complete,
        equity_value_chf=optional_decimal_text(partial, 2) if complete else None,
        valued_partial_chf=optional_decimal_text(partial, 2) or "0.00",
        value_label="Aktien-/ETF-Gesamtwert" if complete else "Bewerteter Teilwert",
        as_of=str(analysis.get("as_of")) if analysis else None,
        last_successful_run=str(analysis.get("completed_at")) if analysis else None,
        status=status,
        status_label=f"{len(valued)}/{len(positions)} bewertet" if positions else "Keine Aktien-/ETF-Positionen",
        unvalued_tickers=[item.ticker for item in positions if item.market_value_chf is None],
    )

def get_equity_position(conn: Connection, position_id: str) -> EquityPosition:


grep -R "CREATE TABLE IF NOT EXISTS cash_balances\|cash_balances" -n src/jarvis_finance/storage src/jarvis_finance | head -80