Automating E-Invoicing Compliance with Open-Source NLP

Community Article
Published June 8, 2026

Across Europe, governments are steadily closing the gap between the informal way businesses exchange invoices today and the structured, machine-readable formats that tax authorities will soon require. The EU's ViDA (VAT in the Digital Age) package mandates EN 16931-compliant electronic invoicing for intra-community B2B transactions from 1 July 2030, while individual member states are moving even faster on domestic timelines. The Netherlands, for instance, is already preparing a Peppol-based legislative framework with draft legislation expected by end of 2026.

For developers and ML engineers, this regulatory momentum opens a genuinely interesting problem space: how do you take the messy, inconsistent pile of PDFs and semi-structured documents that most businesses currently call "invoices" and build a pipeline that extracts, validates, and converts them into structured XML that satisfies EN 16931?

This post walks through a practical open-source NLP approach using models available on the Hugging Face Hub, from document understanding with LayoutLMv3 to field validation with a fine-tuned token classifier.

Why PDFs Are Not E-Invoices

Before diving into the architecture, it helps to be precise about what "e-invoicing compliance" actually means technically.

A PDF invoice is a visual artifact. It encodes a document as a rendered image (or selectable text in a bounding box layout), designed so a human can read it. It tells you nothing about which string is the VAT number versus the invoice number unless you already know the visual grammar of that specific supplier's template.

A compliant e-invoice under EN 16931 is a structured data file, typically XML in UBL 2.1 or CII format, where every field is semantically labelled. The cbc:TaxAmount element is not just a number floating somewhere near the word "VAT"; it is a machine-readable declaration with a currency attribute and a taxonomy code. Accounting software on the receiving end can ingest, validate, and reconcile it without any human in the loop.

The gap between these two worlds is exactly where NLP becomes useful. A large fraction of invoices in circulation today exist only as PDFs or scanned images. Building a compliant pipeline means teaching a model to understand the layout and semantics of those documents well enough to produce a clean structured output.

The Stack

The approach described here uses three open-source components:

  • LayoutLMv3 for document understanding and field extraction
  • A sequence classification head for document-level routing and validation
  • A rule-based XML renderer to assemble the EN 16931 output All models are loaded via transformers and all weights are pulled from the Hub.
from transformers import AutoProcessor, AutoModelForTokenClassification
import torch
 
processor = AutoProcessor.from_pretrained(
    "microsoft/layoutlmv3-base", apply_ocr=True
)
model = AutoModelForTokenClassification.from_pretrained(
    "Theivaprakasham/layoutlmv3-finetuned-invoice"
)

Theivaprakasham/layoutlmv3-finetuned-invoice is already fine-tuned on an invoice dataset and predicts labels such as INVOICE_NUMBER, INVOICE_DATE, BILLER_NAME, BILLER_ADDRESS, DUE_DATE, SUBTOTAL, and TOTAL, which map directly onto required EN 16931 fields.

LayoutLMv3: Why Layout Matters

Standard text-only models struggle with invoices because proximity and spatial position carry meaning that does not survive plain tokenisation. "120.00" appears seventeen times in a purchase order. Which instance is the line-item total, which is the VAT amount, and which is the unit price? A human knows because of where it sits on the page, not because of any surrounding words.

LayoutLMv3 jointly encodes text tokens, their bounding box coordinates, and a patch-based visual representation of the document image. On the FUNSD benchmark, LayoutLMv1 already pushed F1 from 60% (BERT baseline) to 90%. LayoutLMv3 incorporates visual features during pre-training and improves further, as described in the Accelerating Document AI post on the HF blog.

The model takes as input:

from PIL import Image
 
image = Image.open("invoice.png").convert("RGB")
encoding = processor(image, return_tensors="pt")
 
with torch.no_grad():
    outputs = model(**encoding)
 
predictions = outputs.logits.argmax(-1).squeeze().tolist()
token_boxes = encoding.bbox.squeeze().tolist()
tokens = processor.tokenizer.convert_ids_to_tokens(
    encoding["input_ids"].squeeze().tolist()
)

Each predicted label aligns with a token and its bounding box, giving you both the extracted value and its position in the document.

Building the Extraction Pipeline

Once you have token-level predictions, you reassemble them into field-level extractions. Invoice fields are often multi-token spans (addresses, line item descriptions), so you apply a simple BIO merging pass:

def merge_bio_predictions(tokens, predictions, label_map):
    entities = []
    current_entity = None
 
    for token, pred in zip(tokens, predictions):
        label = label_map[pred]
 
        if label.startswith("B-"):
            if current_entity:
                entities.append(current_entity)
            current_entity = {"label": label[2:], "tokens": [token]}
 
        elif label.startswith("I-") and current_entity:
            current_entity["tokens"].append(token)
 
        else:
            if current_entity:
                entities.append(current_entity)
                current_entity = None
 
    return [
        {"label": e["label"], "value": " ".join(e["tokens"]).replace(" ##", "")}
        for e in entities
    ]

This gives you a clean dictionary of extracted fields ready for validation.

Validation: Catching What the Model Misses

