Skip to content

All workInvoice extractor

Wrong total on the invoice.

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()
    if not re.fullmatch(r"[0-9A-Z]{12}[0-9]{2}", s) or len(set(s)) == 1:
        return False
    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

Review screen with the data extracted from a fictional invoice
Data review, with a fictional document.

What broke

  1. 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.

WhatsApp +55 11 99227-9903