Output:
Chunk ID: 0f33b8
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3509
Output:
src/jarvis_finance/api/schemas/positions.py:148:class CashPosition(BaseModel):
src/jarvis_finance/api/schemas/positions.py:168:class CashPositionDetail(CashPosition):
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
    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 = ""
        FROM crypto_transactions ct
        LEFT JOIN crypto_assets ca ON ca.asset_id=ct.asset_id
        LEFT JOIN crypto_wallets fw ON fw.wallet_id=ct.from_wallet_id
        LEFT JOIN crypto_wallets tw ON tw.wallet_id=ct.to_wallet_id
        ORDER BY ct.transaction_datetime DESC, ct.created_at DESC LIMIT ?
        """, (limit,)
    ).fetchall())

def get_platform_overview(conn: Connection) -> list[dict[str, str]]:
    return rowdicts(conn.execute(
        """
        SELECT p.name AS platform, p.platform_type, a.account_name, a.account_type, a.currency,
               a.performance_included, a.is_active
        FROM accounts a JOIN platforms p ON p.platform_id=a.platform_id
        ORDER BY p.name, a.account_name
        """
    ).fetchall())

def get_cash_overview(conn: Connection) -> list[dict[str, str]]:
    # One shared cash read model for the API, Command Center, allocation and reports.
    # The local import avoids coupling the write-oriented cash service into module init.
    from jarvis_finance.services.cash_service import list_cash_positions

    return [
        {
            "account_id": position.account_id,
            "platform": position.platform,
            "account_name": position.account_label,
            "currency": position.currency,
            "amount_original": position.amount,
            "amount_chf": position.used_value_chf,
            "quality_status": "ok" if position.status != "Abgleich offen" else "partial",
        }
        for position in list_cash_positions(conn)
    ]

def get_positions(conn: Connection) -> list[dict[str, str]]:
    result = calculate_positions(conn, persist_alerts=False)
    official = latest_official_postfinance_positions(conn)
    post_snapshot_deltas = post_snapshot_postfinance_quantity_deltas(conn, official)
    audited = latest_audited_market_values(conn)
    roles = postfinance_account_roles(conn)
    trading_cash_id = roles.get("etrading_cash")
    official_instruments = {key[1] for key in official}
    for key in list(result.positions):
        if key[0] == trading_cash_id and key[1] in official_instruments:
            # Instrument-bearing settlement events live on trading cash, while the
            # authoritative holding belongs to the distinct depot role.
            result.positions.pop(key)
    for key, override in official.items():
        result.positions.setdefault(
            key, Position(account_id=override.account_id, instrument_id=override.instrument_id)
        )
    rows: list[dict[str, str]] = []
    for (account_id, instrument_id), pos in sorted(result.positions.items()):
        row = conn.execute(
            """
            SELECT a.account_name, p.name AS platform, i.name, i.ticker, i.isin, i.asset_class, i.currency,
                   i.hedge_status, i.instrument_status, i.valuation_policy, i.corporate_action_status,
                   mp.close AS latest_price, mp.currency AS latest_price_currency, mp.price_date AS latest_price_date,
                   mp.provider AS latest_price_provider
            FROM accounts a JOIN platforms p ON p.platform_id=a.platform_id
            JOIN instruments i ON i.instrument_id=?
            LEFT JOIN market_prices mp ON mp.market_price_id = (
                SELECT market_price_id FROM market_prices
                WHERE instrument_id=i.instrument_id AND quality_status IN ('fresh','ok')
                ORDER BY price_date DESC, created_at DESC LIMIT 1
            )
            WHERE a.account_id=?
            """,
            (instrument_id, account_id),
        ).fetchone()
        override = official.get((account_id, instrument_id))
        effective_quantity = (
            override.quantity + post_snapshot_deltas.get(instrument_id, Decimal("0"))
            if override
            else pos.quantity
        )
        if override and effective_quantity <= 0:
            continue
        market_value = audited.get((account_id, instrument_id))
        use_market_run = bool(
            market_value
            and (not override or market_value.as_of > override.snapshot_date)
        )
        if override and not use_market_run:
            quantity = effective_quantity
            market_price = override.market_price_original
            market_value_chf = (
                override.market_value_chf * effective_quantity / override.quantity
                if override.quantity
                else Decimal("0")
            )
            price_currency = override.price_currency
            price_date = override.valuation_at
            price_provider = "PostFinance official snapshot"
            valuation_source = "postfinance_official_import"
        elif use_market_run and market_value:
            quantity = effective_quantity
            market_price = market_value.close
            market_value_chf = (
                market_value.value_chf * effective_quantity / market_value.quantity
                if market_value.quantity
                else market_value.value_chf
            )
            price_currency = market_value.currency
            price_date = market_value.as_of
            price_provider = market_value.provider
            valuation_source = "audited_market_run"
        else:
            quantity = pos.quantity
            market_price = pos.market_price_original
            market_value_chf = pos.market_value_chf
            price_currency = row["latest_price_currency"] if row else ""
            price_date = date_text(row["latest_price_date"] if row else "")
            price_provider = row["latest_price_provider"] if row else ""
            valuation_source = "canonical_ledger_market"
        rows.append({
            "account_id": account_id,
            "instrument_id": instrument_id,
            "platform": row["platform"] if row else "",
            "account_name": row["account_name"] if row else account_id,
            "asset_class": row["asset_class"] if row else "",
            "name": row["name"] if row else instrument_id,
            "ticker": row["ticker"] if row else "",
            "isin": row["isin"] if row else "",
            "quantity": dec_text(quantity),
            "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 [


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


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


sed -n '220,440p' src/jarvis_finance/ledger/positions.py


Output:
Chunk ID: 160b9a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1481
Output:
        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

def save_position_snapshots(conn: Connection, result: PositionCalculationResult, *, note: str) -> list[str]:
    if not note.strip():
        raise ValueError("position snapshot requires a note")
    now = utc_now()
    snapshot_date = result.as_of_date or now[:10]
    ids: list[str] = []
    for (account_id, instrument_id), pos in result.positions.items():
        row = conn.execute("SELECT platform_id FROM accounts WHERE account_id=?", (account_id,)).fetchone()
        platform_id = row["platform_id"] if row else None
        snapshot_id = stable_id("possnap", snapshot_date, account_id, instrument_id)
        conn.execute(
            """
            INSERT OR REPLACE INTO positions_snapshot(
                position_snapshot_id, snapshot_date, account_id, platform_id, instrument_id,
                quantity, average_cost_original, cost_basis_original, cost_basis_chf,
                market_price_original, market_value_original, market_value_chf,
                unrealized_price_pnl_chf, realized_pnl_chf, income_chf, fees_chf,
                taxes_chf, total_return_chf, data_quality_status, created_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                snapshot_id, snapshot_date, account_id, platform_id, instrument_id,
                str(pos.quantity), str(pos.average_cost_original) if pos.average_cost_original is not None else None,
                str(pos.cost_basis_original), str(pos.cost_basis_chf),
                str(pos.market_price_original) if pos.market_price_original is not None else None,
                str(pos.market_value_original) if pos.market_value_original is not None else None,
                str(pos.market_value_chf) if pos.market_value_chf is not None else None,
                str(pos.unrealized_pnl_chf) if pos.unrealized_pnl_chf is not None else None,
                str(pos.realized_pnl_chf), str(pos.income_chf), str(pos.fees_chf),
                str(pos.taxes_chf), str(pos.total_return_chf) if pos.total_return_chf is not None else None,
                pos.data_quality_status, now,
            ),
        )
        record_audit_event(
            conn,
            source="ledger_positions",
            action="calculate_position_snapshot",
            entity_type="position_snapshot",
            entity_id=snapshot_id,
            new_values={"account_id": account_id, "instrument_id": instrument_id, "snapshot_date": snapshot_date},
            user_text_note=note,
            confirmed=True,
            created_by="system",
        )
        ids.append(snapshot_id)
    conn.commit()
    return ids


