Skip to content

Changelog

All notable changes to heezy infrastructure and services.


[2026-08-14] Docs: four services that were running but undocumented

Added

  • Maintenance and Projects — deployed 2026-08-02, no docs until now. Both are LAN-only Flask apps on their own Postgres databases (maintenance and projects, not the shared heezy one), reached through SWAG at NodePorts 30861 and 30862
  • Mailbot — the three Gmail CronJobs that replaced n8n on 2026-08-10. All three now run --live; they were cut over from shadow mode after the dry runs matched
  • CS1.6 Leaderboard — was listed on the home page with a URL and nothing else. Documents the Loki-tailer pipeline and its nanosecond-timestamp dedupe

Noted while documenting

  • Neither Maintenance nor Projects has any authentication in the app. The only access control is that the hostname resolves solely through the dnsmasq override — no public DNS record, no Cloudflare tunnel hostname, no Access application. Publishing a DNS record for either without an Access app would expose an unauthenticated CRUD API
  • heezy-projects orders the index by priority DESC, which is a lexical sort on text. medium sorts above low above high, so high-priority projects appear last
  • project_tasks and project_log exist in schema.sql and have no routes in app.py
  • PUT /api/projects/<id> writes every column from the payload; a partial body blanks what it omits

[2026-08-12] Receipts: concurrent uploads, lost receipts, and duplicates

Fixed

Concurrent uploads overwrote each other - The OCR poller was never cancelled when a second upload started, and there is one result panel. Two pollers raced and the last to finish owned the DOM - The quick-edit form used bare element ids (qe-total), so the Save button carried one receipt's id while getElementById could resolve to a form rendered for another. Corrections landed on the wrong receipt, which read as "the second upload didn't save" - Field ids are now suffixed with the receipt id, the previous poller is retired, and the panel carries the id it belongs to

Receipts that disappeared - /upload saved the image to NFS before inserting the row. A failed INSERT lost the receipt while the bytes stayed on disk. The image is now removed when the row cannot be written - receipts.upload_date was TEXT holding two incompatible formats — isoformat() from /upload and NOW()::text from heezy-finance. Lexicographic ordering put a space before T, so every finance-created receipt sank below the uploaded ones from the same day. Migrated to timestamptz - GET /receipts was a hardcoded LIMIT 100 with no pagination. At 157 rows, 57 receipts were unreachable from the UI. Now takes limit and offset

"Error loading items" on every receipt with line items - NUMERIC columns return Decimal, which Flask serialises as a quoted string. The items table called .toFixed() on it and threw. A NumericJSONProvider now renders Decimal as a number

The per-item edit button never worked - It inlined JSON.stringify(item) into a double-quoted onclick, so the attribute terminated at the first quote in the JSON. Every line item on every receipt was uneditable

Line item quality - A trailing number was treated as a quantity using \b, and a hyphen is a word boundary, so 997895 KS OIL OW-20 became twenty units of 997895 KS OIL OW-. UPCs were read as quantities up to 716,281,006,230. Now requires whitespace and caps at 100 - Descriptions were built by stripping trailing digits, turning 997920 OIL 5W20 into 997920 OIL 5W - 9 existing items repaired from their raw_line; total_price was correct throughout, so no financial figure moved

Category list drift - Alcohol, Transportation, Retirement Funds, Stock Funds and MittenTech were selectable in heezy-finance but missing from receipts. A test now fails the build on drift

Added

  • reconcile_orphans.py — re-imports images with no database row, dry-run by default
  • GET /receipts returns has_image, so the UI stops guessing from the filename
  • Delete control on orphaned receipts in the heezy-finance Reconcile tab, proxied to the receipts service so the image goes with the row
  • Receipts pod raised to 3000m CPU so concurrent OCR pipelines do not share one core

Incident

Recovering 52 orphaned images created 36 duplicate receipts, including seventeen copies of one Costco receipt. The recovery assumed an image without a row meant a lost receipt; in fact the delete path had been dropping rows while failing to remove files, so most orphans were receipts deleted on purpose. The duplicates were removed and the tool now rolls back any import whose amount and date already exist.


