Paret - AI Expense Ledger
A private expenses app where a model reads bank statements and an assistant edits the ledger, with checks in code, spend caps and an append-only history.
Problem
Bank statements arrive as CSV, Excel, Word or PDF in every bank's own layout. A model can read them, but a ledger cannot accept a number the model made up.
Constraints
- A wrong amount in a ledger is worse than a missing one
- Account and card numbers must not reach a third-party model
- Moves between my own accounts must never count as spending or income
- AI spend has to have a ceiling
Overview
Paret is my personal expenses app: a phone app that takes expenses typed in plain words and sends push notifications, plus a desktop view for importing statements, setting rules and looking back. It is private and holds real financial data, so there is no link and no screenshot. This page covers the architecture only. The first commit is from 25 October 2026. I set the design and the rules and review the work, and coding agents wrote most of the code.
Decisions that carry the design
The model reads, and code checks. A model reads any statement format into rows. Paret then checks that every amount it returned appears in the source file, and it checks the running balance row by row. A file whose balances do not add up is refused rather than imported with a guess.
Reconcile, never delete. Each statement is reconciled against what is already stored: same, changed, added, matched to a pending item, or missing from the statement. A stored transaction is never deleted because a statement does not list it. It is shown to me instead.
Money is integer cents with a currency on every row. Balances are kept per currency and reports convert to euro. Transaction kinds include spend, income, move, fee and conversion, and a move between my own accounts is never counted as spending or income.
Identifiers are stripped before the model sees anything. Account numbers and card numbers are redacted before every model call, and stored assistant messages are stored redacted too.
The assistant works through tools and its changes can be undone. “Ask Paret” reads the ledger with tools. When it changes something, it applies the change at once and shows a card with Undo. Where the model is unsure of a category, the guess counts but is marked as an AI guess so I can find it later.
Spend has ceilings. There is a daily cap on AI spend, a separate cap for reading statements, and monthly caps for the assistant and for the weekly savings review. A cheaper model is the default and a larger one is used only for bigger jobs.
History is append-only, enforced by the database. Postgres triggers record every change and use of the app into a history table, and a trigger refuses updates and deletes on that table. The history can be downloaded as a log for a coding agent to read when something looks wrong.
What I can show
The checks, the triggers and the decisions are in the code and in the decisions log. I have no user numbers, and it has one user. The “AI Expense Tracker” on my CV is an older and different project.
Stack
- Language: TypeScript in a pnpm monorepo
- Web: Astro with React islands, installable PWA with web push
- Data: PostgreSQL with Drizzle, integer cents
- AI: Anthropic API, a small model by default