Output:
Chunk ID: 6842ff
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1564
Output:
102:    cash_total_chf: Decimal
122:    cash: list[PFSnapshotCash], cash_total_chf: Decimal
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):
560:    cash_total = securities_total = total = stock_total = etf_total = None
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)


sed -n '1,230p' tests/unit/test_sprint11_portfolio_completeness.py


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

from collections.abc import Iterator
from datetime import date
from decimal import Decimal
import sqlite3
from sqlite3 import Connection

from fastapi.testclient import TestClient

from jarvis_finance.api.dependencies import get_db
from jarvis_finance.api.main import create_app
from jarvis_finance.api.schemas.market import QuoteRefreshRequest
from jarvis_finance.market_data.prices import EquityPriceQuote
from jarvis_finance.services.equity_service import get_equity_summary, list_equity_positions
from jarvis_finance.services.market_service import _quality_from_error, refresh_equity_quote, refresh_equity_quotes_batch
from jarvis_finance.services.portfolio_analytics import confirmed_canonical_positions, run_daily_market_valuation
from jarvis_finance.services.portfolio_advisor import get_portfolio_advisor_snapshot
from jarvis_finance.storage.migrations import apply_migrations

NOW = "2026-07-26T12:00:00Z"
TARGET = date(2026, 7, 24)

def _date_text(value: date | str | None) -> str:
    return value.isoformat() if isinstance(value, date) else str(value or TARGET.isoformat())

def _connect() -> Connection:
    conn = sqlite3.connect(":memory:", check_same_thread=False)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys=ON")
    apply_migrations(conn)
    conn.execute("INSERT INTO platforms(platform_id,name,platform_type,country,default_currency,created_at) VALUES('postfinance','PostFinance','broker','CH','CHF',?)", (NOW,))
    conn.execute("INSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,created_at) VALUES('etrading','postfinance','Manual Portfolio','brokerage','CHF',?)", (NOW,))
    return conn

def _client(conn: Connection) -> TestClient:
    app = create_app()
    def override() -> Iterator[Connection]:
        yield conn
    app.dependency_overrides[get_db] = override
    return TestClient(app)

def _seed_positions(conn: Connection, count: int, *, transactions: bool = False) -> None:
    for index in range(count):
        instrument_id = f"inst-{index:02d}"
        ticker = f"T{index:02d}"
        isin = f"CH{index:010d}"
        conn.execute("INSERT INTO instruments(instrument_id,asset_class,name,ticker,isin,exchange,currency,provider_symbol,data_provider_primary,created_at) VALUES(?,?,?,?,?,'SIX','CHF',?,'fmp',?)", (instrument_id, "stock", f"Position {index:02d}", ticker, isin, f"{ticker}.SW", NOW))
        conn.execute("INSERT INTO instrument_price_mappings(mapping_id,instrument_id,isin,ticker,exchange,currency,provider,provider_symbol,provider_market,trading_currency,mapping_status,confidence,created_at) VALUES(?,?,?,?,?,'CHF','fmp',?,'SIX','CHF','mapped','1',?)", (f"map-{index:02d}", instrument_id, isin, ticker, "SIX", f"{ticker}.SW", NOW))
        if transactions:
            conn.execute("INSERT INTO transactions(transaction_id,transaction_type,account_id,instrument_id,trade_date,quantity,currency_original,source_type,source_id,row_hash,is_confirmed,quality_status,created_at) VALUES(?, 'initial_position_snapshot','etrading',?,'2026-07-01','1','CHF','confirmed',?,?,1,'ok',?)", (f"tx-{index:02d}", instrument_id, f"source-{index:02d}", f"hash-{index:02d}", NOW))
    conn.commit()

class FreshProvider:
    name = "mock"
    def __init__(self) -> None:
        self.calls: list[str] = []
    def get_price(self, provider_symbol: str, *, price_date: date | None = None) -> EquityPriceQuote:
        self.calls.append(provider_symbol)
        return EquityPriceQuote(provider_symbol=provider_symbol, currency="CHF", close=Decimal("10"), provider="fmp", provider_market="SIX", price_timestamp=f"{_date_text(price_date)}T12:00:00Z", quality_status="fresh")