grep -R "PFSnapshotPosition" -n src tests | head -30 && grep -R "asset_class" -n src/jarvis_finance/imports/postfinance_documents.py | head -20


Output:
Chunk ID: 52bf85
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 256
Output:
grep: src/jarvis_finance/imports/__pycache__/postfinance_documents.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/postfinance_documents.cpython-312.pyc: binary file matches
grep: tests/__pycache__/test_postfinance_sprint13.cpython-311-pytest-9.1.1.pyc: binary file matches
grep: tests/__pycache__/test_postfinance_sprint13.cpython-311.pyc: binary file matches
grep: tests/__pycache__/test_postfinance_sprint13.cpython-311-pytest-9.0.2.pyc: binary file matches
src/jarvis_finance/imports/postfinance_documents.py:72:class PFSnapshotPosition:
src/jarvis_finance/imports/postfinance_documents.py:99:    positions: tuple[PFSnapshotPosition, ...]
src/jarvis_finance/imports/postfinance_documents.py:556:    positions: list[PFSnapshotPosition] = []
src/jarvis_finance/imports/postfinance_documents.py:601:                PFSnapshotPosition(
tests/test_postfinance_sprint13.py:22:    PFSnapshotPosition,
tests/test_postfinance_sprint13.py:183:        PFSnapshotPosition(
76:    asset_class: str


sed -n '540,615p' src/jarvis_finance/imports/postfinance_documents.py && sed -n '160,205p' tests/test_postfinance_sprint13.py


Output:
Chunk ID: df85a1
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1091
Output:
                ("interest_breakdown_source_preserved",),
            )
        )
    if len(events) != 3:
        raise ValueError("PostFinance interest statement must contain three currency components")
    return tuple(events)

