Output:
Chunk ID: 2af65e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 5302
Output:
                    now,
                ),
            )
        for row in statement.cash:
            conn.execute(
                """INSERT INTO truewealth_snapshot_cash(
                     snapshot_cash_id,snapshot_id,source_row_reference,currency,amount_original,fx_rate_to_chf,
                     source_value_chf,source_evidence_json,created_at)
                   VALUES(?,?,?,?,?,?,?,?,?)""",
                (
                    _id("twcash"),
                    snapshot_id,
                    row.source_ref,
                    row.currency,
                    format(row.amount_original, "f"),
                    format(row.fx_rate_to_chf, "f") if row.fx_rate_to_chf is not None else None,
                    _decimal_text(row.value_chf),
                    json.dumps({"source": "official_tax_statement", "estimated": False}),
                    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','truewealth_official_import','ok','Official TrueWealth source total',?,?,?,1)""",
            (
                account_snapshot_id,
                account_id,
                statement.statement_date,
                _decimal_text(statement.source_total_chf),
                now,
                f"{statement.statement_date}T23:59:59Z",
                snapshot_id,
            ),
        )
        conn.commit()
    except IntegrityError as exc:
        conn.rollback()
        duplicate = conn.execute(
            "SELECT batch_id,audit_id FROM truewealth_import_batches WHERE file_sha256=?",
            (statement.file_sha256,),
        ).fetchone()
        if duplicate:
            return {"status": "confirmed", "batch_id": duplicate["batch_id"], "audit_id": duplicate["audit_id"], "idempotent": True}
        if archive_created:
            _remove_new_archive(archive_reference)
        raise ValueError("TrueWealth import conflicted with existing immutable data") from exc
    except Exception:
        conn.rollback()
        if archive_created:
            _remove_new_archive(archive_reference)
        raise
    return {"status": "confirmed", "batch_id": batch_id, "snapshot_id": snapshot_id, "audit_id": audit_id, "idempotent": False}

def _parse_valuation_at(value: str) -> datetime:
    try:
        parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
    except ValueError as exc:
        raise ValueError("Manual valuation timestamp is invalid") from exc
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=UTC)
    return parsed.astimezone(UTC)

def _manual_payload(request: dict[str, Any]) -> dict[str, Any]:
    try:
        value = Decimal(str(request.get("total_value_chf") or ""))
    except InvalidOperation as exc:
        raise ValueError("Manual TrueWealth total is invalid") from exc
    if value <= 0:
        raise ValueError("Manual TrueWealth total must be positive")
    valuation = _parse_valuation_at(str(request.get("valuation_at") or ""))
    note = str(request.get("note") or "").strip()
    if len(note) > 240:
        raise ValueError("Manual TrueWealth note is too long")
    return {
        "total_value_chf": _decimal_text(value),
        "valuation_at": valuation.isoformat().replace("+00:00", "Z"),
        "valuation_date": valuation.date().isoformat(),
        "note": note,
        "source_type": "truewealth_manual_provisional",
    }

def preview_manual_truewealth_value(conn: Connection, request: dict[str, Any]) -> dict[str, Any]:
    account_id = _validate_account(conn)
    payload = _manual_payload(request)
    preview_id, confirmation_id = _preview_identity("manual_value", payload, _revision(conn, account_id))
    return {
        **payload,
        "preview_id": preview_id,
        "confirmation_id": confirmation_id,
        "status": "manual_provisional",
        "warnings": [
            "Position details remain at the latest official import date",
            "No difference is distributed to positions or invented as cash",
        ],
    }

def confirm_manual_truewealth_value(conn: Connection, request: dict[str, Any]) -> dict[str, Any]:
    if request.get("confirm") is not True:
        raise ValueError("Explicit manual TrueWealth confirmation is required")
    preview = preview_manual_truewealth_value(conn, request)
    if request.get("preview_id") != preview["preview_id"] or request.get("confirmation_id") != preview["confirmation_id"]:
        raise ValueError("Manual TrueWealth preview is stale or changed")
    account_id = _validate_account(conn, str(request.get("account_id") or "").strip() or None)
    snapshot_id = _id("acctval")
    now = _now()
    try:
        conn.execute("BEGIN IMMEDIATE")
        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','truewealth_manual_provisional','warning',?,?,?,?,1)""",
            (
                snapshot_id,
                account_id,
                preview["valuation_date"],
                preview["total_value_chf"],
                preview["note"],
                now,
                preview["valuation_at"],
                snapshot_id,
            ),
        )
        audit_id = _insert_audit(
            conn,
            action="truewealth_manual_value_confirm",
            entity_type="account_value_snapshot",
            entity_id=snapshot_id,
            details={"valuation_at": preview["valuation_at"], "source_type": "manual_provisional"},
            note=preview["note"],
            quality="warning",
        )
        conn.commit()
    except Exception:
        conn.rollback()
        raise
    return {"status": "confirmed", "snapshot_id": snapshot_id, "audit_id": audit_id}

def set_manual_truewealth_value_active(conn: Connection, snapshot_id: str, request: dict[str, Any]) -> dict[str, Any]:
    if request.get("confirm") is not True:
        raise ValueError("Explicit manual valuation status confirmation is required")
    active = bool(request.get("active"))
    account_id = _validate_account(conn)
    row = conn.execute(
        """SELECT snapshot_id,is_active FROM account_value_snapshots
           WHERE snapshot_id=? AND account_id=? AND source_type='truewealth_manual_provisional'""",
        (snapshot_id, account_id),
    ).fetchone()
    if not row:
        raise ValueError("Manual TrueWealth valuation was not found")
    if bool(row["is_active"]) == active:
        audits = conn.execute(
            "SELECT audit_id FROM audit_log WHERE entity_type='account_value_snapshot' AND entity_id=? ORDER BY timestamp DESC LIMIT 1",
            (snapshot_id,),
        ).fetchone()
        return {"status": "confirmed", "snapshot_id": snapshot_id, "audit_id": audits["audit_id"] if audits else "", "idempotent": True}
    now = _now()
    note = str(request.get("note") or "").strip()
    try:
        conn.execute("BEGIN IMMEDIATE")
        conn.execute(
            """UPDATE account_value_snapshots SET is_active=?,deactivated_at=?,deactivation_reason=?,updated_at=?
               WHERE snapshot_id=?""",
            (1 if active else 0, None if active else now, None if active else note, now, snapshot_id),
        )
        audit_id = _insert_audit(
            conn,
            action="truewealth_manual_value_reactivate" if active else "truewealth_manual_value_deactivate",
            entity_type="account_value_snapshot",
            entity_id=snapshot_id,
            details={"active": active},
            note=note,
            quality="warning",
        )
        conn.commit()
    except Exception:
        conn.rollback()
        raise
    return {"status": "confirmed", "snapshot_id": snapshot_id, "audit_id": audit_id, "idempotent": False}

def _current_value_row(conn: Connection, account_id: str):
    return conn.execute(
        """SELECT snapshot_id,total_value_chf,valuation_date,
                  COALESCE(valuation_at,valuation_date || 'T23:59:58Z') AS valuation_at,
                  source_type,quality_status,notes,is_active
           FROM account_value_snapshots
           WHERE account_id=? AND COALESCE(is_active,1)=1
           ORDER BY valuation_date DESC,
                    CASE source_type WHEN 'truewealth_official_import' THEN 3
                         WHEN 'truewealth_manual_provisional' THEN 2 ELSE 1 END DESC,
                    COALESCE(valuation_at,created_at) DESC,created_at DESC
           LIMIT 1""",
        (account_id,),
    ).fetchone()

def get_current_truewealth_value(conn: Connection) -> Decimal:
    account_id = _validate_account(conn)
    row = _current_value_row(conn, account_id)
    return Decimal(str(row["total_value_chf"])) if row else Decimal("0")

