Files
fin/.claude/skills/fin-diagnostics-and-tooling/scripts/audit_balances.py
T
EislM0203andClaude Opus 5 cb97d97d81 docs: check in the fin skill library, ignore local Claude settings
Twelve skills that had been sitting uncommitted in the working tree: the
architecture contract, accounting reference, change-control rulebook, failure
archaeology, debugging playbook, diagnostics scripts, ledger-analysis toolkit,
research frontier, run/operate runbook, build-and-env, test-coverage campaign,
and validation/QA guide.

Updates three frontier statuses to match what actually shipped today rather
than checking in a roadmap that describes closed work as open:

- A1 double-entry hardening -> partly-done. The create half is enforced; what
  remains is running the existing audit_linked_legs.py against a prod backup.
- B3 safety rails -> partly-done. Idempotency shipped, including the note that
  the index is not unique-when-not-null and why. dry_run is still open.
- D3 CSV import -> unblocked but still gated. Its stated blocker was B3's
  idempotency keys, which now exist.

Also gitignores .claude/settings.local.json, which is per-machine and was
showing up as untracked on every status.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3qYLuu9jgXsCRXmJ18YGY
2026-08-22 22:09:22 +00:00

87 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Compute per-account balances (SUM(amount - fee)) and flag masking overrides.
READ-ONLY. Usage: python3 audit_balances.py /path/to/fin.db [--allow-overrides] [--json]
Ground truth: api/app/services/net_worth.py — for bank/splitwise/receivable/
payable accounts, balance = SUM(amount - fee) over all transactions, UNLESS
manual_override_balance is set, in which case the override silently replaces
the computed value in net worth. Investment/rsu balances come from holdings x
price_cache, not from transactions (any computed value shown for them here is
just leftover cash legs and is not used by net worth).
Anomalies:
- manual_override_balance set (masks the computed balance; suppress with
--allow-overrides if the owner set it deliberately)
- transactions whose account_id points at a nonexistent account (invisible
to every balance and net-worth calculation)
Exit 0 = clean, 1 = anomalies found.
"""
import argparse
import json
import sqlite3
import sys
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("db")
ap.add_argument("--allow-overrides", action="store_true",
help="do not count manual_override_balance as an anomaly")
ap.add_argument("--json", action="store_true")
args = ap.parse_args()
con = sqlite3.connect(f"file:{args.db}?mode=ro", uri=True)
con.row_factory = sqlite3.Row
rows = con.execute(
"SELECT a.id, a.name, a.type, a.currency, a.is_active, "
" a.manual_override_balance AS override, "
" COUNT(t.id) AS txn_count, "
" COALESCE(SUM(t.amount - t.fee), 0.0) AS computed "
"FROM accounts a LEFT JOIN transactions t ON t.account_id = a.id "
"GROUP BY a.id ORDER BY a.type, a.id").fetchall()
orphans = con.execute(
"SELECT id, account_id, amount, date FROM transactions "
"WHERE account_id NOT IN (SELECT id FROM accounts)").fetchall()
anomalies: list[dict] = []
accounts = []
for r in rows:
d = dict(r)
d["computed"] = round(d["computed"], 2)
if d["override"] is not None:
d["override_delta"] = round(d["override"] - d["computed"], 2)
if not args.allow_overrides:
anomalies.append({
"check": "manual-override", "account_id": d["id"],
"detail": (f"{d['name']} ({d['type']}): override "
f"{d['override']} masks computed {d['computed']} "
f"(delta {d['override_delta']:+})")})
accounts.append(d)
for o in orphans:
anomalies.append({"check": "orphan-transaction", "account_id": o["account_id"],
"detail": (f"txn {o['id']} ({o['date']}, {o['amount']}) points "
f"at nonexistent account {o['account_id']}")})
if args.json:
print(json.dumps({"accounts": accounts, "anomalies": anomalies}, indent=2))
else:
print(f"{'id':>4} {'type':<12} {'active':<6} {'ccy':<4} "
f"{'txns':>5} {'computed':>12} {'override':>10} name")
for d in accounts:
ov = "" if d["override"] is None else f"{d['override']:.2f}"
print(f"{d['id']:>4} {d['type']:<12} {str(bool(d['is_active'])):<6} "
f"{d['currency']:<4} {d['txn_count']:>5} {d['computed']:>12.2f} "
f"{ov:>10} {d['name']}")
for a in anomalies:
print(f"ANOMALY [{a['check']}]: {a['detail']}")
print("CLEAN" if not anomalies else f"{len(anomalies)} anomalies")
return 1 if anomalies else 0
if __name__ == "__main__":
sys.exit(main())