Skip to content
All projects
Artificial IntelligenceIn progress2025

Ledger

Ledger is a privacy-first personal finance tool that ingests messy bank CSV exports and produces a structured, categorised ledger. A small on-device classification pipeline handles the tedious sorting; a clean interface keeps the human in control of the edge cases.

Role
Sole designer & engineer
Year
2025
Status
In progress
Category
Artificial Intelligence
Tools & technologies
Next.jsTypeScriptPythonscikit-learnDuckDBTailwind
Links
GitHub
Ledger cover — an abstract editorial composition of aligned ledger rules in warm neutrals.

The problem

Personal finance apps either demand full bank access or bury you in manual data entry. I wanted something that respected privacy, worked from plain exports, and did the boring categorisation without pretending to be a financial advisor.

Context

Built over a semester alongside a data-mining course. The constraint that shaped everything: no raw transaction data should ever leave the machine, which ruled out most hosted-model approaches and forced a lean, local pipeline.

Process

From spreadsheet to model

I started by hand-labelling a few hundred of my own transactions to understand the real distribution of categories — groceries and transport dominate, but the long tail of one-off merchants is where a naive classifier falls apart.

Rather than reach for a large model, I trained a compact TF-IDF + linear classifier on merchant strings. It runs in milliseconds, is fully explainable, and — crucially — can be retrained on-device as the user corrects it.

  • Merchant-string normalisation to collapse noisy variants
  • Confidence thresholds that route uncertain rows to human review
  • Per-user correction loop that quietly improves accuracy over time

Designing for trust, not automation

The interface never hides an automated decision. Every categorised row shows its confidence, and low-confidence rows surface first. The goal was a tool that feels like a capable assistant, not a black box making claims about your money.

Research

  • 01Interviewed six students about how they currently track spending — most had abandoned an app within a month.
  • 02Benchmarked the linear classifier against a fine-tuned transformer; the accuracy gap (3.1%) did not justify the privacy and latency cost.
  • 03Studied Stripe and Copilot Money for how they present financial data without overwhelming.

Key decisions

Local-first over cloud sync

All parsing and inference run in the browser or a local Python process. Sync is opt-in and encrypted. This closed off some convenience features but made the privacy promise real.

Explainable model over marginal accuracy

A linear model I could inspect beat a black box I could not — especially for a domain where a wrong guess erodes trust instantly.

Human-in-the-loop by default

Corrections are a first-class interaction, not an error state. The system is designed to be taught.

Implementation

The pipeline

Exports are parsed with a tolerant CSV reader that handles the wildly different formats banks produce. Rows land in an in-browser DuckDB instance for fast local querying, which made the analytics views feel instant even over years of data.

The classifier ships as a small serialised model; inference happens client-side. A thin Python service exists only for the heavier retraining step.

Gallery

Ledger interface — categorised transaction rows with inline confidence indicators.
Low-confidence rows surface first, each with its confidence shown inline.
Ledger analytics — a restrained monthly spending breakdown.
Analytics stay deliberately quiet: a few numbers that matter, no dashboard noise.

Outcomes

  • 92% categorisation accuracy on my own two years of transactions after one correction pass.
  • Sub-100ms inference for a full year of data on a mid-range laptop.
  • Currently dogfooding monthly; a small closed beta is planned.

Lessons learned

  • The hardest part of an AI product is rarely the model — it is designing the moments where the model is wrong.
  • Explainability is a feature users feel even when they never open the explanation.
  • Local-first is a real design constraint, not just an engineering one; it changes what the product can promise.