Go app for capturing and archiving HSA-eligible receipts: OIDC/PKCE auth against Authelia, SQLite storage with dual-write (filesystem + DB blob), mobile-first upload, and DB export. Adds AI receipt classification: a config.json catalog of people and categories (seeded into the DB on startup), a prompt builder that derives name-order/initial variants from the data (with same-surname ambiguity handling), and an Anthropic tool-use client behind POST /classify. Tests run against a mock endpoint; a live integration test is env-gated to the cheapest model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
109 lines
No EOL
4.1 KiB
Markdown
109 lines
No EOL
4.1 KiB
Markdown
HSA Receipt Tracker — Requirements (v1)
|
|
Purpose
|
|
Capture and archive HSA-eligible receipts for future reimbursement and tax substantiation. No parsing, no OCR, no reporting.
|
|
Users
|
|
|
|
Two users, both with full access (shared visibility).
|
|
Authentication via Authelia OIDC.
|
|
Authorization via membership in an Authelia group (e.g. hsa-users).
|
|
Not in the group → 403. No other roles or gradations.
|
|
|
|
Core flow
|
|
|
|
User opens app on phone (mobile-first UI; camera access matters).
|
|
Takes a photo of a receipt, or selects an existing image / PDF from device.
|
|
Form prompts for: amount, date, category.
|
|
Submit → image stored to disk, metadata row inserted into DB.
|
|
Confirmation page, with options to view list or add another.
|
|
|
|
Data model
|
|
receipts table:
|
|
|
|
id (UUID)
|
|
uploaded_by (Authelia username or email)
|
|
uploaded_at (server timestamp)
|
|
receipt_date (user-supplied date on the receipt)
|
|
amount_cents (integer — never store money as float)
|
|
category (enum)
|
|
file_path (relative path on disk — the filesystem copy)
|
|
image_data (BLOB — the receipt file bytes, also stored in the DB itself)
|
|
file_size_bytes (integer — convenience for listings/exports)
|
|
original_filename (preserved for reference)
|
|
mime_type
|
|
deleted_at (nullable — soft delete)
|
|
|
|
Categories (fixed list, hardcoded for v1):
|
|
|
|
Medical
|
|
Dental
|
|
Vision
|
|
Pharmacy
|
|
Other
|
|
|
|
Storage
|
|
|
|
Receipt images/PDFs are stored in BOTH places on upload:
|
|
1. Filesystem at a configurable path, filename randomized (UUID) on save
|
|
(file_path) — used as the primary path for serving.
|
|
2. As a BLOB inside the SQLite database (image_data column) — so the single
|
|
.db file is a complete, self-contained dataset (metadata + files).
|
|
|
|
Rationale: the filesystem copy keeps serving simple/efficient; the DB blob makes
|
|
backup and export trivial ("hand over one file" gets everything, even without the
|
|
files directory). Written once at upload; no edit in v1, so the two copies never
|
|
diverge. Acceptable cost because scale is tiny (two users, small files).
|
|
original_filename and mime_type kept as metadata for download/serving.
|
|
Backups remain JM's responsibility outside the app.
|
|
|
|
Auth integration
|
|
|
|
OIDC with PKCE against https://auth.jmopines.com.
|
|
Session cookie after successful callback.
|
|
/login, /callback, /logout, /healthz are public; everything else requires a valid session.
|
|
Group claim (hsa-users) gates access; otherwise 403.
|
|
|
|
Deployment
|
|
|
|
Runs as a systemd service in an LXC.
|
|
Caddy reverse proxy at https://hsa.jmopines.com (TBD: maisym.com vs jmopines.com).
|
|
SQLite DB + filesystem storage. No external dependencies (no Redis, no Postgres, no S3).
|
|
|
|
Operations
|
|
|
|
Soft delete supported (set deleted_at, hide from default list views).
|
|
No edit functionality in v1 — fix mistakes by deleting and re-adding.
|
|
|
|
Database export
|
|
|
|
Authenticated users (hsa-users group) can download the data for offline use.
|
|
|
|
Two endpoints:
|
|
|
|
GET /export/db — downloads a consistent copy of the SQLite database file. Because
|
|
images are stored as BLOBs in the DB, this single file IS the complete dataset
|
|
(metadata + all receipt images). This is the primary export.
|
|
- Must NOT serve the live DB file directly (avoids locking/corruption against the
|
|
running app). Use SQLite's online backup API (or VACUUM INTO a temp file) to
|
|
produce a point-in-time snapshot, then stream that.
|
|
- Content-Type: application/octet-stream; filename like hsa-export-YYYY-MM-DD.db.
|
|
|
|
GET /export/archive (optional convenience) — downloads a zip with the image files
|
|
extracted to normal files (named by original_filename) plus a CSV/JSON of the
|
|
metadata, for someone who wants the pictures as browseable files rather than
|
|
inside a DB.
|
|
- Streamed zip to avoid buffering large archives in memory.
|
|
- filename like hsa-export-YYYY-MM-DD.zip.
|
|
|
|
Notes:
|
|
- Amounts remain integer cents in the export; consumers divide by 100 for dollars.
|
|
- Read-only operation; no app state is mutated.
|
|
|
|
Out of scope for v1
|
|
|
|
OCR / image content parsing
|
|
Reports, totals, dashboards
|
|
CSV / tax-software export
|
|
Reimbursement tracking (paid vs pending status)
|
|
In-place editing of existing receipts
|
|
Multi-tenancy or per-user data isolation
|
|
Notification / reminders |