So far we’ve judged the suggestions by looking at them. That works for one screenshot, but it won’t tell you if a change next week makes things quietly worse. In this lesson we build a test that scores the suggestions against answers we already know, and fails when they slip. Checkpoint: redact-09-evaluation, after redact-08-failure-recovery

How do you score a suggestion?

Start with a page where a person has already marked every private field. Run the app on it, and compare what it proposed with what the person marked. Two things can go wrong, and they need separate numbers. Precision asks: of everything Redact proposed, how much was right? Recall asks: of everything that should have been proposed, how much did Redact find? For a privacy tool, recall matters most. A wrong suggestion costs one click on Reject; a missed account number is the whole failure. F1 combines the two into one number, so neither can hide behind the other.

When is a box “the same” box?

A suggestion and a person’s mark will never line up to the pixel. So we measure overlap: the area they share divided by the area they cover together. That’s called intersection over union, or IoU. Two identical boxes score 1.0, and boxes that don’t touch score 0. Redact counts a suggestion as correct when its label matches and its IoU with a marked region is at least 0.45. More on what these numbers can and can’t tell you: About measuring suggestions.

Why four versions of the same chat?

Real documents aren’t clean. The evaluation uses the same fictional chat four ways: clear, rotated a quarter turn, low-contrast, and partly covered. Each has the same six labelled fields: name, email, address, phone, account and reference. The rotated one matters most here. OCR on a sideways page tends to return many short vertical fragments, one per word. This lesson also teaches the analyzer to notice that, read the page upright, and keep whichever reading has whole lines.

1. Get this lesson’s code

Stop the app and the service if they’re running. From the repository root, redact-course, bring in this lesson’s commit:
No packages change, so there is nothing to install. To see every change, open the comparison on GitHub.

2. Read the scoring rules

Open qvac-service/evaluation.ts. The pass mark is in one place:
A case passes only if precision, recall and F1 are all at least 0.70. The labelled answers are just data, in percentages of the page:
The rotated case doesn’t need new labels. rotateClockwise turns each answer box a quarter turn, the same way the image was turned:
These answers are only used for scoring; the models never see them.

3. Read the rotated-page retry

Open qvac-service/qvac-analyzer.ts. First the analyzer decides whether a page looks sideways:
A landscape page where at least 60% of the blocks are much taller than they are wide is probably text running sideways. If so, it reads the page again, turned upright, and keeps the better reading:
“Better” means more readable text in fewer, longer blocks:
Whole lines like “will use account 8841 0932” beat a dozen one-word fragments. mapCounterClockwiseRetryToOriginal then moves the upright boxes back onto the sideways page, so marks still land in the right place (OCR boxes and rotation).

4. Read the runner

Open qvac-service/evaluation-cli.ts. For each case it sends the image through exactly the same analyzer the app uses:
So the score measures the real pipeline, not a copy of it. At the end it prints the receipt and sets a failing exit code if any case failed:

5. Run the evaluation

The OCR model and Laya must already be cached from earlier lessons. Stop the service, so the evaluation has the models to itself, then run:
--silent keeps npm’s own banner out of the file. The terminal shows one progress line per page as it goes, named by condition rather than case ID: Evaluating clear…, Evaluating rotated…, Evaluating low-contrast… and Evaluating partially-obscured…. The whole run takes about two minutes. When it ends, check the exit status:
0 means every case passed.

6. Read the receipt

Open evaluation-receipt.json. Near the top you’ll see "passed": true. Under results, each case has its scores in a nested result object, results[].result, with precision, recall and f1: On the first three, Laya found all six fields and proposed one more line: keep the amount confidential., which the evaluation’s direction asks it to consider. On the partly covered page, one field is missed and one extra line is proposed. Look in matches to see which regions were found, and with what IoU. Run it again. The numbers are identical, because Laya gives the same answer for the same input.

7. Run one case on its own

When one case fails, run it alone:
The rotated page takes longer than the others, because OCR reads it twice. The other case IDs are chat-clear, chat-low-contrast and chat-partially-obscured.

If something goes wrong

  • QVAC cache is empty: run npm run qvac:ocr-smoke and one analysis in the app first, so both models are downloaded.
  • QVAC evaluation failed: … saying a model couldn’t load: the service may be holding the models; stop npm run qvac:service and try again.
  • Unknown evaluation case: check the spelling of REDACT_EVALUATION_CASE.

8. Check your work

npm test ends with Tests 82 passed (82). You can leave evaluation-receipt.json where it is: this lesson’s .gitignore ignores any *-receipt.json file.

So what just happened?

You turned “it looks right” into numbers that a script can check. The same command now fails if a change makes Redact miss fields or propose nonsense on these four pages. Four fictional images are a course gate, not proof of accuracy on your own documents.

Try it yourself

Open qvac-service/evaluation.ts and change minimumPrecision: 0.7 to 0.9, then run the evaluation. Every case now fails, and echo $? prints 1, even though the suggestions haven’t changed: one extra suggestion out of seven is no longer allowed. Put it back to 0.7 afterwards. Then run the lesson 02 OCR script on the sideways image, npm run qvac:ocr-smoke -- public/test-files/redact/evaluation/chat-rotated.png. The script has no upright retry, so you’ll see the short vertical word blocks that this lesson’s retry exists to fix.

Where to next

Next: Run Redact with the network off (lesson 10)