[2026-06-20] heezy-finance Phase 1-2 + Phase 4 Deployment

Added

Phase 1: Global Date Range Selector - React date picker controls all dashboard views - Persists to localStorage across sessions - Default: last 90 days

Phase 2: Bank Integration & Complete Coverage - Dedicated Bank tab with category breakdown cards - Monthly bar chart showing bank transaction trends - Top merchants table - All views (Overview, Purchases, Categories) now include bank data - 137 bank transactions integrated (debits only, transfers excluded) - Rule-based categorization: Household, Health, Travel, Food, Finance, etc.

Phase 3: Category Drill-Down Modal - Click category to see all transactions in that category - Paginated list (20 per page) - Sortable columns: Date, Source, Vendor, Item, Total, Category - Filters: date range, source (order/receipt/bank)

Phase 4: Hockey Holdem Receipt Ingestion - New receipt category for event expenses - Receipts automatically parsed and categorized - Verified: data in DB with proper timestamps and merchant normalization

Changed

Database schema updates: - receipts: Added date_ts (timestamptz), merchant_normalized, payment_type - bank_transactions: Added category, merchant_normalized - All existing data backfilled

Dashboard API: - /api/overview now includes bank transaction totals - /api/purchases returns all three sources with sortable columns - /api/sync reports counts from all sources - /api/category-drilldown new endpoint for drill-down modal - New /api/bank-stats endpoint for Bank page aggregations

Deployment: - k8s: heezy-finance pinned to nebula-5 (node affinity issue noted) - All code pushed to Gitea (canonical) + GitHub (backup mirror) - Commit: 45d3a4af

Fixed

Gas spend now accurate: $140.52 (was $36) - Root cause: Receipt dates in inconsistent text formats caused silent parse failures - Fix: Use proper date_ts column instead of parsing text dates

Bank transactions visibility: - Root cause: Zero integration in original dashboard - Fix: Full integration into all API endpoints and UI views

Receipts merchant name consistency: - Root cause: "COSTCO" vs "Costco" vs "costco" treated as different merchants - Fix: Backfilled merchant_normalized on all 43 receipts + new receipts auto-normalized

Known Issues

Node Affinity Pinning (heezy-finance) - Pod pinned to nebula-5; cannot reschedule if node fails - Blocking resilience work next session - Fix: Remove affinity + switch PVC to ReadWriteMany or eliminate persistent storage

June 2026 Statements Missing - BoA coverage ends May 2026 - June transactions not yet loaded - Action: Trigger statement parser or manually ingest

Capital One Backfill Incomplete - Only 1 CC statement loaded - Historical backfill pending

CI/CD Lesson Learned - Never push to GitHub origin directly - Gitea is the canonical repo; GitHub is a backup mirror only - Push to Gitea first, then GitHub - Deployments only trigger on Gitea push

See CI/CD docs and Roadmap.

Next Session

Phase 3: Infrastructure Resilience

  1. Remove node affinity pinning from heezy-finance
  2. Fix PVC storage pattern (NFS or remove if not needed)
  3. Load June 2026 statements
  4. Verify all features working correctly

[2026-06-18] heezy-finance Documentation & Data Audit

Added

  • Comprehensive data source audit: orders, receipts, bank transactions
  • Roadmap for 3-phase data cleanup + reconciliation
  • Known issues list (priority-ordered)
  • Daily progress log in README

Fixed

  • Identified root causes of data gaps:
  • Receipt dates in inconsistent formats (text field)
  • Bank transactions completely disconnected from dashboard
  • Gas spend under-reported due to category/date handling

[Earlier] Infrastructure Setup

  • k8s cluster: 5 nodes (nebula-1 through nebula-5)
  • Gitea: http://192.168.1.15:30360/heezy-admin
  • heezy-finance: Initial Flask + vanilla JS dashboard
  • Receipts service: AWS Textract + Ollama categorization
  • Bank statement parser: pdfplumber on big-boi cron job