← SnapReceipt

Self-Checking Math: Why Every Extraction Should Verify Its Own Arithmetic

Sheece Gardezi self-checkingaccuracyocr

Self-Checking Math: Why Every Extraction Should Verify Its Own Arithmetic

AI reads a receipt and says the total is $47.83. How do you know that's right? Most tools ask you to trust the AI. The number appears in a clean field, looks plausible, and flows straight into your expense report or bookkeeping software. But "looks plausible" is exactly how a misread digit becomes a reconciliation headache three weeks later: the books don't balance, and you lose an hour hunting a four-dollar error through a pile of paper. SnapReceipt takes a different position: don't trust the AI, ask the math. Every extraction we produce is cross-checked against its own internal arithmetic before you ever see it. If the numbers on the receipt don't add up the way the receipt itself says they should, the result is flagged for your review, never silently passed through.

This post explains exactly what those checks are, what happens when one fails, and why we think this is the difference between OCR that looks right and OCR that is right.

The arithmetic checks

A receipt is not just text. It's a small system of equations. The line items should sum to the subtotal. The subtotal plus tax should equal the total. The tax should correspond to a plausible rate. These relationships hold on nearly every well-formed receipt, which makes them a free, ground-truth-free way to verify an extraction: you don't need to know what the receipt should say, only whether what the AI read is internally consistent.

Our in-house pipeline runs a battery of these checks on every extraction. This layer is proprietary engineering we built ourselves, and it's what makes the guarantees possible: because the reading, the structuring, and the checking are one pipeline under our control, no result can bypass the math on its way to your screen. Each check returns a structured verdict: the rule name, whether it passed, the expected value, the actual value, a human-readable detail string, and which fields it touched.

Line items must sum to the subtotal

The most basic check: add up every extracted line-item amount and compare it to the printed subtotal. If the AI read nine items and their amounts sum to $43.48 but the printed subtotal is $47.83, something was misread: a digit, a quantity, or a missing line. The comparison uses a tolerance of ±$0.10 rather than a strict cent-level match, because real-world receipts include rounding quirks and service charges that make penny-exact equality too brittle. (We measure subtotal + tax = total holding only about 65-73% of the time at ±$0.01 across benchmark datasets, precisely because of rounding and service charges, so the tolerance is deliberate, not sloppy.)

When a receipt has line items but no printed subtotal, the check adapts: the item sum is compared against the total, or against the total minus tax, since either is a valid reading of such a receipt.

Subtotal plus tax must equal the total

The second check verifies the money chain end to end: subtotal + tax_amount = total_amount. Here again there's a real-world wrinkle to handle: many receipts (especially in GST/VAT countries like Australia) print tax-inclusive prices, where the printed subtotal already equals the total and tax is shown as a component of it. The check passes if either semantics holds: the tax-exclusive form (subtotal + tax = total) or the tax-inclusive form (subtotal = total with tax carved out). What it won't accept is neither: a total that doesn't relate to the subtotal and tax in any coherent way.

Tax must be plausible for the currency

Where the receipt's currency has a known, single national consumption tax rate, the extracted tax is validated against it. The check currently covers five currencies: AUD at 10% (GST), NZD at 15% (GST), GBP at 20% (VAT), IDR at 10% (PB1 restaurant/service tax), and SGD at 9% (GST). Both exclusive and inclusive interpretations are accepted, since a receipt may print either.

For any other currency (where tax rates vary by state, province, or product category and no single rate can be assumed), the check degrades gracefully to a plausibility band: the inferred tax rate must fall between 0% and 25%. A receipt claiming 34% tax on a USD bill gets flagged; one claiming 8.25% does not.

The supporting cast

Alongside the three headline checks, the same pass enforces a set of sanity rules that catch cruder failures:

Every applicable check runs on every extraction. Not on a sample. Not when a heuristic suspects trouble. Every time.

What happens when a check fails

Here is the part that matters most, and the part we are most deliberate about: nothing is silently corrected, and nothing is silently passed.

