Tabular

Bank statement converters can get numbers wrong without telling you. I built Tabular to check its own output against the statement.

How it works

Design decisions

Integer pence

All money is stored as a whole number of pence. Floats can't hold values like 0.10 exactly, and the errors add up over a long statement.

Deterministic parser first

Text PDFs go through a deterministic parser, which has no cost per run and gives the same result every time. Only scans go to Gemini. Each bank is a YAML file, so adding one doesn't need new code.

Reconciliation runs last

Every row goes through the same check, whether it came from the parser or from Gemini. If the model gets a number wrong, the row gets flagged.

Carrying on after an error

When a row doesn't match, the check carries on from the balance printed on the statement. Otherwise every row after the error would get flagged too.

1p tolerance

Statements occasionally round, so the check allows a difference of 1p.

Privacy test

Statements are processed in memory. A test watches every file opened during extraction and fails CI if anything gets opened for writing.

The balance check

This is the core of the check from the Python engine, shortened a bit.

Try it

Patching React2Shell

React2Shell (CVE-2025-55182) has a CVSS score of 10, and Tabular was already live when I patched it. I upgraded to Next.js 15.1.12 and React 19.0.7.

Next steps

Tabular supports Barclays and Starling so far. The next step is adding more UK banks.