Extracted totals that reconcile to the cent

A total that is off in the last decimal looks right and reconciles wrong. How calculated fields come back exact, how to pin money to two decimals, and how to check the result.

5 min read

Ask a general purpose model to extract an invoice and one of the fields you want is often not printed on the page. The tax base when only the total and the rate are shown. The line amount when the document lists quantity and unit price. A converted figure. A discounted subtotal.

The model will give you a number. It got that number by doing arithmetic in its head, in a system built to predict text.

Why this goes wrong quietly

Language models are competent at arithmetic and not reliable at it, which is the worst combination for a pipeline. The failures do not look like failures:

  • The result is off in the last decimal, so it passes a glance and fails a reconciliation.
  • Rounding happens at the wrong step, so the total is a cent away from the sum of its parts.
  • The same document produces slightly different numbers on different runs.
  • A percentage is applied to the wrong base, producing a plausible figure that is wrong by a plausible margin.

None of these trip an alarm. They land in your database, and you find them later in an accounting mismatch, which is expensive to chase precisely because the number looked reasonable.

What Veralens gives you instead

A calculated field comes back calculated, not estimated. A tax base derived from a total and a rate, a line amount from quantity and unit price: you get the number the document implies, not a number that looks like it.

What that buys you:

The same document gives the same answer. Every run, every time. No drift between Tuesday and Wednesday.

Precedence is real. Parentheses, multiplication before addition, unary minus. Not approximately, actually.

Rounding happens once, at the end, on the final value, rather than at some intermediate step you never see.

Getting money onto exactly two decimals

Floating point arithmetic produces 110.60000000000001 from perfectly innocent inputs. That is not a value you want written to a money column.

Declare the step you want with multipleOf and that is what lands in your JSON:

json
{
  "type": "object",
  "properties": {
    "subtotal": { "type": "number", "multipleOf": 0.01 },
    "tax":      { "type": "number", "multipleOf": 0.01 },
    "total":    { "type": "number", "multipleOf": 0.01 },
    "quantity": { "type": "integer" }
  },
  "required": ["total"]
}

You get 110.6, not 110.60000000000001. An integer field comes back as a whole number rather than 3.0000000000000004.

Worth knowing: multipleOf, minimum and maximum are guarantees on the number you receive. pattern, minLength and maxLength are not enforced at all, so a rule about the shape of a string belongs in that field's description instead.

Check it against the document

Arithmetic being correct does not mean the right quantities went into it. That is still a reading task, and reading can be wrong.

Which is why the cheapest quality control in any document pipeline is to use the document's own redundancy:

ts
const lineTotal = invoice.line_items.reduce((sum, item) => sum + item.amount, 0)
const consistent =
  Math.abs(lineTotal - invoice.subtotal) < 0.01 &&
  Math.abs(invoice.subtotal + invoice.tax - invoice.total) < 0.01

if (!consistent) await flagForReview(invoice)

Invoices, receipts, payslips and bank statements almost all carry the same figure twice, once directly and once as a component. That redundancy is free error detection, it costs you two lines of code, and it catches the reading mistakes that correct arithmetic cannot.

The short version

An extraction should tell you what the document says, and the arithmetic on top of it should be arithmetic. Declare the decimal step you need, take the number you get, and check it against the document's own totals.

The quickstart gets you a first extraction in a few minutes, and the invoice walkthrough builds the schema field by field.

Try it on your own documents

Add funds from $5, create a key, and send your first document. Every job reports exactly what it cost.

Start extracting

Keep reading