hsa-app/spec.md
Jean-Michel Tremblay 8b7252dad4 Initial commit: HSA receipt tracker
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>
2026-06-17 21:40:12 -04:00

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