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
87 lines
3.6 KiB
Python
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())
|