Skip to content

Confidence and review routing

  • Lesson 15
  • 55 min
  • Module 5: Validate and review results

What you will be able to do

Decide which documents a person must check, with signals that you can measure. They are validation errors, model values that are not on the page, and the reading scores of the evidence. Measure the model’s own confidence before you trust it, and choose the review threshold from the results.

Before you startWhat to know and to have readyAbout 55 minutes

What you need to know

What you need on your computer

  • Your project at the end of the previous lesson: python -m pytest -q prints 90 passed.

Tested with Python 3.12.13 on macOS (Apple silicon). The Windows and Linux commands are not tested. Every model answer in this lesson is a recording of a real answer, replayed: no key, no cost.

  1. In the project folder, copy the module, its design note and its test from the snapshot:

    Terminal window
    S=../nextia-learning-labs/C13/snapshots/end-of-m05
    cp $S/doc_extract/review.py doc_extract/
    cp $S/docs/review-queue.md docs/
    cp $S/tests/test_review.py tests/
  2. Run the tests:

    Terminal window
    python -m pytest -q

    The last line is 94 passed.

A new colleague joins your team. You ask about each piece of work: “Are you sure?” The answer is always “Yes, completely.” After a month, you notice that most of their work is right, and some is wrong. Their “completely sure” came with both. So their answer tells you nothing about which piece to check.

You learn to look at other things. A sum that does not add up. A smudged number on a scan. A value that you cannot find on the page. These signals you can see for yourself, and they do tell you where the mistakes are likely to be.

Grace asked for the same thing at the start of the course: “Send us only the ones it is not sure about.” The question is: whose “sure”?

The problem: the model is sure of its mistakes

Section titled “The problem: the model is sure of its mistakes”

Tomás has an easy answer: “The vision model gives a confidence for every field. Prompt v2 asks for it. Send a person every document where it is under 0.90.” The course tested this on the model’s recorded answers for all 54 receipt documents. First, every answer where the model is wrong:

D009 vendor model LARKEFIELD (found ) confidence 1 | true Larkfield
D045 tax model None (absent cut_off) confidence 0.96 | true 46.66
D046 vendor model None (absent unreadable) confidence 1.0 | true Larkfield
D046 order_number model None (absent unreadable) confidence 1.0 | true LK-838601
D046 date model None (absent unreadable) confidence 1.0 | true 2026-08-16
D046 currency model None (absent unreadable) confidence 1.0 | true CAD
D046 subtotal model None (absent unreadable) confidence 1.0 | true 249.00
D046 tax model None (absent unreadable) confidence 1.0 | true 32.37
D046 total model None (absent unreadable) confidence 1.0 | true 281.37
D046 payment_method model None (absent unreadable) confidence 1.0 | true Cash
10 wrong answers

Ten wrong answers. Nine of them say confidence 1.0, the highest possible. Eight are D046, the photo that nobody can read: the model said “absent, unreadable” for every field, and it was completely sure. That answer is honest, because the photo is unreadable. But the receipt’s values are lost, and the confidence does not warn anyone. The ninth is D009’s vendor LARKEFIELD, a misread, also at 1.0.

Now route each document by the lowest confidence that the model gave on it:

D009 lowest confidence 0.98 wrong: vendor
D045 lowest confidence 0.96 wrong: tax
D046 lowest confidence 1.0 wrong: vendor, order_number, date, currency, subtotal, tax, total, payment_method
threshold 0.90: 0 reviewed; documents with a wrong value to agents: D009, D045, D046
threshold 0.96: 0 reviewed; documents with a wrong value to agents: D009, D045, D046
threshold 0.97: 1 reviewed; documents with a wrong value to agents: D009, D046
threshold 0.99: 8 reviewed; documents with a wrong value to agents: D046
threshold 1.00: 28 reviewed; documents with a wrong value to agents: D046

At Tomás’s threshold of 0.90, nobody checks anything, and three documents with wrong values go straight to the agents. Even at 1.0, which sends to a person every document that is not “completely sure”, D046 still goes to the agents. Its wrong answers all say 1.0.

The idea, part 1: is a confidence calibrated?

Section titled “The idea, part 1: is a confidence calibrated?”

A calibrated confidence is one that matches reality over many answers. A weather forecaster who says “70% chance of rain” on 100 days is calibrated if it rains on about 70 of them. If it rains on all 100 days, or on 30, the number is wrong, even if each single forecast sounds reasonable.

So you check a confidence by grouping the answers by what they said, and counting how many were right in each group. A tiny example with every value visible: a reader gives 10 answers. It says 0.9 for five of them, and four are right (80%). It says 0.6 for five, and three are right (60%). The 0.9 group is right more often, so the numbers carry some information, but 0.9 overstates 80%.