def test_batch_processes_all_22_and_resume_skips_fresh_cache(monkeypatch) -> None:
    conn = _connect()
    _seed_positions(conn, 22)
    provider = FreshProvider()
    monkeypatch.setattr("jarvis_finance.services.market_service.equity_price_provider_by_name", lambda _name: provider)
    result = refresh_equity_quotes_batch(conn, QuoteRefreshRequest(provider="auto", limit=100, price_date=TARGET.isoformat(), pacing_seconds=0, max_retries=0))
    assert result.total == result.updated == result.valued == result.coverage_total == 22
    assert result.complete is True
    assert len(provider.calls) == 22
    assert conn.execute("SELECT COUNT(*) FROM market_prices WHERE price_date='2026-07-24' AND close IS NOT NULL").fetchone()[0] == 22

    monkeypatch.setattr("jarvis_finance.services.market_service.equity_price_provider_by_name", lambda _name: (_ for _ in ()).throw(AssertionError("fresh prices must be skipped")))
    resumed = refresh_equity_quotes_batch(conn, QuoteRefreshRequest(provider="auto", limit=100, price_date=TARGET.isoformat(), pacing_seconds=0, max_retries=0))
    assert resumed.cached == 22
    assert resumed.updated == 0
    assert resumed.complete is True

def test_batch_does_not_treat_wrong_currency_cache_as_coverage(monkeypatch) -> None:
    conn = _connect()
    _seed_positions(conn, 1)
    conn.execute("""INSERT INTO market_prices(market_price_id,instrument_id,price_date,close,currency,provider,provider_symbol,provider_market,quality_status,created_at)
                    VALUES('wrong-cache','inst-00',?,'99','USD','fmp','T00.SW','SIX','fresh',?)""", (TARGET.isoformat(), NOW))
    conn.commit()
    provider = FreshProvider()
    monkeypatch.setattr("jarvis_finance.services.market_service.equity_price_provider_by_name", lambda _name: provider)
    result = refresh_equity_quotes_batch(conn, QuoteRefreshRequest(provider="auto", limit=100, price_date=TARGET.isoformat(), pacing_seconds=0, max_retries=0))
    assert result.cached == 0 and result.updated == 1 and provider.calls == ["T00.SW"]

    wrong_exchange_conn = _connect()
    _seed_positions(wrong_exchange_conn, 1)
    wrong_exchange_conn.execute("""INSERT INTO market_prices(market_price_id,instrument_id,price_date,close,currency,provider,provider_symbol,provider_market,quality_status,created_at)
                    VALUES('wrong-exchange-cache','inst-00',?,'99','CHF','fmp','T00.SW','NASDAQ','fresh',?)""", (TARGET.isoformat(), NOW))
    wrong_exchange_conn.commit()
    exchange_provider = FreshProvider()
    monkeypatch.setattr("jarvis_finance.services.market_service.equity_price_provider_by_name", lambda _name: exchange_provider)
    exchange_result = refresh_equity_quotes_batch(wrong_exchange_conn, QuoteRefreshRequest(provider="auto", limit=100, price_date=TARGET.isoformat(), pacing_seconds=0, max_retries=0))
    assert exchange_result.cached == 0 and exchange_result.updated == 1 and exchange_provider.calls == ["T00.SW"]

def test_rate_limit_retries_without_marking_instrument_inactive(monkeypatch) -> None:
    conn = _connect()
    _seed_positions(conn, 1)
    class RetryProvider(FreshProvider):
        def get_price(self, provider_symbol: str, *, price_date: date | None = None) -> EquityPriceQuote:
            self.calls.append(provider_symbol)
            if len(self.calls) == 1:
                return EquityPriceQuote(provider_symbol=provider_symbol, currency="CHF", close=None, provider="fmp", provider_market="SIX", price_timestamp=f"{_date_text(price_date)}T12:00:00Z", quality_status="rate_limited", error_message="fmp_rate_limited")
            return EquityPriceQuote(provider_symbol=provider_symbol, currency="CHF", close=Decimal("10"), provider="fmp", provider_market="SIX", price_timestamp=f"{_date_text(price_date)}T12:00:00Z", quality_status="fresh")
    provider = RetryProvider()
    monkeypatch.setattr("jarvis_finance.services.market_service.equity_price_provider_by_name", lambda _name: provider)
    result = refresh_equity_quotes_batch(conn, QuoteRefreshRequest(provider="auto", limit=100, price_date=TARGET.isoformat(), pacing_seconds=0, max_retries=1))
    assert result.updated == 1 and result.results[0]["attempts"] == 2
    assert conn.execute("SELECT instrument_status FROM instruments WHERE instrument_id='inst-00'").fetchone()[0] != "suspected_inactive"