def get_truewealth_summary(conn: Connection) -> dict[str, Any]:
    account_id = _validate_account(conn)
    current = _current_value_row(conn, account_id)
    official = conn.execute(
        """SELECT * FROM truewealth_snapshots
           WHERE account_id=? ORDER BY snapshot_date DESC,created_at DESC LIMIT 1""",
        (account_id,),
    ).fetchone()
    positions: list[dict[str, Any]] = []
    cash: list[dict[str, Any]] = []
    if official:
        positions = [dict(row) for row in conn.execute(
            """SELECT snapshot_position_id,instrument_name,isin,asset_type,quantity,price_currency,
                      source_price,source_value_chf
               FROM truewealth_snapshot_positions WHERE snapshot_id=? ORDER BY instrument_name""",
            (official["snapshot_id"],),
        ).fetchall()]
        cash = [dict(row) for row in conn.execute(
            """SELECT snapshot_cash_id,currency,amount_original,fx_rate_to_chf,source_value_chf
               FROM truewealth_snapshot_cash WHERE snapshot_id=? ORDER BY currency""",
            (official["snapshot_id"],),
        ).fetchall()]
    history = [dict(row) for row in conn.execute(
        """SELECT snapshot_id,total_value_chf,valuation_date,
                  COALESCE(valuation_at,valuation_date || 'T23:59:58Z') AS valuation_at,
                  source_type,quality_status,notes,COALESCE(is_active,1) AS is_active,deactivated_at,deactivation_reason
           FROM account_value_snapshots WHERE account_id=?
           ORDER BY valuation_date DESC,COALESCE(valuation_at,created_at) DESC,created_at DESC""",
        (account_id,),
    ).fetchall()]
    imports = [dict(row) for row in conn.execute(
        """SELECT b.batch_id,b.snapshot_date,b.parser_id,b.parser_version,b.confirmed_at,b.status,
                  s.snapshot_id,s.position_count,s.cash_count,s.source_total_chf,s.securities_total_chf,
                  s.cash_total_chf,s.components_total_chf,s.reconciliation_difference_chf,s.reconciliation_status,
                  s.completeness_status
           FROM truewealth_import_batches b JOIN truewealth_snapshots s ON s.batch_id=b.batch_id
           WHERE b.account_id=? ORDER BY b.snapshot_date DESC,b.confirmed_at DESC""",
        (account_id,),
    ).fetchall()]
    current_type = str(current["source_type"]) if current else "unavailable"
    current_is_manual = current_type == "truewealth_manual_provisional"
    return {
        "portfolio_label": TRUEWEALTH_LABEL,
        "current_value_chf": str(current["total_value_chf"]) if current else None,
        "current_valuation_at": str(current["valuation_at"]) if current else None,
        "current_source_type": current_type,
        "current_is_manual_provisional": current_is_manual,
        "current_label": f"Gesamtwert manuell aktualisiert am {current['valuation_at']}" if current_is_manual else "Gesamtwert gemäss offiziellem Import",
        "positions_snapshot_date": str(official["snapshot_date"]) if official else None,
        "positions_label": f"Positionsdetails gemäss letztem Import vom {official['snapshot_date']}" if official else "Noch keine offiziellen Positionsdetails importiert",
        "mixed_as_of": bool(current_is_manual and official and str(current["valuation_date"]) > str(official["snapshot_date"])),
        "allocation_performance_usable": bool(current and official and not current_is_manual and str(current["valuation_date"]) == str(official["snapshot_date"])),
        "positions": positions,
        "cash": cash,
        "history": history,
        "imports": imports,
        "latest_official": dict(official) if official else None,
    }

--- parser ---
from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal, InvalidOperation
from hashlib import sha256
from io import BytesIO
import re

PARSER_ID = "truewealth_tax_statement"
PARSER_VERSION = "1"
MAX_PDF_BYTES = 5 * 1024 * 1024
_DATE = r"(?P<day>\d{2})\.(?P<month>\d{2})\.(?P<year>\d{4})"
_DATE_PLAIN = r"\d{2}\.\d{2}\.\d{4}"
_ISIN = re.compile(r"^\s*\d+\s+([A-Z]{2}[A-Z0-9]{9}[0-9])\s+-\s+(.+?)\s*$")
_POSITION = re.compile(
    rf"^\s*{_DATE}\s+Bestand\s*/\s*Steuerwert\s*/\s*Ertrag\s+"
    r"([\d’'.,]+)\s+([A-Z]{3})\s+([\d’'.,]+)\s+([\d’'.,]+)(?:\s|$)"
)
_CASH_LABEL = re.compile(r"^\s*(CHF|EUR|GBP|USD)\s+\(")
_CASH_VALUE = re.compile(
    rf"^\s*{_DATE}\s+Steuerwert\s*/\s*Ertrag\s+([A-Z]{{3}})\s+(.+)$"
)

@dataclass(frozen=True)
class TrueWealthPosition:
    source_ref: str
    name: str
    isin: str
    quantity: Decimal
    currency: str
    source_price: Decimal
    source_value_chf: Decimal

@dataclass(frozen=True)
class TrueWealthCash:
    source_ref: str
    currency: str
    amount_original: Decimal
    fx_rate_to_chf: Decimal | None
    value_chf: Decimal

@dataclass(frozen=True)
class TrueWealthStatement:
    statement_date: str
    period_from: str
    period_to: str
    source_total_chf: Decimal
    positions: tuple[TrueWealthPosition, ...]
    cash: tuple[TrueWealthCash, ...]
    file_sha256: str
    page_count: int

    @property
    def securities_total_chf(self) -> Decimal:
        return sum((row.source_value_chf for row in self.positions), Decimal("0"))

    @property
    def cash_total_chf(self) -> Decimal:
        return sum((row.value_chf for row in self.cash), Decimal("0"))

    @property
    def components_total_chf(self) -> Decimal:
        return self.securities_total_chf + self.cash_total_chf

def _iso(day: str, month: str, year: str) -> str:
    return f"{year}-{month}-{day}"

def _decimal(value: str) -> Decimal:
    normalized = value.replace("’", "").replace("'", "").replace(" ", "")
    if normalized.count(",") == 1 and "." not in normalized:
        normalized = normalized.replace(",", ".")
    try:
        return Decimal(normalized)
    except InvalidOperation as exc:
        raise ValueError("TrueWealth statement contains an invalid numeric value") from exc

def _extract_pdf_text(raw: bytes) -> tuple[str, int]:
    if not raw or len(raw) > MAX_PDF_BYTES or not raw.startswith(b"%PDF-"):
        raise ValueError("TrueWealth source must be a bounded PDF")
    try:
        from pypdf import PdfReader

        reader = PdfReader(BytesIO(raw), strict=True)
        if reader.is_encrypted:
            raise ValueError("Encrypted TrueWealth PDFs are not supported")
        pages = [page.extract_text(extraction_mode="layout") or "" for page in reader.pages]
    except ValueError:
        raise
    except Exception as exc:
        raise ValueError("TrueWealth PDF could not be read") from exc
    if not pages or not all(page.strip() for page in pages[:6]):
        raise ValueError("TrueWealth PDF does not contain the expected text layer")
    return "\n\f\n".join(pages), len(pages)

