Output:
Chunk ID: 26a6fd
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3941
Output:

def list_cash_positions(conn: Connection) -> list[CashPosition]:
    rows = conn.execute(
        """
        SELECT a.account_id, a.account_name, a.account_type, a.currency, COALESCE(a.balance_mode,'manual') AS balance_mode, p.name AS platform
        FROM accounts a JOIN platforms p ON p.platform_id=a.platform_id
        WHERE a.is_active=1
          AND (
              a.account_type='cash'
              OR a.account_id IN (
                  SELECT account_id FROM postfinance_account_roles
                  WHERE role='etrading_cash'
              )
          )
        ORDER BY p.name, a.account_name
        """
    ).fetchall()
    positions: list[CashPosition] = []
    official_components = latest_official_postfinance_cash(conn)
    official_by_account: dict[str, list] = {}
    for component in official_components:
        official_by_account.setdefault(component.account_id, []).append(component)
    for row in rows:
        account_id = row["account_id"]
        if account_id in official_by_account:
            for component in official_by_account[account_id]:
                positions.append(
                    CashPosition(
                        id=f"{account_id}:{component.currency}",
                        account_id=account_id,
                        platform=row["platform"],
                        account_label=row["account_name"],
                        account_type=row["account_type"],
                        currency=component.currency,
                        balance_mode="official_components",
                        amount=decimal_text(component.amount_original, 2),
                        amount_chf=decimal_text(component.amount_chf, 2),
                        calculated_balance_chf=decimal_text(component.amount_chf, 2),
                        manual_balance_chf=None,
                        csv_anchor_balance_chf=None,
                        used_value_chf=decimal_text(component.amount_chf, 2),
                        difference_chf="0.00",
                        last_manual_reconciliation=component.snapshot_date,
                        last_imported_booking=_last_imported_booking(conn, account_id),
                        status="Offiziell abgeglichen",
                    )
                )
            continue
        manual = _latest_snapshot(conn, account_id, "manual_balance")
        anchor = _latest_snapshot(conn, account_id, "csv_anchor_balance")
        calculated = _calculated_balance(conn, account_id)
        manual_amount = Decimal(str(manual["amount_chf"])) if manual else None
        mode = row["balance_mode"] or "manual"
        used = manual_amount if mode == "manual" and manual_amount is not None else calculated
        difference = (manual_amount - calculated) if manual_amount is not None else Decimal("0")
        status = "Aktuell"
        if manual_amount is not None and difference != 0:
            status = "Abgleich offen"
        positions.append(
            CashPosition(
                id=f"{account_id}:{row['currency']}",
                account_id=account_id,
                platform=row["platform"],
                account_label=row["account_name"],
                account_type=row["account_type"],
                currency=row["currency"],
                balance_mode=mode,
                amount=decimal_text(used, 2),
                amount_chf=decimal_text(used, 2),
                calculated_balance_chf=decimal_text(calculated, 2),
                manual_balance_chf=decimal_text(manual_amount, 2) if manual_amount is not None else None,
                csv_anchor_balance_chf=decimal_text(anchor["amount_chf"], 2) if anchor else None,
                used_value_chf=decimal_text(used, 2),
                difference_chf=decimal_text(difference, 2),
                last_manual_reconciliation=manual["balance_date"] if manual else None,
                last_imported_booking=_last_imported_booking(conn, account_id),
                status=status,
            )
        )
    return positions

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

def preview_cash_snapshot(conn: Connection, request: CashSnapshotPreviewRequest) -> PreviewResponse:
    row = conn.execute("SELECT account_id, account_type FROM accounts WHERE account_id=?", (request.account_id,)).fetchone()