def test_excluded_instrument_is_not_fetched_or_valued(monkeypatch) -> None:
    conn = _connect()
    _seed_positions(conn, 1, transactions=True)
    conn.execute("UPDATE instruments SET valuation_policy='exclude_from_auto_price_update' WHERE instrument_id='inst-00'")
    conn.commit()
    monkeypatch.setattr("jarvis_finance.services.market_service.equity_price_provider_by_name", lambda *_args: (_ for _ in ()).throw(AssertionError("excluded instrument contacted provider")))
    direct = refresh_equity_quote(conn, "inst-00", QuoteRefreshRequest(provider="auto", price_date=TARGET.isoformat()))
    batch = refresh_equity_quotes_batch(conn, QuoteRefreshRequest(provider="auto", limit=100, price_date=TARGET.isoformat(), pacing_seconds=0))
    assert direct.close is None and direct.quality_status == "missing"
    assert batch.total == 0 and confirmed_canonical_positions(conn, as_of=TARGET.isoformat()) == []

def test_provider_error_classes_future_and_currency_guards(monkeypatch) -> None:
    assert _quality_from_error("fmp_endpoint_restricted") == "plan_restricted"
    assert _quality_from_error("fmp_auth_error") == "auth_error"
    assert _quality_from_error("fmp_rate_limited") == "rate_limited"
    conn = _connect()
    _seed_positions(conn, 1)

    future = EquityPriceQuote(provider_symbol="T00.SW", currency="CHF", close=Decimal("11"), provider="yfinance", provider_market="XSWX", price_timestamp="2026-07-25T12:00:00Z", quality_status="fresh")
    monkeypatch.setattr("jarvis_finance.services.market_service.equity_price_provider_by_name", lambda _name: type("P", (), {"get_price": lambda self, *_args, **_kwargs: future})())
    rejected = refresh_equity_quote(conn, "inst-00", QuoteRefreshRequest(provider="auto", price_date=TARGET.isoformat()))
    assert rejected.quality_status == "future_price_rejected"
    assert conn.execute("SELECT COUNT(*) FROM market_prices").fetchone()[0] == 0

    mismatch = EquityPriceQuote(provider_symbol="T00.SW", currency="USD", close=Decimal("11"), provider="yfinance", provider_market="XSWX", price_timestamp="2026-07-24T12:00:00Z", quality_status="fresh")
    monkeypatch.setattr("jarvis_finance.services.market_service.equity_price_provider_by_name", lambda _name: type("P", (), {"get_price": lambda self, *_args, **_kwargs: mismatch})())
    rejected = refresh_equity_quote(conn, "inst-00", QuoteRefreshRequest(provider="auto", price_date=TARGET.isoformat()))
    assert rejected.quality_status == "currency_mismatch"
    assert conn.execute("SELECT COUNT(*) FROM market_prices").fetchone()[0] == 0

def test_unknown_values_remain_null_and_partial_summary_lists_all_missing() -> None:
    conn = _connect()
    empty = get_equity_summary(conn)
    assert empty.status == "unavailable" and empty.coverage_complete is False and empty.equity_value_chf is None
    _seed_positions(conn, 3, transactions=True)
    class PartialProvider:
        name = "fmp"
        def get_price(self, provider_symbol: str, *, price_date: date | None = None) -> EquityPriceQuote:
            close = Decimal("10") if provider_symbol == "T00.SW" else None
            return EquityPriceQuote(provider_symbol=provider_symbol, currency="CHF", close=close, provider="fmp", provider_market="SIX", price_timestamp=f"{_date_text(price_date)}T12:00:00Z", quality_status="fresh" if close else "endpoint_restricted", error_message=None if close else "fmp_endpoint_restricted")
    result = run_daily_market_valuation(conn, as_of=TARGET.isoformat(), price_providers={"fmp": PartialProvider()})  # type: ignore[dict-item]
    assert result.status == "partial"
    rows = list_equity_positions(conn)
    assert len(rows) == 3
    assert sum(row.market_value_chf is None for row in rows) == 2
    summary = get_equity_summary(conn)
    assert summary.coverage_complete is False
    assert summary.equity_value_chf is None
    assert summary.valued_partial_chf == "10.00"
    assert summary.unvalued_positions == 2
    assert summary.unvalued_tickers == ["T01", "T02"]
    assert all(row.portfolio_share_pct is None for row in rows)