A failed check produces a structured record: which rule failed, what value was expected, what value was actually read, and which fields are implicated. That record surfaces with the result so you can see exactly what tripped and where to look. The result is flagged for human review; you remain the authority on the final numbers. This is an explicit design decision: the checks are a verification layer, not an auto-repair layer. The human is the authority on edits.

Why not auto-fix? Because a mismatch has two possible causes. Sometimes the AI misread the receipt: a smudged 8 became a 3. But sometimes the receipt itself is unusual: a split payment, a manual discount the cashier typed over the printed price, a deposit already paid. Auto-correcting the extraction to force the arithmetic to balance would be right in the first case and would actively corrupt your records in the second. Flagging preserves both the signal and your judgment.

The field-validation layer works the same way. It checks required fields (vendor, invoice number, date, currency, subtotal, tax, total) for presence, validates date formats (accepting both ISO YYYY-MM-DD and Australian printed DD/MM/YYYY forms), verifies Australian Business Numbers against their official checksum (subtract 1 from the first digit, take a weighted sum, and require divisibility by 89), and sanity-checks email and phone formats. Each issue comes back with a severity, the field name, a message, and, where applicable, a concrete suggestion for what to look at.

Fix suggestions, not fixes

When validation finds a problem, it tells you where to look rather than guessing the answer. A failed ABN checksum suggests "Verify ABN digits or check OCR accuracy." An amount inconsistency suggests "Check OCR extraction of monetary values." Line items that don't sum to the subtotal come with "Some items may be missing or misread." These pointers exist to cut your review time: instead of re-checking the whole receipt, you go straight to the field that failed.

Validation also produces a confidence score: an overall number plus per-field scores, weighted so the fields that matter most to bookkeeping (amounts, vendor, invoice number, date) count more. Passing checks raise the score; issues lower it. It's a triage signal: high-confidence results glide through, low-confidence ones get your attention first.

Why this matters for bookkeepers

If you handle receipts for clients, the failure mode you fear isn't an obvious garbage extraction. Those are easy to spot and re-do. It's the plausible error: a total off by $4.35 that posts cleanly, looks normal, and surfaces later as a reconciliation discrepancy you now have to hunt down. The cost of an OCR error isn't the error; it's the hour of forensic work when the books don't balance.

Self-checking arithmetic changes that economics in three ways.

Catching errors at the source. An extraction whose line items don't sum, or whose total doesn't reconcile with subtotal and tax, is caught the moment it's produced, before it becomes a bookkeeping entry. The error is intercepted where it's cheapest to fix.

An audit trail by construction. Because every result carries its check verdicts (which rules ran, which passed, what the expected and actual values were), you have a record of why a receipt was flagged, not just that it was. When a client or an auditor asks how a figure was derived, the answer isn't "the AI said so"; it's "the extraction was verified against the receipt's own printed arithmetic, and here's the check output."

Review time spent where it counts. When most results pass their checks and a minority are flagged, your attention goes to the minority. You stop proofreading every field of every receipt and start reviewing exceptions, which is how review is supposed to work.

The real numbers

We measure this on our own corpus and publish the results. The line-items-sum self-check passes for 86.4% of the 242-scan local corpus: 209 of 242 scans, measured under our published evaluation methodology. Crucially, this measurement needs no ground truth: the receipt's own printed numbers are the reference, which is the entire point of the approach.

The remaining 13.6% are the interesting ones. They are not errors that slipped past us. They are extractions the checks caught and flagged for review. Some fail because of genuine OCR misreads on damaged or skewed scans. Some fail because the receipt genuinely doesn't follow standard arithmetic. Either way, they arrive at your screen marked as needing a look, rather than gliding into your books unexamined. A tool with no self-checks can't tell you which of its outputs are in that 13.6%. Ours can, on every single scan.

You can see the full picture (field-level exact-match accuracy against both public benchmark sets and older Tesseract-based tooling, measured the same way) on our proof page, and the methodology behind the numbers in our companion post, how we measure OCR accuracy. We show the numbers we're still working to improve, not just the flattering ones.

