A bank gives you statements: a folder of OFX, QIF or CSV files, one per account, full of lines like CARD PAYMENT TO TESCO STORES 3456, £23.71. To turn that into an account book you can actually trust, every one of those lines needs an answer to a single question: what is the other account? A grocery payment’s partner is Expenses:Groceries; a salary line’s is Income:Salary; a £500 move to your own savings is a transfer between two of your own accounts — emphatically not income and not an expense. Answering that question a few thousand times, correctly and repeatably, is the real work of bookkeeping. ledgerforge automates it.

It is a small Python engine that reads bank exports and writes a proper double-entry GnuCash book — multi-currency, with a coded chart of accounts and FX prices. GnuCash is a good foundation for two reasons: its file format is open (a book is just an XML or SQLite file that any program can read), and it does real double-entry, the 500-year-old idea that makes a book self-checking. Because every transaction moves value between accounts and the amounts always sum to zero, the book obeys one identity at every moment — Assets − Liabilities = Equity — and a stricter test underneath it: take every account at its recorded value and the signed total across all of them is exactly nought. When it isn’t, something concrete is broken — a missing statement, a duplicated import, a miscoded transfer — and the size of the residual tells you what to hunt for. That is a number you can chase, instead of a mystery.

The pipeline is deliberately boring:

statements (OFX / QIF / CSV)
    → parsers        one small parser per bank format
    → rules          categorise(description) → account, first match wins
    → transfers      detect own-account moves so they never count as income/expense
    → overrides      date- or currency-scoped exceptions that beat the rules
    → GnuCash book   coded, multi-currency, self-checking
    → review toolkit four local HTML pages for the human to check and refine

The subtle piece is transfer detection. Move money from current to savings and it appears in two statements — out of one, into the other. Categorised naively, it becomes both a phantom expense and a phantom income, and both of your totals are now wrong. ledgerforge recognises your own-account markers and books the pair as what it is: an internal transfer between two of your asset accounts.

The design decision I am most pleased with is that the engine carries no private data at all — no account names, no numbers, no filesystem paths. Everything specific to a given ledger arrives through a configuration object loaded from a git-ignored file, and the account numbers used for transfer detection are read at runtime from registries kept outside any repository. That is a hard rule, and it is what lets the engine live in a public repo while the ledgers that use it — in my case a household and a small business, plus a public demo — stay entirely private. The review server is loopback- and LAN-gated for the same reason: you can share the review pages to your own wifi without exposing anything to the internet.

If you want the whole shape of it from first principles — what a GnuCash book is, why double-entry is worth the ceremony, how a shoebox of statements becomes a book — the repository’s HOW_IT_WORKS explains it assuming no accounting background.

You can also try the toolkit live — an interactive demo on entirely fictional data. Browse the resulting GnuCash book (balances, the P&L, and the accounting equation with its double-entry check), tune the categorisation rules, and download the book to open in GnuCash yourself. The repository’s self-contained demo also includes a headless Plaid sandbox rehearsal of the statement-fetch flow against a fake bank — no real accounts involved.

Like most of what I have built this year, ledgerforge was written in close collaboration with Claude. I brought the accounting model, the security rule, and the judgement about what the books should say; the AI brought the parsers, the piecash plumbing, and the patience for the thousand small categorisation cases. It is MIT licensed — take it, fork it, point it at your own statements.