def test_stale_snapshot_values_remain_unknown_and_global_signals_are_suppressed() -> None:
    conn = _connect()
    _seed_positions(conn, 1, transactions=True)

    class StaleProvider:
        name = "fmp"

        def get_price(self, provider_symbol: str, *, price_date: date | None = None) -> EquityPriceQuote:
            return EquityPriceQuote(provider_symbol=provider_symbol, currency="CHF", close=Decimal("10"), provider="fmp", provider_market="SIX", price_timestamp="2026-07-20T12:00:00Z", quality_status="fresh")

    result = run_daily_market_valuation(conn, as_of=TARGET.isoformat(), price_providers={"fmp": StaleProvider()})  # type: ignore[dict-item]
    assert result.status == "partial" and "stale_price" in result.reason_codes
    position = list_equity_positions(conn)[0]
    summary = get_equity_summary(conn)
    assert position.market_value_chf is None and position.status_code == "stale_price"
    assert summary.coverage_complete is False and summary.equity_value_chf is None
    assert get_portfolio_advisor_snapshot(conn).investment_signals == []

def test_equity_get_endpoints_do_not_invoke_provider(monkeypatch) -> None:
    conn = _connect()
    _seed_positions(conn, 1, transactions=True)
    monkeypatch.setattr("jarvis_finance.services.market_service.equity_price_provider_by_name", lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("GET render called provider")))
    client = _client(conn)
    assert client.get("/api/equity/positions").status_code == 200
    assert client.get("/api/equity/summary").status_code == 200


Output:
Chunk ID: 87c82b
Wall time: 1.7685 seconds
Process exited with code 0
Original token count: 20
Output:
.                                                                        [100%]


grep -n "def run_daily_market_valuation\|store_market_price" -n src/jarvis_finance/services/portfolio_analytics.py | head -40


