Savvant
Canadian personal finance on live bank data. Links a user's accounts through Plaid, pulls transactions, debts, and balances continuously, and layers per-user machine learning, anomaly detection, forecasting, and a chatbot whose numbers are never hallucinated.
- Production Plaid integration: Canadian banks syncing live with Fernet-encrypted access tokens, verified ES256 JWT webhooks, and cursor-based sync isolated per row so one bad transaction can never wedge the pipeline.
- Hybrid chatbot where the LLM never does the math. GPT-5.4-mini classifies intent and formats answers; a deterministic Python engine computes every number from real data.
- Per-user ML with disciplined ground truth: only explicit user confirmations train the model, uncertain predictions route to a verification queue, and every correction retrains in the background.
- Double-count-proof by design: in-place pending-to-posted promotion and a typed transaction taxonomy keep spend totals correct across chequing and credit accounts.
FastAPI · PostgreSQL · React · Plaid · LightGBM · IsolationForest · Prophet · GPT-5.4-mini · Railway · Vercel · Cloudflare
Why it exists
A bank app tells you where money went, a category total and a pie chart, but not the patterns behind it. Savvant links a user's Canadian bank accounts through Plaid, keeps transactions, debts, and balances in continuous sync, and answers the pattern question: behavioral spending tags with dollar-quantified tips, anomaly flags with plain-language reasons, subscription tracking that notices price creep, and a month-over-month view of what actually changed.
The project began as a privacy-first PDF statement parser and pivoted to Plaid bank aggregation as the primary data source: continuous, webhook-driven sync replaced monthly uploads, and the PDF path was retired behind a feature flag. Killing my own original architecture when the better design became clear was part of the education.
The other motivation was personal: proving I could take an ML system all the way to production alone. Real third-party bank integration, per-user modeling, API design, frontend, auth, deployment, and the unglamorous edge cases in between, like pending transactions that change their ID when they post.
Architecture decisions
Tradeoffs & known limits
- ML models live on ephemeral disk. A redeploy wipes them. Because per-user retraining is fast and fully automatic, the practical cost is a brief cold start, not data loss. Database-backed model storage is the planned fix.
- Tests run against the real dev database. The test client executes the actual startup lifespan, which once leaked a Postgres advisory lock and hung the suite with nine zombie connections. Lock acquisition now has a timeout and the suite enforces a global timeout, but a test-scoped database is the correct eventual fix.
- The verification loop's acceptance test is lived, not unit-tested. The queue badge should trend toward empty over weeks of normal use as the model learns. If it doesn't, the diagnosis path is labels reaching training, features separating merchants, then the confidence threshold.
- Investments are a read-through only. Nothing is modeled or persisted. Deliberately deferred rather than half-built.