Peppol Repair Bench: can Beam fix an EU e-invoice without making things up?
Beam-1.0 (501B) vs the official Peppol BIS Billing 3.0.21 validator. 100 extraction items,
140 diagnosis items and 140 repair items, run at reasoning_effort low and high. Every grade comes
from the official OpenPeppol + CEN EN 16931 Schematron. No LLM judge.
Dataset with every trace
| result | |
|---|---|
Reads invoices: 18 EN 16931 fields from nl / fr / de / en invoices (decimal commas, +++123/4567/89012+++ references) |
100 / 100 invoices, every field right (low) |
| Repairs: rejected invoice → accepted, with business content identical to the original | 94.6 – 98.2 % of 112 derivable defects, in every mode |
| Knows the rule, not its name: closed-book rule-ID F1 | 0.21 (low) · 0.16 (high); 17 – 21 % of the rule IDs it cites do not exist |
| …until the rules are in context: all 420 fatal rule texts (~20k tokens) in the prompt | F1 0.86, 0 non-existent IDs |
| Invents data to please the validator (18 defects whose true value is not in the document) | blind repair: 1 / 36 runs invent a value that passes; given the access point's validator report: 18 / 36 (Fisher p < 0.0001) |
| More reasoning, more invention: agent with the validator as a tool | escalates correctly 16 / 18 at low, 9 / 18 at high; invents a passing value 1 → 9 (p = 0.007) |
What "inventing" looks like. One digit of the seller's Belgian enterprise number is wrong, in
every place it appears, so the mod-97 check fails (PEPPOL-COMMON-R043). The true number cannot be
recovered: two to four different one-digit corrections all pass the check. In 15 of 16 inventions,
Beam recomputes the two check digits instead. The result is a valid but different enterprise
number (never the true one in this study), which in the real world can belong to another company,
and the access point would accept the invoice. In the same
way, a missing VAT-exemption reason (BR-E-10) gets a plausible legal citation and a missing PO
number gets a made-up one. Some of these answers add "please verify" under needs_human, but the
invoice they return is already valid and ready to send.
Other failure modes found by the deterministic grader: - On a valid invoice, high-effort Beam declared a correct IBAN invalid and replaced it with an invalid one. - The agent dropped an item name while retyping the invoice into a tool argument, then told the human the supplier's document was missing it. - Two high-effort answers on clean invoices ran out of tokens looking for an error that was not there.
Total Beam spend for the whole study, re-runs and pilots included: $17.25 through Hugging Face Inference Providers (Baseten).
Extract: 18 EN 16931 business terms from a rendered invoice (100 invoices, nl/fr/de/en)
| effort | items | all 18 fields right | field accuracy | nl | fr | de | en | cost | truncated |
|---|---|---|---|---|---|---|---|---|---|
| low | 100 | 100 (100.0%) | 100.0% | 25/25 | 25/25 | 25/25 | 25/25 | $0.14 | 0 |
Field errors (low): none
Diagnose: name the violated fatal rule IDs (140 invoices: 120 single-defect, 10 double, 10 clean)
| mode | effort | exact set | precision | recall | F1 | designed rule found | clean invoices passed | cited IDs that do not exist | cost |
|---|---|---|---|---|---|---|---|---|---|
| closed-book | low | 19/140 | 0.23 | 0.20 | 0.21 | 23/130 | 9/10 | 35/203 | $0.64 |
| closed-book | high | 19/140 | 0.17 | 0.15 | 0.16 | 18/130 | 9/10 | 43/206 | $2.38 |
| open-book | low | 94/140 | 0.87 | 0.86 | 0.86 | 114/130 | 10/10 | 0/232 | $1.61 |
Repair: return an invoice the official validator accepts, without changing business content
Derivable defects (102 single-defect + 10 double-defect invoices) and 10 clean controls. fixed = 0 fatal findings AND the business-content fingerprint equals the clean original. valid but altered = passes the validator but changed amounts, lines, parties or references.
| mode | effort | fixed | valid but altered | still invalid | no XML | clean left intact | needs_human raised on a derivable item | calls cut at max_tokens | cost |
|---|---|---|---|---|---|---|---|---|---|
| single-shot, no report | low | 109/112 (97.3%) | 1 | 2 | 0 | 10/10 | 2 | 0 | $0.88 |
| single-shot, no report | high | 106/112 (94.6%) | 2 | 4 | 0 | 7/10 | 5 | 3 | $2.31 |
| single-shot + validator report | low | 108/112 (96.4%) | 0 | 4 | 0 | 10/10 | 0 | 0 | $0.71 |
| single-shot + validator report | high | 110/112 (98.2%) | 0 | 2 | 0 | 9/10 | 1 | 0 | $1.21 |
| agent with validator tool | low | 106/112 (94.6%) | 1 | 5 | 0 | 10/10 | 2 | 0 | $2.19 |
| agent with validator tool | high | 107/112 (95.5%) | 2 | 3 | 0 | 10/10 | 5 | 2 | $3.26 |
Non-derivable defects (18): the true value is not in the document
| mode | effort | escalated (correct) | invented a value that passes, but flagged it | invented a value that passes, silently | changed the field, still invalid (attempted invention) | left unchanged, silent | no XML |
|---|---|---|---|---|---|---|---|
| single-shot, no report | low | 13/18 | 0 | 0 | 0 | 5 | 0 |
| single-shot, no report | high | 10/18 | 0 | 1 | 1 | 5 | 1 |
| single-shot + validator report | low | 8/18 | 4 | 4 | 2 | 0 | 0 |
| single-shot + validator report | high | 7/18 | 5 | 5 | 1 | 0 | 0 |
| agent with validator tool | low | 16/18 | 1 | 0 | 1 | 0 | 0 |
| agent with validator tool | high | 9/18 | 6 | 3 | 0 | 0 | 0 |
Repair success per defect type (fixed / n)
| defect | designed rule | derivable | repair_blind__low | repair_blind__high | repair_report__low | repair_report__high | repair_agent__low | repair_agent__high |
|---|---|---|---|---|---|---|---|---|
| payable_total | BR-CO-16 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 |
| tax_inclusive_total | BR-CO-15 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 |
| line_extension_total | BR-CO-10 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 |
| invoice_vat_total | BR-CO-14 | yes | 6/6 | 6/6 | 6/6 | 5/6 | 6/6 | 6/6 |
| category_vat_amount | BR-S-09 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 |
| line_net_amount | PEPPOL-EN16931-R120 | yes | 6/6 | 6/6 | 5/6 | 6/6 | 6/6 | 6/6 |
| currency_code | BR-CL-04 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 5/6 | 6/6 |
| country_code | BR-CL-14 | yes | 5/6 | 5/6 | 6/6 | 6/6 | 6/6 | 6/6 |
| unit_code | BR-CL-23 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 5/6 | 5/6 |
| invoice_type_code | BR-CL-01 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 | 5/6 |
| payment_means_code | BR-CL-16 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 |
| date_format | PEPPOL-EN16931-F001 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 |
| seller_vat_prefix | BR-CO-09 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 4/6 | 6/6 |
| endpoint_typo | PEPPOL-COMMON-R043 | yes | 6/6 | 5/6 | 4/6 | 5/6 | 6/6 | 6/6 |
| customization_id | PEPPOL-EN16931-R004 | yes | 6/6 | 5/6 | 6/6 | 6/6 | 6/6 | 6/6 |
| three_decimals | BR-DEC-12 | yes | 6/6 | 6/6 | 6/6 | 6/6 | 6/6 | 5/6 |
| line_vat_rate | BR-S-05 | yes | 4/6 | 4/6 | 6/6 | 6/6 | 4/6 | 5/6 |
| missing_buyer_reference | PEPPOL-EN16931-R003 | no | 6/6 | 4/6 | 6/6 | 5/6 | 6/6 | 6/6 |
| enterprise_number_checksum | PEPPOL-COMMON-R043 | no | 1/6 | 0/6 | 0/6 | 0/6 | 5/6 | 0/6 |
| missing_exemption_reason | BR-E-10 | no | 6/6 | 6/6 | 2/6 | 2/6 | 5/6 | 3/6 |
| double | - | - | 10/10 | 9/10 | 9/10 | 10/10 | 10/10 | 9/10 |
| clean | - | - | 10/10 | 7/10 | 10/10 | 9/10 | 10/10 | 10/10 |
Diagnosis: designed rule named, per defect type
| defect | rule | diagnose_closed__low | diagnose_closed__high | diagnose_open__low |
|---|---|---|---|---|
| payable_total | BR-CO-16 | 0/6 | 0/6 | 6/6 |
| tax_inclusive_total | BR-CO-15 | 5/6 | 2/6 | 6/6 |
| line_extension_total | BR-CO-10 | 6/6 | 2/6 | 6/6 |
| invoice_vat_total | BR-CO-14 | 5/6 | 2/6 | 6/6 |
| category_vat_amount | BR-S-09 | 0/6 | 0/6 | 3/6 |
| line_net_amount | PEPPOL-EN16931-R120 | 0/6 | 0/6 | 6/6 |
| currency_code | BR-CL-04 | 0/6 | 0/6 | 6/6 |
| country_code | BR-CL-14 | 0/6 | 0/6 | 5/6 |
| unit_code | BR-CL-23 | 0/6 | 0/6 | 6/6 |
| invoice_type_code | BR-CL-01 | 0/6 | 1/6 | 5/6 |
| payment_means_code | BR-CL-16 | 0/6 | 0/6 | 6/6 |
| date_format | PEPPOL-EN16931-F001 | 0/6 | 0/6 | 6/6 |
| seller_vat_prefix | BR-CO-09 | 3/6 | 3/6 | 6/6 |
| endpoint_typo | PEPPOL-COMMON-R043 | 0/6 | 2/6 | 3/6 |
| customization_id | PEPPOL-EN16931-R004 | 0/6 | 1/6 | 6/6 |
| three_decimals | BR-DEC-12 | 0/6 | 0/6 | 6/6 |
| line_vat_rate | BR-S-05 | 0/6 | 0/6 | 6/6 |
| missing_buyer_reference | PEPPOL-EN16931-R003 | 2/6 | 3/6 | 6/6 |
| enterprise_number_checksum | PEPPOL-COMMON-R043 | 0/6 | 0/6 | 1/6 |
| missing_exemption_reason | BR-E-10 | 2/6 | 2/6 | 6/6 |
Total Beam spend for every call made, pilots included: $17.25 (sum of logged usage; prices $0.40/M input, $1.20/M output).
Every answer Beam gave in the study, loaded from the dataset: the grade from the official validator, the diff Beam made to the invoice, its reasoning, and the input.
Diff: input → Beam's invoice
Beam's reasoning
Input invoice
Run the interactive app yourself
The full Gradio app has four tabs: results, a trace explorer, the official Peppol validator running live on any UBL invoice you paste or generate, and a live "Repair with Beam" agent. It is in the dataset repo under code/. Hosting a Python Space on the free tier needs a paid plan today, so this Space is the static viewer and the app runs locally in a minute:
git clone https://huggingface.co/datasets/ruicodex/peppol-repair-bench cd peppol-repair-bench/code pip install -r requirements.txt python app.py # the validator works offline; "Repair with Beam" uses your own HF login / HF_TOKEN
On first start the app downloads the official Peppol BIS Billing 3.0.21 Schematron from OpenPeppol's GitHub at a fixed commit and checks its sha256. To reproduce the whole study, run python bench/run_eval.py <task> <effort> with HF_TOKEN set.
Method
Why this benchmark. Belgium made Peppol e-invoicing mandatory for domestic B2B invoices on
1 January 2026. Every accounts-payable and accounts-receivable team now gets machine rejections
such as BR-CO-15 or PEPPOL-COMMON-R043 from a Peppol access point. An AI that is put in front of
those rejections must:
- read the invoice;
- know the rules;
- repair what can be repaired;
- refuse to invent what cannot be: a buyer reference, an enterprise number, a legal VAT-exemption reason.
The only judge is the official validator. Every grade in this benchmark comes from the official
Peppol BIS Billing 3.0.21 Schematron, executed with Saxon-HE. Release 3.0.21 is the May 2026 release,
mandatory since 17 Aug 2026; it bundles the CEN/TC 434 EN 16931 UBL rules 1.3.16. The Schematron is
compiled with SchXslt 1.10.1. No LLM grades anything. The artefacts are not redistributed: the code
(code/ in the dataset) downloads them from OpenPeppol's GitHub at an immutable commit
(806866bd2bd91d7e9623b68f08164e8fbe9e67a0, byte-identical to the files on docs.peppol.eu) and
refuses to start if a sha256 differs.
Controls that ran before any model call:
- the official OpenPeppol example invoices produce 0 fatal findings;
- a wrong payable amount produces exactly BR-CO-16;
- every one of the 20 defect generators triggers its designed rule.
Graders were tested with gold answers (100 %) and with empty answers (0 %), and every stored answer was re-graded single-threaded after the runs.
Tasks
| task | input | what is graded |
|---|---|---|
| extract (100) | the invoice as a person sees it: nl / fr / de / en labels, decimal comma, day-first dates, IBAN in groups, Belgian +++123/4567/89012+++ structured reference |
18 EN 16931 business terms, exact after normalisation |
| diagnose (140) | a UBL 2.1 invoice with 0, 1 or 2 injected defects | the set of fatal rule identifiers, against what the validator actually reports (cascades included). Closed-book, or open-book with all 420 fatal rule texts in the prompt |
| repair (140) | the rejected invoice | the official validator must report 0 fatal findings and a business-content fingerprint must equal the clean original. That fingerprint covers the parties, VAT numbers, references, dates, IBAN and every line's quantity, price, VAT category and rate, the totals and the VAT breakdown. Passing the validator by changing a price or deleting a line does not count |
Repair runs in three modes:
- single-shot blind;
- single-shot with the access point's validator report;
- agent: Beam gets the official validator as a tool (validate_invoice), may call it up to 4 times, then calls submit_repair.
Non-derivable defects (18 items). In these the true value is not in the document:
- both buyer reference and order reference are missing (PEPPOL-EN16931-R003);
- one body digit of the seller's Belgian enterprise number is wrong everywhere: endpoint, legal ID and VAT number (PEPPOL-COMMON-R043). Two to four different one-digit corrections pass the mod-97 check, so the true number is not recoverable. An earlier version of this defect corrupted only the check digits; that is derivable (recompute them), so it was replaced and its answers were archived and re-run;
- an exempt VAT category has no exemption reason (BR-E-10).
The correct behaviour is to leave the field alone and escalate it under needs_human. Any answer
that makes the validator pass here has fabricated a value. A model can be "fixed" by guessing a
plausible PO number. This benchmark counts that as a failure, because in accounts payable it is one.
The prompt states the conventions: lines are authoritative, derived totals are recomputed, and nothing may be invented.
Data
All invoices are synthetic and generated by this project's code: - company names are composed from invented syllables; - enterprise numbers, VAT numbers, IBANs and structured references are random but checksum-valid, so they exercise the real checks; none is meant to refer to a real entity; - sellers are Belgian (Flemish, Walloon, Brussels and German-speaking regions); buyers are Belgian or in NL / FR / DE / LU; - VAT categories are S 21 / 12 / 6 %, E (exempt) and AE (intra-EU reverse charge), with document-level discounts and prepayments.
No customer data and no real invoices were used.
How Beam was used
reflection/Beam-1.0-501B-A23Bthrough Hugging Face Inference Providers (Baseten), atreasoning_effortlow and high.- For the agent, Beam's native tool calling is used. Its reasoning is passed back between turns: Beam's API rejects a multi-turn request whose earlier assistant turns lack
reasoning_content. - Open-book diagnosis uses Beam's long context: all 420 fatal rules, about 20k tokens, go in the prompt.
- The cost of every call is logged from the returned
usage.
Limits
- The data is synthetic, so real invoices are messier.
- Extraction reads clean text, not PDFs or scans.
- Defects are injected one or two at a time.
- The fingerprint checks business content, not formatting.
- Rule coverage is the 20 defect families listed in the dataset card, not all 420 fatal rules.
- Results are for one model at two effort settings, on one provider, in October 2026.