The project does this for the model:

Terminal window
python -m doc_extract.review --calibration
The model's own confidence (vision path, prompt v2) against the true values:
confidence 1.0 341 answers, 332 correct (97.4%)
confidence 0.95-0.99 145 answers, 144 correct (99.3%)
confidence 0.80-0.94 0 answers, 0 correct (-)
confidence under 0.80 0 answers, 0 correct (-)
Where the model's 10 wrong answers fall, by the confidence it gave (vision model, prompt v2, 486 field answers on 54 receipt documents, recorded). A calibrated model would put its mistakes in the low groups. This one gave no answer under 0.96.
Text description of the chart

The vision model gave 486 field answers. Its lowest confidence was 0.96. At confidence 1.0: 341 answers, 332 right (97.4%), so 9 wrong. At 0.95 to 0.99: 145 answers, 144 right (99.3%), so 1 wrong. No answer had a confidence under 0.95. The answers that said 1.0 were right less often than the answers that said less.

Every answer says 0.96 or more, so the number cannot separate anything. And the answers at “1.0” are wrong more often (2.6%) than the answers at 0.95–0.99 (0.7%). This model’s confidence is not calibrated on this task. The measure took no new model call: the confidences were in the recorded answers.

Tomás objects: “Then ask the model to be more careful with its numbers.” The course does not try that. A different prompt may give different numbers, and you would have to measure them again in the same way. A signal that you measure yourself does not need the model’s cooperation.

The idea, part 2: certainty from things you can measure

Section titled “The idea, part 2: certainty from things you can measure”

The project’s review decision uses two kinds of signals. Neither of them is the model’s confidence.

  1. Reasons, which always send a document to a person. A reason is a validation error, a conflict between the rules and the model, or an unverified value.
  2. Certainty, a number from 0 to 1 that the project measures. It is the lowest reading score of the evidence of the required fields. A value from the PDF text layer scores 1.0, because the text is stored in the file. A value from OCR has the score that the OCR engine gave its line. A value that only the model gave has no line of its own, so it scores 0.5.

A document goes to review when it has a reason, or when its certainty is under the threshold. The minimum is used because one bad field is enough to make a receipt wrong. Here are three receipts, each field with its score. Start python and type:

from doc_extract import combine, documents, review
for doc in ("D011", "D024", "D017"):
ex, dec = combine.combine(documents.get(doc))
scores = {n: (round(ex.fields[n].score, 2) if dec.source[n] in ("rules", "both") else 0.5)
for n in combine.REQUIRED if ex.fields[n].value is not None}
print(doc, scores, "-> certainty", review.certainty(ex, dec))
D011 {'vendor': 1.0, 'date': 1.0, 'currency': 1.0, 'subtotal': 1.0, 'tax': 1.0, 'total': 1.0} -> certainty 1.0
D024 {'vendor': 1.0, 'date': 0.99, 'currency': 0.99, 'subtotal': 1.0, 'tax': 0.91, 'total': 0.99} -> certainty 0.9076
D017 {'vendor': 1.0, 'date': 1.0, 'currency': 1.0, 'subtotal': 0.5, 'tax': 1.0, 'total': 0.5} -> certainty 0.5

