Skip to content

Finances Hub (finances.justinsforge.com)

STATUS (2026-07-09): DISMANTLED. (Retired 2026-07-05; code + runtime torn down 2026-07-09.) Removed: stray orphaned forge_finances_app.py proc squatting :8096 for 3d (killed), the 36 Flask web + Python data-layer scripts (forge_finances_app.py, all forge_finances_ui_*, forge_finance_{sync,categorize,recurring,holdings,alerts,networth,snapshot,db,accounts,ingest,history_grid,migrate_to_lifeos,set_category,sheet_mirror,receipt_reconcile,recurring_action}.py, tests/js_check), and the 4 retired forge-finance-* systemd units. KEPT (not part of the Flask app): forge_finance_query.py (live Telegram-brain tool; REWIRED 2026-07-09 from the frozen finance.db to live lifeos Postgres finance.* scoped to ws67 via forge_workspace_db.admin_connect(); verified net worth $215,743 / spend / recurring all live), forge_simplefin_client.py (canonical SimpleFIN claim CLI), forge_google_sa.py (SA token helper). CreatorTrack only ever referenced the deleted scripts in "ported from" comments, never at runtime. Live timers are the separate forge-creatortrack-finance-* units. Deletions uncommitted (Justin reviews). The full native rebuild shipped: ingest, writes, icons, alerts, snapshots and books integration all live in CreatorTrack against the finance.* Postgres schema, which is now the SOURCE OF TRUTH. See reference_creatortrack_finance_module. forge-finances.service is disabled, :8096 closed, legacy timers off; finances.justinsforge.com / receipts.justinsforge.com redirect to app.creatortrack.ai/finances. Archives (pre-cutover): /home/justinwieb/forge-backups/finances-cutover-20260705/ (finance.db + pg_dump). finance.db on disk is FROZEN history; do not write to it. Everything below is retained as historical reference for the Python-era implementation.

2026-07-05 mirror outage (fixed): the workspace re-slug (justins-workspace -> justin) broke forge_finance_migrate_to_lifeos.py (exit 1 on every sync since 07-03), freezing the finance.* Postgres mirror while SQLite kept syncing. Fixed by updating WORKSPACE_SLUG default to justin (ws id 67); mirror truncate-reloads from SQLite each sync, safe because ALL writes still land in SQLite via the bridge (verified receipts empty both sides first).

Household + business finance dashboard. Net worth, cash flow, budget, spending, recurring, accounts, transactions, alerts. Rocket Money / Mint replacement. Built 2026-06-08; refactored from a 6k-line monolith into flat blueprint modules + modern UI refresh 2026-06-10 (plan: finances-hub-refactor-redesign-2026-06-10).

Web-layer module map (post 2026-06-10 split)

forge_finances_app.py is a ~110-line entry: registers blueprints, owns /sync, /health, favicons. Pages live in flat forge_finances_ui_* modules:

Module Owns
forge_finances_ui_base.py BASE template (CSS/JS), NAV_ITEMS, ICONS, money/escape helpers, pills, donut + bar + spend charts, shell()
forge_finances_ui_icons.py account/merchant logo + icon subsystem and its routes
forge_finances_ui_data.py shared read accessors (spend_overview, account_summary, recent_transactions, sync widgets); all txn SQL filters COALESCE(superseded,0)=0
forge_finances_ui_charts.py _line_chart, _multi_line_chart, SEG_COLORS (shared by accounts + networth)
forge_finances_ui_dashboard.py /
forge_finances_ui_networth.py /networth (+ sparklines, change badges, by-class grouping)
forge_finances_ui_cashflow.py /cashflow (net-flow hero, 30-day balance forecast from confirmed recurring, safe-to-spend, upcoming bills)
forge_finances_ui_investments.py /investments (holdings hero + allocation bars + per-account positions; empty state has export instructions)
forge_finances_ui_spending.py /spending
forge_finances_ui_budget.py /budget (buckets, targets, autoset, left-to-spend + daily pace, last-month compare)
forge_finances_ui_recurring.py /recurring + ~20 sub-routes
forge_finances_ui_accounts.py /accounts, /account/
forge_finances_ui_transactions.py /transactions (filter bar incl. min/max $, bulk edit via POST /transactions/bulk), /transaction/
forge_finances_ui_categorize.py /categorize + rules engine routes
forge_finances_ui_business.py /business (ERPNext P&L + balance sheets per company, read-only, deep links to edit; WEOP-Operating displays as Wiebelhaus, Gus never consolidated)
forge_finances_ui_alerts.py /alerts

Verification gates: python3 scripts/forge_finances_route_sweep.py (every GET route 200) and python3 scripts/forge_finances_tests.py (money-math fixtures: forecast, alert checks, net-worth classing). Run both after any change + service restart.

Proactive alerts (2026-06-10)

