DEV Community

Cover image for How does AI extract data from documents?
Swathi Rajagopal
Swathi Rajagopal

Posted on AI-assisted

How does AI extract data from documents?

Templates, AI and validation: a practical look at document data extraction, with Python examples.

Every business runs on documents: invoices, purchase orders, delivery receipts, bank statements. And almost every business has someone copying numbers from those documents into a spreadsheet or system.

Automating that work is called document data extraction. There are two broad ways to do it: templates and AI. This article explains how each works, where each one breaks, and how to make AI output safe to use.

How template-based extraction works

Think of a stencil. Lay it over a page, and you can trace the exact shape cut into it, quickly and perfectly, every time. But only if the page lines up with the stencil.

Template-based extraction works the same way. You define where each field sits, or what text pattern surrounds it, and the system pulls out whatever matches. Here's a simple rule-based version using regular expressions:

import re

INVOICE_RULES = {
    "invoice_number": r"Invoice No[.:]?\s*([A-Z0-9/-]+)",
    "invoice_date": r"Date[.:]?\s*(\d{2}/\d{2}/\d{4})",
    "total": r"Total[.:]?\s*(?:Rs\.?|₹)?\s*([\d,]+\.\d{2})",
}


def extract_with_rules(text, rules):
    """Apply each rule to the document text and return what matches."""
    result = {}
    for field, pattern in rules.items():
        match = re.search(pattern, text, re.IGNORECASE)
        result[field] = match.group(1) if match else None
    return result


doc = "Invoice No: INV-2026/0098\nDate: 14/10/2026\nTotal: ₹ 48,500.00"
print(extract_with_rules(doc, INVOICE_RULES))
# {'invoice_number': 'INV-2026/0098', 'invoice_date': '14/10/2026', 'total': '48,500.00'}
Enter fullscreen mode Exit fullscreen mode

For a supplier who always sends the same invoice layout, this is fast, cheap and completely predictable.

Why templates break when document layouts change

Now a second supplier sends an invoice that says Bill Number instead of Invoice No, writes the date as 14-Oct-2026, and labels the total Amount Payable.

doc2 = "Bill Number: B/7781\nDated 14-Oct-2026\nAmount Payable: Rs. 12,300.00"
print(extract_with_rules(doc2, INVOICE_RULES))
# {'invoice_number': None, 'invoice_date': None, 'total': None}
Enter fullscreen mode Exit fullscreen mode

Every field fails. Nothing is technically wrong with the rules. The document simply doesn't match the stencil.

This is the core weakness of templates. Every new layout needs a new template, and every small change a supplier makes to their format can silently break an existing one. With dozens of suppliers, banks or transporters, template maintenance becomes a job in itself.

How AI-based extraction works

AI-based extraction flips the approach. Instead of telling the system where a field is, you describe what you want, and the model finds it, whatever the layout.

A good way to do this is to define a schema: the fields you need, with their types and formats.

INVOICE_SCHEMA = {
    "invoice_number": "string",
    "invoice_date": "date, formatted as YYYY-MM-DD",
    "total": "number, without currency symbols or commas",
    "line_items": "list of {description: string, amount: number}",
}


def build_prompt(document_text, schema):
    """Ask the model to return only JSON that follows the schema."""
    fields = "\n".join(f"- {name}: {desc}" for name, desc in schema.items())
    return (
        "Extract the following fields from the document below.\n"
        f"{fields}\n"
        "Return only valid JSON. Use null for any field you cannot find.\n\n"
        f"Document:\n{document_text}"
    )
Enter fullscreen mode Exit fullscreen mode

You send the prompt to the language model of your choice and parse the JSON it returns. Both invoices above, Invoice No and Bill Number alike, can now be handled by the same instruction, because the model understands that they mean the same thing.

Asking for normalised formats in the schema (ISO dates, plain numbers) also saves a cleanup step later.

Why AI output still needs validation

Here's the catch. A stencil either fits or doesn't, so templates fail loudly. AI rarely fails loudly. It can return a confident, well-formatted answer that is subtly wrong: a misread digit, a total picked from the wrong line, a date in the wrong century.

So never pass AI output straight into a system. Check it first:

from datetime import datetime


def validate_invoice(record):
    """Return a list of problems found in an extracted invoice record."""
    problems = []

    for field in ("invoice_number", "invoice_date", "total"):
        if not record.get(field):
            problems.append(f"missing {field}")

    try:
        datetime.strptime(record.get("invoice_date") or "", "%Y-%m-%d")
    except ValueError:
        problems.append("invoice_date is not a valid date")

    try:
        total = float(record.get("total"))
    except (TypeError, ValueError):
        problems.append("total is not a number")
        total = None

    items = record.get("line_items") or []
    if total is not None and items:
        items_sum = sum(float(item["amount"]) for item in items)
        if abs(items_sum - total) > 1:
            problems.append(f"line items add up to {items_sum:.2f}, but total is {total:.2f}")

    return problems
Enter fullscreen mode Exit fullscreen mode

The last check is the most valuable. A document usually contains its own cross-checks: line items should add up to the total, debits and credits should balance, quantities should match across pages. Using the document's internal logic to test the extraction catches errors that no format check can.

How to route documents that fail validation

Validation tells you something is wrong. The next question is what to do about it.

A practical pattern is to accept clean records automatically and send everything else to a person, with the problems listed:

def route(record):
    problems = validate_invoice(record)
    if not problems:
        return {"status": "auto_approved", "record": record}
    return {"status": "needs_review", "problems": problems, "record": record}
Enter fullscreen mode Exit fullscreen mode

The reviewer then sees exactly what to check, instead of re-reading the whole document. Over time, the share of documents that go straight through grows, and people spend their time only on the cases that genuinely need judgement.

How to choose between templates and AI

It isn't really either-or. In practice, many production systems combine both:

  • Templates for a few high-volume, fixed layouts, where speed, cost and predictability matter most.
  • AI for the long tail of varied layouts, scans and documents you haven't seen before.
  • Validation on everything, whichever method produced it.
  • Human review for whatever validation flags.

If you are starting today with many document formats, AI-first with strong validation is usually the faster path. Templates still earn their place where a layout is fixed and volumes are high.

Wrap up

A stencil is perfect when the page lines up, and useless when it doesn't. AI reads the page the way a person would, which makes it far more flexible, but it also means its work needs checking.

The reliable approach is the one that combines them: describe what you need, let AI handle the variety, test every result against the document's own logic, and bring in a person when something doesn't add up.

Document understanding SaaS platforms such as DocuGenie.AI apply this approach to real-world documents like bank statements, financial statements and proof of delivery (POD). But the principles above apply to any extraction pipeline you build yourself.

Top comments (0)