def parse_truewealth_text(text: str, *, file_sha256: str = "synthetic", page_count: int = 7) -> TrueWealthStatement:
    if "True Wealth AG" not in text or "Steuerauszug in CHF" not in text:
        raise ValueError("PDF is not an evidenced TrueWealth tax statement")
    date_match = re.search(rf"Steuerauszug\s+in\s+CHF\s+{_DATE}", text)
    period_match = re.search(rf"Periode\s+{_DATE_PLAIN}\s*-\s*{_DATE_PLAIN}", text)
    if not date_match or not period_match:
        raise ValueError("TrueWealth statement date or period is missing")
    statement_date = _iso(date_match.group("day"), date_match.group("month"), date_match.group("year"))
    period_dates = re.findall(r"\d{2}\.\d{2}\.\d{4}", period_match.group(0))
    period_from = "-".join(reversed(period_dates[0].split(".")))
    period_to = "-".join(reversed(period_dates[1].split(".")))
    if statement_date != period_to:
        raise ValueError("TrueWealth statement date conflicts with the period end")

    total_match = re.search(
        rf"Total\s+Steuerwert\s+der[\s\S]{{0,500}}?Werte\s+am\s+{re.escape(period_dates[1])}[\s\S]{{0,350}}?\n\s*([\d’']+(?:[.,]\d+)?)\s+",
        text,
    )
    if not total_match:
        raise ValueError("TrueWealth source total is missing")
    source_total = _decimal(total_match.group(1))

    positions: list[TrueWealthPosition] = []
    cash: list[TrueWealthCash] = []
    current_isin: str | None = None
    current_name: list[str] = []
    current_cash_currency: str | None = None
    for raw_line in text.splitlines():
        line = raw_line.rstrip()
        cash_label = _CASH_LABEL.match(line)
        if cash_label:
            current_cash_currency = cash_label.group(1)
            continue
        cash_value = _CASH_VALUE.match(line)
        if cash_value and current_cash_currency:
            currency = cash_value.group(4)
            if currency != current_cash_currency:
                raise ValueError("TrueWealth cash currency is inconsistent")
            numbers = re.findall(r"[\d’'.,]+", cash_value.group(5))
            required = 2 if currency == "CHF" else 3
            if len(numbers) < required:
                raise ValueError("TrueWealth cash value is incomplete")
            amount = _decimal(numbers[0])
            optional_fx = None if currency == "CHF" else _decimal(numbers[1])
            value_chf = _decimal(numbers[1] if currency == "CHF" else numbers[2])
            cash.append(
                TrueWealthCash(
                    source_ref=f"cash:{currency}",
                    currency=currency,
                    amount_original=amount,
                    fx_rate_to_chf=optional_fx,
                    value_chf=value_chf,
                )
            )
            current_cash_currency = None
            continue

        start = _ISIN.match(line)
        if start:
            current_isin = start.group(1)
            current_name = [start.group(2).strip()]
            continue
        if current_isin and line.strip() and not re.match(r"^\s*\d{2}\.\d{2}\.\d{4}", line):
            if not any(token in line for token in ("True Wealth AG", "Kunde", "Kdnr.", "Periode", "Erstellt am", "Kanton", "Valoren-Nr.", "Datum", "ISIN", "Depot ", "USA", "Werte mit Anrechnung")):
                current_name.append(line.strip())
            continue
        position = _POSITION.match(line)
        if position and current_isin:
            row_date = _iso(position.group("day"), position.group("month"), position.group("year"))
            if row_date != statement_date:
                raise ValueError("TrueWealth position date conflicts with statement date")
            positions.append(
                TrueWealthPosition(
                    source_ref=f"position:{current_isin}",
                    name=" ".join(current_name),
                    isin=current_isin,
                    quantity=_decimal(position.group(4)),
                    currency=position.group(5),
                    source_price=_decimal(position.group(6)),
                    source_value_chf=_decimal(position.group(7)),
                )
            )
            current_isin = None
            current_name = []

    if len(positions) < 1 or len({row.isin for row in positions}) != len(positions):
        raise ValueError("TrueWealth positions are missing or duplicated")
    if not cash:
        raise ValueError("TrueWealth cash rows are missing")
    if any(row.currency != "CHF" for row in positions):
        raise ValueError("TrueWealth tax-statement position values must be reported in CHF")
    difference = source_total - (sum((row.source_value_chf for row in positions), Decimal("0")) + sum((row.value_chf for row in cash), Decimal("0")))
    if abs(difference) > Decimal("1.00"):
        raise ValueError("TrueWealth components do not reconcile to the source total")
    return TrueWealthStatement(
        statement_date=statement_date,
        period_from=period_from,
        period_to=period_to,
        source_total_chf=source_total,
        positions=tuple(positions),
        cash=tuple(cash),
        file_sha256=file_sha256,
        page_count=page_count,
    )

def parse_truewealth_tax_statement(raw: bytes) -> TrueWealthStatement:
    text, pages = _extract_pdf_text(raw)
    return parse_truewealth_text(text, file_sha256=sha256(raw).hexdigest(), page_count=pages)


git diff -- src/jarvis_finance/api/main.py pyproject.toml requirements-ci.txt src/jarvis_finance/services/portfolio_data.py src/jarvis_finance/services/portfolio_performance.py scripts/ci_portfolio_phase3_gate.py


Output:
Chunk ID: dd7aaa
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1553
Output:
diff --git a/pyproject.toml b/pyproject.toml
index ea33360..a3f1f33 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,7 +7,7 @@ name = "jarvis-finance"
 version = "0.1.0"
 description = "JARVIS Finance System MVP - code, docs and synthetic fixtures only"
 requires-python = ">=3.11"
-dependencies = []
+dependencies = ["pypdf==6.14.2"]

 [project.optional-dependencies]
 dev = ["pytest>=8", "ruff>=0.5"]
diff --git a/requirements-ci.txt b/requirements-ci.txt
index 6f593ae..375d951 100644
--- a/requirements-ci.txt
+++ b/requirements-ci.txt
@@ -5,6 +5,7 @@ openpyxl==3.1.5
 pandas==3.0.3
 plotly==6.7.0
 pydantic==2.13.4
+pypdf==6.14.2
 pytest==9.0.2
 ruff==0.15.13
 streamlit==1.57.0
diff --git a/scripts/ci_portfolio_phase3_gate.py b/scripts/ci_portfolio_phase3_gate.py
index 22ab947..b91f367 100644
--- a/scripts/ci_portfolio_phase3_gate.py
+++ b/scripts/ci_portfolio_phase3_gate.py
@@ -15,7 +15,7 @@ from jarvis_finance.config.settings import load_settings
 from jarvis_finance.storage.migrations import apply_migrations, get_schema_version

 SPRINT5_COMMIT = "480d7a1b72a950e864c4bb021d3f58af8f8c15f9"
-EXPECTED_SCHEMA = 43
+EXPECTED_SCHEMA = 44

 def _assert_tmp_path(path: Path) -> Path:
@@ -159,8 +159,8 @@ def main() -> None:
     sprint5.close()

     print("phase3_migration_gate=PASS")
-    print("empty_schema=43 integrity=ok ingestion_and_analysis_rows=0")
-    print(f"sprint5_schema=40_to_43 integrity=ok seed_digest={digest_before}")
+    print(f"empty_schema={EXPECTED_SCHEMA} integrity=ok ingestion_and_analysis_rows=0")
+    print(f"sprint5_schema=40_to_{EXPECTED_SCHEMA} integrity=ok seed_digest={digest_before}")
     print("settings_guard=PASS production_paths_rejected=true")

diff --git a/src/jarvis_finance/api/main.py b/src/jarvis_finance/api/main.py
index 069007f..5336423 100644
--- a/src/jarvis_finance/api/main.py
+++ b/src/jarvis_finance/api/main.py
@@ -7,7 +7,7 @@ from fastapi import FastAPI, Request
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import JSONResponse

