← Vinay
Personal Project · Ongoing

A finance tracker that runs itself, every night, without me touching it.

Expenso ingests my bank alert emails, classifies every transaction, flags anomalies, and answers plain-English questions about my spending — automated end to end with a nightly job. Eliminated about 3 hours a week of manual finance tracking.

FastAPI PostgreSQL LangChain Claude Haiku HTMX

Watch it in action

A two-minute walkthrough of the dashboard, classification, and the natural-language query interface.

Product walkthrough

Inside the dashboard

Screenshots from the redesigned app — annual and monthly overviews with matched-period anomaly flagging, the daily spend trend, and the transactions feed.

Expenso year overview: total captured outflow and inflow for 2026, and a month-by-month spending-intensity heatmap.
Year overview
Expenso monthly dashboard: captured outflow, a matched-period comparison to the previous month, and categories flagged for running above their historical average.
Monthly overview, with anomaly flagging
Daily captured outflow chart for the month, with the peak spending day called out.
Daily spend trend
Expenso transactions page with All / Needs review tabs, merchant search, category filtering, and inline per-transaction recategorization.
Transactions

The pipeline

Expenso connects to Gmail via OAuth2, pulls bank alert emails incrementally, and parses each one into a structured transaction — amount, date, direction, merchant, UPI reference. Only confirmed successful transactions are kept; failed or attempted ones are discarded. The whole thing is built to be re-run safely: every insert is idempotent, so a missed night just catches up automatically the next run, with zero duplicate or lost data across 9+ months of nightly runs since 1 December 2025.

Nightly pipeline architecture

Data quality, made visible

A dedicated pipeline-health page reports on the automation itself, not just the spending: messages waiting on extraction or classification, retryable processing failures, and a rolling count of candidate vs. accepted notifications from the last 7 days. If a bank alert can't be confidently parsed into a transaction, it isn't silently dropped or guessed at — it lands in a separate review queue instead, held there until a parser fix reprocesses it. This is distinct from spend classification (below): it's about whether a message could be turned into a transaction at all, not which category that transaction belongs to.

Expenso pipeline-health page showing ingestion, extraction, and classification stage status, plus counts for messages waiting on extraction, transactions waiting on classification, and retryable failures.
Pipeline health

Classification that gets better on its own

Every transaction moves through three tiers before it's filed:

1 · Rule-based

Substring match against 74+ known merchant patterns. Instant, full confidence.

2 · LLM fallback

Unmatched merchants go to Claude Haiku, which returns a confidence score. Anything ≥0.9 is auto-promoted into a new rule for next time.

3 · Manual override

Anything still wrong gets corrected inline — and that correction is learned as a rule too, so the system never makes the same mistake twice.

994
transactions classified automatically
9
spend categories tracked
~3 hrs
manual tracking eliminated / week
Three-tier classification engine

Ask it anything about your spending

A natural-language query interface translates plain-English questions into SQL via Claude Haiku, runs them against PostgreSQL, and returns a formatted answer — including multi-step analytical queries using CTEs. Every generated query is validated before it runs: SELECT/WITH only, no DDL, capped at 50 rows, 8-second timeout.

Ask Expenso interface with suggested questions and a natural-language input box.
Ask Expenso
Natural-language query flow

Design principles

Immutability — raw emails are never modified; every transaction is re-derivable from source. Idempotency — every insert uses ON CONFLICT logic, so re-running any phase is always safe. Self-healing — incremental ingestion means the pipeline recovers from a missed night without intervention. Honest about gaps — every view is labeled as captured from bank notifications, never presented as reconciled truth; missing coverage shows up as an explicit gap, not a silent zero. A macOS launchd job runs the full cycle every night at 00:01.