D011 is a digital PDF: every value comes from the text layer, so its certainty is 1.0. D024 is a scan at a low resolution: the tax line has an OCR score of 0.91, and that is the certainty. D017 is the partner shop’s receipt. Its subtotal and total came from the model, so they score 0.5. The receipt goes to a person at any threshold over 0.5. The model’s values were grounded on the page, but the rules did not read them, so a person confirms them.

  1. Build the queue at the project’s threshold of 0.90:

    Terminal window
    python -m doc_extract.review
    D017 maple-pine-0534-560.pdf certainty 0.50 is under 0.9
    D018 maple-pine-0727-251.pdf certainty 0.50 is under 0.9
    D033 scan-partner-1.jpg certainty 0.50 is under 0.9
    D034 scan-partner-2.jpg certainty 0.50 is under 0.9
    D045 IMG_4880.jpg total: total is missing; currency: unverified: the model's value cannot be found on the page
    D046 IMG_4887.jpg vendor: vendor is missing; date: date is missing; currency: currency is missing; subtotal: subtotal is missing; tax: tax
    D047 IMG_4894.jpg vendor: vendor is missing; date: date is missing
    D048 IMG_5100.jpg certainty 0.50 is under 0.9
    D049 IMG_5101.jpg certainty 0.50 is under 0.9
    D050 receipt-lk-762786.pdf total: subtotal 449.00 + shipping 0 + tax 58.37 = 507.37, but the total is 517.37 (difference +10.00)
    D051 scan0099.jpg total: subtotal 528.00 + shipping 0 + tax 68.64 = 596.64, but the total is 595.74 (difference -0.90)
    11 of 54 documents to review (20%); wrong totals caught 1, wrong totals sent to agents 0
    queue written to out/review-queue.json

    Eleven of the 54 receipt documents go to a person. Five have a reason: the three damaged photos and the two totals that do not add up. Six have a certainty of 0.50: the partner shop’s receipts, whose money values came from the model.

  2. Read one entry of the queue file. In python:

    import json
    queue = json.load(open("out/review-queue.json", encoding="utf-8"))
    print(len(queue), "documents in the queue")
    print(json.dumps(queue[-1], indent=1, ensure_ascii=False))
    11 documents in the queue
    {
    "doc_id": "D051",
    "file": "scan0099.jpg",
    "certainty": 0.9746,
    "reasons": [
    "total: subtotal 528.00 + shipping 0 + tax 68.64 = 596.64, but the total is 595.74 (difference -0.90)"
    ],
    "values": {
    "vendor": "Larkfield",
    "order_number": "LK-444410",
    "date": "2026-07-25",
    "currency": "CAD",
    "subtotal": "528.00",
    "shipping": null,
    "tax": "68.64",
    "total": "595.74",
    "payment_method": "Mastercard"
    }
    }

    Each entry has the reasons in plain words and the values, so the reviewer does not need to run anything.

  3. Open docs/review-queue.md. It is the design of the queue: what goes into it, what the reviewer sees, and what the reviewer can do. You use it in the module practice.

The last line of the queue says wrong totals caught 1. That is D046’s total: no reader saw it, so it is missing, and “missing” counts as wrong against the truth. The totals of D050 and D051 are not wrong readings: the receipts print them. They go to review because they do not add up.

What does a reviewer see? The queue gives the reasons, and the evidence gives the place of each value. For the cut-off photo D045:

A review card for D045. Left, the photo of a Larkfield receipt, cut off just below the HST line, with green boxes around LARKFIELD, the date line, the order line, Subtotal 358.95 and HST 13% 46.66, and a red line along the cut edge. Right, To review: why. Certainty 0.50 (a value only the model gave scores 0.50). Reasons: total: total is missing; currency: unverified: the model's value cannot be found on the page. Values: vendor Larkfield rules; order_number LK-754892 rules; date 2026-06-13 rules; currency CAD unverified; subtotal 358.95 rules; shipping absent (not_printed); tax 46.66 rules; total absent (cut_off); payment_method absent (cut_off).
What a reviewer sees for D045: the photo with the box of every value read by the rules, the reasons for review, and each value with its status. The total and the payment were below the cut edge.
Text description of the diagram

The photo D045 shows a Larkfield receipt cut off just below the tax line. Green boxes mark the values that the rules read: the shop name, the date, the order number, the subtotal 358.95 and the tax 46.66. A red line marks the cut edge. Next to it, the card gives the certainty, 0.50, and two reasons. The total is missing. The currency CAD came from the model, and it is not on the page. The values list shows each field with its status: the total and the payment are absent with the reason cut_off.

The threshold decides how many documents a person checks. A higher threshold sends more documents to review. So does a higher threshold catch more mistakes? Run the sweep:

Terminal window
python -m doc_extract.review --sweep
threshold reviewed wrong totals caught wrong totals to agents
0.00 5 (9%) 1 0
0.50 5 (9%) 1 0
0.80 11 (20%) 1 0
0.90 11 (20%) 1 0
0.95 18 (33%) 1 0
0.97 25 (46%) 1 0
0.99 33 (61%) 1 0
1.00 36 (67%) 1 0

The number of reviewed documents climbs from 5 to 36. The number of wrong totals sent to the agents stays at 0 on every line. Predict before you move the slider: what does the line of wrong values look like when you review fewer documents?

Explore

How many documents does a person check?

01836540123threshold 0.9Documents a person checks (of 54)Documents with a wrong value sent to the agents00.10.20.30.40.50.60.70.80.91Review threshold
01836540123threshold 0.9Documents a person checks (of 54)Documents with a wrong value sent to the agents00.30.50.81Review threshold

Solid line: the project. Dashed line: the model alone, routed by its own confidence. The 54 receipt documents of the course.

At 0.9: the project sends 11 of 54 documents to a person, and 0 with a wrong value to the agents. Routed by its own confidence, the model alone sends 0 to a person, and 3 with a wrong value to the agents.