-from jarvis_finance.api.routers import budget, cash, crypto, crypto_trader, equity, health, market, overview, positions, reports, system
+from jarvis_finance.api.routers import budget, cash, crypto, crypto_trader, equity, health, market, overview, positions, reports, system, truewealth
 from jarvis_finance.api.security import WRITE_METHODS, is_write_request_allowed, resolve_write_mode

 LOCAL_ORIGINS = [
@@ -18,7 +18,12 @@ LOCAL_ORIGINS = [
     "http://localhost:8503",
     "http://127.0.0.1:8503",
 ]
-READ_ONLY_POST_PATHS = {"/api/portfolio/ingestion/preview", "/api/portfolio/policy/preview"}
+READ_ONLY_POST_PATHS = {
+    "/api/portfolio/ingestion/preview",
+    "/api/portfolio/policy/preview",
+    "/api/truewealth/imports/preview",
+    "/api/truewealth/manual-values/preview",
+}

 def _is_safe_local_origin(origin: str) -> bool:
@@ -68,7 +73,7 @@ def create_app(*, write_mode: str | None = None) -> FastAPI:
         return await call_next(request)

     app.state.write_mode = active_write_mode
-    for router in (health.router, overview.router, crypto.router, crypto_trader.router, equity.router, cash.router, reports.router, positions.router, budget.router, system.router, market.router):
+    for router in (health.router, overview.router, crypto.router, crypto_trader.router, equity.router, cash.router, reports.router, positions.router, budget.router, system.router, market.router, truewealth.router):
         app.include_router(router, prefix="/api")
     return app

diff --git a/src/jarvis_finance/services/portfolio_data.py b/src/jarvis_finance/services/portfolio_data.py
index 230f4cb..7a5762e 100644
--- a/src/jarvis_finance/services/portfolio_data.py
+++ b/src/jarvis_finance/services/portfolio_data.py
@@ -1596,8 +1596,11 @@ def build_portfolio_reconciliation(
         cash_value = _stored_decimal(cash["amount_chf"], "Cashbestand") if cash else None
         cash_date = str(cash["balance_date"]) if cash else None
         legacy = conn.execute(
-            """SELECT total_value_chf,valuation_date,source_type FROM account_value_snapshots WHERE account_id=? AND valuation_date<=? AND created_at<=?
-               ORDER BY valuation_date DESC,created_at DESC,snapshot_id DESC LIMIT 1""", (account, as_of, cutoff)
+            """SELECT total_value_chf,valuation_date,source_type FROM account_value_snapshots
+               WHERE account_id=? AND valuation_date<=? AND created_at<=? AND COALESCE(is_active,1)=1
+               ORDER BY valuation_date DESC,
+                        CASE source_type WHEN 'truewealth_official_import' THEN 3 WHEN 'truewealth_manual_provisional' THEN 2 ELSE 1 END DESC,
+                        COALESCE(valuation_at,created_at) DESC,created_at DESC,snapshot_id DESC LIMIT 1""", (account, as_of, cutoff)
         ).fetchone()
         reported_total = _stored_decimal(legacy["total_value_chf"], "Kontototal") if legacy else None
         total_date = str(legacy["valuation_date"]) if legacy else None
diff --git a/src/jarvis_finance/services/portfolio_performance.py b/src/jarvis_finance/services/portfolio_performance.py
index 3539bd8..10a4744 100644
--- a/src/jarvis_finance/services/portfolio_performance.py
+++ b/src/jarvis_finance/services/portfolio_performance.py
@@ -228,13 +228,15 @@ def _load_valuations(
           SELECT s.*,
                  ROW_NUMBER() OVER (
                    PARTITION BY account_id, valuation_date
-                   ORDER BY created_at DESC, snapshot_id DESC
+                   ORDER BY CASE source_type WHEN 'truewealth_official_import' THEN 3 WHEN 'truewealth_manual_provisional' THEN 2 ELSE 1 END DESC,
+                            COALESCE(valuation_at,created_at) DESC, created_at DESC, snapshot_id DESC
                  ) AS rn
           FROM account_value_snapshots s
           WHERE account_id IN ({placeholders})
             AND valuation_date BETWEEN ? AND ?
             AND created_at <= ?
-            AND updated_at IS NULL
+            AND COALESCE(is_active,1)=1
+            AND source_type <> 'truewealth_manual_provisional'
         )
         SELECT * FROM ranked WHERE rn=1 ORDER BY valuation_date, account_id
         """,


grep -n "def apply_migrations\|schema_migrations\|MIGRATION_VERSION" -n src/jarvis_finance/storage/migrations.py | head -40 && sed -n '2120,2205p' src/jarvis_finance/storage/migrations.py


Output:
Chunk ID: 5b1c2f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 503
Output:
9:MIGRATION_VERSION = 44
62:    row = conn.execute("SELECT MAX(version) AS version FROM schema_migrations").fetchone()
2131:def apply_migrations(conn: Connection) -> None:
2133:    existing_initial = conn.execute("SELECT 1 FROM schema_migrations WHERE version = 1").fetchone()
2136:            "INSERT INTO schema_migrations(version, name, applied_at, checksum) VALUES (?, ?, ?, ?)",
2140:    existing = conn.execute("SELECT 1 FROM schema_migrations WHERE version = ?", (MIGRATION_VERSION,)).fetchone()
2143:            "INSERT INTO schema_migrations(version, name, applied_at, checksum) VALUES (?, ?, ?, ?)",
2144:            (MIGRATION_VERSION, MIGRATION_NAME, utc_now(), checksum_sql(MIGRATION_NAME)),
    _create_portfolio_policy_tables(conn)
    _create_portfolio_performance_tables(conn)
    _create_portfolio_ingestion_reconciliation_tables(conn)
    _create_daily_market_analytics_tables(conn)
    _create_postfinance_baseline_mapping_audit_v1(conn)
    _create_truewealth_verified_snapshot_v1(conn)
    _create_grocery_optimizer_v1_tables(conn)
    _add_grocery_price_provider_v1_columns(conn)
    _create_grocery_matching_learning_v2_tables(conn)

def apply_migrations(conn: Connection) -> None:
    conn.executescript(INITIAL_SCHEMA_SQL)
    existing_initial = conn.execute("SELECT 1 FROM schema_migrations WHERE version = 1").fetchone()
    if not existing_initial:
        conn.execute(
            "INSERT INTO schema_migrations(version, name, applied_at, checksum) VALUES (?, ?, ?, ?)",
            (1, "001_initial_schema", utc_now(), checksum_sql(INITIAL_SCHEMA_SQL)),
        )
    _apply_compat_migrations(conn)
    existing = conn.execute("SELECT 1 FROM schema_migrations WHERE version = ?", (MIGRATION_VERSION,)).fetchone()
    if not existing:
        conn.execute(
            "INSERT INTO schema_migrations(version, name, applied_at, checksum) VALUES (?, ?, ?, ?)",
            (MIGRATION_VERSION, MIGRATION_NAME, utc_now(), checksum_sql(MIGRATION_NAME)),
        )
    conn.commit()


sed -n '1,320p' tests/test_truewealth_sprint12.py


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

import base64
from decimal import Decimal
from hashlib import sha256

import pytest

from jarvis_finance.imports.truewealth_tax_statement import (
    TrueWealthCash,
    TrueWealthPosition,
    TrueWealthStatement,
    parse_truewealth_text,
)
from jarvis_finance.services import truewealth_service as service
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations

TEST_ACCOUNT_ID = "account_truewealth_test"

def make_conn():
    conn = connect_memory()
    apply_migrations(conn)
    conn.execute(
        "INSERT INTO platforms(platform_id,name,platform_type,country,default_currency,is_active,created_at) VALUES('p','True Wealth','robo','CH','CHF',1,'2026-01-01')"
    )
    conn.execute(
        """INSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,performance_included,is_active,created_at,portfolio_bucket)
           VALUES(?,'p','Manual Portfolio','managed_portfolio','CHF',1,1,'2026-01-01','truewealth')""",
        (TEST_ACCOUNT_ID,),
    )
    conn.execute(
        """INSERT INTO account_value_snapshots(snapshot_id,account_id,valuation_date,total_value_chf,currency,source_type,quality_status,notes,created_at)
           VALUES('legacy_manual',?,'2026-06-30','10000','CHF','manual_total_value','ok','historical','2026-06-30T12:00:00Z')""",
        (TEST_ACCOUNT_ID,),
    )
    conn.commit()
    return conn

def statement(raw: bytes, date: str = "2026-07-27", total: str = "12000") -> TrueWealthStatement:
    return TrueWealthStatement(
        statement_date=date,
        period_from="2026-01-01",
        period_to=date,
        source_total_chf=Decimal(total),
        positions=(
            TrueWealthPosition("position:CH0000000001", "Synthetic ETF A", "CH0000000001", Decimal("100"), "CHF", Decimal("50"), Decimal("5000")),
            TrueWealthPosition("position:US0000000002", "Synthetic ETF B", "US0000000002", Decimal("20"), "CHF", Decimal("200"), Decimal("4000")),
        ),
        cash=(TrueWealthCash("cash:CHF", "CHF", Decimal("3000"), None, Decimal("3000")),),
        file_sha256=sha256(raw).hexdigest(),
        page_count=7,
    )

def import_request(raw: bytes) -> dict[str, str]:
    return {
        "account_id": TEST_ACCOUNT_ID,
        "file_name": "official.pdf",
        "content_base64": base64.b64encode(raw).decode(),
    }

def confirm_import(conn, raw: bytes):
    request = import_request(raw)
    preview = service.preview_truewealth_import(conn, request)
    return service.confirm_truewealth_import(
        conn,
        {**request, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True},
    )

def test_text_parser_extracts_official_rows_without_inventing_values():
    text = """
True Wealth AG
Periode 01.01.2026 - 27.07.2026
Steuerauszug in CHF 27.07.2026
Total Steuerwert der A, B, DA-1 und USA-Werte am 27.07.2026
12’000 0 0 0
CHF (Synthetic Custodian AG)
27.07.2026 Steuerwert / Ertrag CHF 7’000.00 7’000.00 0.00
0000000 CH0000000001 - Synthetic ETF A, CH
27.07.2026 Bestand / Steuerwert / Ertrag 100.00 CHF 50.00 5’000.00 0.00
True Wealth AG
"""
    parsed = parse_truewealth_text(text, file_sha256="synthetic", page_count=1)
    assert parsed.statement_date == "2026-07-27"
    assert parsed.source_total_chf == Decimal("12000")
    assert parsed.positions[0].isin == "CH0000000001"
    assert parsed.cash[0].amount_original == Decimal("7000.00")
    assert parsed.components_total_chf == Decimal("12000.00")

def test_official_confirm_preserves_legacy_and_is_idempotent(monkeypatch, tmp_path):
    conn = make_conn()
    raw = b"%PDF-1.7 official-a"
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))

    first = confirm_import(conn, raw)
    second = confirm_import(conn, raw)
    summary = service.get_truewealth_summary(conn)

    assert first["idempotent"] is False
    assert second["idempotent"] is True
    assert second["batch_id"] == first["batch_id"]
    assert summary["current_value_chf"] == "12000.00"
    assert summary["current_source_type"] == "truewealth_official_import"
    assert summary["allocation_performance_usable"] is True
    assert len(summary["positions"]) == 2
    assert conn.execute("SELECT COUNT(*) FROM account_value_snapshots WHERE snapshot_id='legacy_manual'").fetchone()[0] == 1
    assert conn.execute("SELECT COUNT(*) FROM truewealth_import_batches").fetchone()[0] == 1
    archived = tmp_path / "imports" / "truewealth" / "archive" / f"{sha256(raw).hexdigest()}.pdf"
    assert archived.read_bytes() == raw
    assert archived.stat().st_mode & 0o777 == 0o600

def test_newer_manual_value_wins_without_changing_positions_and_can_be_reversed(monkeypatch, tmp_path):
    conn = make_conn()
    raw = b"%PDF-1.7 official-b"
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    confirm_import(conn, raw)

    payload = {"total_value_chf": "12500", "valuation_at": "2026-07-28T08:00:00Z", "note": "portal view"}
    preview = service.preview_manual_truewealth_value(conn, payload)
    result = service.confirm_manual_truewealth_value(
        conn,
        {**payload, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True},
    )
    summary = service.get_truewealth_summary(conn)
    assert summary["current_value_chf"] == "12500.00"
    assert summary["current_is_manual_provisional"] is True
    assert summary["positions_snapshot_date"] == "2026-07-27"
    assert summary["mixed_as_of"] is True
    assert summary["allocation_performance_usable"] is False
    assert len(summary["positions"]) == 2

    service.set_manual_truewealth_value_active(conn, result["snapshot_id"], {"active": False, "note": "revert", "confirm": True})
    restored = service.get_truewealth_summary(conn)
    assert restored["current_value_chf"] == "12000.00"
    assert restored["current_source_type"] == "truewealth_official_import"
    assert any(row["snapshot_id"] == result["snapshot_id"] and row["is_active"] == 0 for row in restored["history"])
    service.set_manual_truewealth_value_active(conn, result["snapshot_id"], {"active": True, "note": "reactivate", "confirm": True})
    assert service.get_truewealth_summary(conn)["current_value_chf"] == "12500.00"

def test_older_official_import_cannot_displace_newer_manual(monkeypatch, tmp_path):
    conn = make_conn()
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    newest_raw = b"%PDF-1.7 official-new"
    old_raw = b"%PDF-1.7 official-old"
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value, "2026-07-27" if value == newest_raw else "2026-07-20"))
    confirm_import(conn, newest_raw)
    payload = {"total_value_chf": "12500", "valuation_at": "2026-07-28T08:00:00Z", "note": "newer manual"}
    preview = service.preview_manual_truewealth_value(conn, payload)
    service.confirm_manual_truewealth_value(conn, {**payload, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True})
    confirm_import(conn, old_raw)
    summary = service.get_truewealth_summary(conn)
    assert summary["current_value_chf"] == "12500.00"
    assert summary["positions_snapshot_date"] == "2026-07-27"

def test_same_day_official_has_priority_over_manual(monkeypatch, tmp_path):
    conn = make_conn()
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    payload = {"total_value_chf": "11500", "valuation_at": "2026-07-27T22:00:00Z", "note": "same day"}
    preview = service.preview_manual_truewealth_value(conn, payload)
    service.confirm_manual_truewealth_value(conn, {**payload, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True})
    raw = b"%PDF-1.7 same-day"
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    confirm_import(conn, raw)
    assert service.get_truewealth_summary(conn)["current_source_type"] == "truewealth_official_import"

def test_preview_and_get_are_side_effect_free(monkeypatch):
    conn = make_conn()
    raw = b"%PDF-1.7 preview-only"
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    before = conn.total_changes
    service.preview_truewealth_import(conn, import_request(raw))
    service.get_truewealth_summary(conn)
    assert conn.total_changes == before

def test_immutable_source_tables_reject_update_and_delete(monkeypatch, tmp_path):
    conn = make_conn()
    raw = b"%PDF-1.7 immutable"
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    result = confirm_import(conn, raw)
    with pytest.raises(Exception, match="immutable"):
        conn.execute("UPDATE truewealth_snapshots SET source_total_chf='0' WHERE snapshot_id=?", (result["snapshot_id"],))
    with pytest.raises(Exception, match="cannot be deleted"):
        conn.execute("DELETE FROM truewealth_snapshots WHERE snapshot_id=?", (result["snapshot_id"],))

def test_confirm_rollback_removes_new_archive(monkeypatch, tmp_path):
    conn = make_conn()
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    raw = b"%PDF-synthetic-rollback"
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda _raw: statement(raw))
    request = import_request(raw)
    preview = service.preview_truewealth_import(conn, request)
    conn.execute(
        "CREATE TRIGGER force_truewealth_failure BEFORE INSERT ON truewealth_import_batches "
        "BEGIN SELECT RAISE(ABORT, 'forced'); END"
    )
    with pytest.raises(ValueError, match="conflicted"):
        service.confirm_truewealth_import(
            conn,
            {**request, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True},
        )
    assert not list((tmp_path / "imports" / "truewealth" / "archive").glob("*.pdf"))
    assert conn.execute("SELECT COUNT(*) FROM truewealth_import_batches").fetchone()[0] == 0

def test_same_date_different_official_source_is_blocked(monkeypatch, tmp_path):
    conn = make_conn()
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(tmp_path))
    monkeypatch.setattr(service, "parse_truewealth_tax_statement", lambda value: statement(value))
    confirm_import(conn, b"%PDF-1.7 source-one")
    second = import_request(b"%PDF-1.7 source-two")
    preview = service.preview_truewealth_import(conn, second)
    assert preview["conflict"] is True
    with pytest.raises(ValueError, match="different official TrueWealth source"):
        service.confirm_truewealth_import(
            conn,
            {**second, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True},
        )
    assert conn.execute("SELECT COUNT(*) FROM truewealth_import_batches").fetchone()[0] == 1

def test_manual_value_is_not_hard_deleted_or_overwritten():
    conn = make_conn()
    payload = {"total_value_chf": "12500", "valuation_at": "2026-07-28T08:00:00Z", "note": "immutable value"}
    preview = service.preview_manual_truewealth_value(conn, payload)
    result = service.confirm_manual_truewealth_value(
        conn,
        {**payload, "preview_id": preview["preview_id"], "confirmation_id": preview["confirmation_id"], "confirm": True},
    )
    with pytest.raises(Exception, match="fields are immutable"):
        conn.execute("UPDATE account_value_snapshots SET total_value_chf='1' WHERE snapshot_id=?", (result["snapshot_id"],))
    with pytest.raises(Exception, match="cannot be deleted"):
        conn.execute("DELETE FROM account_value_snapshots WHERE snapshot_id=?", (result["snapshot_id"],))


sed -n '1,140p' src/jarvis_finance/storage/database.py && sed -n '1,120p' src/jarvis_finance/api/dependencies.py


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

import sqlite3
from pathlib import Path

def connect(db_path: str | Path) -> sqlite3.Connection:
    if str(db_path) == ":memory:":
        conn = sqlite3.connect(":memory:", check_same_thread=False)
    else:
        path = Path(db_path).expanduser()
        path.parent.mkdir(parents=True, exist_ok=True)
        conn = sqlite3.connect(path, check_same_thread=False)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys = ON")
    return conn

def connect_memory() -> sqlite3.Connection:
    conn = sqlite3.connect(":memory:", check_same_thread=False)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys = ON")
    return conn
from __future__ import annotations

from collections.abc import Iterator
from sqlite3 import Connection

from fastapi import HTTPException

from jarvis_finance.config.settings import find_repo_root, load_settings
from jarvis_finance.storage.database import connect

def get_db() -> Iterator[Connection]:
    settings = load_settings(repo_root=find_repo_root())
    if not settings.db_path.exists():
        raise HTTPException(status_code=503, detail="Runtime database not available")
    conn = connect(settings.db_path)
    try:
        yield conn
    finally:
        conn.close()


grep -R "TrueWealth\|True Wealth\|truewealth" -n data src tests docs | head -80


Output:
Chunk ID: 2e0b03
Wall time: 0.0001 seconds
Process exited with code 0
Original token count: 6822
Output:
grep: data: No such file or directory
grep: src/jarvis_finance/__pycache__/__init__.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/crypto/__pycache__/manage.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/crypto/__pycache__/assets.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/crypto/__pycache__/wallets.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/crypto/__pycache__/transactions.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/crypto/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/crypto/__pycache__/holdings.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/ledger/__pycache__/corrections.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/ledger/__pycache__/cost_basis.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__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/ledger/__pycache__/cash.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/ledger/__pycache__/performance.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/budget/__pycache__/excel_seed.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/budget/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/market/__pycache__/providers.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/market/__pycache__/__init__.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__/common.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/crypto_wallets_importer.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/instruments_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__/broker_parsers.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/csv_dialect.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/truewealth_tax_statement.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/__init__.cpython-312.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__/dry_run.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__/watchlist_importer.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/crypto_transactions_importer.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/row_hash.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/crypto_holdings_importer.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/broker_mapping.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/accounts_importer.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/imports/__pycache__/instrument_candidates.cpython-311.pyc: binary file matches
src/jarvis_finance.egg-info/SOURCES.txt:26:src/jarvis_finance/api/routers/truewealth.py
src/jarvis_finance.egg-info/SOURCES.txt:41:src/jarvis_finance/api/schemas/truewealth.py
src/jarvis_finance.egg-info/SOURCES.txt:117:src/jarvis_finance/imports/truewealth_tax_statement.py
src/jarvis_finance.egg-info/SOURCES.txt:200:src/jarvis_finance/services/truewealth_service.py
src/jarvis_finance.egg-info/SOURCES.txt:207:tests/test_truewealth_sprint12.py
src/jarvis_finance/imports/instrument_candidates.py:178:    # True Wealth-style blocks: instrument name followed by explicit ISIN and currency/quantity nearby.
src/jarvis_finance/imports/broker_mapping.py:16:SOURCE_PLATFORMS = {"PostFinance", "True Wealth", "Raiffeisen", "Manuell / Sonstige"}
src/jarvis_finance/imports/broker_parsers.py:172:            candidates.append(ParsedBrokerRow("True Wealth", "docx", f"table{ti}:row{ri}", label, asset, cur, _has_amount_marker(cells, "anteile", "quantity", "menge"), _has_amount_marker(cells, "wert", "value"), "True Wealth Portfolio", quality_flags=tuple(dict.fromkeys(flags))))
src/jarvis_finance/imports/broker_parsers.py:173:    return ParsedBrokerFile("True Wealth", "docx", sum(len(t) for t in tables), tuple(candidates), date, status, tuple(f"table_{i}" for i in range(1, len(tables)+1)))
src/jarvis_finance/imports/broker_parsers.py:202:    if platform == "truewealth" and suffix == ".docx":
src/jarvis_finance/imports/truewealth_tax_statement.py:10:PARSER_ID = "truewealth_tax_statement"
src/jarvis_finance/imports/truewealth_tax_statement.py:27:class TrueWealthPosition:
src/jarvis_finance/imports/truewealth_tax_statement.py:38:class TrueWealthCash:
src/jarvis_finance/imports/truewealth_tax_statement.py:47:class TrueWealthStatement:
src/jarvis_finance/imports/truewealth_tax_statement.py:52:    positions: tuple[TrueWealthPosition, ...]
src/jarvis_finance/imports/truewealth_tax_statement.py:53:    cash: tuple[TrueWealthCash, ...]
src/jarvis_finance/imports/truewealth_tax_statement.py:81:        raise ValueError("TrueWealth statement contains an invalid numeric value") from exc
src/jarvis_finance/imports/truewealth_tax_statement.py:86:        raise ValueError("TrueWealth source must be a bounded PDF")
src/jarvis_finance/imports/truewealth_tax_statement.py:92:            raise ValueError("Encrypted TrueWealth PDFs are not supported")
src/jarvis_finance/imports/truewealth_tax_statement.py:97:        raise ValueError("TrueWealth PDF could not be read") from exc
src/jarvis_finance/imports/truewealth_tax_statement.py:99:        raise ValueError("TrueWealth PDF does not contain the expected text layer")
src/jarvis_finance/imports/truewealth_tax_statement.py:103:def parse_truewealth_text(text: str, *, file_sha256: str = "synthetic", page_count: int = 7) -> TrueWealthStatement:
src/jarvis_finance/imports/truewealth_tax_statement.py:104:    if "True Wealth AG" not in text or "Steuerauszug in CHF" not in text:
src/jarvis_finance/imports/truewealth_tax_statement.py:105:        raise ValueError("PDF is not an evidenced TrueWealth tax statement")
src/jarvis_finance/imports/truewealth_tax_statement.py:109:        raise ValueError("TrueWealth statement date or period is missing")
src/jarvis_finance/imports/truewealth_tax_statement.py:115:        raise ValueError("TrueWealth statement date conflicts with the period end")
src/jarvis_finance/imports/truewealth_tax_statement.py:122:        raise ValueError("TrueWealth source total is missing")
src/jarvis_finance/imports/truewealth_tax_statement.py:125:    positions: list[TrueWealthPosition] = []
src/jarvis_finance/imports/truewealth_tax_statement.py:126:    cash: list[TrueWealthCash] = []
src/jarvis_finance/imports/truewealth_tax_statement.py:140:                raise ValueError("TrueWealth cash currency is inconsistent")
src/jarvis_finance/imports/truewealth_tax_statement.py:144:                raise ValueError("TrueWealth cash value is incomplete")
src/jarvis_finance/imports/truewealth_tax_statement.py:149:                TrueWealthCash(
src/jarvis_finance/imports/truewealth_tax_statement.py:166:            if not grep: src/jarvis_finance/reports/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/reports/__pycache__/crypto_inventory.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/database.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/migrations.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/__init__.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/schema.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/migrations.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/schema.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/database.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/storage/__pycache__/__init__.cpython-311.pyc: binary file matches
any(token in line for token in ("True Wealth AG", "Kunde", "Kdnr.", "Periode", "Erstellt am", "Kanton", "Valoren-Nr.", "Datum", "ISIN", "Depot ", "USA", "Werte mit Anrechnung")):
src/jarvis_finance/imports/truewealth_tax_statement.py:173:                raise ValueError("TrueWealth position date conflicts with statement date")
src/jarvis_finance/imports/truewealth_tax_statement.py:175:                TrueWealthPosition(
src/jarvis_finance/imports/truewealth_tax_statement.py:189:        raise ValueError("TrueWealth positions are missing or duplicated")
src/jarvis_finance/imports/truewealth_tax_statement.py:191:        raise ValueError("TrueWealth cash rows are missing")
src/jarvis_finance/imports/truewealth_tax_statement.py:193:        raise ValueError("TrueWealth tax-statement position values must be reported in CHF")
src/jarvis_finance/imports/truewealth_tax_statement.py:196:        raise ValueError("TrueWealth components do not reconcile to the source total")
src/jarvis_finance/imports/truewealth_tax_statement.py:197:    return TrueWealthStatement(
src/jarvis_finance/imports/truewealth_tax_statement.py:209:def parse_truewealth_tax_statement(raw: bytes) -> TrueWealthStatement:
src/jarvis_finance/imports/truewealth_tax_statement.py:211:    return parse_truewealth_text(text, file_sha256=sha256(raw).hexdigest(), page_count=pages)
src/jarvis_finance/storage/migrations.py:10:MIGRATION_NAME = "044_truewealth_verified_snapshot_v1"
src/jarvis_finance/storage/migrations.py:1928:def _create_truewealth_verified_snapshot_v1(conn: Connection) -> None:
src/jarvis_finance/storage/migrations.py:1942:        CREATE TABLE IF NOT EXISTS truewealth_portfolios (
src/jarvis_finance/storage/migrations.py:1954:        CREATE TABLE IF NOT EXISTS truewealth_import_batches (
src/jarvis_finance/storage/migrations.py:1956:            portfolio_id TEXT NOT NULL REFERENCES truewealth_portfolios(portfolio_id),
src/jarvis_finance/storage/migrations.py:1963:            provenance TEXT NOT NULL CHECK(provenance='truewealth_customer_export'),
src/jarvis_finance/storage/migrations.py:1972:        CREATE INDEX IF NOT EXISTS idx_truewealth_batches_portfolio_date
src/jarvis_finance/storage/migrations.py:1973:            ON truewealth_import_batches(portfolio_id, snapshot_date DESC, confirmed_at DESC);
src/jarvis_finance/storage/migrations.py:1975:        CREATE TABLE IF NOT EXISTS truewealth_snapshots (
src/jarvis_finance/storage/migrations.py:1977:            portfolio_id TEXT NOT NULL REFERENCES truewealth_portfolios(portfolio_id),
src/jarvis_finance/storage/migrations.py:1979:            batch_id TEXT NOT NULL UNIQUE REFERENCES truewealth_import_batches(batch_id),
src/jarvis_finance/storage/migrations.py:1996:        CREATE INDEX IF NOT EXISTS idx_truewealth_snapshot_date
src/jarvis_finance/storage/migrations.py:1997:          ON truewealth_snapshots(portfolio_id, snapshot_date DESC);
src/jarvis_finance/storage/migrations.py:1998:        CREATE UNIQUE INDEX IF NOT EXISTS ux_truewealth_snapshot_portfolio_date
src/jarvis_finance/storage/migrations.py:1999:          ON truewealth_snapshots(portfolio_id, snapshot_date);
src/jarvis_finance/storage/migrations.py:2000:        CREATE TABLE IF NOT EXISTS truewealth_snapshot_positions (
src/jarvis_finance/storage/migrations.py:2002:            snapshot_id TEXT NOT NULL REFERENCES truewealth_snapshots(snapshot_id),
src/jarvis_finance/storage/migrations.py:2017:        CREATE INDEX IF NOT EXISTS idx_truewealth_positions_snapshot
src/jarvis_finance/storage/migrations.py:2018:            ON truewealth_snapshot_positions(snapshot_id, isin);
src/jarvis_finance/storage/migrations.py:2020:        CREATE TABLE IF NOT EXISTS truewealth_snapshot_cash (
src/jarvis_finance/storage/migrations.py:2022:            snapshot_id TEXT NOT NULL REFERENCES truewealth_snapshots(snapshot_id),
src/jarvis_finance/storage/migrations.py:2034:        CREATE INDEX IF NOT EXISTS idx_truewealth_cash_snapshot
src/jarvis_finance/storage/migrations.py:2035:            ON truewealth_snapshot_cash(snapshot_id, currency);
src/jarvis_finance/storage/migrgrep: src/jarvis_finance/quality/__pycache__/alerts.cpython-311.pyc: binary file matches
ations.py:2037:        CREATE UNIQUE INDEX IF NOT EXISTS ux_account_value_truewealth_official_reference
src/jarvis_finance/storage/migrations.py:2039:            WHERE source_type='truewealth_official_import' AND source_reference IS NOT NULL;
src/jarvis_finance/storage/migrations.py:2041:        CREATE TRIGGER IF NOT EXISTS truewealth_account_values_no_delete
src/jarvis_finance/storage/migrations.py:2043:        WHEN OLD.source_type IN ('truewealth_official_import','truewealth_manual_provisional')
src/jarvis_finance/storage/migrations.py:2044:        BEGIN SELECT RAISE(ABORT, 'truewealth account valuations cannot be deleted'); END;
src/jarvis_finance/storage/migrations.py:2045:        CREATE TRIGGER IF NOT EXISTS truewealth_official_account_values_immutable
src/jarvis_finance/storage/migrations.py:2047:        WHEN OLD.source_type='truewealth_official_import'
src/jarvis_finance/storage/migrations.py:2048:        BEGIN SELECT RAISE(ABORT, 'official truewealth account valuations are immutable'); END;
src/jarvis_finance/storage/migrations.py:2049:        CREATE TRIGGER IF NOT EXISTS truewealth_manual_value_fields_immutable
src/jarvis_finance/storage/migrations.py:2051:        WHEN OLD.source_type='truewealth_manual_provisional' AND (
src/jarvis_finance/storage/migrations.py:2059:        BEGIN SELECT RAISE(ABORT, 'manual truewealth valuation fields are immutable'); END;
src/jarvis_finance/storage/migrations.py:2061:        CREATE TRIGGER IF NOT EXISTS truewealth_batches_immutable_update
src/jarvis_finance/storage/migrations.py:2062:        BEFORE UPDATE ON truewealth_import_batches
src/jarvis_finance/storage/migrations.py:2063:        BEGIN SELECT RAISE(ABORT, 'truewealth import batches are immutable'); END;
src/jarvis_finance/storage/migrations.py:2064:        CREATE TRIGGER IF NOT EXISTS truewealth_batches_no_delete
grep: src/jarvis_finance/quality/__pycache__/freshness.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/quality/__pycache__/data_quality.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/quality/__pycache__/git_safety.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/quality/__pycache__/checks.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/quality/__pycache__/rules.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/quality/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/__pycache__/demo_data.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/__pycache__/actions.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/__pycache__/data.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/__pycache__/main.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/components/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/components/__pycache__/tables.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_04_crypto.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_02_platforms.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_08_reports.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_00_command_center.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_17_position_add.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_01_portfolio.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_15_manual_review_queue.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_12_crypto_manage.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_10_audit.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_05_wallets.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_07_watchlist.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_16_data_quality_center.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_11_settings_data_quality.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_09_alerts.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_14_import_wizard.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_03_equities_etfs.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/views/__pycache__/page_06_ledger.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/dashboard/pages/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/__pycache__/dependencies.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/__pycache__/main.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/__pycache__/security.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/routers/__pycache__/reports.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/routers/__pycache__/market.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__/truewealth.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/routers/__pycache__/overview.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/routers/__pycache__/crypto.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__/budget.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/routers/__pycache__/system.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/routers/__pycache__/crypto_trader.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/routers/__pycache__/__init__.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/routers/__pycache__/health.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/reports.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/portfolio_performance.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/portfolio_advisor.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/portfolio_data.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/market.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/common.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/truewealth.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/portfolio_policy.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/overview.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/reconciliation.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__/crypto_trader.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/api/schemas/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/market_data/__pycache__/prices.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/market_data/__pycache__/catalog.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/market_data/__pycache__/instruments.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/market_data/__pycache__/mappings.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/market_data/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/market_data/__pycache__/cache.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/market_data/__pycache__/candidates.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/audit/__pycache__/log.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/audit/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/audit/__pycache__/validators.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/cli/__pycache__/main.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/cli/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/runtime/__pycache__/backup.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/runtime/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/config/__pycache__/paths.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/config/__pycache__/settings.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/config/__pycache__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/equity/__pycache__/manage.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/equity/__pycache__/cleanup.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/equity/__pycache__/__init__.cpython-311.pyc: binary file matches
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__/grocery_price_providers.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_advisor.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_data.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/analytics_envelope.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_accounts.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/finance_command_center.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/market_service.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__/crypto_trader_bridge.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_policy.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_cleanup.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/__init__.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/reconciliation_snapshot.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__/budget_common.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__/report_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/reports_v1.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_plans.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__/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__/budget_seed_review.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_csv_imports.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_monthly_import.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/budget_import_upload.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/system_ops.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/crypto_service.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/truewealth_service.cpython-312.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/grocery_optimizer.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/portfolio_analytics.cpython-311.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__/__init__.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/transfer_pairing.cpython-311.pyc: binary file matches
grep: src/jarvis_finance/services/__pycache__/api_helpers.cpython-311.pyc: binary file matches
grep: write error: Broken pipe


nl -ba src/jarvis_finance/services/truewealth_service.py | sed -n '36,82p'


Output:
Chunk ID: 70612f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 548
Output:
    36	    return sha256(json.dumps(value, sort_keys=True, separators=(",", ":"), default=str).encode()).hexdigest()
    37
    38
    39	def _decimal_text(value: Decimal, places: str = "0.01") -> str:
    40	    return format(value.quantize(Decimal(places)), "f")
    41
    42
    43	def _decode_pdf(content_base64: str) -> bytes:
    44	    try:
    45	        return base64.b64decode(content_base64, validate=True)
    46	    except (binascii.Error, ValueError) as exc:
    47	        raise ValueError("TrueWealth PDF is not valid base64") from exc
    48
    49
    50	def _resolve_account_id(conn: Connection) -> str:
    51	    mapped = conn.execute(
    52	        "SELECT account_id FROM truewealth_portfolios WHERE portfolio_id=? AND is_active=1",
    53	        (TRUEWEALTH_PORTFOLIO_ID,),
    54	    ).fetchone()
    55	    if mapped:
    56	        return str(mapped["account_id"])
    57	    configured = os.environ.get("JARVIS_TRUEWEALTH_ACCOUNT_ID", "").strip()
    58	    if configured:
    59	        return configured
    60	    candidates = conn.execute(
    61	        """SELECT DISTINCT a.account_id
    62	           FROM accounts a
    63	           JOIN platforms p ON p.platform_id=a.platform_id
    64	           WHERE a.is_active=1
    65	             AND lower(p.name) LIKE '%true%wealth%'
    66	             AND EXISTS (
    67	                 SELECT 1 FROM account_value_snapshots v
    68	                 WHERE v.account_id=a.account_id AND COALESCE(v.is_active,1)=1
    69	             )
    70	           ORDER BY a.account_id"""
    71	    ).fetchall()
    72	    if len(candidates) != 1:
    73	        raise ValueError("Confirmed TrueWealth target account cannot be resolved unambiguously")
    74	    return str(candidates[0]["account_id"])
    75
    76
    77	def _validate_account(conn: Connection, account_id: str | None = None) -> str:
    78	    resolved = _resolve_account_id(conn)
    79	    if account_id and account_id != resolved:
    80	        raise ValueError("TrueWealth portfolio account does not match the confirmed mapping")
    81	    row = conn.execute(
    82	        """SELECT a.account_id,a.is_active,p.name AS platform


nl -ba src/jarvis_finance/services/truewealth_service.py | sed -n '77,92p'


Output:
Chunk ID: 6a89cd
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 215
Output:
    77	def _validate_account(conn: Connection, account_id: str | None = None) -> str:
    78	    resolved = _resolve_account_id(conn)
    79	    if account_id and account_id != resolved:
    80	        raise ValueError("TrueWealth portfolio account does not match the confirmed mapping")
    81	    row = conn.execute(
    82	        """SELECT a.account_id,a.is_active,p.name AS platform
    83	           FROM accounts a JOIN platforms p ON p.platform_id=a.platform_id
    84	           WHERE a.account_id=?""",
    85	        (resolved,),
    86	    ).fetchone()
    87	    if not row or not row["is_active"] or "true wealth" not in str(row["platform"]).lower():
    88	        raise ValueError("Confirmed TrueWealth target account is unavailable")
    89	    return resolved
    90
    91
    92	def _revision(conn: Connection, account_id: str) -> str:


[P2] Accept TrueWealth platform names without spaces

When the stored platform is named `TrueWealth` (as the UI and existing labels commonly spell it), `_resolve_account_id` can select it via `LIKE '%true%wealth%'`, but `_validate_account` then rejects the same row because it only accepts the literal substring `true wealth`. In that environment every new `/api/truewealth/*` endpoint fails with 422 even though the account was resolved successfully.

[P2] Allow bootstrapping an empty TrueWealth account

For a newly configured TrueWealth account that has no `account_value_snapshots` yet, the resolver ignores it because of the `EXISTS` filter, so summary, manual preview, and official import preview all fail before the first value can be created. This blocks onboarding even when the account exists and the import/preview request supplies an `account_id`.

The new TrueWealth endpoints can reject valid existing account setups and cannot bootstrap an empty TrueWealth account, which blocks core workflows introduced by the patch.