.......................................................                  [100%]
=============================== warnings summary ===============================
.venv/lib/python3.11/site-packages/fastapi/testclient.py:1
  /home/agent/.hermes/worktrees/FinanceManager-sprint23/.venv/lib/python3.11/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
55 passed, 1 warning in 41.39s
F401 [*] `pathlib.Path` imported but unused
 --> src/jarvis_finance/api/routers/reports.py:3:21
  |
1 | from __future__ import annotations
2 |
3 | from pathlib import Path
  |                     ^^^^
4 |
5 | from fastapi import APIRouter, Depends, HTTPException
  |
help: Remove unused import: `pathlib.Path`

F401 [*] `jarvis_finance.api.schemas.reports.ReportMetadata` imported but unused
  --> src/jarvis_finance/api/routers/reports.py:10:48
   |
 9 | from jarvis_finance.api.dependencies import get_db
10 | from jarvis_finance.api.schemas.reports import ReportMetadata
   |                                                ^^^^^^^^^^^^^^
11 | from jarvis_finance.config.settings import find_repo_root, load_settings
12 | from jarvis_finance.services.finance_command_center import generate_monthly_report
   |
help: Remove unused import: `jarvis_finance.api.schemas.reports.ReportMetadata`

F401 [*] `jarvis_finance.services.finance_command_center.generate_monthly_report` imported but unused
  --> src/jarvis_finance/api/routers/reports.py:12:60
   |
10 | from jarvis_finance.api.schemas.reports import ReportMetadata
11 | from jarvis_finance.config.settings import find_repo_root, load_settings
12 | from jarvis_finance.services.finance_command_center import generate_monthly_report
   |                                                            ^^^^^^^^^^^^^^^^^^^^^^^
13 | from jarvis_finance.services.report_service import list_runtime_reports
14 | from jarvis_finance.services.reports_v1 import generate_report_v1, list_reports_v1, preview_report_v1
   |
help: Remove unused import: `jarvis_finance.services.finance_command_center.generate_monthly_report`

F401 [*] `jarvis_finance.services.report_service.list_runtime_reports` imported but unused
  --> src/jarvis_finance/api/routers/reports.py:13:52
   |
11 | from jarvis_finance.config.settings import find_repo_root, load_settings
12 | from jarvis_finance.services.finance_command_center import generate_monthly_report
13 | from jarvis_finance.services.report_service import list_runtime_reports
   |                                                    ^^^^^^^^^^^^^^^^^^^^
14 | from jarvis_finance.services.reports_v1 import generate_report_v1, list_reports_v1, preview_report_v1
   |
help: Remove unused import: `jarvis_finance.services.report_service.list_runtime_reports`

F401 [*] `jarvis_finance.imports.crypto_holdings_importer.import_crypto_holdings_csv` imported but unused
  --> src/jarvis_finance/dashboard/demo_data.py:10:61
   |
 8 | from jarvis_finance.imports.accounts_importer import import_accounts_csv
 9 | from jarvis_finance.imports.cash_balances_importer import import_cash_balances_csv
10 | from jarvis_finance.imports.crypto_holdings_importer import import_crypto_holdings_csv
   |                                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^
11 | from jarvis_finance.imports.crypto_transactions_importer import import_crypto_transactions_csv
12 | from jarvis_finance.imports.crypto_wallets_importer import import_crypto_wallets_csv
   |
help: Remove unused import: `jarvis_finance.imports.crypto_holdings_importer.import_crypto_holdings_csv`

F401 [*] `jarvis_finance.imports.crypto_transactions_importer.import_crypto_transactions_csv` imported but unused
  --> src/jarvis_finance/dashboard/demo_data.py:11:65
   |
 9 | from jarvis_finance.imports.cash_balances_importer import import_cash_balances_csv
10 | from jarvis_finance.imports.crypto_holdings_importer import import_crypto_holdings_csv
11 | from jarvis_finance.imports.crypto_transactions_importer import import_crypto_transactions_csv
   |                                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12 | from jarvis_finance.imports.crypto_wallets_importer import import_crypto_wallets_csv
13 | from jarvis_finance.imports.instruments_importer import import_instruments_csv
   |
help: Remove unused import: `jarvis_finance.imports.crypto_transactions_importer.import_crypto_transactions_csv`

F401 [*] `jarvis_finance.imports.watchlist_importer.import_watchlist_csv` imported but unused
  --> src/jarvis_finance/dashboard/demo_data.py:15:55
   |
13 | from jarvis_finance.imports.instruments_importer import import_instruments_csv
14 | from jarvis_finance.imports.transactions_importer import import_transactions_csv
15 | from jarvis_finance.imports.watchlist_importer import import_watchlist_csv
   |                                                       ^^^^^^^^^^^^^^^^^^^^
16 | from jarvis_finance.imports.common import stable_id, utc_now
17 | from jarvis_finance.crypto.assets import create_crypto_asset
   |
help: Remove unused import: `jarvis_finance.imports.watchlist_importer.import_watchlist_csv`

F401 [*] `pathlib.Path` imported but unused
 --> src/jarvis_finance/dashboard/views/page_11_settings_data_quality.py:6:21
  |
4 | from jarvis_finance.dashboard import data
5 |
6 | from pathlib import Path
  |                     ^^^^
7 |
8 | from jarvis_finance.config.settings import find_repo_root, load_settings
  |
help: Remove unused import: `pathlib.Path`

F841 Local variable `demo` is assigned to but never used
  --> src/jarvis_finance/dashboard/views/page_14_import_wizard.py:19:5
   |
17 |     source = st.selectbox("Quelle", ["PostFinance", "True Wealth", "Raiffeisen", "Manuell / Sonstige"])
18 |     uploaded = st.file_uploader("Datei auswählen — wird nicht ins Repo geschrieben", type=["docx", "xlsx", "csv", "pdf"])
19 |     demo = st.button("Synthetischen Demo-Dry-Run ausführen", disabled=True)
   |     ^^^^
