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.