threshold 0.9Certainty and checks (the project)The model's own confidence (model alone)
Documents a person checks11 (20%)0 (0%)
Wrong totals sent to the agents01
Documents with a wrong value sent to the agents03
Which onesnoneD009 (vendor); D045 (tax); D046 (every printed field)
The 54 receipt documents of the course, routed at every threshold from 0 to 1. Solid line: the project (reasons always go to review; certainty under the threshold also goes). Dashed line: the model alone, routed by its own confidence. Measured on the project's results and the model's recorded answers.
Text description of the explorer

One control sets the review threshold from 0 to 1. The top chart shows how many of the 54 documents a person checks. With the project’s routing, 5 documents at any threshold up to 0.50, then 11 from 0.51 to 0.90, then more as the threshold climbs: 18 at 0.95, 33 at 0.99 and 36 at 1.00. With the model alone, routed by its own confidence, nobody checks anything up to 0.96, then 1 document at 0.97, 3 at 0.98, 8 at 0.99 and 28 at 1.00. The bottom chart shows the documents with a wrong value that go straight to the agents. With the project’s routing, the line is flat at 0 for every threshold. The only document with wrong values, D046, has validation errors, and errors always go to a person. With the model’s confidence, 3 documents go to the agents up to 0.96 (D009 vendor, D045 tax, D046 every printed field), 2 at 0.97 and 0.98, and 1 (D046) at 0.99 and 1.00. The answer: with the project’s routing, no wrong total reaches the agents at any threshold. The errors set the floor, not the threshold.

The flat line at 0 is the good news. Errors and unverified values go to review at every threshold, so the only wrong document, D046, never reaches the agents. The threshold does not decide whether a wrong total is caught. Validation does.

So what does the threshold buy? On this set, nothing more: every document that it adds is right. The course checked every value that the rules read on a scan or a photo, grouped by its OCR reading score:

reading score under 0.90 1 values, 0 wrong (0.0%)
reading score 0.90-0.95 10 values, 0 wrong (0.0%)
reading score 0.95-0.99 77 values, 0 wrong (0.0%)
reading score 0.99-1.00 145 values, 0 wrong (0.0%)

None of the 233 values is wrong, at any score. The OCR mistakes of this set are values that the rules missed, and a missing value is a validation error. So the threshold is insurance for errors that validation cannot see, like a wrong amount that still adds up. You do not know the size of that risk until you see such errors. Two practical rules follow:

  • Start with a threshold that sends the documents you do not trust yet: here 0.90, which sends every receipt where the model filled a gap.
  • Measure again on your own labelled documents when the documents change: a new shop, a new phone camera, a new OCR version. Then move the threshold if the errors move.

Mei sums it up for Omar: “Eleven documents in 54 go to a person, about one in five. Five of them must go. The other six are the partner shop, until we write rules for its layout.”

When to route by certainty, and what it costs

Section titled “When to route by certainty, and what it costs”
  • Route by reasons first. An error or an unverified value is a fact about the document, not a guess. It goes to a person at any threshold.
  • Use a certainty that you can measure and explain: a reading score, the reader that found the value, a check that passed. Measure the model’s own confidence before you use it. Here it was not calibrated, so the project does not use it.
  • What it costs: every reviewed document costs a person’s time. At 0.90, 11 of 54 (20%); at 1.00, 36 (67%), with no extra error caught on this set.
  • When not to use a threshold: when you have no labelled documents to measure it on. Then review a fixed sample first, label it, and set the threshold from that.

Common mistake

Review everything that is not perfect

Symptom: After a bad week, a manager asks for “maximum safety”: the threshold goes to 1.00. The queue grows from 11 to 36 documents out of 54. The reviewers fall behind, and they start to accept cards after a quick look.

Cause: The higher threshold adds 25 documents, and on this set all 25 are right. The sweep shows it: wrong totals to the agents is 0 at 0.90 and still 0 at 1.00. A queue full of right documents teaches reviewers that the queue is usually wrong. That is the risk: a real error then gets a quick “accept” too. The tired reviewers are a constructed story; the counts are the course’s measurement.

Diagnose: For each threshold, count the reviewed documents and the errors they catch, on documents that you labelled. If the errors caught do not grow with the queue, the extra reviews buy nothing.

Fix: Keep the threshold where the measurement puts it (0.90 here), and keep the reasons that always route. Spend the reviewers’ time on the documents that have a reason. If you want more safety, add a check that catches a known kind of error, such as the vendor list in the module practice.

Consequence in AI work: human review is a limited resource. A review step only protects you if the people in it have time to look carefully.

