← All posts Engineering · 8 min read

Three ways your totals disagree: rounding, currency, tax

When an extracted invoice does not add up, the parser is the most likely culprit but not the only one. Some invoices genuinely do not add up, and the difference between those two cases is worth more than a repaired number.

RBRita Ben-Ari · Maintainer, core
2 July 2026

About 4% of the invoices we ingest fail the reconciliation check described in the line-item post. Roughly two thirds of those are our extraction being wrong. The remaining third are documents where the arithmetic on the page is genuinely inconsistent, and understanding why matters, because those cases are the ones a person actually needs to see.

Rounding, and where it was applied

A line of 3 units at 4.995 each is 14.985. Printed, it is 14.99 or 14.98 depending on the rule, and the invoice total may be built from the rounded line values or from the unrounded ones. Both are defensible. They differ by a cent per line, and on a 200-line invoice they differ by enough to fail an exact-match check.

Add to that the rounding mode itself. Half-up is the common default. Half-even shows up in anything built on certain accounting libraries. Some jurisdictions specify rounding direction for tax specifically, in a way that does not match the rounding used everywhere else on the same document.

So the reconciliation is an equality with a tolerance, derived from the line count: a few cents on a short invoice, more on a long one. A discrepancy inside tolerance is recorded and not flagged. A discrepancy outside it is flagged and never silently corrected.

Currency, and the rate that is not on the page

Multi-currency invoices carry at least two failure modes. The first is representational: the same symbol means different currencies in different countries, a bare dollar sign being the obvious case, and the disambiguating information is usually in the vendor address, some distance from the number.

The second is that a converted total depends on a rate and a date that may not be printed anywhere. An invoice showing a foreign-currency subtotal and a local-currency total implies a rate; that rate is derivable by division, but which date it corresponds to (invoice date, delivery date, payment date) is a policy question the document does not answer.

We store the currency as extracted, per amount, never converted. Conversion is a query-time concern with an explicit rate source, and an answer involving conversion states the rate it used. An indexed amount that has been silently converted is a number with no provenance, and it is very hard to get that back.

Store what the document says. Compute the rest at question time, where the assumptions can be stated out loud.

Tax, which is where the real disagreements live

Tax is the largest single source of legitimate mismatch. Common shapes:

  • Mixed rates. Some lines standard-rated, some reduced, some exempt, with a single tax figure at the bottom that only reconciles if you know which line is which.
  • Reverse charge. Tax shown as zero with a note in the text, and a total that is correct precisely because the tax is the recipient's problem.
  • Tax on shipping, sometimes. Depends on jurisdiction and on whether shipping is a separate supply, and the invoice rarely explains its choice.
  • Rounding at the tax-code level. Tax computed per rate group and rounded per group, never per line or per invoice, which produces small differences from every other method.
  • Withholding. An amount deducted at the bottom so that the payable total is intentionally less than the sum plus tax.

Across the flagged set, tax accounts for 46% of genuine mismatches, rounding 31%, currency 12%, and the remainder are invoices with an actual mistake on them: a line that does not belong, a total typed instead of computed, a discount applied twice.

Implementation notes that are not optional

  • Never a binary float for money. Decimal arithmetic with explicit scale, or integer minor units. This is old advice and it is still routinely ignored.
  • Keep both numbers. The value as printed, and the value as computed. Never overwrite one with the other. The pair is the evidence.
  • Attach a currency to every amount at parse time. An amount without a currency is not a number, it is half of one, and the missing half gets guessed later by something with less context.
  • Tolerances live in configuration. Different finance teams have different thresholds for caring, and the difference between them is a config value, not a code change.

What the user sees

An unreconciled invoice answers questions normally, with one addition: the answer says the document does not reconcile, by how much, and which of the three categories the difference most resembles. That last part is a heuristic and is labelled as one.

The alternative, repairing the total so everything looks clean, was in an early prototype for exactly one week. It made the demo better. It also meant that a document with a real error on it became indistinguishable from a correct one — the specific outcome an answer engine over financial documents exists to prevent.

Money handling is in quire/money.py, roughly 300 lines with a property-based test suite that is considerably longer. If you find a rounding case it gets wrong, that is a bug report we will act on quickly.