forge_finance_alerts.py runs as ExecStartPost on every sync: bills due within 24h, large transactions (default $500+, posted_at-guarded so bulk imports can't flood), flex-budget 80/100% crossings, recurring price increases (>=15% and >=$2), low checking balances. Once-per-key dedup in data/finance/alerts_state.json (90d prune), feed for the /alerts tab in alerts_feed.json, thresholds overridable in alerts_config.json. Pushes one digest per run via forge_notify.sh info.

Investments / Fidelity holdings (2026-06-10)

No Fidelity API for individuals, so: Justin exports the Positions CSV (fidelity.com -> Accounts -> Positions -> download) and drops it in /mnt/workspace/Forge/Fidelity Positions/ (Drive-synced). forge_finance_holdings.py --auto (ExecStartPost on every sync) imports new drops (snapshot-replace per account label, archives to processed/), then marks every symbol to market via Yahoo's keyless chart endpoint (SPAXX/money market pinned at $1). DB table: holdings (UNIQUE account_label+symbol). Re-export is only needed after trades; prices refresh daily on their own. Account labels match to SimpleFIN accounts by last4 then name. Parser is regression-tested in forge_finances_tests.py.

Serving + DB hardening (2026-06-10)

Waitress (not Flask dev server) bound to 127.0.0.1:8096; the CF tunnel origin is local so nothing on LAN/tailnet reaches it unauthenticated. forge_finance_db.connect() sets WAL + busy_timeout 5000 (web reads overlap timer writes). Sync logs N balances written (missing/errors) and pages a warning when a run writes zero balances (net worth silently froze 06-09/06-10 before this). One-shot split tool (historical): forge_finances_refactor_mover.py. Full pre-refactor backup: /home/justinwieb/forge-backups/finances-20260609-231319/.

Where the data lives (the answer to "how is this stored/accessed/modified")

Layer What Location
Source of truth SQLite DB, all accounts/transactions/balances/manual values/recurring/snapshots forge/data/finance/finance.db on Console (gitignored)
Secrets SimpleFIN access URL; Google service-account key ~/.forge-secrets/simplefin.env, ~/.forge-secrets/google-sa.json (chmod 600)
Bank feed SimpleFIN Bridge (read-only), ~90-day window per pull; we retain everything pulled forever forge_simplefin_client.py
Human mirror Google Sheet "Wieb Finances" (new, Forge-owned), id 1u7zTvLJI2pom-h_e55ueSLO8K5plCUA46NqaA2rYLBQ shared to [email protected]
Web surface Flask app on Console:8096, finances.justinsforge.com via VR Alliance CF tunnel + CF Access ([email protected]) forge_finances_app.py, forge-finances.service

DB tables: accounts, transactions, balances, manual_values, weekly_snapshots, recurring. Schema + migrations in forge_finance_db.py (app runs d.init() on startup to auto-apply migrations).

How it's modified

  • Daily 07:00 CT forge-finance-sync.timer: SimpleFIN sync -> categorize -> recurring scan (fail-loud notify).
  • Weekly Sun 18:00 CT forge-finance-snapshot.timer: writes signed net-worth contributions per account into weekly_snapshots.
  • In the web UI: per-transaction edit (category/scope/note/tax-deductible/ignore/flag); per-recurring edit (nickname/description/category/Personal-Business). User edits are sticky (user_category, user_edited flags) and survive re-sync/re-scan.
  • CLIs: forge_finance_accounts.py (manual accounts + labeling), forge_finance_categorize.py --apply, forge_finance_recurring.py --scan, forge_finance_import_history.py (read-only sheet history import).

Scripts

  • forge_simplefin_client.py - SimpleFIN client (UA header required; CDN 403s python-urllib)
  • forge_google_sa.py - mints Google API tokens from the SA key (PyJWT, no google-auth)
  • forge_finance_db.py - schema + helpers + migrations
  • forge_finance_sync.py - SimpleFIN -> DB (idempotent on simplefin_txn_id)
  • forge_finance_categorize.py - merchant/keyword -> transactions.category (16 cats; preserves user_category)
  • forge_finance_recurring.py - cadence detection + amount_cv (fixed vs varies) + categories
  • forge_finance_networth.py - net worth rollup (live balances + manual values, signed)
  • forge_finance_snapshot.py - weekly snapshots
  • forge_finance_import_history.py - parse Wieb Spending Plan export -> 2026 history (READ-ONLY source)
  • forge_finances_app.py - the web app

Pages

Dashboard (live accounts + spend chart + recent txns + upcoming recurring), Net Worth (total + per-entity + history chart + 1M-ALL ranges + per-account sparklines/change badges + Assets/Debt by-entity or by-class views), Spending (donut + category table + month nav + Personal/Business + txn list, income-vs- spend bars), Budget (fixed/flex/non-monthly buckets, targets with autosave, left-to-spend + daily pace, last-month compare), Accounts (grouped, clickable -> per-account page), Transactions (filter bar: search/date/category/scope/account/direction/min-max $; Select mode bulk edit for category/scope/ignore), Recurring (Fixed-monthly hero + calendar + confirm/ignore + per-item edit), Categorize (triage + rules engine), Alerts (flagged transactions). UI: dark Material look, card depth, page fade-in, fixed glass bottom tab bar on mobile.

Data model notes

  • Net worth = live SimpleFIN balances (signed as reported, cards negative) + latest manual values (+asset/-liability). Only active=1 AND include_in_nw=1.
  • Dashboard Accounts card = SimpleFIN accounts only; manual/static assets show on Net Worth.
  • Manual assets seeded from the spreadsheet import: home $430k (garage conversion folded in), cars, HSA, Krystal 401k=Fidelity Rollover (dedup'd), loans, parents loan, tax bill.
  • SimpleFIN returns NO holdings, so individual Fidelity positions aren't available.

Open / roadmap

  • Multi-year history: workbook has Net Worth 2023/2024/2025/2026 tabs; rclone gdrive: exports the full xlsx (rclone --drive-export-formats xlsx backend copyid gdrive: <id> <out>). Import 2023-2025 next.
  • Google Sheet mirror write-back (Task 10) once SA has Editor on the new sheet.
  • ERPNext: bulk-ingest 12-24 months of historical bank transactions into transactions (idempotent) as ERPNext data becomes available.
  • Net Worth Assets/Debt class groupings + 1M/3M/6M/1Y/ALL range toggle.
  • Transactions top-filter bar (dates/categories/accounts/amounts) like Rocket Money.

[Claude Code, 2026-06-08]