def _parse_snapshot(raw: bytes) -> PFSnapshot:
    text, page_count = _pdf_text(raw)
    timestamp = re.search(r"(\d{1,2}/\d{1,2}/\d{4})\s+(\d{1,2}:\d{2}:\d{2})", text)
    if not timestamp:
        raise ValueError("PostFinance account overview lacks an exact valuation timestamp")
    date = _date(timestamp.group(1))
    assert date is not None
    valuation_at = f"{date}T{timestamp.group(2)}"
    positions: list[PFSnapshotPosition] = []
    cash: list[PFSnapshotCash] = []
    section: str | None = None
    in_positions = False
    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
        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",),
    )
    positions = tuple(
        PFSnapshotPosition(
            sha256(f"row-{index}".encode()).hexdigest()[:32],
            f"Synthetic Asset {index + 1:02d}",
            f"synthetic asset {index + 1:02d}",
            "stock" if index < 11 else "etf",
            Decimal("1"),
            Decimal("50"),
            Decimal("50"),
            Decimal("100"),
            "CHF",
            Decimal("100"),
            Decimal("4.17"),
        )
        for index in range(22)
    )
    snapshot = PFSnapshot(
        overview_hash,
        5,
        "2026-07-27T09:14:30+02:00",
        positions,
        (
            PFSnapshotCash("EUR", Decimal("100"), Decimal("1"), Decimal("100")),
            PFSnapshotCash("USD", Decimal("100"), Decimal("1"), Decimal("100")),


Output:
Chunk ID: 00dac6
Wall time: 2.1064 seconds
Process exited with code 0
Original token count: 25
Output:
........                                                                 [100%]
8 passed in 1.91s


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


Output:
Chunk ID: e8848b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2544
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)
    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:
    position = next((p for p in list_equity_positions(conn) if p.position_id == position_id), None)
    if position is None:
        raise HTTPException(status_code=404, detail="Equity position not found")
    return position


sed -n '1,120p' src/jarvis_finance/ledger/cash.py


Output:
Chunk ID: 8af236
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1062
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,


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


Output:
Chunk ID: f264aa
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 328
Output:
   185	    conn: Connection,
   186	    account_id: str,
   187	    snapshot_date: str,
   188	) -> dict[str, tuple[Decimal, Decimal | None]]:
   189	    rows = conn.execute(
   190	        """SELECT * FROM transactions
   191	           WHERE account_id=? AND trade_date>?
   192	             AND COALESCE(is_confirmed,0)=1 AND COALESCE(is_voided,0)=0
   193	           ORDER BY trade_date,created_at,transaction_id""",
   194	        (account_id, snapshot_date),
   195	    ).fetchall()
   196	    deltas: dict[str, tuple[Decimal, Decimal | None]] = {}
   197	    for row in rows:
   198	        original, amount_chf = _signed_cash_delta(row)
   199	        if original == 0 and amount_chf in {None, Decimal("0")}:
   200	            continue
   201	        currency = str(row["currency_original"])
   202	        previous_original, previous_chf = deltas.get(
   203	            currency,
   204	            (Decimal("0"), Decimal("0")),
   205	        )
   206	        deltas[currency] = (
   207	            previous_original + original,
   208	            None
   209	            if previous_chf is None or amount_chf is None
   210	            else previous_chf + amount_chf,
   211	        )
   212	    return deltas
   213
   214
   215	def list_cash_positions(conn: Connection) -> list[CashPosition]:


Output:
Chunk ID: b2e59c
Wall time: 30.0014 seconds
Process running with session ID 85616
Original token count: 7
Output:
...........................


