Finance Copilot
Finance Copilot is a local-first personal finance application for parsing bank statements, organising transactions and analysing expenses.
uvx finance-copilot
That's it. The app installs, starts on localhost, and opens in your browser. Drop in a bank statement PDF (or click "Load demo data") and watch every transaction get parsed, balance-checked, and categorized. No account, no cloud, no data leaving your computer.
Requires uv (one installer command, no Python needed). Prefer pipx?
pipx run finance-copilotworks too. First run takes a minute while dependencies download.On Python 3.15 the app runs with built-in embeddings off, because their runtime has no 3.15 build yet. For the full experience run
uvx --python 3.14 finance-copilot.
Out of the box you get statement parsing with running-balance verification, rule-based and history-based categorization, expense groups, people tracking, and insights. Add AI when you want it: point Settings › AI & Model at Ollama or paste any OpenAI-compatible API key.
Highlights
- 100% local - SQLite database + Ollama for inference, everything stays on your machine
- Multi-stage annotation pipeline - Your rules > built-in rules > learned merchants > RAG Direct > RAG Prompted > Plain LLM, with graceful fallbacks
- Your own rules - Settings > Rules: a word or phrase and a category, with a live preview of what it matches before you save
- Human-in-the-loop - Low-confidence predictions surface in an intuitive and fast review queue for correction
- Bank statement parsing - Dedicated Kotak parser plus a generic parser that handles most banks' tabular statement PDFs, with every row verified against the running balance; UPI metadata extracted automatically
- Vector similarity search - sqlite-vec powers few-shot retrieval for better categorization over time
- React dashboard - Transaction management, annotation review, expense groups, and insights/charts
How the Pipeline Works
When you trigger auto-annotation, each transaction flows through four stages. The first match wins:
Transaction
|
v
[Stage 1: Rules] - Known-person match (UPI handle from people table),
| confidence: 0.95 then merchant keywords & UPI notes (~70 built-in rules)
| no match? ↓
v
[Stage 2: RAG Direct] - Find top-5 similar annotated transactions via embeddings
| confidence: dynamic If best match similarity >= 0.92 and donor is trusted,
| copy annotation (cosine × agreement × margin factors)
| no match? ↓
v
[Stage 3: RAG Prompted] - Few-shot LLM call with retrieved examples as context
| confidence: dynamic llm_conf × calibrated dampening (base 0.92, adapts with feedback)
| no match? ↓
v
[Stage 4: Plain LLM] - Cold LLM call without examples
| confidence: dynamic llm_conf × calibrated dampening (base 0.85, adapts with feedback)
v
Annotation saved → below threshold (0.85)? → Review Queue
Confidence dampening for Stages 3 and 4 uses Bayesian calibration, starting from static base values but dynamically adjusting per (source, category) as human feedback (confirmations, corrections) accumulates.
See docs/annotation-pipeline.md for the full Mermaid flowchart.
Tech Stack
| Layer | Technology |
|---|---|
| Backend | FastAPI (async Python), Pydantic v2 |
| Frontend | React 18, React Router, Vite |
| Styling | Tailwind CSS, Lucide React icons |
| Database | SQLite + sqlite-vec (vectors in the same DB file) |
| LLM | Ollama — qwen3.5:4b (inference), nomic-embed-text (embeddings) |
| PDF Parsing | pdfplumber |
| Charts | Chart.js + react-chartjs-2 |
| CLI | Typer + Rich (terminal review queue) |
| IDs | ULID (sortable, unique) |
Developing
Prerequisites
- Python 3.10+
- Node.js 18+
- Ollama installed and running
Pull the required models:
ollama pull qwen3.5:4b
ollama pull nomic-embed-text
Backend
# Install dependencies (creates .venv from pyproject.toml + uv.lock)
uv sync
# Start the API server (runs migrations automatically on startup)
# Binds to 127.0.0.1 by default — loopback only, so your statements are not
# exposed on the network. Set RELOAD=1 for auto-reload during development.
RELOAD=1 uv run python -m finance_copilot
The server binds to
127.0.0.1by default. It has no authentication yet, so only expose it on your LAN deliberately: setHOST=0.0.0.0(you will get a startup warning).PORTdefaults to8000.
Frontend
cd ui
npm install
npm run dev # Dev server at http://localhost:5173 (proxies API to :8000)
npm run build # Production build → ui/dist/ (served by FastAPI)
Environment Variables
Create a .env file in the project root:
OLLAMA_URL=http://localhost:11434
OLLAMA_MODEL=qwen3.5:4b
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
CONFIDENCE_THRESHOLD=0.85
For password-protected statement PDFs, enter the password in the upload form — it is sent with the upload request, not stored.
All values have sensible defaults in finance_copilot/config.py — the .env file is optional unless you need to override them.
CLI Review Queue
uv run python -m finance_copilot.cli review
Interactive terminal UI for reviewing and correcting low-confidence annotations.
Project Structure
finance_copilot/
├── api/routes/ # FastAPI endpoints (statements, transactions, annotations, ...)
├── db/
│ ├── migrations/ # Versioned SQL migrations (applied once, tracked in schema_migrations)
│ └── queries/ # SQL query builders
├── models/ # Pydantic schemas
├── parsers/
│ └── banks/ # Bank-specific PDF parsers (kotak.py)
├── pipeline/ # Annotation engine (rules, embed, annotate, llm, calibration)
├── config.py # All settings (Pydantic BaseSettings, reads .env)
├── cli.py # Typer CLI for terminal review
└── main.py # FastAPI app entry point
ui/
├── finance_copilot/
│ ├── pages/ # Dashboard, ReviewQueue, Transactions, Upload, Groups, People, Insights
│ ├── components/ # AnnotationPanel, CategoryPicker, TransactionTable, TagInput, ...
│ ├── contexts/ # StatementContext, ToastContext
│ └── lib/api.js # HTTP client wrapper
└── vite.config.js
data/ # SQLite database (gitignored)
docs/ # Pipeline documentation
Your Data
Everything lives in one SQLite file, finance.db, in the app's data folder:
~/Library/Application Support/FinanceCopilot on macOS, %LOCALAPPDATA%\FinanceCopilot on Windows, and ~/.local/share/FinanceCopilot on Linux.
Pass --data-dir to keep it somewhere else.
Settings › Data downloads a backup of the whole ledger.
The file leaves out your AI provider's API key, so it is safe to share or attach to a bug report.
Restore from the same tab: the app checks the file, saves your current data as pre-restore-<timestamp>.db next to finance.db, then swaps the backup in.
To restore by hand instead, quit the app, replace finance.db in the data folder with the backup file, and start it again.
Delete any finance.db-wal and finance.db-shm files next to it first, or SQLite may replay them over the restored file.
Demo Data
Click "Load demo data" on an empty dashboard to explore with four months of fictional transactions, people, and an expense group. To build a separate demo database instead (it never touches your real ledger):
uv run python scripts/seed_demo_data.py # writes data/demo.db
uv run python scripts/seed_demo_data.py --wipe # remove demo rows
The demo data includes:
- Pre-annotated transactions from all pipeline stages (rule, rag_direct, rag_prompted, llm, manual)
- Low-confidence annotations that appear in the review queue
- Unannotated transactions ready for auto-annotate
- Synthetic people for known-person matching, and a settled Goa trip group
After seeding, trigger auto-annotate on the remaining unannotated transactions to see the full pipeline in action.
Bank Statement Parsing
Uploads are auto-detected by the parser registry (finance_copilot/parsers/registry.py):
- Dedicated parsers (currently Kotak) are tried first.
- The generic parser (
finance_copilot/parsers/generic.py) handles everything else: it extracts tables from the PDF, infers which columns are date / description / debit / credit / balance from the header row, and then verifies every row arithmetically against the running balance (balance[i] == balance[i-1] ± amount). It only claims a statement when ≥80% of those checks pass, so it cannot silently ingest wrong amounts — unsupported layouts are rejected with an error instead.
Limitations of the generic parser: it needs a text-native PDF (no scans/OCR), a tabular layout with a header row, and a running-balance column (so most savings account statements work; credit-card statements generally won't yet).
Adding a Dedicated Bank Parser
Only needed when the generic parser can't handle a bank's layout:
- Create a new file under
finance_copilot/parsers/banks/(e.g.,sbi.py) - Subclass
StatementParserfromfinance_copilot/parsers/base.py - Implement the
parse()method to extract transactions from the PDF - Register it in
finance_copilot/parsers/registry.pybeforeGenericStatementParser
Demo Screenshots
Dashboard
Transaction overview with auto-annotate trigger and confidence scores
Review Queue
Low-confidence annotations surfaced for human correction
Upload & Parse
Drag-and-drop PDF upload with bank auto-detection
Transactions
Full transaction table with category chips, inline editing, and filters
Insights
Spending breakdowns and trends
Groups
Group shared expenses (trips, events) and track who paid vs. who settled their split
People
Known counterparties with UPI handles and relationships, so transfers get categorized correctly
Testing
uv run pytest finance_copilot/tests/
Metadata
Release files for finance-copilot 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| finance_copilot-0.1.0.tar.gz | 314.8 kB | With provenance | Details for finance_copilot-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| finance_copilot-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for finance_copilot-0.1.0-py3-none-any.whl |
Total release size: 684.2 kB
Release files / finance_copilot-0.1.0.tar.gz
| Download URL | finance_copilot-0.1.0.tar.gz |
|---|---|
| Size | 314.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3afc025b91971b56ceb4a4f0006e38749d19977c48c55bc650569e5694b719a4
|
|
BLAKE2b-256 checksum How to use checksums |
639a135fb936fd5d36adbca555d94f7a1180fe832a0a3ab1fdc7a0f6e629f4b2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logRelease files / finance_copilot-0.1.0-py3-none-any.whl
| Download URL | finance_copilot-0.1.0-py3-none-any.whl |
|---|---|
| Size | 369.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ca2551e2d1edafc8a7af37a172b8e0e12ef6d399d6e27345bf365eb7635d9654
|
|
BLAKE2b-256 checksum How to use checksums |
79f602632e0bf3671807daaabb1dbe4f47f22a2a4ff245d64743c45640480a5d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logSecurity for finance-copilot 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for finance_copilot-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.