Extraction accuracy is not enough on its own. An EN 16931-compliant invoice must contain specific mandatory fields, and those fields must pass format checks before the XML is emitted. A separate validation step using regex patterns and field-presence checks prevents malformed invoices from reaching the output pipeline.

import re
from dataclasses import dataclass, field
from typing import Optional
 
@dataclass
class InvoiceFields:
    invoice_number: Optional[str] = None
    invoice_date: Optional[str] = None
    seller_vat: Optional[str] = None
    buyer_vat: Optional[str] = None
    total_amount: Optional[str] = None
    vat_amount: Optional[str] = None
    currency: str = "EUR"
 
VAT_PATTERN = re.compile(r"^[A-Z]{2}[0-9A-Z]{2,12}$")
DATE_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}$")
 
def validate_invoice(fields: InvoiceFields) -> list[str]:
    errors = []
 
    if not fields.invoice_number:
        errors.append("Missing mandatory field: invoice_number")
    if not fields.invoice_date or not DATE_PATTERN.match(fields.invoice_date):
        errors.append("Invalid or missing invoice_date (expected ISO 8601)")
    if fields.seller_vat and not VAT_PATTERN.match(fields.seller_vat):
        errors.append(f"Invalid seller VAT format: {fields.seller_vat}")
    if not fields.total_amount:
        errors.append("Missing mandatory field: total_amount")
 
    return errors

VAT number format validation is particularly important in a cross-border European context. The regex above handles the basic EU format; a production system would additionally call a VIES lookup for live verification.

Rendering EN 16931-Compliant XML

With validated fields in hand, the final step renders the structured output. The UBL 2.1 schema is verbose but deterministic:

from lxml import etree
 
def render_ubl_invoice(fields: InvoiceFields) -> bytes:
    nsmap = {
        None: "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2",
        "cbc": "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2",
        "cac": "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2",
    }
    root = etree.Element("Invoice", nsmap=nsmap)
    cbc = "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
 
    etree.SubElement(root, f"{{{cbc}}}CustomizationID").text = (
        "urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0"
    )
    etree.SubElement(root, f"{{{cbc}}}ID").text = fields.invoice_number
    etree.SubElement(root, f"{{{cbc}}}IssueDate").text = fields.invoice_date
    etree.SubElement(root, f"{{{cbc}}}InvoiceTypeCode").text = "380"
    etree.SubElement(root, f"{{{cbc}}}DocumentCurrencyCode").text = fields.currency
 
    return etree.tostring(root, pretty_print=True, xml_declaration=True, encoding="UTF-8")

The CustomizationID value signals Peppol BIS Billing 3.0 conformance, which is the profile used for Peppol network delivery across the Netherlands, Belgium, Germany, and most of the EU.

Fine-Tuning on Your Own Invoice Dataset

The Theivaprakasham/layoutlmv3-finetuned-invoice checkpoint is a strong starting point, but invoice layouts vary significantly across suppliers, countries, and ERPs. If your use case involves a narrow document type (say, Dutch government supplier invoices or NLCIUS-formatted exports), fine-tuning on a small labelled dataset of your own invoices usually produces substantial gains.

The Hub's datasets library makes it straightforward to structure annotation data for token classification:

from datasets import Dataset
 
# Each example: image path, word tokens, bounding boxes, NER labels
dataset = Dataset.from_dict({
    "image": [...],
    "words": [...],
    "boxes": [...],
    "ner_tags": [...],
})

A few hundred labelled examples are typically sufficient for a domain-specific fine-tune, especially when starting from a strong document-understanding checkpoint. Annotation can be done with tools like Label Studio or Prodigy, both of which export formats compatible with the token classification training loop.

Where This Fits in a Real Compliance Workflow

The NLP extraction pipeline described here handles the hardest part of the problem: converting unstructured or semi-structured source documents into validated structured data. But a production compliance workflow also needs:

  • Peppol connectivity to deliver invoices over the four-corner network
  • VIES VAT validation for cross-border buyer/seller verification
  • Archiving in accordance with national retention rules (7 years in the Netherlands)
  • Error handling and human review queues for low-confidence extractions The good news is that the NLP layer is cleanly separable from the delivery and archival infrastructure. The model produces a validated InvoiceFields object; downstream systems consume it. This makes it straightforward to slot into existing ERP integrations or financial automation platforms.

Structured by Design, Compliant by Default

E-invoicing compliance is arriving across Europe on a defined legislative timeline. For developers, it is a concrete and solvable document AI problem with real stakes: bad extraction means invalid invoices, payment delays, and eventually regulatory penalties.

The Hugging Face ecosystem provides the key building blocks. LayoutLMv3 handles the layout-aware field extraction that text-only models cannot reliably perform. Fine-tuned checkpoints on the Hub accelerate time to a working baseline. The validation and XML rendering layers are standard Python. And as the EU harmonises around EN 16931 and Peppol, the target output format is well-defined and stable.

If you are working on invoice processing, document AI, or financial compliance automation, this is a good moment to build. The models are ready; the regulatory window is open.

Community

Sign up or log in to comment