Output:
Chunk ID: aa1368
Wall time: 30.0017 seconds
Process running with session ID 85616
Original token count: 0
Output:


Output:
Chunk ID: 92b37f
Wall time: 30.0012 seconds
Process running with session ID 85616
Original token count: 0
Output:


Output:
Chunk ID: ffd42c
Wall time: 30.0013 seconds
Process running with session ID 85616
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


grep -n "CASH\|DEPOT\|make_conn" -n tests/test_postfinance_sprint13.py | head -50


Output:
Chunk ID: e768cd
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 470
Output:
37:DEPOT = "account_pf_depot_test"
38:CASH = "account_pf_trading_cash_test"
41:def make_conn():
49:        (DEPOT,),
53:        (CASH,),
75:                DEPOT,
238:    conn = make_conn()
285:    assert roles["etrading_depot"] == DEPOT
286:    assert roles["etrading_cash"] == CASH
287:    assert roles["efinance"] not in {DEPOT, CASH}
298:    assert {row["account_id"] for row in transfer_rows} == {roles["efinance"], CASH}
310:    conn = make_conn()
336:    conn = make_conn()
381:    conn = make_conn()
416:    conn = make_conn()
421:        (DEPOT,),
431:    conn = make_conn()
483:    conn = make_conn()
522:        (CASH,),
557:    assert sum(Decimal(item.used_value_chf) for item in cash.positions if item.account_id == CASH) == Decimal("27684.07")
558:    usd_position = next(item for item in cash.positions if item.account_id == CASH and item.currency == "USD")
562:    assert sum(Decimal(row["market_value_chf"]) for row in rows if row["account_id"] == DEPOT) == Decimal("293465.16")
563:    assert not [row for row in rows if row["account_id"] == CASH and row["asset_class"] in {"stock", "equity", "etf"}]
578:        [row for row in rows if row["account_id"] == DEPOT],
583:            "account_id": DEPOT, "instrument_id": row["instrument_id"],
622:    conn = make_conn()
642:    conn.execute("UPDATE accounts SET account_type='checking' WHERE account_id=?", (CASH,))
647:            if item.account_id == CASH
652:        "SELECT platform_id FROM accounts WHERE account_id=?", (CASH,)
796:        (CASH, selected.instrument_id, sha256(b"post-snapshot-buy").hexdigest()),
806:        (CASH,),
810:        item for item in get_cash_summary(conn).positions if item.account_id == CASH
819:        if row["account_id"] == DEPOT and row["instrument_id"] == selected.instrument_id
825:        if item.account_id == DEPOT and item.instrument_id == selected.instrument_id


sed -n '41,90p' tests/test_postfinance_sprint13.py


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


grep -R "postfinance_account_roles" -n src | head -30


Output:
Chunk ID: 06ea52
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 694
Output:
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/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:153:    roles = postfinance_account_roles(conn)
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/cash_service.py:225:                  SELECT account_id FROM postfinance_account_roles


git diff --unified=30 -- src/jarvis_finance/services/postfinance_service.py | sed -n '430,590p'


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


sed -n '520,835p' src/jarvis_finance/services/postfinance_service.py