Change one condition: the threshold 0.95 instead of 0.90. Predict which kinds of documents join the queue: digital PDFs, scans or photos? Then run:

Terminal window
python -m doc_extract.review --threshold 0.95
Show the answer
D017 maple-pine-0534-560.pdf certainty 0.50 is under 0.95
D018 maple-pine-0727-251.pdf certainty 0.50 is under 0.95
D020 scan0002.jpg certainty 0.94 is under 0.95
D024 scan0006.jpg certainty 0.91 is under 0.95
D033 scan-partner-1.jpg certainty 0.50 is under 0.95
D034 scan-partner-2.jpg certainty 0.50 is under 0.95
D040 IMG_4845.jpg certainty 0.92 is under 0.95
D042 IMG_4859.jpg certainty 0.95 is under 0.95
D043 IMG_4866.jpg certainty 0.94 is under 0.95
D044 IMG_4873.jpg certainty 0.94 is under 0.95
D045 IMG_4880.jpg total: total is missing; currency: unverified: the model's value cannot be found on the page
D046 IMG_4887.jpg vendor: vendor is missing; date: date is missing; currency: currency is missing; subtotal: subtotal is missing; tax: tax
D047 IMG_4894.jpg vendor: vendor is missing; date: date is missing
D048 IMG_5100.jpg certainty 0.50 is under 0.95
D049 IMG_5101.jpg certainty 0.50 is under 0.95
D050 receipt-lk-762786.pdf total: subtotal 449.00 + shipping 0 + tax 58.37 = 507.37, but the total is 517.37 (difference +10.00)
D051 scan0099.jpg total: subtotal 528.00 + shipping 0 + tax 68.64 = 596.64, but the total is 595.74 (difference -0.90)
D058 warranty-claim-wc-2026-0418.pdf certainty 0.92 is under 0.95
18 of 54 documents to review (33%); wrong totals caught 1, wrong totals sent to agents 0
queue written to out/review-queue.json

Seven documents join. They are the scans D020 and D024, the photos D040, D042, D043 and D044, and the warranty claim D058, whose receipt page is a scan. Their certainty is from 0.91 to 0.95, because OCR read a field of each at that score. No digital PDF joins: the text layer scores 1.0. D042 has a certainty of 0.9485, which prints as 0.95, and it is under the threshold. Every added document is right, so the queue grows by seven reviews and catches no new error.

Check your understanding

Explain why the project does not route by the model's confidence, with two numbers from the course.

Show the answer

The model’s confidence is not calibrated on this task. First, its lowest confidence on 486 answers is 0.96, so a threshold can hardly separate anything. Second, 9 of its 10 wrong answers say 1.0, the highest value. The answers at 1.0 are right 97.4% of the time, and the answers at 0.95–0.99 are right 99.3%. Routed by that confidence at 0.90, three documents with wrong values, D009, D045 and D046, go straight to the agents.

Check your understanding

Predict: a receipt from the text layer has a total that does not add up. Its certainty is 1.0. Does it go to review at the threshold 0.50?

Show the answer

Yes. A total that does not add up is a validation error, and an error is a reason. A document with a reason goes to review at any threshold, even when its certainty is 1.0. This is D050: a digital PDF, certainty 1.0, and in the queue at every threshold, as review --threshold 0.5 shows with its 5 documents.

Check your understanding

Practical check: run python -m doc_extract.review --threshold 0.5. How many documents are in the queue, which ones leave it compared with 0.90, and why?

Show the answer

Five documents: D045, D046, D047, D050 and D051, each with a reason. The six partner-shop receipts (D017, D018, D033, D034, D048, D049) leave the queue. Their subtotal and total came from the model, so their certainty is 0.50. And 0.50 is not under the threshold 0.50. The wrong totals sent to the agents stay at 0. Grace decides if the partner-shop values can go straight to the agents. They are grounded on the page, but no rule read them.

Recap

  • A confidence is calibrated when it matches how often answers are right. The model’s is not: lowest 0.96, and 9 of its 10 wrong answers say 1.0.
  • The project’s review decision: a reason (validation error, conflict, unverified value) always goes to a person. Otherwise, the certainty is compared with the threshold.
  • At 0.90: 11 of 54 documents to review (20%), 0 wrong totals to the agents. The line is flat at every threshold: the errors set the floor.
  • On this set, a higher threshold adds only right documents. Measure the threshold on your own labelled documents, and again when they change.
  • A reviewer sees the page with the boxes, the reasons and the values with their status.

Next, the reviewer acts: a correction is recorded next to the original value, with a name, a note and a version, and nothing is erased.

Next: Correction workflow