Finance Tracker
AI-powered personal accounting
What Finance Tracker Does
Finance Tracker is a personal expense categorization tool with AI-powered auto-tagging. You import bank/credit card transactions (CSV), the AI categorizes them (groceries, rent, subscriptions, etc.), and you get charts showing where your money actually goes.
The core purpose: Stop wondering where $3,000 disappeared last month. Let AI read transaction descriptions and tell you "you spent $847 on food delivery this month."
The Architecture
Three-Pillar Breakdown
🗄️ DATABASE: PostgreSQL │ ├─ users User accounts (via Keycloak SSO) ├─ transactions Imported bank transactions ├─ categories Expense categories (custom + defaults) ├─ categorization_rules "If description contains 'UBER' → Transportation" └─ ai_suggestions OpenAI's category guesses (stored for review) 🖥️ INTERFACE: Next.js (Frontend) │ ├─ /dashboard Spending overview (charts, totals by category) ├─ /import CSV upload interface ├─ /transactions List view with manual category overrides ├─ /categories Manage custom categories └─ /settings OpenAI API key input, budget goals ⚡ API LOGIC: Python (FastAPI) Backend │ ├─ /api/import Parse CSV, extract transactions ├─ /api/categorize Send to OpenAI, get category predictions ├─ /api/transactions CRUD for transaction management └─ /api/reports Generate spending reports
Why Split Frontend/Backend?
Finance Tracker started as a weekend experiment. The architecture reflects that:
- Frontend (Next.js): Fast prototyping, good for charts and forms
- Backend (Python): Better CSV parsing libs, easier OpenAI integration, pandas for data analysis
If we were starting today, we'd probably use Next.js for the whole thing (like Rental Platform). But it works, so we haven't refactored.
The AI Categorization Flow
1. User uploads CSV (e.g., from Chase credit card)
└─ Example row:
Date: 2025-01-15
Description: "UBER *TRIP 2X3Y4"
Amount: -$18.42
2. Backend parses CSV → extract transactions
3. For each transaction, check rules first:
└─ IF description matches rule → apply category
└─ ELSE → send to OpenAI API
4. OpenAI API call:
Prompt: "Categorize this transaction:
Description: UBER *TRIP 2X3Y4
Amount: $18.42
Choose from: Food, Transportation, Entertainment,
Housing, Utilities, Healthcare, Other"
Response: "Transportation"
5. Store AI suggestion in database (not auto-applied)
6. User reviews suggestions in UI:
└─ Accept (save category)
└─ Override (choose different category)
└─ Create rule ("Always categorize UBER as Transportation")
7. Once approved, charts update with new data
Why Not Auto-Apply AI Categories?
AI gets it wrong sometimes. Examples we've seen:
- "AMAZON MKTPLACE" → AI guessed "Shopping", but it was actually groceries (Amazon Fresh)
- "SHELL OIL" → AI guessed "Transportation", but it was gift cards (not gas)
- "VENMO PAYMENT" → AI guessed "Other", but it was rent (split with roommate)
So the flow is: AI suggests, human decides, create rules to avoid re-categorizing next month.
The Rule System
Once you categorize "STARBUCKS" as "Food", Finance Tracker offers to create a rule:
RULE CREATION PROMPT:
"Create a rule so future Starbucks transactions are auto-categorized?"
IF YES:
└─ Save rule:
IF description CONTAINS "STARBUCKS"
THEN category = "Food"
NEXT MONTH:
└─ New transaction: "STARBUCKS #1234 SAN FRANCISCO"
└─ Rule matches → auto-categorize as "Food"
└─ No AI call needed (faster + saves OpenAI API costs)
Over time, you build a personal rule set. After 3-4 months, AI is only needed for truly novel transactions.
The Tech Stack
| Layer | Technology | Why This Choice |
|---|---|---|
| Frontend | Next.js + TypeScript | Charts (Recharts), forms, SSR for dashboard. |
| Backend | Python + FastAPI | Pandas for CSV parsing, OpenAI Python SDK, easy data analysis. |
| Database | PostgreSQL | Store transactions, categories, rules. Date-range queries. |
| AI | OpenAI API (GPT-4) | Transaction description → category prediction. |
| Auth | Keycloak (SSO) | Same login as other BENED apps. |
| Deployment | Docker (2 containers) | Next.js frontend + Python backend, nginx reverse proxy. |
CSV Import Pattern
Different banks have different CSV formats. Finance Tracker uses column mapping:
CHASE CREDIT CARD CSV:
Columns: Transaction Date, Post Date, Description, Category, Type, Amount
BANK OF AMERICA CSV:
Columns: Date, Description, Amount, Running Bal.
IMPORT INTERFACE:
User uploads CSV
└─ Backend detects headers
└─ Shows mapping UI:
"Which column is the transaction date?"
→ User selects "Transaction Date"
"Which column is the description?"
→ User selects "Description"
"Which column is the amount?"
→ User selects "Amount"
└─ Save mapping preferences per bank
└─ Next import auto-detects bank and uses saved mapping
Spending Reports
The dashboard shows:
- Pie chart: Spending by category (this month)
- Line chart: Spending trends over time
- Top merchants: Where you spend most (e.g., "Amazon $1,234, Starbucks $287")
- Budget vs actual: If you set category budgets ("Food: $500/mo"), shows overage
Backend generates these via SQL queries:
SPENDING BY CATEGORY (THIS MONTH):
SELECT category, SUM(amount) as total
FROM transactions
WHERE user_id = ?
AND date >= '2025-01-01'
AND date < '2025-02-01'
GROUP BY category
ORDER BY total DESC;
SPENDING TREND (LAST 6 MONTHS):
SELECT
DATE_TRUNC('month', date) as month,
SUM(amount) as total
FROM transactions
WHERE user_id = ?
AND date >= NOW() - INTERVAL '6 months'
GROUP BY month
ORDER BY month;
Staging vs Production
| Staging | Production | |
|---|---|---|
| URL | stagingfinance.bened.works | finance.bened.works |
| Code | /opt/bened-finance-tracker | /srv/bened-finance-tracker |
| Database | finance_staging | finance_production |
| OpenAI | Shared API key (test mode) | User's own API key (bring-your-own) |
| Purpose | Feature dev, test imports | Real users, real transactions |
Privacy Considerations
Bank transactions are SENSITIVE data. Design choices to protect privacy:
- No automatic bank connections: Users upload CSVs manually (we don't use Plaid or similar aggregators)
- OpenAI doesn't see amounts: We only send transaction descriptions to AI, not dollar amounts
- Data stays in BENED database: Not shared with third parties
- User can purge data: "Delete all transactions" button wipes everything
What's Missing (Known Gaps)
- Recurring transaction detection: "You pay Netflix $15.99 every month" insights
- Budget alerts: Email when you exceed category budget
- Multi-account support: Currently one account per user (can't combine checking + credit card)
- Export to CSV: No way to export categorized data back out
- Mobile app: Web-only (responsive, but no native iOS/Android)
The Deployment Story
PRODUCTION DEPLOY:
1. Build frontend:
cd frontend && npm run build
→ Generates static files in .next/
2. Build Docker images:
docker build -t finance-frontend:latest ./frontend
docker build -t finance-backend:latest ./backend
3. Stop old containers:
docker stop finance-frontend finance-backend
docker rm finance-frontend finance-backend
4. Start new containers:
docker run -d --name finance-frontend \\
--network bened-platform_bened-network \\
finance-frontend:latest
docker run -d --name finance-backend \\
--network bened-platform_bened-network \\
--env-file .env \\
finance-backend:latest
5. Nginx at finance.bened.works proxies:
└─ / → frontend container
└─ /api → backend container
How This Connects to Other Apps
Finance Tracker is mostly standalone, but:
Finance Tracker → Operations Portal (Planned) └─ Export categorized expenses └─ Used for LLC/nonprofit expense reporting Keycloak → Finance Tracker └─ SSO login (same user identity across BENED apps)
The Honest Assessment
Finance Tracker is a personal tool that became a product. It started as "I need to see where my money goes", became useful for the team, and now we're wondering if others would pay for it.
What works well:
- AI categorization is 85-90% accurate (better than manual tagging)
- CSV import handles most US banks
- Charts make spending patterns obvious
What needs work:
- Multi-account support (most people have checking + credit + savings)
- Recurring transaction insights ("Your subscriptions cost $247/mo")
- Better mobile UX (works, but not optimized)
It's in the "useful internally, not quite ready for public launch" phase.