20 |     st.write({"source": source, "file_type": getattr(uploaded, "type", "") if uploaded else "", "demo_dry_run_available_in_tests": Tru…
   |
help: Remove assignment to unused variable `demo`

E701 Multiple statements on one line (colon)
   --> src/jarvis_finance/imports/broker_mapping.py:584:15
    |
582 |     isin = validate_isin(isin)
583 |     row = conn.execute("SELECT * FROM broker_import_review_items WHERE review_item_id=?", (review_item_id,)).fetchone()
584 |     if not row: raise ValueError("review item not found")
    |               ^
585 |     flags = _without_flags(_load_flags(row), {"missing_isin", "name_only_not_auto_mapped"})
586 |     conn.execute("UPDATE broker_import_review_items SET isin=?, quality_flags_json=?, reviewer_note=?, updated_at=? WHERE review_item…
    |

E701 Multiple statements on one line (colon)
   --> src/jarvis_finance/imports/broker_mapping.py:594:15
    |
592 |     note = _require_note(note)
593 |     row = conn.execute("SELECT * FROM broker_import_review_items WHERE review_item_id=?", (review_item_id,)).fetchone()
594 |     if not row: raise ValueError("review item not found")
    |               ^
595 |     new_ticker = (ticker or row["ticker"] or "").strip().upper() or None
596 |     new_exchange = (exchange or row["exchange"] or "").strip().upper() or None
    |

E701 Multiple statements on one line (colon)
   --> src/jarvis_finance/imports/broker_mapping.py:613:15
    |
611 |     note = _require_note(note)
612 |     row = conn.execute("SELECT * FROM broker_import_review_items WHERE review_item_id=?", (review_item_id,)).fetchone()
613 |     if not row: raise ValueError("review item not found")
    |               ^
614 |     if not row["isin"] and not (row["ticker"] and row["exchange"] and confirm_ticker_exchange):
615 |         raise ValueError("Name-only rows cannot be marked as mapped")
    |

E701 Multiple statements on one line (colon)
   --> src/jarvis_finance/imports/broker_mapping.py:630:15
    |
628 |     row = conn.execute("SELECT * FROM broker_import_review_items WHERE review_item_id=?", (review_item_id,)).fetchone()
629 |     acct = conn.execute("SELECT account_id, account_type FROM accounts WHERE account_id=? AND is_active=1", (account_id,)).fetchone()
630 |     if not row: raise ValueError("review item not found")
    |               ^
631 |     if not acct: raise ValueError("account not found")
632 |     if row["detected_asset_class"] == "cash" and acct["account_type"] not in {"cash", "reserve", "other"}:
    |

E701 Multiple statements on one line (colon)
   --> src/jarvis_finance/imports/broker_mapping.py:631:16
    |
629 |     acct = conn.execute("SELECT account_id, account_type FROM accounts WHERE account_id=? AND is_active=1", (account_id,)).fetchone()
630 |     if not row: raise ValueError("review item not found")
631 |     if not acct: raise ValueError("account not found")
    |                ^
632 |     if row["detected_asset_class"] == "cash" and acct["account_type"] not in {"cash", "reserve", "other"}:
633 |         raise ValueError("cash review items must map to cash/reserve/other accounts")
    |

E701 Multiple statements on one line (colon)
   --> src/jarvis_finance/imports/broker_mapping.py:644:15
    |
642 |     note = _require_note(note)
643 |     row = conn.execute("SELECT * FROM broker_import_review_items WHERE review_item_id=?", (review_item_id,)).fetchone()
644 |     if not row: raise ValueError("review item not found")
    |               ^
645 |     conn.execute("UPDATE broker_import_review_items SET snapshot_date_confirmed=1, reviewer_note=?, updated_at=? WHERE review_item_id…
646 |     readiness = refresh_review_item_readiness(conn, review_item_id)
    |

E701 Multiple statements on one line (colon)
   --> src/jarvis_finance/imports/broker_mapping.py:653:15
    |
651 |     note = _require_note(note, "Ignore requires a note")
652 |     row = conn.execute("SELECT * FROM broker_import_review_items WHERE review_item_id=?", (review_item_id,)).fetchone()
653 |     if not row: raise ValueError("review item not found")
    |               ^
654 |     conn.execute("UPDATE broker_import_review_items SET review_status='ignored', import_readiness_status='ignored', reviewer_note=?, …
655 |     return _audit_review_change(conn, review_item_id=review_item_id, action="ignore_review_item", old={"review_status": row["review_s…
    |

E701 Multiple statements on one line (colon)
   --> src/jarvis_finance/imports/broker_mapping.py:661:15
    |
659 |     note = _require_note(note)
660 |     row = conn.execute("SELECT * FROM broker_import_review_items WHERE review_item_id=?", (review_item_id,)).fetchone()
661 |     if not row: raise ValueError("review item not found")
    |               ^
662 |     readiness = calculate_review_item_readiness(row)
663 |     if readiness not in {"ready_for_import", "ignored"}:
    |

F811 Redefinition of unused `get_broker_review_items` from line 321
   --> src/jarvis_finance/imports/broker_mapping.py:669:5
    |
669 | def get_broker_review_items(conn: Connection, *, status: str | None = None, source_platform: str | None = None, dry_run_id: str | Non…
    |     ^^^^^^^^^^^^^^^^^^^^^^^ `get_broker_review_items` redefined here
670 |     clauses=[]; params=[]
671 |     for col,val in [("review_status",status),("source_platform",source_platform),("dry_run_id",dry_run_id),("detected_asset_class",as…
    |
   ::: src/jarvis_finance/imports/broker_mapping.py:321:5
    |
321 | def get_broker_review_items(conn: Connection, *, status: str | None = "open", limit: int = 100) -> list[dict[str, str]]:
    |     ----------------------- previous definition of `get_broker_review_items` here
322 |     params: list[Any] = []
323 |     where = ""
    |
help: Remove definition: `get_broker_review_items`

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/imports/broker_mapping.py:670:15
    |
669 | def get_broker_review_items(conn: Connection, *, status: str | None = None, source_platform: str | None = None, dry_run_id: str | Non…
670 |     clauses=[]; params=[]
    |               ^
671 |     for col,val in [("review_status",status),("source_platform",source_platform),("dry_run_id",dry_run_id),("detected_asset_class",as…
672 |         if val:
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/imports/broker_mapping.py:673:39
    |
671 |     for col,val in [("review_status",status),("source_platform",source_platform),("dry_run_id",dry_run_id),("detected_asset_class",as…
672 |         if val:
673 |             clauses.append(f"{col}=?"); params.append(val)
    |                                       ^
674 |     if quality_flag:
675 |         clauses.append("quality_flags_json LIKE ?"); params.append(f'%"{quality_flag}"%')
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/imports/broker_mapping.py:675:52
    |
673 |             clauses.append(f"{col}=?"); params.append(val)
674 |     if quality_flag:
675 |         clauses.append("quality_flags_json LIKE ?"); params.append(f'%"{quality_flag}"%')
    |                                                    ^
676 |     where = "WHERE " + " AND ".join(clauses) if clauses else ""
677 |     params.append(limit)
    |

E701 Multiple statements on one line (colon)
   --> src/jarvis_finance/imports/broker_mapping.py:694:15
    |
692 | def get_review_item_detail(conn: Connection, review_item_id: str) -> dict[str, Any]:
693 |     row = conn.execute("SELECT * FROM broker_import_review_items WHERE review_item_id=?", (review_item_id,)).fetchone()
694 |     if not row: raise ValueError("review item not found")
    |               ^
695 |     audit = conn.execute("SELECT timestamp, action, entity_type, entity_id, user_text_note, created_by FROM audit_log WHERE entity_id…
696 |     return {"item": {k: "" if row[k] is None else str(row[k]) for k in row.keys()}, "audit_history": [{k: "" if a[k] is None else str…
    |

E701 Multiple statements on one line (colon)
   --> src/jarvis_finance/imports/broker_mapping.py:720:15
    |
718 |     note = _require_note(note)
719 |     row = conn.execute("SELECT * FROM broker_import_dry_runs WHERE dry_run_id=?", (dry_run_id,)).fetchone()
720 |     if not row: raise ValueError("dry run not found")
    |               ^
721 |     now=utc_now()
722 |     if action == "archive":
    |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:12:8
   |
11 | def dec(v,line,field):
12 |     try: return Decimal(clean(v))
   |        ^
13 |     except (InvalidOperation, ValueError) as exc: raise ValueError(f"line {line}: {field} invalid decimal") from exc
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:13:49
   |
11 | def dec(v,line,field):
12 |     try: return Decimal(clean(v))
13 |     except (InvalidOperation, ValueError) as exc: raise ValueError(f"line {line}: {field} invalid decimal") from exc
   |                                                 ^
14 |
15 | def acct(conn,name):
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:16:94
   |
15 | def acct(conn,name):
16 |     r=conn.execute('SELECT account_id FROM accounts WHERE account_name=?',(name,)).fetchone(); return r['account_id'] if r else None
   |                                                                                              ^
17 |
18 | def import_cash_balances_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:19:53
   |
18 | def import_cash_balances_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
19 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
   |                                                     ^
20 |     new=existing=0; vals=[]; seen=set()
21 |     for line,row in enumerate(rows,start=2):
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:20:19
   |
18 | def import_cash_balances_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
19 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
20 |     new=existing=0; vals=[]; seen=set()
   |                   ^
21 |     for line,row in enumerate(rows,start=2):
22 |         try:
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:20:28
   |
18 | def import_cash_balances_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
19 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
20 |     new=existing=0; vals=[]; seen=set()
   |                            ^
21 |     for line,row in enumerate(rows,start=2):
22 |         try:
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:24:26
   |
22 |         try:
23 |             rh=compute_row_hash(row)
24 |             if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
   |                          ^
25 |             seen.add(rh); aid=acct(conn,require_text(row,'account_name',line))
26 |             if not aid: raise ValueError(f"line {line}: account not found")
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:25:25
   |
23 | …     rh=compute_row_hash(row)
24 | …     if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
25 | …     seen.add(rh); aid=acct(conn,require_text(row,'account_name',line))
   |                   ^
26 | …     if not aid: raise ValueError(f"line {line}: account not found")
27 | …     cur=require_text(row,'currency',line).upper(); amount=dec(row.get('amount_original'),line,'amount_original'); fx=dec(row.get('fx…
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:26:23
   |
24 | …     if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
25 | …     seen.add(rh); aid=acct(conn,require_text(row,'account_name',line))
26 | …     if not aid: raise ValueError(f"line {line}: account not found")
   |                 ^
27 | …     cur=require_text(row,'currency',line).upper(); amount=dec(row.get('amount_original'),line,'amount_original'); fx=dec(row.get('fx…
28 | …     date=require_text(row,'balance_date',line); cid=stable_id('cashsnap',aid,date,cur)
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:27:58
   |
25 | …     seen.add(rh); aid=acct(conn,require_text(row,'account_name',line))
26 | …     if not aid: raise ValueError(f"line {line}: account not found")
27 | …     cur=require_text(row,'currency',line).upper(); amount=dec(row.get('amount_original'),line,'amount_original'); fx=dec(row.get('fx…
   |                                                    ^
28 | …     date=require_text(row,'balance_date',line); cid=stable_id('cashsnap',aid,date,cur)
29 | …     exists=conn.execute('SELECT 1 FROM transactions WHERE transaction_id=?',(cid,)).fetchone() is not None
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:27:121
   |
25 | …     seen.add(rh); aid=acct(conn,require_text(row,'account_name',line))
26 | …     if not aid: raise ValueError(f"line {line}: account not found")
27 | …     cur=require_text(row,'currency',line).upper(); amount=dec(row.get('amount_original'),line,'amount_original'); fx=dec(row.get('fx…
   |                                                                                                                   ^
28 | …     date=require_text(row,'balance_date',line); cid=stable_id('cashsnap',aid,date,cur)
29 | …     exists=conn.execute('SELECT 1 FROM transactions WHERE transaction_id=?',(cid,)).fetchone() is not None
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:28:55
   |
26 | …     if not aid: raise ValueError(f"line {line}: account not found")
27 | …     cur=require_text(row,'currency',line).upper(); amount=dec(row.get('amount_original'),line,'amount_original'); fx=dec(row.get('fx…
28 | …     date=require_text(row,'balance_date',line); cid=stable_id('cashsnap',aid,date,cur)
   |                                                 ^
29 | …     exists=conn.execute('SELECT 1 FROM transactions WHERE transaction_id=?',(cid,)).fetchone() is not None
30 | …     existing+=1 if exists else 0; new+=0 if exists else 1
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:30:41
   |
28 |             date=require_text(row,'balance_date',line); cid=stable_id('cashsnap',aid,date,cur)
29 |             exists=conn.execute('SELECT 1 FROM transactions WHERE transaction_id=?',(cid,)).fetchone() is not None
30 |             existing+=1 if exists else 0; new+=0 if exists else 1
   |                                         ^
31 |             vals.append((cid,aid,date,cur,amount,fx,clean(row.get('notes')) or 'Initial cash snapshot import'))
32 |         except ValueError as exc: errors.append(str(exc))
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:32:33
   |
30 |             existing+=1 if exists else 0; new+=0 if exists else 1
31 |             vals.append((cid,aid,date,cur,amount,fx,clean(row.get('notes')) or 'Initial cash snapshot import'))
32 |         except ValueError as exc: errors.append(str(exc))
   |                                 ^
33 |     status='failed' if errors else ('committed' if commit else 'dry_run_ok')
34 |     if commit and not errors:
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/cash_balances_importer.py:37:99
   |
35 | …     now=utc_now()
36 | …     for cid,aid,date,cur,amount,fx,note in vals:
37 | …         if conn.execute('SELECT 1 FROM transactions WHERE transaction_id=?',(cid,)).fetchone(): continue
   |                                                                                                 ^
38 | …         chf=amount*fx if fx is not None else None
39 | …         conn.execute("""INSERT INTO transactions(transaction_id,transaction_type,account_id,trade_date,gross_amount_original,net_amo…
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:13:8
   |
12 | def dec(v,line,field):
13 |     try: return Decimal(clean(v))
   |        ^
14 |     except (InvalidOperation, ValueError) as exc: raise ValueError(f"line {line}: {field} invalid decimal") from exc
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:14:49
   |
12 | def dec(v,line,field):
13 |     try: return Decimal(clean(v))
14 |     except (InvalidOperation, ValueError) as exc: raise ValueError(f"line {line}: {field} invalid decimal") from exc
   |                                                 ^
15 |
16 | def _wallet(conn,name):
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:17:98
   |
16 | def _wallet(conn,name):
17 |     r=conn.execute("SELECT wallet_id FROM crypto_wallets WHERE wallet_name=?",(name,)).fetchone(); return r['wallet_id'] if r else None
   |                                                                                                  ^
18 |
19 | def _asset(conn,symbol,cg,coin):
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:22:13
   |
20 |     if cg:
21 |         r=conn.execute("SELECT asset_id FROM crypto_assets WHERE coingecko_id=?",(cg,)).fetchone()
22 |         if r: return r['asset_id']
   |             ^
23 |     rows=conn.execute("SELECT asset_id FROM crypto_assets WHERE symbol=?",(symbol,)).fetchall()
24 |     return rows[0]['asset_id'] if len(rows)==1 else None
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:27:53
   |
26 | def import_crypto_holdings_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
27 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
   |                                                     ^
28 |     new=existing=0; vals=[]; seen=set()
29 |     for line,row in enumerate(rows,start=2):
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:28:19
   |
26 | def import_crypto_holdings_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
27 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
28 |     new=existing=0; vals=[]; seen=set()
   |                   ^
29 |     for line,row in enumerate(rows,start=2):
30 |         try:
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:28:28
   |
26 | def import_crypto_holdings_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
27 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
28 |     new=existing=0; vals=[]; seen=set()
   |                            ^
29 |     for line,row in enumerate(rows,start=2):
30 |         try:
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:32:26
   |
30 | …     try:
31 | …         rh=compute_row_hash(row)
32 | …         if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
   |                        ^
33 | …         seen.add(rh); wallet=require_text(row,'wallet_name',line); coin=require_text(row,'coin_name',line); symbol=require_text(row,…
34 | …         if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:33:25
   |
31 | …     rh=compute_row_hash(row)
32 | …     if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
33 | …     seen.add(rh); wallet=require_text(row,'wallet_name',line); coin=require_text(row,'coin_name',line); symbol=require_text(row,'sym…
   |                   ^
34 | …     if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
35 | …     wid=_wallet(conn,wallet)
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:33:70
   |
31 | …     rh=compute_row_hash(row)
32 | …     if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
33 | …     seen.add(rh); wallet=require_text(row,'wallet_name',line); coin=require_text(row,'coin_name',line); symbol=require_text(row,'sym…
   |                                                                ^
34 | …     if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
35 | …     wid=_wallet(conn,wallet)
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:33:111
   |
31 | …     rh=compute_row_hash(row)
32 | …     if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
33 | …     seen.add(rh); wallet=require_text(row,'wallet_name',line); coin=require_text(row,'coin_name',line); symbol=require_text(row,'sym…
   |                                                                                                         ^
34 | …     if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
35 | …     wid=_wallet(conn,wallet)
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:33:159
   |
31 | …
32 | …")
33 | …,'coin_name',line); symbol=require_text(row,'symbol',line).upper(); qty=dec(row.get('quantity'),line,'quantity')
   |                                                                    ^
34 | …
35 | …
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:34:22
   |
32 | …     if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
33 | …     seen.add(rh); wallet=require_text(row,'wallet_name',line); coin=require_text(row,'coin_name',line); symbol=require_text(row,'sym…
34 | …     if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
   |                ^
35 | …     wid=_wallet(conn,wallet)
36 | …     if not wid: raise ValueError(f"line {line}: wallet not found")
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:36:23
   |
34 |             if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
35 |             wid=_wallet(conn,wallet)
36 |             if not wid: raise ValueError(f"line {line}: wallet not found")
   |                       ^
37 |             cg=clean(row.get('coingecko_id')) or None; aid=_asset(conn,symbol,cg,coin)
38 |             exists=False
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:37:54
   |
35 | …     wid=_wallet(conn,wallet)
36 | …     if not wid: raise ValueError(f"line {line}: wallet not found")
37 | …     cg=clean(row.get('coingecko_id')) or None; aid=_asset(conn,symbol,cg,coin)
   |                                                ^
38 | …     exists=False
39 | …     if aid: exists=conn.execute("SELECT 1 FROM crypto_holdings WHERE wallet_id=? AND asset_id=?",(wid,aid)).fetchone() is not None
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:39:19
   |
37 | …     cg=clean(row.get('coingecko_id')) or None; aid=_asset(conn,symbol,cg,coin)
38 | …     exists=False
39 | …     if aid: exists=conn.execute("SELECT 1 FROM crypto_holdings WHERE wallet_id=? AND asset_id=?",(wid,aid)).fetchone() is not None
   |             ^
40 | …     existing += 1 if exists else 0; new += 0 if exists else 1
41 | …     vals.append((aid,coin,symbol,cg,wid,qty,require_text(row,'verification_status',line),clean(row.get('last_verified_at')) or None,…
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:40:43
   |
38 | …         exists=False
39 | …         if aid: exists=conn.execute("SELECT 1 FROM crypto_holdings WHERE wallet_id=? AND asset_id=?",(wid,aid)).fetchone() is not No…
40 | …         existing += 1 if exists else 0; new += 0 if exists else 1
   |                                         ^
41 | …         vals.append((aid,coin,symbol,cg,wid,qty,require_text(row,'verification_status',line),clean(row.get('last_verified_at')) or N…
42 | …     except ValueError as exc: errors.append(str(exc))
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:42:33
   |
40 |             existing += 1 if exists else 0; new += 0 if exists else 1
41 |             vals.append((aid,coin,symbol,cg,wid,qty,require_text(row,'verification_status',line),clean(row.get('last_verified_at')) or…
42 |         except ValueError as exc: errors.append(str(exc))
   |                                 ^
43 |     status='failed' if errors else ('committed' if commit else 'dry_run_ok')
44 |     if commit and not errors:
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:46:23
   |
44 |     if commit and not errors:
45 |         for aid,coin,symbol,cg,wid,qty,vs,last,legacy,note in vals:
46 |             if not aid: aid=create_crypto_asset(conn,coin_name=coin,symbol=symbol,coingecko_id=cg)
   |                       ^
47 |             if conn.execute("SELECT 1 FROM crypto_holdings WHERE wallet_id=? AND asset_id=?",(wid,aid)).fetchone(): continue
48 |             create_initial_holding_snapshot(conn,asset_id=aid,wallet_id=wid,quantity=qty,verification_status=vs,last_verified_at=last,…
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_holdings_importer.py:47:115
   |
45 |         for aid,coin,symbol,cg,wid,qty,vs,last,legacy,note in vals:
46 |             if not aid: aid=create_crypto_asset(conn,coin_name=coin,symbol=symbol,coingecko_id=cg)
47 |             if conn.execute("SELECT 1 FROM crypto_holdings WHERE wallet_id=? AND asset_id=?",(wid,aid)).fetchone(): continue
   |                                                                                                                   ^
48 |             create_initial_holding_snapshot(conn,asset_id=aid,wallet_id=wid,quantity=qty,verification_status=vs,last_verified_at=last,…
49 |     sid=create_import_session(conn,import_type='crypto_holdings',source_filename=source_filename,file_hash=source_hash(path),status=st…
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:13:37
   |
11 | def dec(v,line,field,default=None):
12 |     t=clean(v)
13 |     if not t and default is not None: return default
   |                                     ^
14 |     try: return Decimal(t)
15 |     except (InvalidOperation, ValueError) as exc: raise ValueError(f"line {line}: {field} invalid decimal") from exc
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:14:8
   |
12 |     t=clean(v)
13 |     if not t and default is not None: return default
14 |     try: return Decimal(t)
   |        ^
15 |     except (InvalidOperation, ValueError) as exc: raise ValueError(f"line {line}: {field} invalid decimal") from exc
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:15:49
   |
13 |     if not t and default is not None: return default
14 |     try: return Decimal(t)
15 |     except (InvalidOperation, ValueError) as exc: raise ValueError(f"line {line}: {field} invalid decimal") from exc
   |                                                 ^
16 |
17 | def wallet(conn,name,required=True):
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:18:33
   |
17 | def wallet(conn,name,required=True):
18 |     if not name and not required: return None
   |                                 ^
19 |     r=conn.execute("SELECT wallet_id FROM crypto_wallets WHERE wallet_name=?",(name,)).fetchone()
20 |     if not r: raise ValueError(f"wallet not found: {name}")
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:20:13
   |
18 |     if not name and not required: return None
19 |     r=conn.execute("SELECT wallet_id FROM crypto_wallets WHERE wallet_name=?",(name,)).fetchone()
20 |     if not r: raise ValueError(f"wallet not found: {name}")
   |             ^
21 |     return r['wallet_id']
22 | def asset(conn,symbol,cg=None):
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:25:13
   |
23 |     if cg:
24 |         r=conn.execute("SELECT asset_id FROM crypto_assets WHERE coingecko_id=?",(cg,)).fetchone()
25 |         if r: return r['asset_id']
   |             ^
26 |     rs=conn.execute("SELECT asset_id FROM crypto_assets WHERE symbol=?",(symbol.upper(),)).fetchall()
27 |     if len(rs)!=1: raise ValueError(f"asset not uniquely found: {symbol}")
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:27:18
   |
25 |         if r: return r['asset_id']
26 |     rs=conn.execute("SELECT asset_id FROM crypto_assets WHERE symbol=?",(symbol.upper(),)).fetchall()
27 |     if len(rs)!=1: raise ValueError(f"asset not uniquely found: {symbol}")
   |                  ^
28 |     return rs[0]['asset_id']
29 | def account(conn,name):
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:30:16
   |
28 |     return rs[0]['asset_id']
29 | def account(conn,name):
30 |     if not name: return None
   |                ^
31 |     r=conn.execute("SELECT account_id FROM accounts WHERE account_name=?",(name,)).fetchone()
32 |     if not r: raise ValueError(f"account not found: {name}")
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:32:13
   |
30 |     if not name: return None
31 |     r=conn.execute("SELECT account_id FROM accounts WHERE account_name=?",(name,)).fetchone()
32 |     if not r: raise ValueError(f"account not found: {name}")
   |             ^
33 |     return r['account_id']
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:36:53
   |
35 | def import_crypto_transactions_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
36 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
   |                                                     ^
37 |     new=existing=0; vals=[]; seen=set()
38 |     for line,row in enumerate(rows,start=2):
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:37:19
   |
35 | def import_crypto_transactions_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
36 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
37 |     new=existing=0; vals=[]; seen=set()
   |                   ^
38 |     for line,row in enumerate(rows,start=2):
39 |         try:
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:37:28
   |
35 | def import_crypto_transactions_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
36 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
37 |     new=existing=0; vals=[]; seen=set()
   |                            ^
38 |     for line,row in enumerate(rows,start=2):
39 |         try:
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:41:26
   |
39 | …     try:
40 | …         rh=compute_row_hash(row)
41 | …         if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
   |                        ^
42 | …         seen.add(rh)
43 | …         if conn.execute("SELECT 1 FROM crypto_transactions WHERE tx_hash=?",(clean(row.get('tx_hash')),)).fetchone() and clean(row.g…
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:44:28
   |
42 | …     seen.add(rh)
43 | …     if conn.execute("SELECT 1 FROM crypto_transactions WHERE tx_hash=?",(clean(row.get('tx_hash')),)).fetchone() and clean(row.get('…
44 | …         existing+=1; continue
   |                      ^
45 | …     tx=require_text(row,'transaction_type',line).lower(); aid=asset(conn,require_text(row,'asset_symbol',line),clean(row.get('coinge…
46 | …     if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:45:65
   |
43 | …     if conn.execute("SELECT 1 FROM crypto_transactions WHERE tx_hash=?",(clean(row.get('tx_hash')),)).fetchone() and clean(row.get('…
44 | …         existing+=1; continue
45 | …     tx=require_text(row,'transaction_type',line).lower(); aid=asset(conn,require_text(row,'asset_symbol',line),clean(row.get('coinge…
   |                                                           ^
46 | …     if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
47 | …     vals.append((tx,aid,qty,row,line)); new+=1
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:45:159
   |
43 | …et('tx_hash')),)).fetchone() and clean(row.get('tx_hash')):
44 | …
45 | …t(row,'asset_symbol',line),clean(row.get('coingecko_id')) or None); qty=dec(row.get('quantity'),line,'quantity')
   |                                                                    ^
46 | …
47 | …
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:46:22
   |
44 | …             existing+=1; continue
45 | …         tx=require_text(row,'transaction_type',line).lower(); aid=asset(conn,require_text(row,'asset_symbol',line),clean(row.get('co…
46 | …         if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
   |                    ^
47 | …         vals.append((tx,aid,qty,row,line)); new+=1
48 | …     except ValueError as exc: errors.append(str(exc))
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:47:47
   |
45 |             tx=require_text(row,'transaction_type',line).lower(); aid=asset(conn,require_text(row,'asset_symbol',line),clean(row.get('…
46 |             if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
47 |             vals.append((tx,aid,qty,row,line)); new+=1
   |                                               ^
48 |         except ValueError as exc: errors.append(str(exc))
49 |     status='failed' if errors else ('committed' if commit else 'dry_run_ok')
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:48:33
   |
46 |             if qty<=0: raise ValueError(f"line {line}: quantity must be > 0")
47 |             vals.append((tx,aid,qty,row,line)); new+=1
48 |         except ValueError as exc: errors.append(str(exc))
   |                                 ^
49 |     status='failed' if errors else ('committed' if commit else 'dry_run_ok')
50 |     if commit and not errors:
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:52:82
   |
50 |     if commit and not errors:
51 |         for tx,aid,qty,row,line in vals:
52 |             note=clean(row.get('notes')) or 'synthetic crypto transaction import'; feeq=dec(row.get('fee_quantity'),line,'fee_quantity…
   |                                                                                  ^
53 |             if tx=='transfer': record_crypto_transfer(conn,asset_id=aid,from_wallet_id=wallet(conn,require_text(row,'from_wallet_name'…
54 |             elif tx=='buy': record_crypto_buy(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,to_wall…
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:52:150
   |
50 | …
51 | …
52 | …feeq=dec(row.get('fee_quantity'),line,'fee_quantity',Decimal('0')); feeo=dec(row.get('fee_original'),line,'fee_original',Decimal('0')…
   |                                                                    ^
53 | …_id=wallet(conn,require_text(row,'from_wallet_name',line)),to_wallet_id=wallet(conn,require_text(row,'to_wallet_name',line)),quantity…
54 | …text(row,'account_name',line)),asset_id=aid,to_wallet_id=wallet(conn,require_text(row,'to_wallet_name',line)),quantity=qty,gross_amou…
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:52:218
   |
50 | …
51 | …
52 | …feeo=dec(row.get('fee_original'),line,'fee_original',Decimal('0')); fx=dec(row.get('fx_rate_to_chf'),line,'fx_rate_to_chf',None) if c…
   |                                                                    ^
53 | …_id=wallet(conn,require_text(row,'to_wallet_name',line)),quantity=qty,fee_quantity=feeq,tx_hash=clean(row.get('tx_hash')) or None,not…
54 | …,require_text(row,'to_wallet_name',line)),quantity=qty,gross_amount_original=dec(row.get('gross_amount_original'),line,'gross_amount_…
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:53:30
   |
51 | …     for tx,aid,qty,row,line in vals:
52 | …         note=clean(row.get('notes')) or 'synthetic crypto transaction import'; feeq=dec(row.get('fee_quantity'),line,'fee_quantity',…
53 | …         if tx=='transfer': record_crypto_transfer(conn,asset_id=aid,from_wallet_id=wallet(conn,require_text(row,'from_wallet_name',l…
   |                            ^
54 | …         elif tx=='buy': record_crypto_buy(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,to_wallet…
55 | …         elif tx=='sell': record_crypto_sell(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,from_wa…
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:54:27
   |
52 | …     note=clean(row.get('notes')) or 'synthetic crypto transaction import'; feeq=dec(row.get('fee_quantity'),line,'fee_quantity',Deci…
53 | …     if tx=='transfer': record_crypto_transfer(conn,asset_id=aid,from_wallet_id=wallet(conn,require_text(row,'from_wallet_name',line)…
54 | …     elif tx=='buy': record_crypto_buy(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,to_wallet_id=…
   |                     ^
55 | …     elif tx=='sell': record_crypto_sell(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,from_wallet…
56 | …     elif tx=='fee': record_crypto_transfer(conn,asset_id=aid,from_wallet_id=wallet(conn,require_text(row,'from_wallet_name',line)),t…
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:55:28
   |
53 | …     if tx=='transfer': record_crypto_transfer(conn,asset_id=aid,from_wallet_id=wallet(conn,require_text(row,'from_wallet_name',line)…
54 | …     elif tx=='buy': record_crypto_buy(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,to_wallet_id=…
55 | …     elif tx=='sell': record_crypto_sell(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,from_wallet…
   |                      ^
56 | …     elif tx=='fee': record_crypto_transfer(conn,asset_id=aid,from_wallet_id=wallet(conn,require_text(row,'from_wallet_name',line)),t…
57 | …     else: raise ValueError(f"line {line}: unsupported crypto transaction type")
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:56:27
   |
54 |             elif tx=='buy': record_crypto_buy(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,to_wall…
55 |             elif tx=='sell': record_crypto_sell(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,from_…
56 |             elif tx=='fee': record_crypto_transfer(conn,asset_id=aid,from_wallet_id=wallet(conn,require_text(row,'from_wallet_name',li…
   |                           ^
57 |             else: raise ValueError(f"line {line}: unsupported crypto transaction type")
58 |     sid=create_import_session(conn,import_type='crypto_transactions',source_filename=source_filename,file_hash=source_hash(path),statu…
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_transactions_importer.py:57:17
   |
55 |             elif tx=='sell': record_crypto_sell(conn,account_id=account(conn,require_text(row,'account_name',line)),asset_id=aid,from_…
56 |             elif tx=='fee': record_crypto_transfer(conn,asset_id=aid,from_wallet_id=wallet(conn,require_text(row,'from_wallet_name',li…
57 |             else: raise ValueError(f"line {line}: unsupported crypto transaction type")
   |                 ^
58 |     sid=create_import_session(conn,import_type='crypto_transactions',source_filename=source_filename,file_hash=source_hash(path),statu…
59 |     return ImportResult(sid,'crypto_transactions',status,len(rows),new if not errors else 0,existing,len(errors),errors)
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_wallets_importer.py:12:55
   |
11 | def import_crypto_wallets_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
12 |     rows, errors=read_csv_rows(path, REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
   |                                                       ^
13 |     new=existing=0; validated=[]; seen=set()
14 |     for line,row in enumerate(rows,start=2):
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_wallets_importer.py:13:19
   |
11 | def import_crypto_wallets_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
12 |     rows, errors=read_csv_rows(path, REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
13 |     new=existing=0; validated=[]; seen=set()
   |                   ^
14 |     for line,row in enumerate(rows,start=2):
15 |         try:
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_wallets_importer.py:13:33
   |
11 | def import_crypto_wallets_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
12 |     rows, errors=read_csv_rows(path, REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
13 |     new=existing=0; validated=[]; seen=set()
   |                                 ^
14 |     for line,row in enumerate(rows,start=2):
15 |         try:
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_wallets_importer.py:17:26
   |
15 |         try:
16 |             rh=compute_row_hash(row)
17 |             if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
   |                          ^
18 |             seen.add(rh); name=require_text(row,"wallet_name",line); typ=require_text(row,"wallet_type",line)
19 |             exists=conn.execute("SELECT 1 FROM crypto_wallets WHERE wallet_name=?",(name,)).fetchone() is not None
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_wallets_importer.py:18:25
   |
16 |             rh=compute_row_hash(row)
17 |             if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
18 |             seen.add(rh); name=require_text(row,"wallet_name",line); typ=require_text(row,"wallet_type",line)
   |                         ^
19 |             exists=conn.execute("SELECT 1 FROM crypto_wallets WHERE wallet_name=?",(name,)).fetchone() is not None
20 |             existing += 1 if exists else 0; new += 0 if exists else 1
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_wallets_importer.py:18:68
   |
16 |             rh=compute_row_hash(row)
17 |             if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
18 |             seen.add(rh); name=require_text(row,"wallet_name",line); typ=require_text(row,"wallet_type",line)
   |                                                                    ^
19 |             exists=conn.execute("SELECT 1 FROM crypto_wallets WHERE wallet_name=?",(name,)).fetchone() is not None
20 |             existing += 1 if exists else 0; new += 0 if exists else 1
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/crypto_wallets_importer.py:20:43
   |
18 | …         seen.add(rh); name=require_text(row,"wallet_name",line); typ=require_text(row,"wallet_type",line)
19 | …         exists=conn.execute("SELECT 1 FROM crypto_wallets WHERE wallet_name=?",(name,)).fetchone() is not None
20 | …         existing += 1 if exists else 0; new += 0 if exists else 1
   |                                         ^
21 | …         validated.append((name,typ,clean(row.get('platform_provider')) or None,clean(row.get('network_chain')) or None,clean(row.get…
22 | …     except ValueError as exc: errors.append(str(exc))
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_wallets_importer.py:22:33
   |
20 |             existing += 1 if exists else 0; new += 0 if exists else 1
21 |             validated.append((name,typ,clean(row.get('platform_provider')) or None,clean(row.get('network_chain')) or None,clean(row.g…
22 |         except ValueError as exc: errors.append(str(exc))
   |                                 ^
23 |     status='failed' if errors else ('committed' if commit else 'dry_run_ok')
24 |     if commit and not errors:
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/crypto_wallets_importer.py:26:99
   |
24 |     if commit and not errors:
25 |         for name,typ,provider,chain,notes in validated:
26 |             if conn.execute("SELECT 1 FROM crypto_wallets WHERE wallet_name=?",(name,)).fetchone(): continue
   |                                                                                                   ^
27 |             create_wallet(conn,wallet_name=name,wallet_type=typ,platform_provider=provider,network_chain=chain,notes=notes)
28 |     sid=create_import_session(conn,import_type='crypto_wallets',source_filename=source_filename,file_hash=source_hash(path),status=sta…
   |

F401 [*] `dataclasses.field` imported but unused
  --> src/jarvis_finance/imports/instrument_candidates.py:8:36
   |
 6 | import re
 7 | import zipfile
 8 | from dataclasses import dataclass, field
   |                                    ^^^^^
 9 | from decimal import Decimal, InvalidOperation
10 | from pathlib import Path
   |
help: Remove unused import: `dataclasses.field`

E741 Ambiguous variable name: `l`
   --> src/jarvis_finance/imports/instrument_candidates.py:185:34
    |
183 | …         continue
184 | …     isin = isin_match.group(0)
185 | …     prev_candidates = [l for l in lines[max(0, idx - 5):idx - 1] if not any(x in l.upper() for x in ("POSITIONSWERT", "ISIN", "CHF …
    |                                ^
186 | …     name = _norm_space(prev_candidates[-1] if prev_candidates else raw.replace(isin, ""))[:240]
187 | …     window = " ".join(lines[idx:min(len(lines), idx + 7)])
    |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:8:58
   |
 6 | from .dry_run import read_csv_rows
 7 | from .row_hash import compute_row_hash
 8 | REQUIRED_COLUMNS={"name","asset_class","reason","status"}; VALID_STATUS={'active','paused','rejected','closed'}; VALID_CLASS={'Stock',…
   |                                                          ^
 9 | def dec(v):
10 |     t=clean(v)
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:8:112
   |
 6 | from .dry_run import read_csv_rows
 7 | from .row_hash import compute_row_hash
 8 | REQUIRED_COLUMNS={"name","asset_class","reason","status"}; VALID_STATUS={'active','paused','rejected','closed'}; VALID_CLASS={'Stock',…
   |                                                                                                                ^
 9 | def dec(v):
10 |     t=clean(v)
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/watchlist_importer.py:11:13
   |
 9 | def dec(v):
10 |     t=clean(v)
11 |     if not t: return None
   |             ^
12 |     return Decimal(t)
13 | def import_watchlist_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:14:53
   |
12 |     return Decimal(t)
13 | def import_watchlist_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
14 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
   |                                                     ^
15 |     new=existing=0; vals=[]; seen=set()
16 |     for line,row in enumerate(rows,start=2):
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:15:19
   |
13 | def import_watchlist_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
14 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
15 |     new=existing=0; vals=[]; seen=set()
   |                   ^
16 |     for line,row in enumerate(rows,start=2):
17 |         try:
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:15:28
   |
13 | def import_watchlist_csv(conn:Connection,path:str|Path,*,commit:bool,source_filename:str|None=None)->ImportResult:
14 |     rows,errors=read_csv_rows(path,REQUIRED_COLUMNS); source_filename=source_filename or Path(path).name
15 |     new=existing=0; vals=[]; seen=set()
   |                            ^
16 |     for line,row in enumerate(rows,start=2):
17 |         try:
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/watchlist_importer.py:19:26
   |
17 | …     try:
18 | …         rh=compute_row_hash(row)
19 | …         if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
   |                        ^
20 | …         seen.add(rh); name=require_text(row,'name',line); ac=require_text(row,'asset_class',line); reason=require_text(row,'reason',…
21 | …         if ac not in VALID_CLASS: raise ValueError(f"line {line}: invalid asset_class")
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:20:25
   |
18 | …     rh=compute_row_hash(row)
19 | …     if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
20 | …     seen.add(rh); name=require_text(row,'name',line); ac=require_text(row,'asset_class',line); reason=require_text(row,'reason',line…
   |                   ^
21 | …     if ac not in VALID_CLASS: raise ValueError(f"line {line}: invalid asset_class")
22 | …     if status not in VALID_STATUS: raise ValueError(f"line {line}: invalid status")
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:20:61
   |
18 | …     rh=compute_row_hash(row)
19 | …     if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
20 | …     seen.add(rh); name=require_text(row,'name',line); ac=require_text(row,'asset_class',line); reason=require_text(row,'reason',line…
   |                                                       ^
21 | …     if ac not in VALID_CLASS: raise ValueError(f"line {line}: invalid asset_class")
22 | …     if status not in VALID_STATUS: raise ValueError(f"line {line}: invalid status")
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:20:102
   |
18 | …     rh=compute_row_hash(row)
19 | …     if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
20 | …     seen.add(rh); name=require_text(row,'name',line); ac=require_text(row,'asset_class',line); reason=require_text(row,'reason',line…
   |                                                                                                ^
21 | …     if ac not in VALID_CLASS: raise ValueError(f"line {line}: invalid asset_class")
22 | …     if status not in VALID_STATUS: raise ValueError(f"line {line}: invalid status")
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:20:142
   |
18 | …
19 | …sh in source file")
20 | …xt(row,'asset_class',line); reason=require_text(row,'reason',line); status=require_text(row,'status',line).lower()
   |                                                                    ^
21 | …id asset_class")
22 | …invalid status")
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/watchlist_importer.py:21:37
   |
19 | …     if rh in seen: raise ValueError(f"line {line}: duplicate row hash in source file")
20 | …     seen.add(rh); name=require_text(row,'name',line); ac=require_text(row,'asset_class',line); reason=require_text(row,'reason',line…
21 | …     if ac not in VALID_CLASS: raise ValueError(f"line {line}: invalid asset_class")
   |                               ^
22 | …     if status not in VALID_STATUS: raise ValueError(f"line {line}: invalid status")
23 | …     wid=stable_id('watch',name,ac); exists=conn.execute('SELECT 1 FROM watchlist WHERE watchlist_id=?',(wid,)).fetchone() is not None
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/watchlist_importer.py:22:42
   |
20 | …     seen.add(rh); name=require_text(row,'name',line); ac=require_text(row,'asset_class',line); reason=require_text(row,'reason',line…
21 | …     if ac not in VALID_CLASS: raise ValueError(f"line {line}: invalid asset_class")
22 | …     if status not in VALID_STATUS: raise ValueError(f"line {line}: invalid status")
   |                                    ^
23 | …     wid=stable_id('watch',name,ac); exists=conn.execute('SELECT 1 FROM watchlist WHERE watchlist_id=?',(wid,)).fetchone() is not None
24 | …     existing += 1 if exists else 0; new += 0 if exists else 1
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:23:43
   |
21 | …     if ac not in VALID_CLASS: raise ValueError(f"line {line}: invalid asset_class")
22 | …     if status not in VALID_STATUS: raise ValueError(f"line {line}: invalid status")
23 | …     wid=stable_id('watch',name,ac); exists=conn.execute('SELECT 1 FROM watchlist WHERE watchlist_id=?',(wid,)).fetchone() is not None
   |                                     ^
24 | …     existing += 1 if exists else 0; new += 0 if exists else 1
25 | …     vals.append((wid,name,ac,reason,status,dec(row.get('target_entry_price')),clean(row.get('target_entry_currency')) or None,clean(…
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/imports/watchlist_importer.py:24:43
   |
22 | …         if status not in VALID_STATUS: raise ValueError(f"line {line}: invalid status")
23 | …         wid=stable_id('watch',name,ac); exists=conn.execute('SELECT 1 FROM watchlist WHERE watchlist_id=?',(wid,)).fetchone() is not…
24 | …         existing += 1 if exists else 0; new += 0 if exists else 1
   |                                         ^
25 | …         vals.append((wid,name,ac,reason,status,dec(row.get('target_entry_price')),clean(row.get('target_entry_currency')) or None,cl…
26 | …     except (ValueError,InvalidOperation) as exc: errors.append(str(exc))
   |

E701 Multiple statements on one line (colon)
  --> src/jarvis_finance/imports/watchlist_importer.py:26:52
   |
24 |             existing += 1 if exists else 0; new += 0 if exists else 1
25 |             vals.append((wid,name,ac,reason,status,dec(row.get('target_entry_price')),clean(row.get('target_entry_currency')) or None,…
26 |         except (ValueError,InvalidOperation) as exc: errors.append(str(exc))
   |                                                    ^
27 |     status_txt='failed' if errors else ('committed' if commit else 'dry_run_ok')
28 |     if commit and not errors:
   |

F401 [*] `uuid` imported but unused
 --> src/jarvis_finance/ledger/corrections.py:3:8
  |
1 | from __future__ import annotations
2 |
3 | import uuid
  |        ^^^^
4 | from sqlite3 import Connection
  |
help: Remove unused import: `uuid`

F401 [*] `dataclasses.field` imported but unused
 --> src/jarvis_finance/market_data/catalog.py:7:36
  |
5 | import re
6 | import time
7 | from dataclasses import dataclass, field
  |                                    ^^^^^
8 | from pathlib import Path
9 | from sqlite3 import Connection
  |
help: Remove unused import: `dataclasses.field`

E402 Module level import not at top of file
  --> src/jarvis_finance/market_data/catalog.py:15:1
   |
13 | ISIN_RE = re.compile(r"^[A-Z]{2}[A-Z0-9]{9}[0-9]$")
14 |
15 | from jarvis_finance.audit.log import record_audit_event
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16 | from jarvis_finance.imports.common import stable_id, utc_now
17 | from jarvis_finance.market_data.candidates import ProviderCandidate, LookupUnavailable
   |

E402 Module level import not at top of file
  --> src/jarvis_finance/market_data/catalog.py:16:1
   |
15 | from jarvis_finance.audit.log import record_audit_event
16 | from jarvis_finance.imports.common import stable_id, utc_now
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17 | from jarvis_finance.market_data.candidates import ProviderCandidate, LookupUnavailable
18 | from jarvis_finance.market_data.instruments import ensure_instrument_metadata_quality
   |

E402 Module level import not at top of file
  --> src/jarvis_finance/market_data/catalog.py:17:1
   |
15 | from jarvis_finance.audit.log import record_audit_event
16 | from jarvis_finance.imports.common import stable_id, utc_now
17 | from jarvis_finance.market_data.candidates import ProviderCandidate, LookupUnavailable
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18 | from jarvis_finance.market_data.instruments import ensure_instrument_metadata_quality
19 | from jarvis_finance.quality.alerts import create_alert
   |

E402 Module level import not at top of file
  --> src/jarvis_finance/market_data/catalog.py:18:1
   |
16 | from jarvis_finance.imports.common import stable_id, utc_now
17 | from jarvis_finance.market_data.candidates import ProviderCandidate, LookupUnavailable
18 | from jarvis_finance.market_data.instruments import ensure_instrument_metadata_quality
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
19 | from jarvis_finance.quality.alerts import create_alert
   |

E402 Module level import not at top of file
  --> src/jarvis_finance/market_data/catalog.py:19:1
   |
17 | from jarvis_finance.market_data.candidates import ProviderCandidate, LookupUnavailable
18 | from jarvis_finance.market_data.instruments import ensure_instrument_metadata_quality
19 | from jarvis_finance.quality.alerts import create_alert
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20 |
21 | ASSET_CLASSES = {"equity", "stock", "etf", "crypto", "cash", "other"}
   |

F401 [*] `datetime.timezone` imported but unused
 --> src/jarvis_finance/reports/crypto_inventory.py:6:32
  |
4 | import json
5 | import uuid
6 | from datetime import datetime, timezone
  |                                ^^^^^^^^
7 | from decimal import Decimal, ROUND_HALF_UP
8 | from pathlib import Path
  |
help: Remove unused import: `datetime.timezone`

F401 [*] `sqlite3` imported but unused
 --> src/jarvis_finance/services/asset_price_refresh.py:5:8
  |
3 | import hashlib
4 | import json
5 | import sqlite3
  |        ^^^^^^^
6 | import uuid
7 | from dataclasses import asdict
  |
help: Remove unused import: `sqlite3`

F401 [*] `dataclasses.asdict` imported but unused
 --> src/jarvis_finance/services/asset_price_refresh.py:7:25
  |
5 | import sqlite3
6 | import uuid
7 | from dataclasses import asdict
  |                         ^^^^^^
8 | from datetime import UTC, datetime, timedelta
9 | from pathlib import Path
  |
help: Remove unused import: `dataclasses.asdict`

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_analytics_v2.py:151:34
    |
149 |     cum_income = cum_expense = Decimal("0")
150 |     for month, values in sorted(monthly.items()):
151 |         income = values["income"]; expense = values["expense"]; net = income - expense
    |                                  ^
152 |         cum_income += income; cum_expense += expense
153 |         cashflow.append({"month": month, "income_chf": _fmt(income), "expense_chf": _fmt(expense), "net_cashflow_chf": _fmt(net)})
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_analytics_v2.py:151:63
    |
149 |     cum_income = cum_expense = Decimal("0")
150 |     for month, values in sorted(monthly.items()):
151 |         income = values["income"]; expense = values["expense"]; net = income - expense
    |                                                               ^
152 |         cum_income += income; cum_expense += expense
153 |         cashflow.append({"month": month, "income_chf": _fmt(income), "expense_chf": _fmt(expense), "net_cashflow_chf": _fmt(net)})
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_analytics_v2.py:152:29
    |
150 |     for month, values in sorted(monthly.items()):
151 |         income = values["income"]; expense = values["expense"]; net = income - expense
152 |         cum_income += income; cum_expense += expense
    |                             ^
153 |         cashflow.append({"month": month, "income_chf": _fmt(income), "expense_chf": _fmt(expense), "net_cashflow_chf": _fmt(net)})
154 |         trend.append({"month": month, "cumulative_income_chf": _fmt(cum_income), "cumulative_expense_chf": _fmt(cum_expense), "cumula…
    |

F841 Local variable `audit_id` is assigned to but never used
   --> src/jarvis_finance/services/budget_categories.py:222:5
    |
220 |         (canonical_id, name, category_type, sort_order, ts, ts),
221 |     )
222 |     audit_id = record_audit_event(conn, source="phase110_cleanup", action="ensure_canonical_category", entity_type="budget_category",…
    |     ^^^^^^^^
223 |     return canonical_id
    |
help: Remove assignment to unused variable `audit_id`

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_categories.py:296:16
    |
294 |         ORDER BY c.sort_order, c.name
295 |     """).fetchall()
296 |     mappings=[]; review=[]; empty=[]
    |                ^
297 |     for r in rows:
298 |         name=str(r["name"])
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_categories.py:296:27
    |
294 |         ORDER BY c.sort_order, c.name
295 |     """).fetchall()
296 |     mappings=[]; review=[]; empty=[]
    |                           ^
297 |     for r in rows:
298 |         name=str(r["name"])
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_categories.py:372:15
    |
371 | def reorder_categories(conn: Connection, category_ids: list[str], *, category_type: str = "expense") -> dict:
372 |     ts = now(); updated = 0; old_order=[]; new_order=[]
    |               ^
373 |     for idx, cid in enumerate(category_ids, start=1):
374 |         row = conn.execute("SELECT category_id, category_type, sort_order FROM budget_categories WHERE category_id=? AND category_typ…
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_categories.py:372:28
    |
371 | def reorder_categories(conn: Connection, category_ids: list[str], *, category_type: str = "expense") -> dict:
372 |     ts = now(); updated = 0; old_order=[]; new_order=[]
    |                            ^
373 |     for idx, cid in enumerate(category_ids, start=1):
374 |         row = conn.execute("SELECT category_id, category_type, sort_order FROM budget_categories WHERE category_id=? AND category_typ…
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_categories.py:372:42
    |
371 | def reorder_categories(conn: Connection, category_ids: list[str], *, category_type: str = "expense") -> dict:
372 |     ts = now(); updated = 0; old_order=[]; new_order=[]
    |                                          ^
373 |     for idx, cid in enumerate(category_ids, start=1):
374 |         row = conn.execute("SELECT category_id, category_type, sort_order FROM budget_categories WHERE category_id=? AND category_typ…
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_categories.py:403:139
    |
401 | …
402 | …un", "synthetic"]):
403 | …t_category_id=NULL, updated_at=? WHERE category_id=?", (ts, cid)); archived_test += 1
    |                                                                   ^
404 | …rchive_test_category", entity_type="budget_category", entity_id=cid, old_values=row_to_dict(row), new_values={"is_active": 0}, creat…
405 | …
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_categories.py:410:139
    |
408 | …ated_at=? WHERE category_id=?", (admin_id, ts, cid))
409 | …ed_category_id=?, proposed_category_name=?, updated_at=? WHERE proposed_category_id=?", (admin_id, "Sonstiges / Administration", ts,…
410 | …t_category_id=NULL, updated_at=? WHERE category_id=?", (ts, cid)); merged_duplicates += 1
    |                                                                   ^
411 | …erge_category", entity_type="budget_category", entity_id=admin_id, new_values={"source_category_id": cid, "target_name": "Sonstiges …
412 | …
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_categories.py:414:139
    |
412 | …
413 | …
414 | …t_category_id=NULL, updated_at=? WHERE category_id=?", (ts, cid)); flattened += 1
    |                                                                   ^
415 | …rchive_visual_parent_category", entity_type="budget_category", entity_id=cid, old_values=row_to_dict(row), new_values={"is_active": …
416 | …
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_categories.py:423:139
    |
421 | …ated_at=? WHERE category_id=?", (target, ts, cid))
422 | …ed_category_id=?, updated_at=? WHERE proposed_category_id=?", (target, ts, cid))
423 | …t_category_id=NULL, updated_at=? WHERE category_id=?", (ts, cid)); merged_duplicates += 1
    |                                                                   ^
424 | …erge_duplicate_category", entity_type="budget_category", entity_id=target, new_values={"source_category_id": cid}, created_by="syste…
425 | …
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_categories.py:428:130
    |
426 | …
427 | …
428 | …t_category_id=NULL, updated_at=? WHERE category_id=?", (ts, cid)); flattened += 1
    |                                                                   ^
429 | …
430 | …_CATEGORIES.index(name) + 1) * 10)
    |

F401 [*] `sqlite3.Connection` imported but unused
 --> src/jarvis_finance/services/budget_common.py:5:21
  |
3 | from datetime import UTC, datetime
4 | from decimal import Decimal, InvalidOperation
5 | from sqlite3 import Connection
  |                     ^^^^^^^^^^
6 | from uuid import uuid4
  |
help: Remove unused import: `sqlite3.Connection`

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/budget_data_explorer.py:38:48
   |
36 |     date_to = filters.get("date_to") or ""
37 |     if date_from:
38 |         clauses.append("t.transaction_date>=?"); params.append(date_from)
   |                                                ^
39 |     if date_to:
40 |         clauses.append("t.transaction_date<=?"); params.append(date_to)
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/budget_data_explorer.py:40:48
   |
38 |         clauses.append("t.transaction_date>=?"); params.append(date_from)
39 |     if date_to:
40 |         clauses.append("t.transaction_date<=?"); params.append(date_to)
   |                                                ^
41 |     if month:
42 |         clauses.append("substr(t.transaction_date,1,7)=?"); params.append(month)
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/budget_data_explorer.py:42:59
   |
40 |         clauses.append("t.transaction_date<=?"); params.append(date_to)
41 |     if month:
42 |         clauses.append("substr(t.transaction_date,1,7)=?"); params.append(month)
   |                                                           ^
43 |     elif year:
44 |         clauses.append("substr(t.transaction_date,1,4)=?"); params.append(year)
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/budget_data_explorer.py:44:59
   |
42 |         clauses.append("substr(t.transaction_date,1,7)=?"); params.append(month)
43 |     elif year:
44 |         clauses.append("substr(t.transaction_date,1,4)=?"); params.append(year)
   |                                                           ^
45 |     if tx_mode == "income":
46 |         clauses.append("t.transaction_type IN ('income','refund')")
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/budget_data_explorer.py:50:42
   |
48 |         clauses.append("t.transaction_type IN ('expense','fee')")
49 |     if filters.get("category_id"):
50 |         clauses.append("t.category_id=?"); params.append(str(filters["category_id"]))
   |                                          ^
51 |     if filters.get("account_id"):
52 |         clauses.append("t.account_id=?"); params.append(str(filters["account_id"]))
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/budget_data_explorer.py:52:41
   |
50 |         clauses.append("t.category_id=?"); params.append(str(filters["category_id"]))
51 |     if filters.get("account_id"):
52 |         clauses.append("t.account_id=?"); params.append(str(filters["account_id"]))
   |                                         ^
53 |     if filters.get("source_type"):
54 |         clauses.append("t.source_type=?"); params.append(str(filters["source_type"]))
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/budget_data_explorer.py:54:42
   |
52 |         clauses.append("t.account_id=?"); params.append(str(filters["account_id"]))
53 |     if filters.get("source_type"):
54 |         clauses.append("t.source_type=?"); params.append(str(filters["source_type"]))
   |                                          ^
55 |     if filters.get("merchant"):
56 |         clauses.append("lower(COALESCE(t.payee,'') || ' ' || COALESCE(t.description,'') || ' ' || COALESCE(m.display_name,'')) LIKE lo…
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/budget_data_explorer.py:62:104
   |
60 |         params.append(f"%{filters['search']}%")
61 |     if filters.get("amount_min"):
62 |         clauses.append("CAST(COALESCE(t.amount_chf,t.amount_original,'0') AS REAL) >= CAST(? AS REAL)"); params.append(str(filters["am…
   |                                                                                                        ^
63 |     if filters.get("amount_max"):
64 |         clauses.append("CAST(COALESCE(t.amount_chf,t.amount_original,'0') AS REAL) <= CAST(? AS REAL)"); params.append(str(filters["am…
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/budget_data_explorer.py:64:104
   |
62 |         clauses.append("CAST(COALESCE(t.amount_chf,t.amount_original,'0') AS REAL) >= CAST(? AS REAL)"); params.append(str(filters["am…
63 |     if filters.get("amount_max"):
64 |         clauses.append("CAST(COALESCE(t.amount_chf,t.amount_original,'0') AS REAL) <= CAST(? AS REAL)"); params.append(str(filters["am…
   |                                                                                                        ^
65 |     if filters.get("tag"):
66 |         clauses.append("EXISTS (SELECT 1 FROM budget_transaction_tags xt JOIN budget_tags xg ON xg.tag_id=xt.tag_id WHERE xt.budget_tr…
   |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_data_explorer.py:104:26
    |
102 | def get_budget_data_explorer(conn: Connection, **filters: str) -> dict[str, Any]:
103 |     rows = _base_confirmed_rows(conn, filters)
104 |     income = Decimal("0"); expense = Decimal("0"); total = Decimal("0")
    |                          ^
105 |     category_totals: dict[str, Decimal] = defaultdict(Decimal)
106 |     merchant_totals: dict[str, Decimal] = defaultdict(Decimal)
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_data_explorer.py:104:50
    |
102 | def get_budget_data_explorer(conn: Connection, **filters: str) -> dict[str, Any]:
103 |     rows = _base_confirmed_rows(conn, filters)
104 |     income = Decimal("0"); expense = Decimal("0"); total = Decimal("0")
    |                                                  ^
105 |     category_totals: dict[str, Decimal] = defaultdict(Decimal)
106 |     merchant_totals: dict[str, Decimal] = defaultdict(Decimal)
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_data_explorer.py:108:27
    |
106 |     merchant_totals: dict[str, Decimal] = defaultdict(Decimal)
107 |     merchant_counts: Counter[str] = Counter()
108 |     largest = Decimal("0"); largest_row: dict[str, Any] | None = None
    |                           ^
109 |     views = []
110 |     for row in rows:
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_data_explorer.py:133:29
    |
131 |         views.append(view)
132 |         if amount > largest:
133 |             largest = amount; largest_row = view
    |                             ^
134 |     count = len(rows)
135 |     avg = Decimal("0") if count == 0 else total / Decimal(count)
    |

F841 Local variable `avg` is assigned to but never used
   --> src/jarvis_finance/services/budget_data_explorer.py:135:5
    |
133 |             largest = amount; largest_row = view
134 |     count = len(rows)
135 |     avg = Decimal("0") if count == 0 else total / Decimal(count)
    |     ^^^
136 |     top_category = max(category_totals.items(), key=lambda kv: kv[1])[0] if category_totals else "—"
137 |     top_merchant = max(merchant_totals.items(), key=lambda kv: kv[1])[0] if merchant_totals else "—"
    |
help: Remove assignment to unused variable `avg`

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_data_explorer.py:186:15
    |
184 |     y = int(year)
185 |     if m == 0:
186 |         y -= 1; m = 12
    |               ^
187 |     prev_month = f"{y:04d}-{m:02d}"
188 |     prev_filters = dict(filters) | {"month": prev_month}
    |

F401 [*] `json` imported but unused
 --> src/jarvis_finance/services/budget_import_upload.py:4:8
  |
3 | import hashlib
4 | import json
  |        ^^^^
5 | import os
6 | import re
  |
help: Remove unused import: `json`

F401 [*] `jarvis_finance.services.budget_common.new_id` imported but unused
  --> src/jarvis_finance/services/budget_import_upload.py:12:51
   |
10 | from urllib.parse import urlencode
11 |
12 | from jarvis_finance.services.budget_common import new_id, now, row_to_dict
   |                                                   ^^^^^^
13 | from jarvis_finance.services.budget_csv_imports import IMPORT_PROFILES, import_budget_csv_text, parse_budget_csv_text
14 | from jarvis_finance.services.budget_monthly_import import _source_from_profile
   |
help: Remove unused import: `jarvis_finance.services.budget_common.new_id`

F401 [*] `datetime.datetime` imported but unused
 --> src/jarvis_finance/services/budget_monthly_import.py:6:22
  |
4 | import subprocess
5 | from collections import Counter, defaultdict
6 | from datetime import datetime
  |                      ^^^^^^^^
7 | from pathlib import Path
8 | from sqlite3 import Connection
  |
help: Remove unused import: `datetime.datetime`

F401 [*] `jarvis_finance.services.budget_csv_imports.detect_import_profile` imported but unused
  --> src/jarvis_finance/services/budget_monthly_import.py:14:56
   |
12 | from jarvis_finance.audit.log import record_audit_event
13 | from jarvis_finance.services.budget_common import new_id, now, row_to_dict
14 | from jarvis_finance.services.budget_csv_imports import detect_import_profile, import_budget_csv_text, parse_budget_csv_text
   |                                                        ^^^^^^^^^^^^^^^^^^^^^
15 | from jarvis_finance.services.budget_imports import _normalise, _normalized_merchant_name, candidate_user_count_summary, create_budget_…
   |
help: Remove unused import: `jarvis_finance.services.budget_csv_imports.detect_import_profile`

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_monthly_import.py:187:60
    |
185 |         params.append(f"%{source_scope.lower()}%")
186 |     if exclude_id:
187 |         source_clause += " AND transaction_candidate_id<>?"; params.append(exclude_id)
    |                                                            ^
188 |     sql = "SELECT COUNT(*) FROM budget_transaction_candidates WHERE status NOT IN ('confirmed','ignored','covered_by_migros','covered…
189 |     return int(conn.execute(sql, params).fetchone()[0] or 0)
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/budget_monthly_import.py:227:61
    |
225 |         source_clause = ""
226 |         if row["source_scope"] not in {"", "all", None}:
227 |             source_clause = " AND lower(source_type) LIKE ?"; params.append(f"%{str(row['source_scope']).lower()}%")
    |                                                             ^
228 |         params.extend([row["category_id"], row["category_name"], rule["rule_id"], row["merchant_pattern"], now()])
229 |         sql = """
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/grocery_optimizer.py:376:30
    |
374 |     replaceable = 0
375 |     for option in item_options:
376 |         item = option["item"]; item_price = option["item_price"]
    |                              ^
377 |         safe_in_allowed = [m for m in option["safe"] if m["retailer"] in allowed_retailers]
378 |         best = min(safe_in_allowed, key=lambda m: _money(m["candidate_price_text"]), default=None)
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/grocery_optimizer.py:430:27
    |
428 |     if not row:
429 |         raise ValueError("run_not_found")
430 |     run = row_to_dict(row); summary = json.loads(run["summary_json"])
    |                           ^
431 |     items = _items_for_receipt(conn, run["receipt_id"])
432 |     date = items[0].get("purchase_date") if items else run["run_date"][:10]
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/grocery_optimizer.py:468:107
    |
466 |     lines += ["", "## Quellen", "Alle Alternativen enthalten Quellen-URLs und Zeitstempel im lokalen Cache.", "", "## Disclaimer", "P…
467 |     path.write_text("\n".join(lines), encoding="utf-8")
468 |     conn.execute("UPDATE grocery_optimization_runs SET report_path=? WHERE run_id=?", (str(path), run_id)); conn.commit()
    |                                                                                                           ^
469 |     return {"run_id": run_id, "format": "markdown", "report_path": str(path), "runtime_only": True}
    |

F401 [*] `hashlib` imported but unused
 --> src/jarvis_finance/services/grocery_price_providers.py:3:8
  |
1 | from __future__ import annotations
2 |
3 | import hashlib
  |        ^^^^^^^
4 | import html
5 | import json
  |
help: Remove unused import: `hashlib`

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/reports_v1.py:41:26
   |
39 | def _month_bounds(period: str) -> tuple[str, str, str]:
40 |     month = period if len(period) == 7 else f"{period[:4]}-01"
41 |     year = int(month[:4]); mon = int(month[5:7])
   |                          ^
42 |     next_year = year + (1 if mon == 12 else 0)
43 |     next_mon = 1 if mon == 12 else mon + 1
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/reports_v1.py:75:48
   |
73 |     ).fetchall()
74 |     totals = {r[0]: _d(r[1]) for r in rows}
75 |     income = totals.get("income", Decimal("0")); expense = totals.get("expense", Decimal("0")); net = income - expense
   |                                                ^
76 |     return {
77 |         "period": month,
   |

E702 Multiple statements on one line (semicolon)
  --> src/jarvis_finance/services/reports_v1.py:75:95
   |
73 |     ).fetchall()
74 |     totals = {r[0]: _d(r[1]) for r in rows}
75 |     income = totals.get("income", Decimal("0")); expense = totals.get("expense", Decimal("0")); net = income - expense
   |                                                                                               ^
76 |     return {
77 |         "period": month,
   |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/reports_v1.py:194:38
    |
192 |         sections.update({"candidates_by_source": _review_source_counts(conn, year), "merchant_rule_status": [], "next_review_actions"…
193 |     elif report_type == "crypto_status":
194 |         p = _portfolio_sections(conn); sections.update({"crypto_summary": p["summary"], "wallet_verification": "lokale Daten", "crypt…
    |                                      ^
195 |     elif report_type == "portfolio_status":
196 |         p = _portfolio_sections(conn); sections.update({"portfolio_summary": p["summary"], "largest_positions": p["largest_positions"…
    |

E702 Multiple statements on one line (semicolon)
   --> src/jarvis_finance/services/reports_v1.py:196:38
    |
194 |         p = _portfolio_sections(conn); sections.update({"crypto_summary": p["summary"], "wallet_verification": "lokale Daten", "crypt…
195 |     elif report_type == "portfolio_status":
196 |         p = _portfolio_sections(conn); sections.update({"portfolio_summary": p["summary"], "largest_positions": p["largest_positions"…
    |                                      ^
197 |     return {"purpose": "finance_report_v1_preview", "report_type": report_type, "period": period, "totals": totals, "review": review,…
    |

Found 167 errors.
[*] 20 fixable with the `--fix` option (3 hidden fixes can be enabled with the `--unsafe-fixes` option).

__HERMES_CWD_8d46a20096ed__/home/agent/.hermes/worktrees/FinanceManager-sprint23__HERMES_CWD_8d46a20096ed__
