Skip to main content
Values that OCR extracts from invoice photos are mostly right, but checking every one by hand takes as long as typing them in. Jev can check them instead: it boxes each value on the photo and flags the ones that look wrong, so a reviewer only looks at those. Each extracted field gets a box on the invoice photo, the box tightens on a zoomed-in
pass, and a crop of it is checked against the value: the misread invoice numbers and date
turn orange for review, and every correct value turns blue On these two supplier invoices, an extractor read the supplier, invoice number, date, amount due and bank details, and got three values wrong. Jev flags all three. Jev checks each value twice: against the whole page (the page check) and against a crop of its box (the crop check). A value that scores below 0.25 on either check goes to review. In the table, “extracted” is what the extractor read and “printed” is what the invoice says. The code below starts from extractions.json, the values the OCR and LLM steps produced, and leaves those two steps out. You can make that file with Tesseract and your LLM of choice.

Setup

Then set TYPESAFE_API_KEY. Download the two invoice photos, extractions.json and json_cache.json from typesafe-public-examples, and put them beside the script, with the photos in an inputs/ folder. Every API response is saved in json_cache.json, so running the cookbook reuses those saved responses and you get the published numbers without calling the API. Delete that file to send every request again.
Note - Images and Box questions are dreamer-ready, which means they are not yet ready for production, and their request format and answers can change between releases. They need Jev 1.14, and every request needs the TypeSafe-Danger-Zone-Images: true header. The client below sets that header.
The numbers below came from jev-1.14.0 on 2026-10-15.

Load the photos and what the extractor read

The two photos are invoices a restaurant received from its suppliers, taken with a phone. One is a creased carbon copy from Milkwood Mushrooms. The other is from Siyakhula Trading and has a shadow down the middle, making it a challenging OCR task. The extracted values are in extractions.json. We used Apple Vision’s text recognition (OCR) to read the photos, and an LLM to read the output of that and return the structured values. Any extractor works here, because the check only needs the photo and the values.
Jev accepts images between 280 and 1568 pixels on each side. Larger images are shrunk, and small crops are enlarged so their text is readable. An image that is still too small goes in the middle of a white canvas, and its place on the canvas is kept, so a box Jev draws on the canvas can be converted back to a position on the crop.

Find each field on the page

The first of the three requests sends the whole photo, with two questions for each field:
  • A Box question that finds the field. It includes the extracted value as a hint about which text to box. This box decides where to zoom in.
  • A Noul question that asks whether the extracted value is that field on this page. Its P(yes) is the first of the two checks, the page check.
The page check catches INV03070 (0.02) and 16/09/2028 (0.17), but passes the misread AN400074 at 0.88. A photo of the whole page shows small print in little detail, so these first boxes are rough.

Zoom in and box each field again

For each field, cut out a region around its first box and enlarge it. The region is at least 30% of the page wide and 15% high, so the field stays inside it even if the first box was off. Then ask the same Box question about each region. Each region’s image goes inside its question’s instructions, all the regions go in one request, and client.attach_assets() sends the images with it. The code then pads each new box on every side, so that cropping to it can’t cut off part of the value.

Check each value against its crop

Crop the photo to each padded box and ask one Noul question per field: does this crop show the extracted value? Each crop goes inside its own question’s instructions. The answer’s P(yes) is the crop check.

Flag the fields a person should check

A field goes to review when either check scores below BAR (0.25).
Neither check catches all three misreads alone. The crop check catches AN400074 (0.18), which the page check passed at 0.88. The page check catches 16/09/2028 (0.17), which the crop check passed at 0.66. Both catch INV03070. All the correct values pass both checks. The lowest scores among them are the Milkwood account number at 0.35 on the page check and the Siyakhula supplier name at 0.31 on the crop check. On each photo, every box is numbered as in the # column above. Orange marks a field to review and blue one that passed, so a reviewer can check each value where it is printed.
output output