Personal Finance Platform
Full-Stack Engineer
This is a self-hosted web app that replaced a yearly budgeting spreadsheet. Bank and card transactions arrive on their own through Plaid, get cleaned up and categorized, and only the ones the app is unsure about need a person to look at them. It runs in Docker on a home server, reachable only from the home network or over a VPN, and it backs itself up and tests its own restores.
How is the finance app put together?
It is one container. FastAPI (Python 3.12) serves both the JSON API and the built React app, SQLAlchemy 2 maps the data, and Alembic migrations evolve the schema. The React front end uses TypeScript, TanStack Query for server state, and Recharts for the dashboards.
All of the financial math (taxes, budgets, shared expenses, card rewards, and paycheck estimates) lives in a core package with no I/O, which keeps it unit-testable; services around it handle bank sync, categorization, imports, and backups. Rules that change over time are data, not code: tax brackets live in one YAML file per year, and card-reward rules and merchant clean-up patterns are editable YAML files too.
How do transactions get from the bank into the app?
Plaid connects each institution, and a scheduler inside the app (APScheduler) pulls new transactions several times a day, plus statements, holdings, and a connection health check every morning. If the server was off, missed jobs run when it comes back. Bank-connection tokens are encrypted with a key kept outside the database, and the login password is hashed with Argon2.
Development runs against Plaid's sandbox and a separate database, so it never touches real data.
How does categorization learn from corrections?
New purchases land in an inbox grouped by merchant, each with a cleaned-up name and a suggested category. Confirming or correcting a group turns the correction into a rule, so the next transaction from that merchant arrives already filled in. Transfers, card payments, and deposits are recognized and kept out of spending, paystub PDFs are parsed with pdfplumber for exact paycheck figures, and nightly jobs detect recurring bills and record a net-worth snapshot.
How do you migrate years of spreadsheet history safely?
An importer built on openpyxl reads the original workbooks, with a dry-run mode that reports what it would change before anything is applied. A verify command then compares the app's totals with each workbook, and golden tests run that comparison against the real files, so the migration is checked by the numbers rather than by eye.
What happens if the home server dies?
Every night the app writes a compressed database snapshot, keeping 14 daily and 12 monthly copies, optionally encrypted with age. A second machine copies new backups off the server twice a day, and every Sunday a job restores the latest backup to prove it actually works. Deploying is a single make target that copies the code to the server, builds the image there, and restarts the app.