grep: src/jarvis_finance/budget/__pycache__/excel_seed.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/migrations.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/postfinance_schema.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/routers/__pycache__/equity.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/routers/__pycache__/positions.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/routers/__pycache__/cash.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/positions.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/positions.cpython-312.pyc: binary file matches
src/jarvis_finance/budget/excel_seed.py:103:            position_idx = next((i for i, h in enumerate(headers) if h in POSITION_HEADERS), None)
src/jarvis_finance/budget/excel_seed.py:109:                position = _text(row[position_idx]) if position_idx is not None and position_idx < len(row) else ""
src/jarvis_finance/storage/migrations.py:2002:            snapshot_position_id TEXT PRIMARY KEY,
src/jarvis_finance/storage/postfinance_schema.py:76:            snapshot_position_id TEXT PRIMARY KEY,
src/jarvis_finance/api/routers/cash.py:10:from jarvis_finance.services.cash_service import confirm_cash_snapshot, get_cash_position_detail, get_cash_summary, preview_cash_snapshot
src/jarvis_finance/api/routers/cash.py:21:@router.get("/cash/positions/{position_id}", response_model=CashPositionDetail)
src/jarvis_finance/api/routers/cash.py:22:def cash_position(position_id: str, conn: Connection = Depends(get_db)) -> CashPositionDetail:
src/jarvis_finance/api/routers/cash.py:23:    return get_cash_position_detail(conn, position_id)
src/jarvis_finance/api/routers/positions.py:73:@router.get("/portfolio/positions/{position_id}")
src/jarvis_finance/api/routers/positions.py:74:def portfolio_position_detail(position_id: str, conn: Connection = Depends(get_db)):
src/jarvis_finance/api/routers/positions.py:75:    return get_equity_position_detail(conn, position_id)
src/jarvis_finance/api/routers/positions.py:103:@router.post("/positions/{position_id}/remove/preview", response_model=RemovePositionPreviewResponse)
src/jarvis_finance/api/routers/positions.py:104:def position_remove_preview(position_id: str, conn: Connection = Depends(get_db)) -> RemovePositionPreviewResponse:
src/jarvis_finance/api/routers/positions.py:105:    return preview_remove_position(conn, position_id)
src/jarvis_finance/api/routers/positions.py:108:@router.post("/positions/{position_id}/remove/confirm", response_model=ConfirmResponse)
src/jarvis_finance/api/routers/positions.py:109:def position_remove_confirm(position_id: str, request: GenericConfirmRequest, conn: Connection = Depends(get_db)) -> ConfirmResponse:
src/jarvis_finance/api/routers/positions.py:110:    return confirm_remove_position(conn, position_id, request)
src/jarvis_finance/api/routers/equity.py:25:@router.get("/equity/positions/{position_id}", response_model=EquityPosition)
src/jarvis_finance/api/routers/equity.py:26:def equity_position(position_id: str, conn: Connection = Depends(get_db)) -> EquityPosition:
src/jarvis_finance/api/routers/equity.py:27:    return get_equity_position(conn, position_id)
src/jarvis_finance/api/schemas/positions.py:83:    position_id: str
src/jarvis_finance/api/schemas/positions.py:227:    position_id: str
src/jarvis_finance/api/schemas/positions.py:339:    position_id: str
src/jarvis_finance/api/schemas/positions.py:355:    position_id: str
src/jarvis_finance/services/manual_entry_service.py:571:def get_cash_position_detail(conn: Connection, position_id: str) -> CashPositionDetail:
src/jarvis_finance/services/manual_entry_service.py:572:    position = next((p for p in get_cash_summary(conn).positions if p.id == position_id), None)
src/jarvis_finance/services/manual_entry_service.py:573:    account_id, currency = position_id.split(":", 1)
src/jarvis_finance/services/manual_entry_service.py:586:        payload = {"id": position_id, "platform": row["platform"], "account_label": row["account_name"], "currency": row["currency"], "amount": optional_decimal_text(row["amount_original"]) or "0", "amount_chf": optional_decimal_text(row["amount_chf"], 2), "status": row["quality_status"] or "ok"}
src/jarvis_finance/services/manual_entry_service.py:595:def get_equity_position_detail(conn: Connection, position_id: str):
src/jarvis_finance/services/manual_entry_service.py:596:    account_id, instrument_id = position_id.split(":", 1)
src/jarvis_finance/services/manual_entry_service.py:598:        base = get_equity_position(conn, position_id)
src/jarvis_finance/services/manual_entry_service.py:612:            "position_id": position_id,
src/jarvis_finance/services/manual_entry_servicegrep: src/jarvis_finance/services/__pycache__/postfinance_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_advisor.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__/equity_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/manual_entry_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/truewealth_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/equity_service.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/postfinance_service.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/cash_service.cpython-312.pyc: binary file matches
.py:846:def _position_ids(position_id: str) -> tuple[str, str]:
src/jarvis_finance/services/manual_entry_service.py:847:    if ":" not in position_id:
src/jarvis_finance/services/manual_entry_service.py:848:        raise HTTPException(status_code=422, detail="position_id must be account_id:instrument_id")
src/jarvis_finance/services/manual_entry_service.py:849:    return tuple(position_id.split(":", 1))  # type: ignore[return-value]
src/jarvis_finance/services/manual_entry_service.py:877:    account_id, instrument_id = _position_ids(request.position_id)
src/jarvis_finance/services/manual_entry_service.py:902:    account_id, instrument_id = _position_ids(request.position_id)
src/jarvis_finance/services/manual_entry_service.py:920:    audit_id = _audit(conn, action="equity_sell_confirm", entity_type="position", entity_id=request.position_id, payload={**request.model_dump(), "net_amount_original": _format_decimal(net), "gross_amount_original": _format_decimal(gross)}, note=request.note, quality_status="ok" if fx_status != "missing" else "missing_fx")
src/jarvis_finance/services/manual_entry_service.py:926:    _position_ids(request.position_id)
src/jarvis_finance/services/manual_entry_service.py:943:    account_id, instrument_id = _position_ids(request.position_id)
src/jarvis_finance/services/manual_entry_service.py:958:    audit_id = _audit(conn, action="equity_dividend_confirm", entity_type="position", entity_id=request.position_id, payload={**request.model_dump(), "net_amount_original": _format_decimal(net), "tax_original": _format_decimal(taxes)}, note=request.note, quality_status="ok" if fx_status != "missing" else "missing_fx")
src/jarvis_finance/services/manual_entry_service.py:979:def preview_remove_position(conn: Connection, position_id: str) -> RemovePositionPreviewResponse:
src/jarvis_finance/services/manual_entry_service.py:980:    account_id, instrument_id = position_id.split(":", 1)
src/jarvis_finance/services/manual_entry_service.py:994:    return RemovePositionPreviewResponse(position_id=position_id, transaction_count=len(rows), safe_to_remove=safe, reason=reason, affected_transactions=1 if safe else len(rows), consequences=["Transaktion wird als storniert markiert", "Audit-Eintrag wird geschrieben", "Dashboard liest danach den aktualisierten Bestand"] if safe else ["Kein hartes Löschen", "Bitte Korrektur-/Storno-Workflow verwenden"])
src/jarvis_finance/services/manual_entry_service.py:997:def confirm_remove_position(conn: Connection, position_id: str, request: GenericConfirmRequest) -> ConfirmResponse:
src/jarvis_finance/services/manual_entry_service.py:1000:    preview = preview_remove_position(conn, position_id)
src/jarvis_finance/services/manual_entry_service.py:1003:    account_id, instrument_id = position_id.split(":", 1)
src/jarvis_finance/services/manual_entry_service.py:1007:    audit_id = _audit(conn, action="position_remove_confirm", entity_type="position", entity_id=position_id, payload={"transaction_id": row["transaction_id"], "position_id": position_id}, note=request.note or "Position entfernt", quality_status="ok")
src/jarvis_finance/services/manual_entry_service.py:1009:    return ConfirmResponse(status="confirmed", entity_id=position_id, audit_id=audit_id, message="Position storniert")
src/jarvis_finance/services/portfolio_advisor.py:194:                signal_id=f"equity-{equity.position_id}",
src/jarvis_finance/services/portfolio_advisor.py:195:                asset_key=equity.position_id,
src/jarvis_finance/services/equity_service.py:165:            position_id=f"{account_id}:{instrument_id}",
src/jarvis_finance/services/equity_service.py:233:def get_equity_position(conn: Connection, position_id: str) -> EquityPosition:
src/jarvis_finance/services/equity_service.py:234:    position = next((p for p in list_equity_positions(conn) if p.position_id == position_id), None)
src/jarvis_finance/services/postfinance_service.py:745:                """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(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
src/jarvis_finance/services/truewealth_service.py:358:                     snapshot_position_id,snapshot_id,source_row_reference,instrument_name,isin,asset_type,quantity,
src/jarvis_finance/services/truewealth_service.py:593:            """SELECT snapshot_position_id,instrument_name,isin,asset_type,quantity,price_currency,
src/jarvis_finance/services/cash_service.py:304:def get_cash_position_detail(conn: Connection, position_id: str) -> CashPositionDetail:
src/jarvis_finance/services/cash_service.py:305:    position = next((p for p in get_cash_summary(conn).positions if p.id == position_id), None)


grep -R "get_cash_overview\|amount_original" -n src tests | head -100