Output:
Chunk ID: 9b0d83
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 26
Output:
26:    store_market_price,
382:def run_daily_market_valuation(
504:                store_market_price(


sed -n '480,525p' src/jarvis_finance/services/portfolio_analytics.py


Output:
Chunk ID: ff2c49
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 694
Output:
                reasons.add("currency_missing")
                continue
            if expected_currency and quote_currency != expected_currency:
                missing.append({"instrument_id": position.instrument_id, "reason_code": "currency_mismatch"})
                reasons.add("currency_mismatch")
                continue
            if not quote.currency:
                quote = replace(quote, currency=quote_currency)
            quote_age = _business_day_age(quote_as_of, effective)
            quality = "fresh" if 0 <= quote_age <= 2 else "stale"
            if quality == "stale":
                reasons.add("stale_price")
            existing_price = conn.execute(
                """SELECT close,currency,provider_symbol FROM market_prices
                    WHERE instrument_id=? AND price_date=? AND provider=?""",
                (position.instrument_id, quote_as_of.isoformat(), quote.provider or mapping["provider"]),
            ).fetchone()
            same_persisted_price = bool(
                existing_price
                and _decimal(existing_price["close"]) == quote.close
                and str(existing_price["currency"] or "").upper() == quote_currency
                and str(existing_price["provider_symbol"] or "") == str(mapping["provider_symbol"] or "")
            )
            if not from_cache and not same_persisted_price:
                store_market_price(
                    conn, instrument_id=position.instrument_id, price_date=quote_as_of.isoformat(),
                    close=quote.close, adjusted_close=quote.adjusted_close, currency=quote_currency,
                    provider=quote.provider or mapping["provider"], provider_symbol=quote.provider_symbol or mapping["provider_symbol"],
                    provider_market=quote.provider_market or mapping["provider_market"],
                    price_timestamp=quote.price_timestamp, quality_status=quality,
                    error_message=quote.error_message, fetched_at=now, price_type="unadjusted_close", run_id=run_id,
                )
                price_writes += 1
            quotes[(position.account_id, position.instrument_id)] = (position, quote, quote_as_of, mapping)

        currencies = sorted({quote.currency.upper() for _, quote, _, _ in quotes.values()})
        rates: dict[str, Decimal] = {}
        rate_dates: dict[str, date] = {}
        fx = fx_provider or FrankfurterFxProvider()
        fx_writes = 0
        for currency in currencies:
            provider_name = "identity" if currency == "CHF" else getattr(fx, "name", "fx_provider")
            rate, rate_as_of = _fetch_fx_rate(fx, currency, effective)
            if rate is None or rate_as_of is None:
                reasons.add("fx_rate_missing")
                continue


nl -ba src/jarvis_finance/services/cash_service.py | sed -n '177,215p' && nl -ba src/jarvis_finance/dashboard/data.py | sed -n '537,556p'


Output:
Chunk ID: 1190a7
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 815
Output:
   177	def list_cash_positions(conn: Connection) -> list[CashPosition]:
   178	    rows = conn.execute(
   179	        """
   180	        SELECT a.account_id, a.account_name, a.account_type, a.currency, COALESCE(a.balance_mode,'manual') AS balance_mode, p.name AS platform
   181	        FROM accounts a JOIN platforms p ON p.platform_id=a.platform_id
   182	        WHERE a.is_active=1 AND a.account_type='cash'
   183	        ORDER BY p.name, a.account_name
   184	        """
   185	    ).fetchall()
   186	    positions: list[CashPosition] = []
   187	    official_components = latest_official_postfinance_cash(conn)
   188	    official_by_account: dict[str, list] = {}
   189	    for component in official_components:
   190	        official_by_account.setdefault(component.account_id, []).append(component)
   191	    for row in rows:
   192	        account_id = row["account_id"]
   193	        if account_id in official_by_account:
   194	            for component in official_by_account[account_id]:
   195	                positions.append(
   196	                    CashPosition(
   197	                        id=f"{account_id}:{component.currency}",
   198	                        account_id=account_id,
   199	                        platform=row["platform"],
   200	                        account_label=row["account_name"],
   201	                        account_type=row["account_type"],
   202	                        currency=component.currency,
   203	                        balance_mode="official_components",
   204	                        amount=decimal_text(component.amount_original, 2),
   205	                        amount_chf=decimal_text(component.amount_chf, 2),
   206	                        calculated_balance_chf=decimal_text(component.amount_chf, 2),
   207	                        manual_balance_chf=None,
   208	                        csv_anchor_balance_chf=None,
   209	                        used_value_chf=decimal_text(component.amount_chf, 2),
   210	                        difference_chf="0.00",
   211	                        last_manual_reconciliation=component.snapshot_date,
   212	                        last_imported_booking=_last_imported_booking(conn, account_id),
   213	                        status="Offiziell abgeglichen",
   214	                    )
   215	                )
   537
   538
   539	def get_cash_overview(conn: Connection) -> list[dict[str, str]]:
   540	    # One shared cash read model for the API, Command Center, allocation and reports.
   541	    # The local import avoids coupling the write-oriented cash service into module init.
   542	    from jarvis_finance.services.cash_service import list_cash_positions
   543
   544	    return [
   545	        {
   546	            "account_id": position.account_id,
   547	            "platform": position.platform,
   548	            "account_name": position.account_label,
   549	            "currency": position.currency,
   550	            "amount_original": position.amount,
   551	            "amount_chf": position.used_value_chf,
   552	            "quality_status": "ok" if position.status != "Abgleich offen" else "partial",
   553	        }
   554	        for position in list_cash_positions(conn)
   555	    ]
   556


sed -n '660,720p' src/jarvis_finance/dashboard/data.py && sed -n '1135,1170p' src/jarvis_finance/dashboard/data.py


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

        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")


Output:
Chunk ID: e88adf
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 23
Output:
/home/agent/.hermes/worktrees/FinanceManager-hotfix-sprint13-global-portfolio-aggregation


[P2] Include non-cash PostFinance cash role rows

When the confirmed `etrading_cash` role points at an account typed `bank` or `checking` (which `_resolve_accounts` explicitly accepts), the official cash components are never emitted because this new branch only runs while iterating `rows` from the cash-only query above. In that setup `latest_official_postfinance_cash()` returns components, but `list_cash_positions()`/cash summary still drops them, understating cash and global portfolio totals.

The shared cash read model can omit confirmed official PostFinance cash when the accepted cash-role account is typed as bank/checking rather than cash, causing incorrect portfolio totals for that valid configuration.