Invoices and purchase orders, as PDF or photo, become checked data. The AI reads the document; the code redoes quantity times price, the sum of items and the total, validates the CNPJ (the Brazilian company ID), including the new alphanumeric format, and checks the digit of the access key. It is a demo with fictional documents and companies.
Status
demo
Period
Sep 2026
Stack
Python 3.12, FastAPI, AI API with structured output, Pydantic, openpyxl, reportlab, pytest
Done by me
Server, validation and exports in Python, and the review screen.
Context
Fictional documents and companies, no real client.
Inside
Transcribe, never correct
The model is told to copy what is printed, even when it is wrong. If it corrected things, validation would never see the error in the document. The sample has an error on purpose: 25 x 18.90 printed as 427.50, and the system points it out on the line and on the subtotal.
Two schemas
Giving the model 28 optional fields returned "400 Schema is too complex". The way out was a transport schema with every field required and a domain schema, with a round-trip test that proves nothing is lost.
The 2026 CNPJ
The alphanumeric CNPJ has been valid since July 2026: each character counts as its code minus 48, and the last two digits stay numeric. The same care goes to the access key. In the calculations, money becomes Decimal, never float, with a tolerance based on the smallest unit of the currency.
Python
def is_valid_cnpj(raw: str) -> bool:
s = re.sub(r"[.\-/\s]", "", raw or"").upper()
ifnot re.fullmatch(r"[0-9A-Z]{12}[0-9]{2}", s) or len(set(s)) == 1:
returnFalse
vals = [ord(c) - 48 for c in s[:12]]
w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
d1 = _cnpj_digit(vals, w1)
d2 = _cnpj_digit(vals + [d1], [6] + w1)
return s[12:] == f"{d1}{d2}"
How I know it works
In a real run on September 25, 2026, with 3 fictional documents: 53 of 53, 47 of 47 and 53 of 53 fields correct, between 9.9 and 11.4 seconds each. Three documents and one run: it is not a benchmark.
What this does not do
It is a demo. No client uses it, and it does not measure accuracy on real documents.
If the API fails, there is one retry. It is not a reprocessing queue.
Screens
Data review, with a fictional document.
What broke
On the first run, the invoice key came back with 42 and 43 digits.
The key validation caught it. The report from that run is kept with the code.
Credits: Demo with fictional documents, no real client.
Tell me the problem.
One message is enough to start. I reply myself and, if it makes sense, I send a written proposal: what is in, what is out, deadline and price.