← All posts Engineering · 11 min read

An invoice is a table wearing a document costume

The header fields are easy and nobody cares about them. The line items are where the money is, and the line items are the part every parser gets wrong. The good news: an invoice checks its own arithmetic, and almost no other document does.

SDSofia Duarte · Maintainer, document parsing
18 August 2026

Vendor name, invoice number, issue date, due date, total. Every extraction demo shows those five fields, because those five fields are easy. They sit in predictable places, they are short, and a regex plus a bit of layout sense gets you to 97% without much thought.

They are also the fields nobody had a problem with. The questions people actually bring to a pile of invoices are about the middle of the page: what did we pay for this line, how many of them, which of these charges is the recurring one, why is this month 12% higher than last month. All of that lives in the line-item table, the least reliably extracted structure in the entire document.

Eight ways the line-item table lies

  • Wrapped descriptions. A long description wraps to a second visual line with empty numeric columns. Naively, that is a line item with no quantity and no amount. It is not a line item at all.
  • Continuation across pages. The table restarts on page two, sometimes with the header repeated, sometimes not, sometimes with slightly different column widths because the generator re-flowed it.
  • Subtotal rows inside the table. Group subtotals sit in the same grid as items. Sum the amount column blindly and you double-count every grouped section.
  • Discount and credit rows. Negative amounts, sometimes written with a leading minus, sometimes in parentheses, sometimes as a positive number under a column headed Less.
  • Column drift. Unit price and amount swap positions between two vendors, and once, memorably, between page one and page two of the same invoice.
  • Merged and implied cells. One quantity spanning three description rows. The quantity belongs to all three, or to the first, and the document does not say which.
  • Units hidden in text. "3" in the quantity column and "per 100 units" in the description. Multiply and you are off by two orders of magnitude.
  • Tax per line. Some invoices carry a rate column per line, some apply one rate at the bottom, some do both and expect you to notice that the bottom one only covers the untaxed lines.

None of these are exotic. Across a sample of 40,000 invoices from three donated corpora, 31% contained at least one of them, and vendors are remarkably consistent. If a vendor's template does one of these, every invoice from that vendor does it.

The arithmetic is free ground truth

Here is the property that makes invoices better than almost every other document type: they carry their own check digit. The line amounts, the discounts, the tax and the total are supposed to agree. When your extraction is wrong, they usually stop agreeing.

So we do not trust an extracted table until it reconciles. Quantity times unit price should equal the line amount, per line. The line amounts, plus or minus adjustment rows, should equal the subtotal. Subtotal plus tax should equal the total. Three cheap equalities, run at ingestion, before anything is indexed.

An invoice tells you when you parsed it wrong. Use that, or you are throwing away the only labelled data you get for free.

The reconciliation catches most of the list above, and it catches them without anyone labelling anything:

FailureCaught by arithmeticNote
Wrapped description counted as a rowYesRow contributes zero, harmless, but flagged as a shape anomaly
Subtotal row counted as an itemYesSum overshoots by exactly a group subtotal, which is a recognisable signature
Discount sign lostYesOvershoots by exactly twice the discount
Unit price and amount swappedUsuallyNot when quantity is 1, which is why quantity-1 invoices need column-header confidence
Missing continuation pageYesSum undershoots. The most valuable single check we run
OCR digit error in a line amountUsuallyUnless the same error also appears in the total, which happens on regenerated scans
Per-100 unit pricingNoArithmetic is internally consistent, the meaning is still wrong. Needs the description read
Rate applied to the wrong linesSometimesOnly when the tax total is itemised and not a single figure

What we do when it does not reconcile

The tempting move is to repair the number: adjust the line that makes the sum work out. We do not do that, ever, and the reasoning is the same one that runs through everything here: a silently repaired invoice is indistinguishable from a correct one, and the day the repair is wrong nobody can tell.

Instead the document is indexed with an unreconciled flag, both the printed total and the computed total are stored, and any answer touching that invoice says so. In practice around 4% of invoices land in that state. Of those, roughly two thirds are our parsing and one third is a genuinely inconsistent invoice, which the finance people involved were, in every case we followed up on, quite pleased to be told about.

Vendor templates are the cheap win

Invoices are not arbitrary documents. They come from a small number of templates, repeatedly, for years. Once a vendor's layout has reconciled ten times, the column mapping for that vendor is known, and new invoices from it can be checked against the known shape instead of inferred from scratch.

It is a lookup table keyed on a layout fingerprint, nothing cleverer, and it moved line-item accuracy from 91% to 98.6% on repeat vendors. The remaining errors concentrate almost entirely in first-time vendors and in the month after a vendor changes their template — exactly the moment the reconciliation check starts failing and tells you to look.

The reconciliation rules are in quire/ingest/invoice/reconcile.py, and they are about 200 lines. The tests are longer than the implementation. Correct, for money code.