DEV Community

Cover image for Extracting invoice fields with validation: what auto-posts and what goes to a human
Slava Kotenev
Slava Kotenev

Posted on

Extracting invoice fields with validation: what auto-posts and what goes to a human

Pulling an invoice number or a total out of text is the easy half of the job. The useful half is deciding, for every field of every document, whether it goes straight into the accounting system or lands on a person's desk. Here is how a small demo handles it. Everything below is test data from a public synthetic dataset, not a client result.

What the demo does

Input: the OCR text of an invoice. Output: invoice number, date, IBAN and gross total, each with a confidence. Fields with low confidence are routed to a human instead of being posted.

The data is mychen76/invoices-and-receipts_ocr_v1: OCR text plus ground-truth fields. The invoices are synthetic with a fixed layout, and only the invoice-type documents are used (484 of about 2,200; the rest are receipts with a different schema).

Candidates first, then a ranker

For each field the code generates candidates with patterns, then a small logistic regression picks the right one using context: the words around the candidate, its position, its format. This matters most for the total, because an invoice contains many amounts (net, tax, line items, gross) and "the biggest number" is not the answer.

Validation beats confidence for IBANs

For IBANs the demo does not trust a score at all. It validates the ISO 13616 checksum. If OCR confused one character (S for 5, O for 0, a dropped or doubled character) and exactly one repair makes the checksum pass, it applies the repair. If no unique valid value exists, the field goes to a human.

def iban_ok(s: str) -> bool:
    s = s.replace(" ", "").upper()
    rearranged = s[4:] + s[:4]
    digits = "".join(str(int(c, 36)) for c in rearranged)
    return int(digits) % 97 == 1
Enter fullscreen mode Exit fullscreen mode

A checksum is a hard gate a model cannot talk its way past.

Stack of documents with one field highlighted for review

Results on 74 held-out documents (test data)

Field Docs scored Accuracy when automated Automated share
Invoice number 74 100% 97.3%
Invoice date 74 100% 100%
IBAN 56 100% 100%
Gross total 74 98.6% 100%

96.4% of clean documents were fully automated (all four fields), and 98.2% of those had every field correct. Thresholds were tuned on a validation split for a 98% target; the held-out set was used for reporting only.

Honest caveats

  • 74 documents with a fixed synthetic layout is a small, friendly test. Real, varied invoices will score lower, and a pilot starts by measuring on the client's own sample.
  • In 18 of the 74 documents the dataset's IBAN label itself fails the checksum while the OCR text passes it, so the label is corrupted. Those are excluded from the IBAN score and counted separately in results.json. Check your ground truth before blaming the model.
  • The input is OCR text. Turning scans and PDFs into that text is a separate, replaceable step.

Takeaways

  1. Decide per field what is allowed to auto-post, with a number as the bar.
  2. Use deterministic validation (checksums, date ranges, sum checks) wherever it exists.
  3. Route what you cannot validate to a human, and make that route cheap.
  4. Audit your labels before you measure.

Code and numbers: ai-invoice-extraction-demo. Portfolio and contacts: @Kotenev01_bot.


I write about practical, measurable AI adoption for business: demos on test data, no invented case studies. Telegram channel: @kotenev01_en

Top comments (0)