Output:
Chunk ID: 3ffd8b
Wall time: 0.0001 seconds
Process exited with code 0
Original token count: 3949
Output:
        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,
                    _text(event.quantity),
                    _text(event.price_original),
                    _text(event.gross_original),
                    _text(event.fee_original) or "0",
                    _text(event.tax_original) or "0",
                    _text(event.net_original),
                    event.currency,
                    _text(event.fx_rate_to_chf),
                    event.internal_transfer_group,
                    event.quality_status,
                    json.dumps(event.reason_codes),
                    now,
                ),
            )
            for kind, amount in (
                ("gross", event.gross_original),
                ("fee", event.fee_original),
                ("tax", event.tax_original),
                ("net", event.net_original),
            ):
                if amount is not None and event.currency:
                    conn.execute(
                        "INSERT INTO postfinance_event_components(component_id,event_id,component_type,amount_original,currency,provenance_document_hash,created_at) VALUES(?,?,?,?,?,?,?)",
                        (
                            _id("pfcomponent"),
                            event_id,
                            kind,
                            _text(amount),
                            event.currency,
                            event.document_hash,
                            now,
                        ),
                    )
            _canonical_transaction(conn, event_id, event, account_id, instrument_id, now)
        lots: dict[str, list[dict[str, Any]]] = defaultdict(list)
        for event in sorted(
            bundle.events,
            key=lambda item: (
                item.occurred_on,
                0 if item.event_type == "buy" else 1,
                item.event_fingerprint,
            ),
        ):
            instrument_id = event_instruments[event.event_fingerprint]
            if (
                event.event_type == "buy"
                and instrument_id
                and event.quantity
                and event.price_original
                and event.currency
            ):
                lots[instrument_id].append({"event": event, "remaining": event.quantity})
            elif event.event_type == "sell" and instrument_id and event.quantity:
                to_consume = event.quantity
                for lot in lots[instrument_id]:
                    if to_consume <= 0:
                        break
                    consumed = min(lot["remaining"], to_consume)
                    lot["remaining"] -= consumed
                    to_consume -= consumed
        for instrument_id, instrument_lots in lots.items():
            for lot in instrument_lots:
                event = lot["event"]
                conn.execute(
                    """INSERT INTO postfinance_cost_basis_lots(lot_id,event_id,instrument_id,acquired_on,quantity_acquired,quantity_remaining,unit_cost_original,fees_original,currency,method,provenance_document_hash,created_at) VALUES(?,?,?,?,?,?,?,?,?,'documented_trade_fifo',?,?)""",
                    (
                        _id("pflot"),
                        f"pfevent_{event.event_fingerprint[:24]}",
                        instrument_id,
                        event.occurred_on,
                        _text(event.quantity),
                        _text(lot["remaining"]),
                        _text(event.price_original),
                        _text(event.fee_original) or "0",
                        event.currency,
                        event.document_hash,
                        now,
                    ),
                )
        for position in snapshot.positions:
            instrument_id = position_map[position.row_reference]
            matched_lots = lots.get(instrument_id, [])
            quantity = sum((lot["remaining"] for lot in matched_lots), Decimal("0"))
            complete = (
                bool(matched_lots)
                and quantity == position.quantity
                and len({lot["event"].currency for lot in matched_lots}) == 1
            )
            calculated = (
                sum(
                    (
                        lot["remaining"] * (lot["event"].price_original or 0)
                        + (lot["event"].fee_original + lot["event"].tax_original)
                        * (lot["remaining"] / lot["event"].quantity)
                        for lot in matched_lots
                        if lot["event"].quantity
                    ),
                    Decimal("0"),
                )
                if complete
                else None
            )
            status = "complete" if complete else "partial" if matched_lots else "unavailable"
            conn.execute(
                """INSERT INTO postfinance_snapshot_positions(snapshot_position_id,snapshot_id,source_row_reference,instrument_id,asset_class,quantity,provider_average_cost_original,provider_cost_total_original,price_original,price_currency,value_chf,weight_pct,computed_cost_basis_original,computed_cost_basis_status,provenance_json,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
                (
                    _id("pfposition"),
                    snapshot_id,
                    position.row_reference,
                    instrument_id,
                    position.asset_class,
                    _text(position.quantity),
                    _text(position.provider_average_cost_original),
                    _text(position.provider_cost_total_original),
                    _text(position.market_price_original),
                    position.price_currency,
                    _money(position.market_value_chf),
                    _text(position.weight_pct),
                    _text(calculated),
                    status,
                    json.dumps(
                        {
                            "provider": "official_account_overview",
                            "calculated": "documented_trade_fifo",
                            "estimated": False,
                        },
                        sort_keys=True,
                    ),
                    now,
                ),
            )
            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,portfolio_weight_pct,category,data_quality_status,source_type,source_reference,created_at) SELECT ?,?,?,a.platform_id,?,?,?,?,?,?,?,?,?,?,?,?,?,? FROM accounts a WHERE a.account_id=?""",
                (
                    _id("position"),
                    snapshot.valuation_at[:10],
                    accounts["etrading_depot"],
                    instrument_id,
                    _text(position.quantity),
                    _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,
            ),
        )


[P2] Include sell cash deltas after official snapshots

When a confirmed post-snapshot PostFinance trade uses `transaction_type='sell'`, this new replay path silently drops the cash proceeds because `_signed_cash_delta()` only recognizes `partial_sell` and `full_sell`, while the quantity replay already treats `sell` as a valid sell. In that scenario the equity quantity decreases but the trading-cash total is not increased, so cash summaries and portfolio totals become understated after ordinary sells.

The patch introduces a new official-snapshot-plus-deltas cash read model, but it omits valid post-snapshot sell cash movements while applying their quantity deltas. This can make cash and portfolio totals incorrect after supported sell transactions.