heezy-finance — Daily Log¶
2026-08-11 (later)¶
Docs audit against live state¶
Every page in the heezy-finance and Services sections re-verified against the running cluster, the
heezy database, and the source in heezy-containers. The section had last been touched on
2026-07-07 and had drifted badly.
Corrected:
- Taxonomy. Moved to
categories.pyand grown from 16 documented entries to 27.Personal CareisPersonal Care & Wellness.Pet Careis implemented, not backlogged.Income,Retail,Shopping,Savings, andTransferwere documented as categories and are not inCATEGORY_ICONS realtech debt is gone. Every money column isnumeric. The Phase 3 migration section was describing work already done- Counts. orders 161 → 284, receipts 43 → 155, receipt_items 342 → 821, bank_statements 64 → 80, bank_transactions 1,203 → 1,582, accounts "194+" → 27
boacrdNoneghost rows: 0. Fixed and documented as still open- Nav. Eight pages, not seven.
/statementsreplaced/record-balances, which is still routed but unlinked./bank,/purchases,/trendsredirect to/. Added a Statements page doc - Spending page uses two period pickers, not a date range picker with localStorage
- Targets page was documented as user-editable milestone goals. It is five hardcoded targets reading live account balances, with 7% projections
- Budget page referenced a
budgetstable. It isspend_budgetsplusannual_budgets - Phase 6 (remove node affinity) is done. Phase 7 shipped inside the app rather than as a
separate
heezy-finance-reconcileservice - Statement parser does not run on big-boi from a host cron. It is the
heezy-statement-scannerCronJob./var/log/statement-ingest.logdoes not exist - Receipts OCR is Textract, pinned to
textract(DetectDocumentText) in the deployment even though the code default isexpense(AnalyzeExpense). No S3 in the image path - Cloudflare Access lives in
terraform-heezy, not acloudflare-terraformrepo. Added statements.heezy.info and emulatorjs, plus the home-IP bypass policies - OpenBao secret path table was wrong on five entries. Rebuilt from the ExternalSecrets and roles
Bugs found while auditing¶
- Manual balance entry is broken.
statements.htmlandrecord-balances.htmlboth POST to/api/account-balanceswithaccount_idin the body. Only/api/account-balances/<int:account_id>exists, so it 404s. Verified live /sankeyredirects to/money, where there is no Sankey. The chart is still in#page-overviewinindex.html- 12 receipts have a NULL
date_tsand are dropped by every date-filtered query. The June backfill covered all 51 rows that existed then; volume has since tripled budget_alerts.pykeeps its own copy ofCATEGORY_ICONSand does not importcategories.py, which is the exact duplication that module exists to preventvw_sankey_spendhas no Capital One branch but admits every credit card statement, so a Cap1 credit card charge would file underDirect Debit. Nothing is misfiled today, Cap1 is checking and savings onlyget_all_purchases()still parsesorders.order_date, the raw text column, against the project's own rule to useorder_date_ts- The OpenBao doc published a live AppRole
role_idandsecret_id. Removed. Still in git history, andmcp-server.jsuses the same values as a hardcoded fallback. Rotate
2026-08-11¶
Ten weeks of missing orders recovered (123 orders, 165 items)¶
Started as three Grafana alerts. Two were real bugs with unrelated causes; one was a false positive.
Bug 1 — sync crash-looping on invalid_grant. The heezy-finance-sync CronJob lost its
envFrom: heezy-finance-gmail and silently fell back to /credentials/gmail-oauth.json, the
hand-maintained copy that went stale in June (see 2026-07-08, where it was patched by hand rather
than removed). The container crash-looped six times and exhausted its backoff limit at 21:00 and
22:00 UTC. Fixed by restoring envFrom, then deleting the file fallback entirely — it had caused
two outages and prevented none. Credentials are now env-only, and a test asserts the fallback stays
gone.
Bug 2 — the actual cause of the data gap. 28a7171 (2026-06-02, "replace hardcoded sender
allowlist with broad subject keyword search") deleted the clause that was doing all the work. Amazon
confirmations have the subject Ordered: "<item>", which matches none of the fourteen phrases that
replaced the senders. Steam, Apple, Walmart and Grubhub went with it.
The last ingested order was dated 2026-06-01 13:29 UTC; the commit landed the next day. Running the
production query unchanged over May, while the sync was believed healthy, returned 5 emails and 0
from amazon.com — the subject phrases had never matched Amazon. Fixed by making the query the
union of the two clauses. A bare subject:"Ordered:" was deliberately rejected: it matches ~380
messages to the allowlist's ~124, because the forward mailbot copies every confirmation as
Fwd: Ordered: from a personal address.
False positive — slskd disconnect alert. The rule counted log lines matching "Waiting about",
believing it to be the reconnect backoff. It is the per-file download retry message. Over the 13
hours behind three pages, the pod logged 47 of them and zero disconnects — it connected once at
11:12:50 and stayed connected. Repointed at the connection lifecycle
(Attempting to connect to|Disconnected from the Soulseek server).
Backfill¶
Added --dry-run to amazon_orders.py first, since a real run archives Amazon mail out of the inbox
and that does not reverse. Three dry runs: 4 orders (pre-fix), 124 (post-query-fix, 1 duplicate), 123
(post-normalization, clean).
Backfilled --hours 1750: 123 orders, 165 items, covering 2026-06-02 17:29 → 2026-08-11
03:02. Gmail reconciled exactly — 122 confirmations labeled Finance/Amazon, 0 left in the inbox,
119 archived by the run, matching the 119 new Amazon orders.
Data repairs¶
- Order ID normalization. Dedupe is exact string equality on a primary key, so one Woot order was
about to insert twice as
#212850333and#212850333. Added_normalize_order_id(), which also made the backfill idempotent for every LLM-parsed vendor. - 15 legacy IDs rewritten (14 Woot with a leading
#, 1 Celebration Cinema with a doubled space), 31 items repointed, 0 orphans. Needed a copy/repoint/delete becauseorder_items.order_idhasON DELETE CASCADEand noON UPDATE. - 41
grand_totalvalues reconstructed from priced line items, $503.02. Recorded spend since June 2 went $1,094.39 → $1,597.41.
Still open¶
grand_totalis unparseable on roughly a third of current-format Amazon confirmations. Line items save us, but that is luck, not design. 2 orders remain without a total because an item is unpriced.- Amazon
Shipped:/Delivered:mail is trashed on purpose to keep the inbox clear. Not a bug.
PRs: ansible-heezy #16; heezy-containers #40, #41, #42, #43, #44
2026-07-08¶
- money-history chart fixed: Trimmed leading cash-only months (Nov 2025–Apr 2026) from trend chart — these had investments=0/retirement=0 before EJ statements were loaded, causing a misleading spike from ~$20k to $1.96M. Now starts May 2026 (first complete month).
- FIRE target fixed:
annual_spendalready excludescategory='Mortgage'; was double-subtractingmtg_annual. Result was FIRE target $168k (wrong). Fixed toannual_spend * 25. Correct: $1.13M target, 106% funded. - api/uncategorized-items 500 fixed:
receiptsquery referenced non-existentdescriptioncolumn;ordersquery used wrong column names (id,title,total_cost— none exist). Fixed queries; endpoint returns 200/empty list. - api/reconcile/unreconciled-by-category 500 fixed:
receipts.dateistext, not a date object —.strftime()called on string. Fixed withhasattrcheck + str truncation fallback. - heezy-finance-sync Gmail fixed: k8s secret
heezy-finance-secretshad stale refresh token. Patched with working token from n8n variables. Verified via manual test job (clean sync, exit 0). - Cluster hygiene: Force-deleted 9 stale Completed pods from old
statementsdeployment rollouts. Cluster now clean — 35 Running pods, no orphaned pods. - All 17 heezy-finance API endpoints: 200 OK
-
Commits: heezy-containers
e81381f,ce3a4c3,23163f3; q-mcp4875967,2298c49,d884ae8,14e710c -
Data Standards doc published (
heezy-finance/data-standards.md) — canonical reference for dates, numerics, categories, IDs, source tracking - Receipts
date_tsbackfill complete: 2 remaining NULL rows (NUVU FUELS 2026-06-18/19) backfilled — all 51 receipts now have normalizedtimestamptz - Known
realtech debt documented:receipts.total,receipts.items_total,orders.grand_total,order_items.unit_price,receipt_items.*— scheduled for Phase 3numeric(12,2)migration - Category list documented with icons and rules; Phase 3
categoriesFK table spec captured
2026-07-07¶
Statement Parser Bug Fixes (3 bugs, 3 PDFs)¶
Three PDFs failed to ingest with a non-descriptive "3 error(s)" response. Root cause traced through parse.py → detect_bank() → upsert_account_balance() → accounts table.
Bug 1 — Missing accounts: BoA CC last4=7503 and Chase Amazon last4=2609 had no rows in the accounts table. upsert_account_balance() silently skips balance updates when no matching account exists — no error surfaced. Fixed by inserting both accounts with display_group='debt'.
Bug 2 — Capital One false-detected as Edward Jones: Capital One PDFs can contain "EDWARD JONES" in transaction descriptions (EJ investment withdrawals). detect_bank() was checking EDJ before Cap1, causing misidentification. Fixed by swapping check order in parse.py — Cap1 is now checked before EDJ.
Bug 3 — Chase Amazon wrong closing date: CHASE_AMAZON_CLOSE_RE matched Opening/Closing Date 05/20/26 - 06/19/26 and captured the first date (05/20) instead of the closing date (06/19). Fixed with a new primary regex that specifically matches the Opening/Closing Date X - Y format and captures the second date, with the old regex kept as fallback.
DB cleanup:
- Deleted stale edjbrok0000_2026-06-30 row inserted during failed Cap1 upload
- Deleted bad chasecrd2609_2026-05-20 statement + 98 transactions; re-uploaded Chase CC.pdf → chasecrd2609_2026-06-19 ✅
- Deleted stale account_balances row for Chase Amazon (account 194) dated 2026-05-20
- Deactivated old account id=5 (Chase last4=7503, stale data)
New accounts added: - id=193: BoA Visa Signature (last4=7503, credit_card, debt) - id=194: Chase Amazon Visa (last4=2609, credit_card, debt)
Commits: heezy-containers — two commits fixing Cap1/EDJ detection and Chase closing date regex
Docs Updated¶
data-sources.md— rewrote Bank Transactions section: accurate parser list, upload endpoint, parser quirks table, account setup requirementspages/record-balances.md— corrected upload endpoint (/api/statements/upload, fieldfiles), replaced NFS cron pipeline description with accurate synchronous in-process flow, updated supported formats
Known Issue Documented — boacrdNone (12 rows)¶
12 bank_statements rows with account_last4 = NULL, ending_balance = NULL, 0 transactions. Source files are eStmt_YYYY-MM-DD.pdf — BoA CC PDFs where the last4 regex failed to match. These are ghost rows that block re-upload and pollute the statements table. Separate parser bug, not fixed this session.
2026-06-21¶
2026-06-20¶
- CI/CD: Fixed all 4 Gitea pipeline repos post
.github/workflows/removal - heezy-containers: Fixed
[[ ]]bashism in notify job,curl -sf→curl -sin deploy job - heezy-k8s auto-deploy:
- Clone from internal Gitea (not github.com)
- kubectl served from internal Gitea release
tools-v1(dl.k8s.io → 300s timeout from DinD → 1s internal) - KUBE_CONFIG secret used directly (OpenBao kubeconfig path not populated)
- All workflow steps now complete in ~31s total
- ansible-heezy: Workflow detection confirmed ✅
- terraform-heezy: Still blocked on one-time manual IAM bootstrap (Trent)
- AGENTS.md: Added mandatory startup procedure (git pull q-mcp + scan skills registry)
Outage Recovery (nebula-5 offline)¶
- Root cause: 13+ services had hard nodeAffinity to nebula-5; node went offline and pods couldn't reschedule
- Fix: removed nodeAffinity from all heezy-k8s deployments (commit 3bad4fd)
- Rule established: never add nodeAffinity unless hardware-specific requirement exists
heezy-finance Crash Fixes¶
- Fixed 3 sequential startup bugs: missing typing imports, duplicate route registration, SQL column name errors
- Category drill-down (/api/category-transactions) now working
finance.heezy.info Live¶
- Added SWAG proxy conf + volumeMount entry in deployment.yaml
- DNS: dnsmasq (192.168.1.29) + Pi-hole both updated → 192.168.1.25 (SWAG VIP)
- Phases 1-4 verified working: date selector, lookback, drill-down, Hockey Holdem receipt
CI/CD Root Cause Found¶
- Gitea act-runner was executing both .gitea/workflows/ AND .github/workflows/
- .github/ versions used actions/checkout@v4 which failed (can't reach github.com from DinD)
- Fix: Trent removed .github/workflows/ from all 4 repos
- All future workflows: .gitea/workflows/ only
Terraform IAM (in progress)¶
- Created productionATerraformStateBackend IAM role in terraform-state-role.tf
- Added iam:* permissions to github-runner-user policy
- Blocked on bootstrap: github-runner-user needs manual IAMFullAccess attachment once
2026-06-19 (evening)¶
- nvidia-gpu-exporter deployed to big-boi (192.168.1.21:9835), Prometheus scraping with labels host=big-boi, gpu=gtx1070
- Split DNS .heezy.local zone established in Pi-hole with 11 records (nfs, bigboi, lgtm, nebula1-5, vault, dnsmasq)
- heezy-k8s manifests: hardcoded NFS IPs replaced with nfs.heezy.local DNS names; k8s coredns queries Pi-hole for resolution
- heezy-finance: Income→Spending Sankey diagram deployed on Bank page with year/month filters (Plotly, 3-layer flow)
- heezy-finance: Vendor drilldown feature deployed — click category cards for per-vendor breakdown + monthly stacked chart
- New API endpoints:
/api/sankey(year/month filter),/api/category-drilldown(category detail) - Documentation: GPU monitoring section added to infrastructure/index.md, new infrastructure/dns.md page covering split DNS architecture + Pi-hole config + all .heezy.local records
2026-06-19 (afternoon)¶
- Category taxonomy overhaul planned: split Health & Personal Care → Health Care Costs + Personal Care; split Household → Household + Mortgage + Utilities; added Hockey and Pet Care categories. Implementation backlogged. See Categories.
2026-06-19¶
- LGTM disk crisis: 192.168.1.10 hit 90% — root cause was lgtm-cleanup.sh targeting mimir/blocks/ instead of mimir/blocks/anonymous/. 33 blocks accumulated since April. Freed 42GB, disk 90% → 60%. Ansible role + live script both fixed.
- Receipts OCR fix: bare 'card' keyword in skip_keywords was silently dropping items like "SHARP CARD / HOCKEY". Narrowed to compound phrases (debit card, credit card, etc.).
- Receipts missing images: all 43 pre-NFS receipts lost images (ephemeral storage). NFS PVC working for new uploads. Added placeholder in UI for missing images.
- Backup system: full overhaul — NFS structure, Ansible role, OpenBao policy, S3 lifecycle, monthly archive workflow. ~250GB of stale data purged from NFS.
- Runners: comprehensive doc written covering current setup and lean runner proposal.
- heezy-finance: added "Last Purchased" date column to Trends → Top 10 Items (All Time).
2026-06-18¶
Phase 1 + Phase 2 — Data consolidation + dashboard improvements
Phase 1 (completed)¶
- Full audit of all data sources and dashboard code
- Dashboard was reading orders + receipts but had zero integration with
bank_transactions receipts.datetext column had mixed formats — causing silent parse failures; gas showed $36 instead of $140.52- Added
date_ts(timestamptz),merchant_normalized,payment_typetoreceipts - Added
category,merchant_normalizedtobank_transactions - Backfilled
date_tson all 43 existing receipts - Built rule-based bank transaction categorizer (Ollama too slow on CPU for this)
- Updated
app.pyto usedate_tsfor receipts; integrated bank_transactions as third source - Updated receipts service to populate
date_ts+merchant_normalizedon all new inserts - Fixed BoA parser: was hardcoded to
checking— now detects credit card statements from first 3000 chars of PDF - Fixed Cap1 parser: transactions were assigned to wrong account due to
stmt_idlookup using flushed-only map - Statement auto-ingest: NFS watch dir pipeline confirmed end-to-end (2 PDFs → parsed → DB)
- docs.heezy.info: MkDocs Material site deployed to Cloudflare Pages with Gitea pipeline
- All public heezy sites added to Cloudflare Access (Google OAuth, trentnielsen84 + brdrgrl18)
Gas spend fixed: $140.52 (was $36). June 2026 total: ~$2,226 across all sources.
Phase 2 (in progress)¶
- Bank page: Added dedicated Bank tab to dashboard — category breakdown cards, Plotly monthly bar chart, top merchants table
- Bank page "This Month": Falls back to most recent month with data instead of showing empty cards for current month (May 2026 shown)
- Purchases table: All 6 columns now sortable (Date, Source, Vendor, Item, Total, Category) — click header to sort, ▲▼ indicators
- Purchases date defaults: Start Date defaults to first of current month, End Date to today on first page load
- payment_type on receipts: Dropdown added to both edit flows (post-upload + inline): cash / credit / debit / check / other
- CI/CD: Fixed all
deploy-*.ymltrigger paths (were using repo name instead of k8s app dir); addedrollout restarttoauto-deploy.yamlso:latestimages actually roll out - Monthly reminder: Cron fires 1st of each month at 10am ET to prompt NFS statement drop
Blocked: BoA CC statements + additional monthly statements — manual download from bank portal required.
Evening session (continued)¶
- n8n Amazon forwarding fixed: Workflow had been erroring on every run since 2026-06-13. Root cause: two nodes (
Log Forward to DB,Log Skip to DB) existed in DB but not in n8n's compiled in-memory graph. n8n caches the workflow graph at activation time and never reloads from DB on restart — only a REST API PUT forces a recompile. Fix: stripped the two missing nodes, rewired connections to route around them. Execs 3694+ allsuccess. - Lesson:
N8N_PROTOCOL=httpsblocks HTTP write ops (PUT/POST) on localhost with 403, even via SSH tunnel. To unblock: temporarily setN8N_PROTOCOL=https_DISABLED, restart, make API call, restore. - Dev agent context: Created
dev-agent/CONTEXT.md— comprehensive reference for spawned dev subagents covering repos, git remotes, CI/CD gotchas, DB, SSH, services, metrics requirement, and commit style. - CF Access resource rename (deferred): Attempted to rename
cloudflare_access_application→cloudflare_zero_trust_access_applicationin Terraform. Neithermoved{}blocks norstate mvwork in cloudflare provider v4 for cross-type renames — provider v4 can't refresh the new type, so plan shows destroy+create. Revertedaccess.tfto original names. Defer until provider v5 upgrade. - Docs updated:
services/cloudflare-access.mdadded,statement-parser.md+data-sources.md+ci-cd.mdupdated via docs subagent.
2026-07-08 — Statement parser quality sweep¶
Root causes found and fixed (4 bugs):
-
boacrdNone ghost statements (
scan_statements.py) — WhenBOA_CC_ACCT_REregex misses last4 on first parse,account_last4=Noneproduces a ghostboacrdNone_*statement_id alongside the real one. Guard added: skipinsert_bank_statementwhenaccount_last4=Noneandaccount_type=credit_card. Deleted 12 existing ghost rows. (heezy-containers ca17a25) -
Cap1 PDFs misdetected as BoA (
parse.pydetect_bank) — Cap1 statements contain "Bank of America" in transaction descriptions (transfers).detect_bankcheckedBOA_DETECTfirst, misidentifying them asboa, thenparse_boaextracted 0 transactions. Fixed detection order to Cap1 → BoA → EDJ. (heezy-containers c2f4046) Reprocessed 9 files from error/ back to new/. -
FK violation on multi-account PDFs (
scan_statements.py) —insert_transactionsfired beforeinsert_bank_statementfor multi-account Cap1 PDFs (Holla checking + Our Savings). Second account's transactions hit FK constraint onbank_transactions.statement_id → bank_statements.statement_id. Fixed: insert all statement rows first, then insert transactions. (heezy-containers 41e6b3b) -
Cap1 Holla (8641) txns assigned to wrong account (
parse.pyparse_cap1) — For multi-account PDFs, transactions were assignedstmt_id = acct_stmts.get(cur_last4)at parse time, butacct_stmtsonly populates onflush_account()(triggered by next account section). Holla txns all gotNone, then the back-fill logic reassigned them all to the last-seen account (4306/savings). Fixed: store_acct_last4per txn, resolve tostmt_idafter all flushes complete. (heezy-containers 7c0efff)
Result: cap1chk8641_* — 8 months now have transactions (was 0 each). Total bank_transactions: 1,405 across 43 distinct statements.
Commits: ca17a25, c2f4046, 41e6b3b, 7c0efff (heezy-containers); 436f7bc, 4d4617f, b38d638 (ansible-heezy sync); f8ac70b + 565fc3a (heezy-k8s deploy-trigger)
Still 0-txn: boachk1897_2026-06-25 — statement row exists (inserted 2026-07-06), PDF gone. Needs Trent to re-drop eStmt_2026-06-25.pdf to NFS.