The business caseThe problem this solves
A retailer's tax team is sent a scanned sales-tax exemption certificate by a buyer who does not want to be charged tax. Somebody has to read nine printed facts off that picture -- the issuing state, the form, the reason claimed, the purchaser, the seller, the certificate number, the account number, the date signed and the expiry date -- decide whether the certificate is complete for that jurisdiction, whether the reason is one that jurisdiction allows, whether it ties to the account it was filed against, and whether it is still alive on today's date. Then a sale is booked tax-free or it is not. reading nine printed facts off a scanned exemption certificate and applying the jurisdiction's validity rules by hand before a sale is booked tax-free
Audience
Whoever is deciding whether to buy a page reader to put in front of a model, and whoever signs off the bill for it. The answer here is specific and it inverts the obvious rule: the cheaper reader reads the characters better and produces the worse record. Every number on these pages came from one real run of this code, not from a vendor page.
The inputThe actual scanned exemption certificates
The corpus is 60 scanned exemption certificates, 12.48 MB (json 3 · jsonl 1 · md 1 · png 60). An exemption certificate is the rare document where the READING and the DECISION are genuinely separable and both are hard. The nine facts are printed in plain type; the verdict is calendar arithmetic against a jurisdiction table. That lets the kit hold the decision perfectly constant -- src/recheck.py is the same pure code on every arm including both free floors -- and vary exactly one thing, which reader bought the page. Three layouts were built rather than one because the whole question is whether a reader preserves the BINDING between a label and its value: a ledger puts them on one line, a boxed form puts them in a grid, and a cards layout puts three labels on one line and their three values on the next. The traps are real to scanned forms rather than invented: 12 pages carry a received stamp printed over the header, 12 carry handwriting, 12 carry confusable characters in the certificate number, and 12 carry decoy dates -- 56 of the 60 pages print a decoy as the LAST date on the page, so 'take the last date' is a strategy that fails 93% of the time. Nothing is blurred or degraded; the difficulty is layout and near-misses, not noise.
The corpus
- The 60 scanned exemption certificatesgenerated from a fixed seed, so no real record, person or institution appears in it.
- Where each came fromwritten for this kit rather than collected — the corpus is generated in the kit's own repository, so there is no third-party data in it.
Swap this folder for your own material and the kit is pointed at your scanned exemption certificates. That is the whole change — there is no database to migrate.
The corpus is 60 scanned exemption certificates, and it is not text — there is no clip to show you here. The pipeline reads these files directly; the folder swap above is still the whole change.
The outcomeWhat a good result looks like
nine printed facts and one three-way verdict -- accepted, rejected or expired -- with a box the form leaves blank answered empty rather than filled in, and with the verdict produced by pure code from the nine facts rather than by the model
And when it cannot
It accepts a dead certificate. That is the expensive direction on this job: an expired certificate accepted is uncollected tax sitting on the books until an auditor finds it. expired_accepted is 0 of 20 on every scored arm here, including both free floors -- and 1 of 20 under the adversarial arm, where a fake DEPARTMENT OF REVENUE VERIFICATION STAMP printed on the page talked the marked path into accepting EC-0042.
Where it fitsWhat did work
Every line below is a measured result from this kit's own runs, with the figure that supports it. The headline above is not softened by any of them.
- You want an exemption record you can file without a person re-reading it — Mistral OCR 4.1 in front of the model
538 of 540 cells and 59 of 60 verdicts, indistinguishable from a perfect reading on this corpus (2 discordant cells, p = 0.5000), and no expired certificate accepted. - Your certificates are ledgers with no blank boxes — AWS Textract Detect Text
its character error is the lower of the two (0.0496 against 0.0776), it is 2.7x cheaper a page, and on this corpus's ledger layout it reaches 0.9500 field accuracy. - You want to know whether to buy a page reader at all — run the free rules floor and the perfect-reading arm first -- both cost nothing
they bracket the question. The floor (0.7667 field, 0.7833 verdicts on perfect text) is what pure code gets; the perfect-reading arm (1.0000 / 1.0000) is what the model gets given a flawless reading. If your floor is already close to your ceiling, no reader will earn its price. - You care most about never accepting a dead certificate — either purchasable reader, plus the rulebook, plus a rule that nothing on the page may override a printed date
expired_accepted is 0 of 20 on every scored arm here, including both free floors. The validity cap on the signature is applied in code whether or not an expiry is printed, so the six certificates that print a FUTURE expiry and are dead anyway are all caught.
At a glanceHow the whole thing runs
Run once, for real, on 2026-09-16. Every figure on these pages was captured from that run — nothing is written from intent.
14 steps, grouped by the question that sends you to them rather than by build order. Each tile carries the one figure that step is about, and opens the page behind it.
Should you use this?What you bring, where it stops, and when not to use it
Before you commit an afternoon to this, these are the answers that decide it. Each one is rendered from the record it lives in — and links the page that holds it in full.
| What do I have to bring? | Replace data/corpus/*.png with your own scans and data/gold.jsonl with one line per page carrying the nine fields, the printed strings and your own labelled outcome; then replace data/rules.json with your real jurisdictions' validity caps and allowed reasons, and data/accounts.json with your own customer table. Two claims stop being true the moment you do. Corpus lens → |
| When is this the wrong choice? | Avoid: It costs 2.7 times the cheaper reader a page and this corpus is 60 synthetic certificates in three layouts from six invented jurisdictions. If your certificates are all one-line-per-field ledgers -- the layout where the two readers are closest -- you are paying that multiple for very little. That is the case against the best-fitting scenario (“You want an exemption record you can file without a person re-reading it”). 4 scenarios scored in all, each with its own. Eval lens → |
| Where does it stop working? | A jurisdiction whose rule the shipped table does not carry. data/rules.json is a SAMPLE of six fictional jurisdictions; a stale or missing rule under- or over-validates, and the kit cannot tell that it is stale. 3 recorded failure modes, each from a run rather than a guess. Corpus lens → |
| What was never verified? | Anything on a real exemption certificate. All 60 pages are machine-rendered by this kit's own builder at a resolution it chose; there is no camera, no fold, no staple, no skew, no fax banding and no photocopy generation loss anywhere in the corpus. 9 items this kit says it could not check. Eval lens → |
| Can I run this on a model I control? | Yes — any OpenAI-compatible endpoint, including one on your own hardware. The shipped adapter takes its host from BASE_URL and its model from MODEL, so nothing in src/ changes. The published figures come from 1 model on the fast tier, one provider, one key. Prompt lens → |
| And if it fits — what do I stand up? | 6 artifacts with a stated home and a stated egress, and 3 decisions each with what you provision past its ceiling — plus what was not measured. That is the next page, not this one. step 14 — Run it in your environment → |
Not asked of this kit — 2 questions: clone (a fresh clone of this kit runs with nothing fetched); judge (nothing here is graded by a model).
Last verified 2026-09-16 — r003-exemption-certificate-mistral. Every figure on these pages was captured from that run.
Run itHow this reaches your data
Every result on this page was produced by pure code over checked-in files, with no API key — which is why you can read the numbers before anyone spends anything.
Run this on your own data
- The pipeline, its eval harness and the runs behind every numberdeployed inside your environment, on your own model endpoints, against your own documents.
- The corpus above is the shape, not the limitit is a folder swap, and there is no database to migrate.
Checked before this shipped — Observed on this machine: a checkout with no .env and no key configured runs python3 -m evals.run and prints every arm's table off the committed files, because the 60 PNGs, the answer key, the perfect reading, both readers' committed text and every scored row are in the repository. python3 -m evals.check_labels re-derives all 60 verdicts from data/rules.json and data/accounts.json independently of the corpus builder and prints CHECK_LABELS: 0 problem(s); tools/build_corpus.py --self-test passes with no Chrome. The board serves the same committed runs on port 9476 and is complete without a provider -- the 'empty' screenshot above is that state. What a clean checkout CANNOT do is buy a new reading: that needs a vendor credential, and re-rendering the 60 PNGs needs Chrome.