Output:
Chunk ID: ca3069
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 8062
Output:
grep: src/jarvis_finance/crypto/__pycache__/transactions.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/ledger/__pycache__/positions.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/ledger/__pycache__/positions.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/ledger/__pycache__/cash.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/execution.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/transactions_importer.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/postfinance_etrading.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/cash_balances_importer.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/postfinance_documents.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/truewealth_tax_statement.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/postfinance_documents.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/crypto_transactions_importer.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/migrations.cpython-311.pyc: binary file matches
src/jarvis_finance/crypto/transactions.py:64:    gross_amount_original: Decimal | None = None,
src/jarvis_finance/crypto/transactions.py:78:            price_original, currency_original, gross_amount_original, fee_quantity,
src/jarvis_finance/crypto/transactions.py:87:            currency, _ds(gross_amount_original),
src/jarvis_finance/crypto/transactions.py:91:            _chf(gross_amount_original, fx_rate_to_chf) if gross_amount_original is not None else None,
src/jarvis_finance/crypto/transactions.py:104:    gross_amount_original: Decimal,
src/jarvis_finance/crypto/transactions.py:111:    txid = stable_id("ledger", "crypto", tx_type, account_id, source_id, gross_amount_original, fee_original)
src/jarvis_finance/crypto/transactions.py:114:    sign_net = gross_amount_original - fee_original if tx_type in {"partial_sell", "full_sell"} else gross_amount_original
src/jarvis_finance/crypto/transactions.py:118:            transaction_id, transaction_type, account_id, trade_date, gross_amount_original,
src/jarvis_finance/crypto/transactions.py:119:            fee_original, net_amount_original, currency_original, fx_rate_to_chf, fx_status,
src/jarvis_finance/crypto/transactions.py:125:            txid, tx_type, account_id, now[:10], _ds(gross_amount_original), _ds(fee_original), _ds(sign_net),
src/jarvis_finance/crypto/transactions.py:127:            _chf(gross_amount_original, fx_rate_to_chf), _chf(fee_original, fx_rate_to_chf), _chf(sign_net, fx_rate_to_chf),
src/jarvis_finance/crypto/transactions.py:174:        gross_amount_original=fee_original,
src/jarvis_finance/crypto/transactions.py:235:    gross_amount_original: Decimal,
src/jarvis_finance/crypto/transactions.py:253:        gross_amount_original=gross_amount_original,
src/jarvis_finance/crypto/transactions.py:265:        gross_amount_original=gross_amount_original,
src/jarvis_finance/crypto/transactions.py:297:    gross_amount_original: Decimal,
src/jarvis_finance/crypto/transactions.py:319:        gross_amount_original=gross_amount_original,
src/jarvis_finance/crypto/transactions.py:332:        gross_amount_original=gross_amount_original,
src/jarvis_finance/ledger/cash.py:16:    amount_original: Decimal = Decimal("0")
src/jarvis_finance/ledger/cash.py:35:    gross = _d(row["gross_amount_original"])
src/jarvis_finance/ledger/cash.py:36:    net = _d(row["net_amount_original"]) if row["net_amount_original"] is not None else None
src/jarvis_finance/ledger/cash.py:88:        balance.amount_original += original
src/jarvis_finance/ledger/cash.py:95:        if balance.amount_original < 0:
src/jarvis_finance/ledger/cash.py:96:            msg = f"Negative cash balance for account={balance.account_id} currency={balance.currency}: {balance.amount_original}"
src/jarvis_finance/ledger/cash.py:107:                    evidence={"currency": balance.currency, "amount_original": str(balance.amount_original)},
src/jarvis_finance/ledger/cash.py:132:            transaction_id, transaction_type, account_id, trade_date, gross_amount_original,
src/jarvis_finance/ledger/cash.py:133:            net_amount_original, currency_original, fx_rate_to_chf, fx_status,
src/jarvis_finance/ledger/positions.py:166:        gross = _d(row["gross_amount_original"])
src/jarvis_finance/ledger/positions.py:186:            income_original = _d(row["net_amount_original"]) if row["net_amount_original"] is not None else gross - fee - tax
src/jarvis_finance/imports/execution.py:51:          quantity, price_original, gross_amount_original, fee_original, tax_original, net_amount_original,
src/jarvis_finance/imports/postfinance_documents.py:89:    amount_original: Decimal
src/jarvis_finance/imports/postfinance_documents.py:130:    raw = [item.amount_original * item.fx_rate_to_chf for item in cash]
src/jarvis_finance/imports/crypto_transactions_importer.py:54:            elif tx=='buy': record_crypto_buy(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,to_wallet_id=wallet(conn,require_text(row,'to_wallet_name',line)),quantity=qty,gross_amount_original=dec(row.get('gross_amount_original')grep: src/jarvis_finance/storage/__pycache__/postfinance_schema.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/schema.cpython-311.pyc: binary file matches
,line,'gross_amount_original'),fee_original=feeo,fee_currency=clean(row.get('fee_currency')) or None,currency=require_text(row,'currency_original',line),fx_rate_to_chf=fx,note=note)
src/jarvis_finance/imports/crypto_transactions_importer.py:55:            elif tx=='sell': record_crypto_sell(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,from_wallet_id=wallet(conn,require_text(row,'from_wallet_name',line)),quantity=qty,gross_amount_original=dec(row.get('gross_amount_original'),line,'gross_amount_original'),fee_original=feeo,fee_currency=clean(row.get('fee_currency')) or None,currency=require_text(row,'currency_original',line),fx_rate_to_chf=fx,note=note)
src/jarvis_finance/imports/transactions_importer.py:112:        "gross_amount_original": _decimal(row.get("gross_amount_original")),
src/jarvis_finance/imports/transactions_importer.py:115:        "net_amount_original": _decimal(row.get("net_amount_original")),
src/jarvis_finance/imports/transactions_importer.py:160:            gross = item["gross_amount_original"]
src/jarvis_finance/imports/transactions_importer.py:163:            net = item["net_amount_original"]
src/jarvis_finance/imports/transactions_importer.py:173:                    settlement_date, quantity, price_original, gross_amount_original,
src/jarvis_finance/imports/transactions_importer.py:174:                    fee_original, tax_original, net_amount_original, currency_original,
src/jarvis_finance/imports/cash_balances_importer.py:9:REQUIRED_COLUMNS={"account_name","balance_date","currency","amount_original"}
src/jarvis_finance/imports/cash_balances_importer.py:27:            cur=require_text(row,'currency',line).upper(); amount=dec(row.get('amount_original'),line,'amount_original'); fx=dec(row.get('fx_rate_to_chf'),line,'fx_rate_to_chf') if clean(row.get('fx_rate_to_chf')) else (Decimal('1') if cur=='CHF' else None)
src/jarvis_finance/imports/cash_balances_importer.py:39:            conn.execute("""INSERT INTO transactions(transaction_id,transaction_type,account_id,trade_date,gross_amount_original,net_amount_original,currency_original,fx_rate_to_chf,fx_status,gross_amount_chf,net_amount_chf,source_type,is_confirmed,quality_status,notes,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?)""",(cid,'initial_cash_snapshot',aid,date,str(amount),str(amount),cur,str(fx) if fx is not None else None,'ok' if fx is not None else 'missing',str(chf) if chf is not None else None,str(chf) if chf is not None else None,'csv','ok' if fx is not None else 'incomplete',note,now))
src/jarvis_finance/imports/cash_balances_importer.py:40:            record_audit_event(conn,source='csv_import',action='initial_cash_snapshot',entity_type='transaction',entity_id=cid,new_values={'amount_original':str(amount),'currency':cur},user_text_note=note,confirmed=True,created_by='importer')
src/jarvis_finance/imports/postfinance_etrading.py:67:    amount_original: Decimal
src/jarvis_finance/imports/truewealth_tax_statement.py:41:    amount_original: Decimal
src/jarvis_finance/imports/truewealth_tax_statement.py:152:                    amount_original=amount,
src/jarvis_finance/storage/schema.py:1:INITIAL_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS schema_migrations (\n    version INTEGER PRIMARY KEY,\n    name TEXT NOT NULL,\n    applied_at TEXT NOT NULL,\n    checksum TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS platforms (\n    platform_id TEXT PRIMARY KEY,\n    name TEXT NOT NULL UNIQUE,\n    platform_type TEXT NOT NULL,\n    country TEXT,\n    default_currency TEXT NOT NULL DEFAULT 'CHF',\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS accounts (\n    account_id TEXT PRIMARY KEY,\n    platform_id TEXT NOT NULL REFERENCES platforms(platform_id),\n    account_name TEXT NOT NULL,\n    account_type TEXT NOT NULL,\n    currency TEXT NOT NULL DEFAULT 'CHF',\n    performance_included INTEGER NOT NULL DEFAULT 1,\n    is_health_reserve INTEGER NOT NULL DEFAULT 0,\n    target_cash_min_chf NUMERIC,\n    target_cash_max_chf NUMERIC,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT,\n    UNIQUE(platform_id, account_name)\n);\n\nCREATE TABLE IF NOT EXISTS instruments (\n    instrument_id TEXT PRIMARY KEY,\n    asset_class TEXT NOT NULL,\n    name TEXT NOT NULL,\n    ticker TEXT,\n    isin TEXT,\n    exchange TEXT,\n    currency TEXT NOT NULL,\n    country TEXT,\n    sector TEXT,\n    industry TEXT,\n    provider_symbol TEXT,\n    data_provider_primary TEXT,\n    position_category TEXT,\n    ter TEXT,\n    distribution_policy TEXT,\n    index_name TEXT,\n    fund_domicile TEXT,\n    benchmark TEXT,\n    data_provider_fallback TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS transactions (\n    transaction_id TEXT PRIMARY KEY,\n    transaction_type TEXT NOT NULL,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    instrument_id TEXT REFERENCES instruments(instrument_id),\n    trade_date TEXT NOT NULL,\n    settlement_date TEXT,\n    quantity TEXT,\n    price_original TEXT,\n    gross_amount_original TEXT,\n    fee_original TEXT DEFAULT '0',\n    tax_original TEXT DEFAULT '0',\n    net_amount_original TEXT,\n    currency_original TEXT NOT NULL,\n    fx_rate_to_chf TEXT,\n    fx_source TEXT,\n    fx_status TEXT NOT NULL DEFAULT 'ok',\n    gross_amount_chf TEXT,\n    fee_chf TEXT,\n    tax_chf TEXT,\n    net_amount_chf TEXT,\n    source_type TEXT NOT NULL,\n    source_id TEXT,\n    external_transaction_id TEXT,\n    row_hash TEXT,\n    is_confirmed INTEGER NOT NULL DEFAULT 1,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS positions_snapshot (\n    position_snapshot_id TEXT PRIMARY KEY,\n    snapshot_date TEXT NOT NULL,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    platform_id TEXT NOT NULL REFERENCES platforms(platform_id),\n    instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),\n    quantity NUMERIC NOT NULL,\n    average_cost_original NUMERIC,\n    cost_basis_original NUMERIC,\n    cost_basis_chf NUMERIC,\n    market_price_original NUMERIC,\n    market_value_original NUMERIC,\n    market_fx_rate_to_chf NUMERIC,\n    market_value_chf NUMERIC,\n    unrealized_price_pnl_chf NUMERIC,\n    unrealized_fx_pnl_chf NUMERIC,\n    realized_pnl_chf NUMERIC,\n    income_chf NUMERIC,\n    fees_chf NUMERIC,\n    taxes_chf NUMERIC,\n    total_return_chf NUMERIC,\n    portfolio_weight_pct NUMERIC,\n    category TEXT,\n    data_quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL,\n    UNIQUE(snapshot_date, account_id, instrument_id)\n);\n\nCREATE TABLE IF NOT EXISTS cash_balances (\n    cash_balance_id TEXT PRIMARY KEY,\n    account_id TEXT NOT NULL REFERENCES accounts(account_id),\n    balance_date TEXT NOT NULL,\n    currency TEXT NOT NULL,\n    amount_original NUMERIC NOT NULL,\n    fx_rate_to_chf NUMERIC,\n    amount_chf NUMERIC,\n    source_type TEXT NOT NULL,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    UNIQUE(account_id, balance_date, currency, source_type)\n);\n\nCREATE TABLE IF NOT EXISTS fx_rates (\n    fx_rate_id TEXT PRIMARY KEY,\n    base_currency TEXT NOT NULL,\n    quote_currency TEXT NOT NULL DEFAULT 'CHF',\n    rate_date TEXT NOT NULL,\n    rate_timestamp TEXT,\n    rate NUMERIC NOT NULL,\n    provider TEXT NOT NULL,\n    rate_type TEXT NOT NULL,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL,\n    UNIQUE(base_currency, quote_currency, rate_date, provider, rate_type)\n);\n\nCREATE TABLE IF NOT EXISTS market_prices (\n    market_price_id TEXT PRIMARY KEY,\n    instrument_id TEXT NOT NULL REFERENCES instruments(instrument_id),\n    price_date TEXT NOT NULL,\n    price_timestamp TEXT,\n    open NUMERIC,\n    high NUMERIC,\n    low NUMERIC,\n    close NUMERIC NOT NULL,\n    adjusted_close NUMERIC,\n    currency TEXT NOT NULL,\n    provider TEXT NOT NULL,\n    provider_symbol TEXT,\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL,\n    UNIQUE(instrument_id, price_date, provider)\n);\n\nCREATE TABLE IF NOT EXISTS crypto_wallets (\n    wallet_id TEXT PRIMARY KEY,\n    wallet_name TEXT NOT NULL UNIQUE,\n    wallet_type TEXT NOT NULL,\n    platform_provider TEXT,\n    network_chain TEXT,\n    wallet_address TEXT,\n    owner TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    last_verified_at TEXT,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS crypto_assets (\n    asset_id TEXT PRIMARY KEY,\n    coin_name TEXT NOT NULL,\n    symbol TEXT NOT NULL,\n    coingecko_id TEXT UNIQUE,\n    network_chain_default TEXT,\n    is_stablecoin INTEGER NOT NULL DEFAULT 0,\n    price_provider_primary TEXT NOT NULL DEFAULT 'CoinGecko',\n    price_provider_fallback TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS crypto_holdings (\n    crypto_holding_id TEXT PRIMARY KEY,\n    asset_id TEXT NOT NULL REFERENCES crypto_assets(asset_id),\n    wallet_id TEXT NOT NULL REFERENCES crypto_wallets(wallet_id),\n    quantity TEXT NOT NULL,\n    acquisition_source TEXT,\n    last_verified_at TEXT,\n    verification_status TEXT NOT NULL DEFAULT 'unverified',\n    legacy_snapshot_value_original TEXT,\n    legacy_snapshot_value_chf TEXT,\n    legacy_snapshot_currency TEXT,\n    legacy_snapshot_date TEXT,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT,\n    UNIQUE(asset_id, wallet_id)\n);\n\nCREATE TABLE IF NOT EXISTS crypto_transactions (\n    crypto_transaction_id TEXT PRIMARY KEY,\n    transaction_id TEXT REFERENCES transactions(transaction_id),\n    transaction_type TEXT NOT NULL,\n    asset_id TEXT NOT NULL REFERENCES crypto_assets(asset_id),\n    quantity TEXT NOT NULL,\n    price_original TEXT,\n    currency_original TEXT,\n    gross_amount_original TEXT,\n    fee_quantity TEXT,\n    fee_original TEXT,\n    fee_currency TEXT,\n    fx_rate_to_chf TEXT,\n    fx_source TEXT,\n    amount_chf TEXT,\n    from_wallet_id TEXT REFERENCES crypto_wallets(wallet_id),\n    to_wallet_id TEXT REFERENCES crypto_wallets(wallet_id),\n    transaction_datetime TEXT NOT NULL,\n    tx_hash TEXT,\n    source TEXT NOT NULL,\n    confirmation_status TEXT NOT NULL DEFAULT 'confirmed',\n    parse_confidence NUMERIC,\n    original_input_text TEXT,\n    notes TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS crypto_prices (\n    crypto_price_id TEXT PRIMARY KEY,\n    asset_id TEXT NOT NULL REFERENCES crypto_assets(asset_id),\n    coingecko_id TEXT,\n    price_currency TEXT NOT NULL,\n    price TEXT NOT NULL,\n    provider TEXT NOT NULL DEFAULT 'CoinGecko',\n    provider_timestamp TEXT,\n    fetched_at TEXT NOT NULL,\n    quality_status TEXT NOT NULL DEFAULT 'fresh',\n    error_message TEXT\n);\n\nCREATE TABLE IF NOT EXISTS watchlist (\n    watchlist_id TEXT PRIMARY KEY,\n    instrument_id TEXT REFERENCES instruments(instrument_id),\n    crypto_asset_id TEXT REFERENCES crypto_assets(asset_id),\n    name TEXT NOT NULL,\n    asset_class TEXT NOT NULL,\n    reason TEXT NOT NULL,\n    target_entry_price NUMERIC,\n    target_entry_currency TEXT,\n    desired_position_size_chf NUMERIC,\n    desired_weight_pct NUMERIC,\n    trigger_rules TEXT,\n    risk_notes TEXT,\n    investment_case TEXT,\n    bear_case TEXT,\n    sources TEXT,\n    status TEXT NOT NULL DEFAULT 'active',\n    next_review_date TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE TABLE IF NOT EXISTS reports (\n    report_id TEXT PRIMARY KEY,\n    report_type TEXT NOT NULL,\n    title TEXT NOT NULL,\n    period_start TEXT,\n    period_end TEXT,\n    generated_at TEXT NOT NULL,\n    file_path TEXT,\n    format TEXT NOT NULL,\n    data_quality_status TEXT NOT NULL DEFAULT 'ok',\n    summary_json TEXT,\n    created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS alerts (\n    alert_id TEXT PRIMARY KEY,\n    priority TEXT NOT NULL,\n    category TEXT NOT NULL,\n    entity_type TEXT,\n    entity_id TEXT,\n    rule_id TEXT,\n    message TEXT NOT NULL,\n    evidence_json TEXT,\n    status TEXT NOT NULL DEFAULT 'active',\n    created_at TEXT NOT NULL,\n    resolved_at TEXT,\n    muted_until TEXT,\n    last_seen_at TEXT,\n    occurrence_count INTEGER NOT NULL DEFAULT 1,\n    fingerprint TEXT,\n    dedup_key TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_alerts_dedup ON alerts(dedup_key, status);\n\nCREATE TABLE IF NOT EXISTS audit_log (\n    audit_id TEXT PRIMARY KEY,\n    timestamp TEXT NOT NULL,\n    source TEXT NOT NULL,\n    action TEXT NOT NULL,\n    entity_type TEXT NOT NULL,\n    entity_id TEXT NOT NULL,\n    old_values_json TEXT,\n    new_values_json TEXT,\n    user_text_note TEXT,\n    original_input_text TEXT,\n    confirmed INTEGER NOT NULL DEFAULT 1,\n    confirmation_timestamp TEXT,\n    auto_parsed INTEGER NOT NULL DEFAULT 0,\n    parse_confidence NUMERIC,\n    created_by TEXT NOT NULL DEFAULT 'system',\n    quality_status TEXT NOT NULL DEFAULT 'ok',\n    created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS import_sessions (\n    import_session_id TEXT PRIMARY KEY,\n    import_type TEXT NOT NULL,\n    source_filename TEXT NOT NULL,\n    source_hash TEXT,\n    started_at TEXT NOT NULL,\n    finished_at TEXT,\n    status TEXT NOT NULL,\n    rows_total INTEGER DEFAULT 0,\n    rows_imported INTEGER DEFAULT 0,\n    rows_failed INTEGER DEFAULT 0,\n    errors_json TEXT,\n    notes TEXT\n);\n\nCREATE TABLE IF NOT EXISTS decision_journal (\n    decision_id TEXT PRIMARY KEY,\n    decision_date TEXT NOT NULL,\n    decision_type TEXT NOT NULL,\n    entity_type TEXT,\n    entity_id TEXT,\n    system_recommendation TEXT,\n    human_decision TEXT NOT NULL,\n    rationale TEXT NOT NULL,\n    investment_case TEXT,\n    risks TEXT,\n    exit_rule TEXT,\n    alternatives_considered TEXT,\n    sources TEXT,\n    review_date TEXT,\n    outcome_status TEXT,\n    outcome_return_chf NUMERIC,\n    created_at TEXT NOT NULL,\n    updated_at TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_accounts_platform_id ON accounts(platform_id);\nCREATE INDEX IF NOT EXISTS idx_transactions_account_date ON transactions(account_id, trade_date);\nCREATE INDEX IF NOT EXISTS idx_transactions_instrument_date ON transactions(instrument_id, trade_date);\nCREATE INDEX IF NOT EXISTS idx_transactions_type ON transactions(transaction_type);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_external_id ON transactions(external_transaction_id) WHERE external_transaction_id IS NOT NULL AND external_transaction_id != '';\nCREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_row_hash ON transactions(row_hash) WHERE row_hash IS NOT NULL AND row_hash != '';\nCREATE INDEX IF NOT EXISTS idx_crypto_transactions_asset_datetime ON crypto_transactions(asset_id, transaction_datetime);\nCREATE INDEX IF NOT EXISTS idx_alerts_priority_status ON alerts(priority, status);\nCREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id);\n"
src/jarvis_finance/storage/migrations.py:42:    "transactions": {"quantity", "price_original", "gross_amount_original", "fee_original", "tax_original", "net_amount_original", "fx_rate_to_chf", "gross_amount_chf", "fee_chf", "tax_chf", "net_amount_chf"},
src/jarvis_finance/storage/migrations.py:44:    "crypto_transactions": {"quantity", "price_original", "gross_amount_original", "fee_quantity", "fee_original", "fx_rate_to_chf", "amount_chf"},
src/jarvis_finance/storage/migrations.py:576:            amount_original TEXT NOT NULL,
src/jarvis_finance/storage/migrations.py:605:            amount_original TEXT NOT NULL,
src/jarvis_finance/storage/migrations.py:750:            amount_original TEXT,
src/jarvis_finance/storage/migrations.py:786:                amount_original TEXT,
src/jarvis_finance/storage/migrations.py:809:                transaction_date, description, merchant, amount_original, currency_original,
src/jarvis_finance/storage/migrations.py:814:                transaction_date, description, megrep: 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/dashboard/views/__pycache__/page_13_equity_manage.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_03_equities_etfs.cpython-311.pyc: binary file matches
rchant, amount_original, currency_original,
src/jarvis_finance/storage/migrations.py:850:            amount_original TEXT,
src/jarvis_finance/storage/migrations.py:862:            amount_original TEXT NOT NULL,
src/jarvis_finance/storage/migrations.py:912:                amount_original TEXT NOT NULL,
src/jarvis_finance/storage/migrations.py:928:                description, payee, merchant_id, amount_original, currency_original, fx_rate_to_chf,
src/jarvis_finance/storage/migrations.py:933:                description, payee, merchant_id, amount_original, currency_original, fx_rate_to_chf,
src/jarvis_finance/storage/migrations.py:954:                amount_original TEXT,
src/jarvis_finance/storage/migrations.py:981:                transaction_date, description, merchant, amount_original, currency_original,
src/jarvis_finance/storage/migrations.py:989:                transaction_date, description, merchant, amount_original, currency_original,
src/jarvis_finance/storage/migrations.py:1017:            amount_original TEXT NOT NULL,
src/jarvis_finance/storage/migrations.py:1023:        INSERT OR IGNORE INTO budget_transfers__phase18_fixed(transfer_id, from_transaction_id, to_transaction_id, from_account_id, to_account_id, amount_original, currency_original, fx_rate_to_chf, notes, created_at)
src/jarvis_finance/storage/migrations.py:1024:        SELECT transfer_id, from_transaction_id, to_transaction_id, from_account_id, to_account_id, amount_original, currency_original, fx_rate_to_chf, notes, created_at FROM budget_transfers;
src/jarvis_finance/storage/migrations.py:1037:            amount_original TEXT,
src/jarvis_finance/storage/migrations.py:1042:        INSERT OR IGNORE INTO budget_import_line_items__phase18_fixed(line_item_id, transaction_candidate_id, source_file_label, receipt_key, source_row_or_range, item_name, quantity, is_promotion, amount_original, currency_original, raw_fingerprint, created_at)
src/jarvis_finance/storage/migrations.py:1043:        SELECT line_item_id, transaction_candidate_id, source_file_label, receipt_key, source_row_or_range, item_name, quantity, is_promotion, amount_original, currency_original, raw_fingerprint, created_at FROM budget_import_line_items;
src/jarvis_finance/storage/migrations.py:1051:            amount_original TEXT NOT NULL,
src/jarvis_finance/storage/migrations.py:1056:        INSERT OR IGNORE INTO budget_candidate_splits__phase18_fixed(split_id, transaction_candidate_id, category_id, amount_original, notes, created_at, updated_at)
src/jarvis_finance/storage/migrations.py:1057:        SELECT split_id, transaction_candidate_id, category_id, amount_original, notes, created_at, updated_at FROM budget_candidate_splits;
src/jarvis_finance/storage/migrations.py:1553:            amount_original TEXT NOT NULL,
src/jarvis_finance/storage/migrations.py:1572:            "signed_amount_original": "TEXT",
src/jarvis_finance/storage/migrations.py:2026:            amount_original TEXT NOT NULL,
src/jarvis_finance/storage/postfinance_schema.py:99:            amount_original TEXT NOT NULL,
src/jarvis_finance/storage/postfinance_schema.py:136:            amount_original TEXT NOT NULL,
src/jarvis_finance/dashboard/views/page_13_equity_manage.py:342:                        gross_amount_original_text=gross,
src/jarvis_finance/dashboard/views/page_03_equities_etfs.py:32:    for row in data.get_cash_overview(conn):
src/jarvis_finance/dashboard/views/page_03_equities_etfs.py:33:        if data.d(row.get("amount_original")) == 0:
src/jarvis_finance/dashboard/views/page_03_equities_etfs.py:47:            "quantity": row.get("amount_original", ""),
src/jarvis_finance/dashboard/views/page_03_equities_etfs.py:314:                result = add_equity_transaction(conn, account_id=row["account_id"], instrument_id=row["instrument_id"], transaction_type="manual_adjustment", trade_date=str(row.get("price_date") or "2026-01-01"), quantity_text=qty, gross_amount_original_text=gross or "0", currency=row.get("currency") or "CHF", note=note, confirm=confirm, fx_status="not_needed" if (row.get("currency") or "CHF") == grep: src/jarvis_finance/equity/__pycache__/manage.cpython-311.pyc: binary file matches
"CHF" else "missing")
src/jarvis_finance/dashboard/views/page_03_equities_etfs.py:321:            tx = data.rowdicts(conn.execute("SELECT trade_date, transaction_type, net_amount_original, currency_original, quality_status, notes FROM transactions WHERE account_id=? AND currency_original=? AND COALESCE(is_voided,0)=0 ORDER BY trade_date DESC, created_at DESC LIMIT 50", (row.get("account_id"), row.get("currency"))).fetchall())
src/jarvis_finance/dashboard/data.py:540:def get_cash_overview(conn: Connection) -> list[dict[str, str]]:
src/jarvis_finance/dashboard/data.py:551:            "amount_original": position.amount,
src/jarvis_finance/dashboard/data.py:709:    for row in get_cash_overview(conn):
src/jarvis_finance/dashboard/data.py:723:               i.name AS instrument, i.ticker, t.quantity, t.gross_amount_original,
src/jarvis_finance/dashboard/data.py:724:               t.net_amount_original, t.currency_original, t.fx_status, t.quality_status,
src/jarvis_finance/dashboard/data.py:1171:    cash_rows = get_cash_overview(conn)
src/jarvis_finance/equity/manage.py:255:            quantity, gross_amount_original, fee_original, tax_original, net_amount_original,
src/jarvis_finance/equity/manage.py:520:    gross_amount_original_text: str,
src/jarvis_finance/equity/manage.py:543:    gross = parse_decimal_text(gross_amount_original_text, field_name="gross amount")
src/jarvis_finance/services/manual_entry_service.py:505:        INSERT INTO transactions(transaction_id, transaction_type, account_id, trade_date, gross_amount_original, net_amount_original, currency_original, fx_rate_to_chf, fx_status, gross_amount_chf, net_amount_chf, source_type, is_confirmed, quality_status, notes, created_at)
src/jarvis_finance/services/manual_entry_service.py:512:        INSERT OR IGNORE 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)
src/jarvis_finance/services/manual_entry_service.py:561:        INSERT INTO transactions(transaction_id, transaction_type, account_id, instrument_id, trade_date, quantity, gross_amount_original, net_amount_original, currency_original, fx_rate_to_chf, fx_source, fx_status, gross_amount_chf, net_amount_chf, source_type, is_confirmed, quality_status, notes, created_at)
src/jarvis_finance/services/manual_entry_service.py:577:            SELECT p.name AS platform, a.account_name, cb.currency, cb.amount_original, cb.amount_chf, cb.quality_status, MAX(cb.created_at) AS last_change
src/jarvis_finance/services/manual_entry_service.py:586:        payload = {"id": position_id, "platform": row["platform"], "account_label": row["account_name"], "currency": row["currency"], "amount": optional_decimal_text(row["amount_original"]) or "0", "amount_chf": optional_decimal_text(row["amount_chf"], 2), "status": row["quality_status"] or "ok"}
src/jarvis_finance/services/manual_entry_service.py:858:    rows = conn.execute("SELECT quantity, gross_amount_original FROM transactions WHERE account_id=? AND instrument_id=? AND coalesce(is_voided,0)=0 AND CAST(quantity AS TEXT) > '0' AND gross_amount_original IS NOT NULL", (account_id, instrument_id)).fetchall()
src/jarvis_finance/services/manual_entry_service.py:862:        cost = Decimal(str(row["gross_amount_original"] or "0"))
grep: src/jarvis_finance/services/__pycache__/budget_analytics_v2.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_transactions.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_performance.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__/budget_analytics.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_recurring.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__/reconciliation_snapshot.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/manual_entry_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_planning.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_performance.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/truewealth_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_data_explorer.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_imports.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_aggregation.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_import_status.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_overview.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_categories.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/postfinance_service.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/transfer_pairing.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/cash_service.cpython-312.pyc: binary file matches
grep: write error: Broken pipe


sed -n '60,120p' src/jarvis_finance/api/schemas/positions.py && sed -n '1,60p' src/jarvis_finance/dashboard/views/page_03_equities_etfs.py && sed -n '690,720p' src/jarvis_finance/dashboard/data.py && sed -n '1160,1185p' src/jarvis_finance/dashboard/data.py


Output:
Chunk ID: 3082b1
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1890
Output:
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] = []

from __future__ import annotations

from decimal import Decimal

from jarvis_finance.dashboard.actions import action_button, edit_mode_enabled, read_only_hint
from jarvis_finance.dashboard.components.tables import show_table
from jarvis_finance.dashboard import data
from jarvis_finance.equity.manage import add_equity_transaction
from jarvis_finance.market_data.prices import equity_price_provider_by_name, refresh_market_prices
from jarvis_finance.services.portfolio_advisor import get_portfolio_advisor_snapshot
from jarvis_finance.dashboard.views.page_01_portfolio import _drilldown_rows, _import_rows, _lookthrough_rows, _plotly_donut

PAGE_TITLE = "Portfolio"

def _go_to_add_position(st) -> None:
    if hasattr(st, "session_state"):
        st.session_state["jarvis_requested_page"] = "Position hinzufügen"

def _equity_rows(conn) -> list[dict]:
    return [
        r for r in data.get_positions(conn)
        if str(r.get("asset_class", "")).lower() in {"stock", "equity", "etf"}
        and data.d(r.get("quantity")) != 0
        and str(r.get("instrument_status", "")).lower() != "inactive"
    ]

def _cash_rows(conn) -> list[dict]:
    rows: list[dict] = []
    for row in data.get_cash_overview(conn):
        if data.d(row.get("amount_original")) == 0:
            continue
        key = f"cash::{row.get('platform','')}::{row.get('account_name','')}::{row.get('currency','')}"
        rows.append({
            "_kind": "cash",
            "_key": key,
            "account_id": row.get("account_id", ""),
            "instrument_id": "",
            "platform": row.get("platform", ""),
            "account_name": row.get("account_name", ""),
            "asset_class": "cash",
            "name": row.get("account_name") or row.get("platform") or "Cash",
            "ticker": "",
            "isin": "",
            "quantity": row.get("amount_original", ""),
            "currency": row.get("currency", ""),
            "market_price_original": "1" if row.get("currency") == "CHF" else "",
            "price_currency": row.get("currency", ""),
            "price_date": "",
            "price_provider": "cash ledger",
            "market_value_chf": row.get("amount_chf", ""),
            "price_status": "ok" if row.get("amount_chf") else "missing_fx",
            "fx_status": "ok" if row.get("amount_chf") else "missing_fx",
            "cost_basis_chf": "",
            "quality_warnings": "" if row.get("amount_chf") else "missing_fx",
            "data_quality_status": row.get("quality_status", ""),
        })
    return rows
            "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(
    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="")


grep -n "def calculate_positions" -A80 src/jarvis_finance/ledger/positions.py


Output:
Chunk ID: fca28b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1255
Output:
135:def calculate_positions(
136-    conn: Connection,
137-    *,
138-    as_of_date: str | None = None,
139-    max_price_fx_time_delta_hours: int = 24,
140-    persist_alerts: bool = True,
141-) -> PositionCalculationResult:
142-    def add_warning(pos: Position, rule_id: str, message: str) -> None:
143-        _add_warning(conn, pos, rule_id, message, persist=persist_alerts)
144-
145-    result = PositionCalculationResult(as_of_date=as_of_date)
146-    lots: dict[tuple[str, str], WeightedAverageLot] = {}
147-    params: tuple[object, ...] = ()
148-    where = "WHERE instrument_id IS NOT NULL AND COALESCE(is_voided, 0) = 0"
149-    if as_of_date:
150-        where += " AND trade_date <= ?"
151-        params = (as_of_date,)
152-    rows = conn.execute(
153-        f"""
154-        SELECT * FROM transactions
155-        {where}
156-        ORDER BY account_id, instrument_id, trade_date, created_at, transaction_id
157-        """,
158-        params,
159-    ).fetchall()
160-    for row in rows:
161-        key = (row["account_id"], row["instrument_id"])
162-        pos = result.positions.setdefault(key, Position(row["account_id"], row["instrument_id"]))
163-        lot = lots.setdefault(key, WeightedAverageLot())
164-        t = row["transaction_type"]
165-        qty = _d(row["quantity"])
166-        gross = _d(row["gross_amount_original"])
167-        fee = _d(row["fee_original"])
168-        tax = _d(row["tax_original"])
169-        fx = _d(row["fx_rate_to_chf"], "1") if row["fx_rate_to_chf"] is not None else None
170-        if fx is None or row["fx_status"] == "missing":
171-            add_warning(pos, "missing_fx", "Historic FX is missing; precise CHF total return cannot be calculated.")
172-        if t in {"initial_position_snapshot", "buy"}:
173-            if qty > 0:
174-                cost_original = gross + fee
175-                cost_chf = (gross + fee) * fx if fx is not None else Decimal("0")
176-                lot.buy(qty, cost_original, cost_chf)
177-                pos.fees_chf += fee * fx if fx is not None else Decimal("0")
178-                pos.taxes_chf += tax * fx if fx is not None else Decimal("0")
179-        elif t in {"partial_sell", "full_sell"}:
180-            if qty > 0:
181-                proceeds_chf = (gross - fee - tax) * fx if fx is not None else Decimal("0")
182-                lot.sell(qty, proceeds_chf)
183-                pos.fees_chf += fee * fx if fx is not None else Decimal("0")
184-                pos.taxes_chf += tax * fx if fx is not None else Decimal("0")
185-        elif t in {"dividend", "etf_distribution"}:
186-            income_original = _d(row["net_amount_original"]) if row["net_amount_original"] is not None else gross - fee - tax
187-            if fx is not None:
188-                pos.income_chf += income_original * fx
189-                pos.fees_chf += fee * fx
190-                pos.taxes_chf += tax * fx
191-        if t == "initial_position_snapshot":
192-            add_warning(pos, "snapshot_only", "Initial snapshot lacks full transaction history; performance is incomplete.")
193-            if gross == 0 or fx is None:
194-                add_warning(pos, "cost_basis_uncertain", "Cost basis is uncertain for this initial snapshot.")
195-    for key, lot in lots.items():
196-        pos = result.positions[key]
197-        meta = _instrument_metadata(conn, pos.instrument_id)
198-        pos.hedge_status = str(meta.get("hedge_status") or "unknown")
199-        pos.instrument_status = str(meta.get("instrument_status") or "unknown")
200-        pos.valuation_policy = str(meta.get("valuation_policy") or "live_price")
201-        pos.corporate_action_status = str(meta.get("corporate_action_status") or "not_checked")
202-        if pos.hedge_status == "unknown":
203-            add_warning(pos, "hedge_status_unknown", "Currency hedge status is unknown; FX attribution is incomplete.")
204-        if pos.instrument_status in {"delisted", "suspended", "merged", "inactive"} and pos.valuation_policy in {"live_price", "exclude_from_auto_price_update"}:
205-            add_warning(pos, "delisted_or_suspended", "Instrument status excludes or limits automatic valuation.")
206-        if pos.corporate_action_status in {"suspected", "confirmed"} or meta.get("split_or_corporate_action_review_required"):
207-            add_warning(pos, "corporate_action_suspected", "Corporate action review is required before high-confidence valuation.")
208-        pos.quantity = lot.quantity.normalize()
209-        pos.cost_basis_original = _money(lot.cost_basis_original) or Decimal("0")
210-        pos.cost_basis_chf = _money(lot.cost_basis_chf) or Decimal("0")
211-        pos.average_cost_original = lot.average_cost_original
212-        pos.average_cost_chf = lot.average_cost_chf
213-        pos.realized_pnl_chf = _money(lot.realized_pnl_chf) or Decimal("0")
214-        pos.income_chf = _money(pos.income_chf) or Decimal("0")
215-        pos.fees_chf = _money(pos.fees_chf) or Decimal("0")


nl -ba src/jarvis_finance/dashboard/data.py | sed -n '540,556p'


Output:
Chunk ID: 78ca60
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 224
Output:
   540	def get_cash_overview(conn: Connection) -> list[dict[str, str]]:
   541	    # One shared cash read model for the API, Command Center, allocation and reports.
   542	    # The local import avoids coupling the write-oriented cash service into module init.
   543	    from jarvis_finance.services.cash_service import list_cash_positions
   544
   545	    return [
   546	        {
   547	            "account_id": position.account_id,
   548	            "platform": position.platform,
   549	            "account_name": position.account_label,
   550	            "currency": position.currency,
   551	            "amount_original": position.amount,
   552	            "amount_chf": position.used_value_chf,
   553	            "quality_status": "ok" if position.status != "Abgleich offen" else "partial",
   554	        }
   555	        for position in list_cash_positions(conn)
   556	    ]


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


Output:
Chunk ID: 7861e2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 521
Output:
   300	    conn.commit()
   301	    return ConfirmResponse(status="confirmed", entity_id=snapshot_id, audit_id=audit_id, message="Kontostand gespeichert · Audit geschrieben")
   302
   303
   304	def get_cash_position_detail(conn: Connection, position_id: str) -> CashPositionDetail:
   305	    position = next((p for p in get_cash_summary(conn).positions if p.id == position_id), None)
   306	    if not position:
   307	        raise HTTPException(status_code=404, detail="Cash position not found")
   308	    account_id = position.account_id
   309	    rows = conn.execute(
   310	        """
   311	        SELECT snapshot_id, snapshot_type, balance_date, amount_chf, source, COALESCE(note,'') AS note, audit_id
   312	        FROM cash_account_snapshots
   313	        WHERE account_id=?
   314	        ORDER BY balance_date DESC, created_at DESC
   315	        LIMIT 30
   316	        """,
   317	        (account_id,),
   318	    ).fetchall()
   319	    history: list[CashSnapshot] = []
   320	    previous: Decimal | None = None
   321	    for row in reversed(rows):
   322	        current = Decimal(str(row["amount_chf"]))
   323	        diff = None if previous is None else decimal_text(current - previous, 2)
   324	        previous = current
   325	        history.append(CashSnapshot(snapshot_id=row["snapshot_id"], snapshot_type=row["snapshot_type"], balance_date=row["balance_date"], amount_chf=decimal_text(current, 2), source=row["source"], note=row["note"], difference_to_previous_chf=diff, audit_hint="Audit vorhanden" if row["audit_id"] else None))
   326	    history = list(reversed(history))
   327	    return CashPositionDetail(**position.model_dump(), last_change=position.last_manual_reconciliation, history=history, available_actions=[ActionItem(label="Einzahlung", enabled=True), ActionItem(label="Auszahlung", enabled=True), ActionItem(label="Kontostand aktualisieren", enabled=True), ActionItem(label="CSV-Anfangsstand setzen", enabled=True), ActionItem(label="Abgleich bestätigen", enabled=True), ActionItem(label="Verlauf anzeigen", enabled=True)])


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


Output:
Chunk ID: c7e9c6
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 934
Output:
                    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] Preserve original cash amounts for non-CHF balances

When `get_cash_overview()` is called for a normal non-CHF cash account, `list_cash_positions()` sets `position.amount` to the CHF used value, not the original-currency balance. Mapping that field to `amount_original` makes downstream dashboard rows treat a CHF amount as (for example) USD/EUR quantity, so cash holdings and detail tables display incorrect foreign-currency balances whenever existing cash accounts are not CHF and are not covered by the new official PostFinance components.

The patch introduces a regression in the dashboard cash read model for non-CHF cash accounts by losing the original-currency amount. This affects displayed portfolio cash rows even though the new PostFinance-specific aggregation tests pass.