Why doesn't everyone do this?

This is our thesis, and we'll state it as an opinion rather than a claim about anyone else's internals: self-checking arithmetic is harder than it looks, and most of the industry optimizes for output that looks right rather than output that is right.

To run these checks you need three things that plain OCR doesn't give you. First, structured extraction: you can't verify that line items sum to a subtotal if your pipeline produces a blob of text rather than typed line items with amounts, quantities, and unit prices. Second, arithmetic logic that respects the real world: tax-inclusive versus tax-exclusive pricing, rounding tolerances, currency-specific rates, receipts with no printed subtotal. Naive strict equality fails constantly on real receipts; naive looseness catches nothing. Getting the tolerances right requires measuring against real scans, which is how the ±$0.10 base tolerance in our checks was chosen. Third, a UI that surfaces failures honestly: a flag, a reason, and a suggestion, instead of a quiet "correction" or a quiet pass.

Each of those is a deliberate investment in our in-house pipeline. The structured extraction, the arithmetic engine and the honest UI are all ours, built as one system rather than assembled from third-party parts. The last one is also a cultural choice: it means admitting, in the product itself, that the AI is sometimes wrong. We think that admission is the foundation of trust. A system that flags 13.6% of its own outputs for review is more trustworthy than one that claims, implicitly by never flagging anything, to be right 100% of the time.

Try it on your own receipts

The checks described here run on every extraction, on every plan, including the free one: 10 lifetime AI extractions with line items included, no card required, with Pro at US$12/month flat when you want unlimited extraction (details). The best way to understand self-checking math is to watch it work on a receipt from your own wallet: snap one, and see not just what the AI read, but the arithmetic verdict that came with it. Try it in the app.

FAQ

Does SnapReceipt auto-correct numbers when a check fails?

No. A failed check flags the result for your review and shows you which rule failed, the expected and actual values, and a suggestion for where to look. But the numbers are never rewritten for you. Auto-correction can't distinguish an OCR misread from a receipt that genuinely breaks standard arithmetic (split payments, manual overrides), so the human stays the authority on edits.

What exactly is checked on every receipt?

Line items must sum to the printed subtotal (or, if there is no printed subtotal, to the total or total-minus-tax); subtotal plus tax must equal the total under either tax-exclusive or tax-inclusive pricing; tax must match a known national rate for AUD, NZD, GBP, IDR, or SGD, or fall in a plausible 0-25% band for other currencies; totals must be at least the subtotal; tax must be smaller than the total; amounts must be non-negative; and each line item's quantity times unit price must match its amount. Every applicable check runs on every extraction.

What happens to the 13.6% of scans that fail the self-check?

They're flagged for human review with the specific failed check identified. Not passed through, not dropped, not silently adjusted. The 13.6% figure (33 of 242 scans in our local corpus) represents caught problems, which is the system working as intended: some are OCR misreads on difficult scans, some are receipts that don't follow normal arithmetic, and all of them get your attention instead of slipping into your books.

Why is the tolerance ±$0.10 instead of exact to the cent?

Because real receipts round. Service charges, per-item rounding, and tax-inclusive pricing mean strict cent-level equality fails on a large share of perfectly correct extractions. Our measurements show subtotal + tax = total holds only about 65-73% of the time at ±$0.01 across benchmark datasets. The tolerance scales slightly with the magnitude of the amounts, and it was chosen by measuring against real scans, not guessed.

Does this work for currencies other than Australian dollars?

Yes. The tax-rate check validates against known rates for AUD (10% GST), NZD (15% GST), GBP (20% VAT), IDR (10% PB1), and SGD (9% GST). For every other currency (where rates vary too much by region or product to assume a single number), the inferred tax rate must fall within a plausible 0-25% band. All the other checks (line-item sums, subtotal-plus-tax, non-negative amounts, quantity-times-price) are currency-agnostic and run regardless.

Try SnapReceipt on your own receipts. Free tier, no card required.

Open the app →