Skip to main content

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-copilot works 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.1 by default. It has no authentication yet, so only expose it on your LAN deliberately: set HOST=0.0.0.0 (you will get a startup warning). PORT defaults to 8000.

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):

  1. Dedicated parsers (currently Kotak) are tried first.
  2. 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:

  1. Create a new file under finance_copilot/parsers/banks/ (e.g., sbi.py)
  2. Subclass StatementParser from finance_copilot/parsers/base.py
  3. Implement the parse() method to extract transactions from the PDF
  4. Register it in finance_copilot/parsers/registry.py before GenericStatementParser

Demo Screenshots

Dashboard

Dashboard Transaction overview with auto-annotate trigger and confidence scores

Review Queue

Review Queue Low-confidence annotations surfaced for human correction

Upload & Parse

Upload Drag-and-drop PDF upload with bank auto-detection

Transactions

Transactions Full transaction table with category chips, inline editing, and filters

Insights

Insights Spending breakdowns and trends

Groups

Groups Group shared expenses (trips, events) and track who paid vs. who settled their split

People

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)

Source distribution for finance-copilot 0.1.0
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)

Table of built distributions (wheels) for finance-copilot 0.1.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page