---
title: "Extraire une facture dans un format imposé"
description: "Faire rendre au modèle les champs d'une facture dans un format fixé, et les vérifier."
url: https://meridal.fr/apprendre/extraire-une-facture-dans-un-format-impose/
language: fr
written: 2026-10-09
verified: 2026-10-09
license: CC BY 4.0
---

[Apprendre](https://meridal.fr/apprendre/) › [Vos documents](https://meridal.fr/apprendre/dossier/documents/)

S'exercer · Niveau 3, Construire · 30 minutes

# Extraire une facture dans un format imposé

Trois factures du même mois, arrivées sous trois formes : une Factur-X, un PDF, un scan. Les mettre toutes dans le même format, celui de votre import comptable, en lisant les données quand elles existent, en faisant lire le reste par un modèle, et en arrêtant par des règles ce qui ne tient pas.

Écrit le 9 octobre 2026 · Vérifié le 9 octobre 2026

## Vous saurez

- Reconnaître la forme d'une facture, et la lire en conséquence
- Lire une Factur-X sans aucun modèle
- Demander au modèle de laisser vide ce qu'il ne lit pas
- Contrôler l'identité du fournisseur et les montants
- Mesurer une extraction contre un corrigé

- [← Avant Extraction des chiffres d'un document](https://meridal.fr/apprendre/extraction-des-chiffres/)
- [↑ Plus général Le RAG : une IA qui répond avec les documents de l'entreprise](https://meridal.fr/apprendre/rag/)
- [↓ Plus concret Atelier : interroger vos documents sans programmer](https://meridal.fr/apprendre/atelier-documents/)
- [→ Ensuite Liste de contrôle : un assistant sur vos documents](https://meridal.fr/apprendre/avant-de-brancher-un-assistant/)

Rappel de la page précédente

Répondez de tête, puis ouvrez. [Faire sortir les chiffres d'un document](https://meridal.fr/apprendre/extraction-des-chiffres/)

**1 L'outil rend un tableau complet, au bon format. Les chiffres sont-ils justes ?**

Pas forcément. Les éditeurs garantissent la forme, pas les valeurs : un chiffre mal lu ou inventé a la même forme qu'un juste. Seules des règles, ou une personne, le repèrent.

**2 Un fournisseur vous envoie désormais des factures électroniques au format UBL. Faut-il les extraire ?**

Non : elles arrivent déjà en données, transmises par une plateforme agréée. L'extraction reste utile pour les documents qui arrivent encore en PDF ou sur papier.

L'essentiel

1. Une facture qui porte ses données, comme une Factur-X, se lit sans modèle : le programme ouvre le fichier joint et recopie les champs.
2. Le modèle ne sert qu'aux PDF sans données et aux scans ; on lui demande de recopier, de ne rien calculer, et de laisser vide ce qu'il ne lit pas.
3. Les règles vérifient l'identité du fournisseur, avec la clé du SIREN et le numéro de TVA, puis les montants ; une facture qui échoue va à une personne.
4. Une erreur de recopie qui respecte les règles, un numéro de facture par exemple, ne se voit qu'en comparant à un corrigé.
5. Depuis le 1er septembre 2026, toute entreprise doit pouvoir recevoir des factures électroniques ; celles qui arrivent en données n'ont pas besoin d'être lues par un modèle.

## Objectif

Mettre trois factures fournisseurs dans un même format de dix champs, prêt pour l'import comptable : le fournisseur, son SIREN, son numéro de TVA, le numéro de facture, les dates d'émission et d'échéance, le total hors taxe, le taux de TVA, la TVA et le total TTC. Une facture n'entre dans le fichier d'import que si toutes les règles passent ; les autres vont à une personne, avec la raison.

Les trois factures sont fabriquées : entreprises fictives, numéros et montants inventés. Les SIREN commencent par 00 et ont une clé valide ; ne les cherchez pas dans un annuaire. L'appel au modèle (étape 3) suit la documentation d'Anthropic et n'a pas encore été exécuté par Meridal ; ses lectures, utilisées aux étapes 4 et 5, sont fabriquées. Toutes les autres étapes ont tourné : leurs sorties sont réelles.

La même chaîne, à l'échelle d'un cabinet de cession : [le modèle financier rempli depuis les liasses fiscales](https://meridal.fr/realisations/modele-financier/).

## Données fournies

Trois factures reçues en septembre 2026 par une menuiserie fictive. Elles disent les mêmes choses, mais ne se lisent pas de la même façon.

1. # Facture A

   Document fabriqué pour l'exercice de Meridal : entreprise fictive, numéros et montants inventés. Reçue en Factur-X : un PDF qui porte en pièce jointe le fichier factur-x.xml.

   **Atelier Rivière** (entreprise fictive) SIREN 004128757 · TVA FR62004128757

   **FACTURE n° F-2026-0918** Date : 18/09/2026 Échéance : 18/10/2026 Client : Menuiserie Valcourt (entreprise fictive), SIREN 007391642

   | Désignation | Montant HT |
   |---|---|
   | Maintenance des machines, septembre | 1 200,00 € |

   Total hors taxe : 1 200,00 € TVA à 20 % : 240,00 € **Total TTC : 1 440,00 €**
2. # Facture B

   Document fabriqué pour l'exercice de Meridal : entreprise fictive, numéros et montants inventés. Reçue en PDF ordinaire : la page contient du texte, sans données jointes.

   **Imprimerie du Port** (entreprise fictive) SIREN 002583169 · TVA FR92002583169

   **FACTURE n° 8834** Date : 22/09/2026 Échéance : 22/10/2026 Client : Menuiserie Valcourt (entreprise fictive), SIREN 007391642

   | Désignation | Montant HT |
   |---|---|
   | Impression de 500 catalogues | 1 850,00 € |
   | Livraison | 150,00 € |

   Total hors taxe : 2 000,00 € TVA à 20 % : 400,00 € **Total TTC : 2 400,00 €**
3. # Facture C

   Document fabriqué pour l'exercice de Meridal : entreprise fictive, numéros et montants inventés. Reçue scannée : la page n'est qu'une image, un peu de travers ; une tache couvre la date d'échéance.

   **Transports Lemaire** (entreprise fictive) SIREN 006513477 · TVA FR84006513477

   **FACTURE n° T-77** Date : 25/09/2026 Échéance : (illisible : une tache couvre la date) Client : Menuiserie Valcourt (entreprise fictive), SIREN 007391642

   | Désignation | Montant HT |
   |---|---|
   | Livraison régionale | 800,00 € |

   Total hors taxe : 800,00 € TVA à 20 % : 160,00 € **Total TTC : 960,00 €**

Les PDF se fabriquent avec `fabriquer.py`, que l'étape 1 suppose lancé : `a.pdf` porte son fichier `factur-x.xml` en pièce jointe, `b.pdf` a une couche de texte, `c.pdf` n'est qu'une image. Ce ne sont pas des Factur-X conformes à la norme, qui demande un PDF/A-3 et un profil validé : seulement de quoi exercer la lecture.

**Voir le programme qui fabrique les trois PDF**

`fabriquer.py`

```
"""Fabrique les trois factures de l'exercice dans pdf/, et l'image du scan dans c.png : a.pdf porte son fichier factur-x.xml en pièce jointe,
b.pdf est un PDF ordinaire avec sa couche de texte, c.pdf ne contient qu'une image, comme un scan.
Ce ne sont pas des Factur-X conformes (PDF/A-3, profil validé) : seulement de quoi exercer la lecture."""
import io, os
from decimal import Decimal
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from pypdf import PdfReader, PdfWriter
from donnees import FACTURES, CLIENT

os.makedirs("pdf", exist_ok=True)
FR = lambda v: f"{Decimal(v):,.2f}".replace(",", " ").replace(".", ",") + " €"
JOUR = lambda d: f"{d[8:]}/{d[5:7]}/{d[:4]}"

def lignes_texte(f):
    t = [f"{f['fournisseur']} (entreprise fictive)", f"SIREN {f['siren']}  ·  TVA {f['tva_intracom']}", "",
         f"FACTURE n° {f['numero']}", f"Date : {JOUR(f['date_emission'])}",
         f"Échéance : {JOUR(f['date_echeance']) if f['date_echeance'] else '    '}",
         f"Client : {CLIENT[0]}, SIREN {CLIENT[1]}", ""]
    t += [f"{lib} : {FR(v)} HT" for lib, v in f["lignes"]]
    t += ["", f"Total hors taxe : {FR(f['total_ht'])}", f"TVA à {f['taux_tva']} % : {FR(f['total_tva'])}", f"Total TTC : {FR(f['total_ttc'])}"]
    return t

def pdf_texte(f):
    buf = io.BytesIO(); c = canvas.Canvas(buf, pagesize=A4); y = 780
    for l in lignes_texte(f):
        c.setFont("Helvetica-Bold" if l.startswith("FACTURE") else "Helvetica", 11); c.drawString(60, y, l); y -= 20
    c.save(); return buf.getvalue()

def xml_cii(f):
    """Les champs de la facture en Cross Industry Invoice, les chemins que lit la bibliothèque factur-x."""
    return f'''<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
 <rsm:ExchangedDocument>
  <ram:ID>{f["numero"]}</ram:ID>
  <ram:TypeCode>380</ram:TypeCode>
  <ram:IssueDateTime><udt:DateTimeString format="102">{f["date_emission"].replace("-", "")}</udt:DateTimeString></ram:IssueDateTime>
 </rsm:ExchangedDocument>
 <rsm:SupplyChainTradeTransaction>
  <ram:ApplicableHeaderTradeAgreement>
   <ram:SellerTradeParty>
    <ram:Name>{f["fournisseur"]}</ram:Name>
    <ram:SpecifiedLegalOrganization><ram:ID schemeID="0002">{f["siren"]}</ram:ID></ram:SpecifiedLegalOrganization>
    <ram:SpecifiedTaxRegistration><ram:ID schemeID="VA">{f["tva_intracom"]}</ram:ID></ram:SpecifiedTaxRegistration>
   </ram:SellerTradeParty>
  </ram:ApplicableHeaderTradeAgreement>
  <ram:ApplicableHeaderTradeSettlement>
   <ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
   <ram:ApplicableTradeTax><ram:CalculatedAmount>{f["total_tva"]}</ram:CalculatedAmount><ram:TypeCode>VAT</ram:TypeCode><ram:BasisAmount>{f["total_ht"]}</ram:BasisAmount><ram:CategoryCode>S</ram:CategoryCode><ram:RateApplicablePercent>{f["taux_tva"]}</ram:RateApplicablePercent></ram:ApplicableTradeTax>
   <ram:SpecifiedTradePaymentTerms><ram:DueDateDateTime><udt:DateTimeString format="102">{f["date_echeance"].replace("-", "")}</udt:DateTimeString></ram:DueDateDateTime></ram:SpecifiedTradePaymentTerms>
   <ram:SpecifiedTradeSettlementHeaderMonetarySummation>
    <ram:TaxBasisTotalAmount>{f["total_ht"]}</ram:TaxBasisTotalAmount>
    <ram:TaxTotalAmount currencyID="EUR">{f["total_tva"]}</ram:TaxTotalAmount>
    <ram:GrandTotalAmount>{f["total_ttc"]}</ram:GrandTotalAmount>
    <ram:DuePayableAmount>{f["total_ttc"]}</ram:DuePayableAmount>
   </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
  </ram:ApplicableHeaderTradeSettlement>
 </rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>
'''

def pdf_scan(f):
    im = Image.new("L", (1240, 1754), 248); d = ImageDraw.Draw(im)
    police = ImageFont.truetype("/System/Library/Fonts/Supplemental/Arial.ttf", 30); y = 140
    for l in lignes_texte(f):
        d.text((120, y), l, fill=40, font=police); y += 52
    d.ellipse((560, 395, 720, 455), fill=150)   # une tache sur la date d'échéance
    im = im.rotate(-1.2, fillcolor=248).filter(ImageFilter.GaussianBlur(0.8))
    im.save("c.png")   # la page en image, que lit extraire.py
    buf = io.BytesIO(); c = canvas.Canvas(buf, pagesize=A4)
    tmp = io.BytesIO(); im.save(tmp, "PNG"); tmp.seek(0)
    from reportlab.lib.utils import ImageReader
    c.drawImage(ImageReader(tmp), 0, 0, *A4); c.save(); return buf.getvalue()

w = PdfWriter(clone_from=PdfReader(io.BytesIO(pdf_texte(FACTURES["a"]))))
w.add_attachment("factur-x.xml", xml_cii(FACTURES["a"]).encode())
w.write("pdf/a.pdf")
open("pdf/b.pdf", "wb").write(pdf_texte(FACTURES["b"]))
open("pdf/c.pdf", "wb").write(pdf_scan(FACTURES["c"]))
print("fabriquées :", ", ".join(sorted(os.listdir("pdf"))))
```

## Consignes

### Étape 1. Reconnaître la forme

Avant toute lecture, le programme regarde ce que contient le fichier : des données jointes, du texte, ou une image seule. La forme décide de la lecture, et du coût.

`trier.py`

```
from pypdf import PdfReader

def forme(chemin):
    """Données jointes, texte, ou image seule : ce qui décide de la façon de lire."""
    lecteur = PdfReader(chemin)
    if "factur-x.xml" in lecteur.attachments:
        return "Factur-X : lire le XML joint, sans modèle"
    texte = "".join(page.extract_text() or "" for page in lecteur.pages)
    if len(texte.strip()) > 50:
        return f"PDF avec texte ({len(texte)} caractères) : le modèle lit le texte"
    return "image seule : le modèle lit l'image, ou un OCR avant lui"

for nom in ("a.pdf", "b.pdf", "c.pdf"):
    print(nom, "→", forme(f"pdf/{nom}"))
```

La sortie`python3 trier.py`

```
a.pdf → Factur-X : lire le XML joint, sans modèle
b.pdf → PDF avec texte (344 caractères) : le modèle lit le texte
c.pdf → image seule : le modèle lit l'image, ou un OCR avant lui
```

Essayé le 9 octobre 2026 avec Python 3.12.7, pypdf 6.17.0 et reportlab 4.3.1, sur les factures fabriquées de la page.

### Étape 2. Lire la Factur-X sans modèle

Factur-X est un standard franco-allemand : un PDF lisible par une personne, qui porte les données de la facture dans un fichier XML pour les machines. C'est l'un des trois formats que toute entreprise et toute plateforme agréée doivent accepter en réception. Les chemins des champs sont ceux qu'utilise la bibliothèque libre factur-x d'Akretion.

`lire_facturx.py`

```
import json
import xml.etree.ElementTree as ET
from pypdf import PdfReader

NS = {"rsm": "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100",
      "ram": "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100",
      "udt": "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"}
VENDEUR = "rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:SellerTradeParty/"
REGLEMENT = "rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/"
TOTAUX = REGLEMENT + "ram:SpecifiedTradeSettlementHeaderMonetarySummation/"
CHAMPS = {
    "fournisseur": VENDEUR + "ram:Name",
    "siren": VENDEUR + "ram:SpecifiedLegalOrganization/ram:ID",
    "tva_intracom": VENDEUR + "ram:SpecifiedTaxRegistration/ram:ID[@schemeID='VA']",
    "numero": "rsm:ExchangedDocument/ram:ID",
    "date_emission": "rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString",
    "date_echeance": REGLEMENT + "ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime/udt:DateTimeString",
    "total_ht": TOTAUX + "ram:TaxBasisTotalAmount",
    "taux_tva": REGLEMENT + "ram:ApplicableTradeTax/ram:RateApplicablePercent",
    "total_tva": TOTAUX + "ram:TaxTotalAmount",
    "total_ttc": TOTAUX + "ram:GrandTotalAmount",
}

def date_iso(s):   # format 102 : AAAAMMJJ
    return f"{s[:4]}-{s[4:6]}-{s[6:]}" if s else None

xml = PdfReader("pdf/a.pdf").attachments["factur-x.xml"][0]
racine = ET.fromstring(xml)
facture = {nom: racine.findtext(chemin, namespaces=NS) for nom, chemin in CHAMPS.items()}
facture["date_emission"] = date_iso(facture["date_emission"])
facture["date_echeance"] = date_iso(facture["date_echeance"])
print(json.dumps(facture, ensure_ascii=False, indent=1))
json.dump(facture, open("lecture_a.json", "w"), ensure_ascii=False)
```

La sortie`python3 lire_facturx.py`

```
{
 "fournisseur": "Atelier Rivière",
 "siren": "004128757",
 "tva_intracom": "FR62004128757",
 "numero": "F-2026-0918",
 "date_emission": "2026-09-18",
 "date_echeance": "2026-10-18",
 "total_ht": "1200.00",
 "taux_tva": "20",
 "total_tva": "240.00",
 "total_ttc": "1440.00"
}
```

Essayé le 9 octobre 2026 avec Python 3.12.7, pypdf 6.17.0 et reportlab 4.3.1, sur les factures fabriquées de la page.

Aucun modèle, aucune erreur de lecture possible : les montants sont ceux que le fournisseur a envoyés. Les dates arrivent au format 102, AAAAMMJJ, et se convertissent.

### Étape 3. Faire lire le reste par un modèle

Pour `b.pdf`, le modèle lit le texte de la page ; pour `c.pdf`, l'image. Chaque champ peut rester vide : sur des documents denses ou des formulaires, la faute typique des modèles est d'inventer une valeur pour un champ vide. La consigne le dit, et le format le permet. Le format imposé garantit la forme, pas la justesse des valeurs. Sur une image floue, tournée ou petite, Anthropic prévient que Claude peut se tromper. Les mêmes appels chez OpenAI, Google et Mistral : [réponse dans un format imposé](https://meridal.fr/apprendre/format-impose/).

`extraire.py`

```
import base64
from typing import Optional
import anthropic
from pydantic import BaseModel, Field

class Facture(BaseModel):
    fournisseur: Optional[str]
    siren: Optional[str] = Field(description="9 chiffres, sans espace")
    tva_intracom: Optional[str] = Field(description="numéro de TVA du fournisseur, sans espace")
    numero: Optional[str]
    date_emission: Optional[str] = Field(description="AAAA-MM-JJ")
    date_echeance: Optional[str] = Field(description="AAAA-MM-JJ")
    total_ht: Optional[str] = Field(description="tel qu'écrit, point décimal, sans symbole")
    taux_tva: Optional[str]
    total_tva: Optional[str]
    total_ttc: Optional[str]

CONSIGNE = ("Recopie les champs de cette facture. Ne calcule rien. "
            "Si un champ est absent ou illisible, laisse-le vide (null) : n'en devine aucun.")

client = anthropic.Anthropic()

def lire(contenu):
    reponse = client.messages.parse(
        model="claude-sonnet-5-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": contenu + [{"type": "text", "text": CONSIGNE}]}],
        output_format=Facture,
    )
    return reponse.parsed_output

# b.pdf : le texte de la page, tiré par pypdf
from pypdf import PdfReader
texte = PdfReader("pdf/b.pdf").pages[0].extract_text()
print(lire([{"type": "text", "text": texte}]))

# c.pdf : la page en image (un PNG tiré du scan)
png = base64.standard_b64encode(open("c.png", "rb").read()).decode()
print(lire([{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": png}}]))
```

La sortie`python3 extraire.py`

**Pas encore exécuté par Meridal.** Les lectures fabriquées utilisées à l'étape 4 sont dans `lectures_modele.json` : celle de b.pdf porte un numéro de TVA aux deux derniers chiffres inversés ; celle de c.pdf laisse l'échéance vide.

### Étape 4. Contrôler, puis écrire le fichier d'import

Six règles, de l'identité aux montants. Le SIREN a neuf chiffres, dont le dernier est une clé de contrôle, calculée par la formule de Luhn ; le numéro de TVA français est FR, une clé de deux chiffres, puis le SIREN ; les taux de TVA de France métropolitaine sont 20 %, 10 %, 5,5 % et 2,1 %. Les montants restent en texte et se convertissent en `Decimal`, qui ne les arrondit pas.

`controler.py`

```
import csv, json, re
from decimal import Decimal

TAUX = {Decimal("20"), Decimal("10"), Decimal("5.5"), Decimal("2.1")}

def cle_siren(siren):
    """Clé de Luhn : les chiffres de rang pair, lus de gauche à droite, comptent double."""
    total = 0
    for rang, c in enumerate(siren, start=1):
        d = int(c) * (2 if rang % 2 == 0 else 1)
        total += d - 9 if d > 9 else d
    return total % 10 == 0

def regles(f):
    vides = [nom for nom, v in f.items() if v in (None, "")]
    if vides:
        return [f"champ vide : {', '.join(vides)}"]
    ht, taux, tva, ttc = (Decimal(f[k]) for k in ("total_ht", "taux_tva", "total_tva", "total_ttc"))
    echecs = []
    if not (re.fullmatch(r"\d{9}", f["siren"]) and cle_siren(f["siren"])):
        echecs.append("SIREN invalide")
    if not re.fullmatch(r"FR\d{2}" + f["siren"], f["tva_intracom"]):
        echecs.append("le n° de TVA ne contient pas le SIREN")
    if taux not in TAUX:
        echecs.append(f"taux {taux} % inconnu en France métropolitaine")
    if abs(ht * taux / 100 - tva) > Decimal("0.01"):
        echecs.append("TVA ≠ HT × taux")
    if ht + tva != ttc:
        echecs.append("TTC ≠ HT + TVA")
    if f["date_echeance"] < f["date_emission"]:
        echecs.append("échéance avant l'émission")
    return echecs

lot = {"a": json.load(open("lecture_a.json"))} | {k: v for k, v in json.load(open("lectures_modele.json")).items() if k in "bc"}
retenues = []
for nom, f in lot.items():
    echecs = regles(f)
    print(f"{nom}  {f['fournisseur']:<20} {'à importer' if not echecs else 'à revoir : ' + ' ; '.join(echecs)}")
    if not echecs:
        retenues.append(f)
with open("import.csv", "w", newline="") as sortie:
    w = csv.DictWriter(sortie, fieldnames=list(lot["a"]), delimiter=";")
    w.writeheader(); w.writerows(retenues)
print(f"import.csv : {len(retenues)} facture(s)")
```

La sortie`python3 controler.py`

```
a  Atelier Rivière      à importer
b  Imprimerie du Port   à revoir : le n° de TVA ne contient pas le SIREN
c  Transports Lemaire   à revoir : champ vide : date_echeance
import.csv : 1 facture(s)
```

Essayé le 9 octobre 2026 avec Python 3.12.7, pypdf 6.17.0 et reportlab 4.3.1, sur les factures fabriquées de la page.

### Étape 5. Mesurer l'extraction

La Factur-X donne un corrigé : sa page lue par le modèle se compare à ses données jointes. Dix champs, comparés un à un.

`mesurer.py`

```
import json

# a.pdf a deux versions : ses données jointes (le corrigé) et la page, lue par le modèle
corrige = json.load(open("lecture_a.json"))
lue = json.load(open("lectures_modele.json"))["a_vue"]
justes = [nom for nom in corrige if lue[nom] == corrige[nom]]
for nom in corrige:
    if nom not in justes:
        print(f"écart sur {nom} : lu {lue[nom]!r}, attendu {corrige[nom]!r}")
print(f"{len(justes)} champs justes sur {len(corrige)}")
```

La sortie`python3 mesurer.py`

```
écart sur numero : lu 'F-2026-0916', attendu 'F-2026-0918'
9 champs justes sur 10
```

Essayé le 9 octobre 2026 avec Python 3.12.7, pypdf 6.17.0 et reportlab 4.3.1, sur les factures fabriquées de la page.

## Critères de réussite

- `trier.py` range chaque fichier dans la bonne forme
- `lire_facturx.py` rend les dix champs de a.pdf sans appel à un modèle
- La consigne du modèle demande de recopier, de ne rien calculer, et de laisser vide ce qui est illisible
- `controler.py` n'écrit dans `import.csv` que la facture a b est arrêtée par son numéro de TVA, c par son échéance vide
- `mesurer.py` trouve l'écart sur le numéro de facture 9 champs justes sur 10

### Mentions obligatoires

Les mentions obligatoires d'une facture comprennent la date d'émission, un numéro unique pris dans une séquence continue, l'identité du vendeur et son numéro de TVA, le décompte de chaque ligne avec son taux, les totaux hors taxe et TTC, la date de règlement. Avec la facturation électronique s'ajoutent le SIREN du client, l'adresse de livraison si elle diffère, la nature des opérations, biens ou services, et l'option pour la TVA sur les débits ; d'abord pour les grandes entreprises et les ETI, puis pour les PME à partir du 1er septembre 2027. Les dix champs de l'exercice n'en sont qu'une partie : ceux dont la comptabilité a besoin pour enregistrer et payer.

## Indications

**pypdf ne trouve pas de pièce jointe dans une vraie Factur-X**

**Cause**Certains logiciels nomment autrement le fichier joint : les anciennes versions allemandes utilisent `zugferd-invoice.xml` ou `ZUGFeRD-invoice.xml`.

**À faire**Chercher aussi ces noms, ou passer par la fonction `get_xml_from_pdf` de la bibliothèque factur-x, qui les connaît.

**Le texte tiré d'un PDF est dans le désordre**

**Cause**Un PDF place des morceaux de texte sur la page ; l'ordre de lecture n'y est pas toujours écrit, surtout en colonnes et en tableaux.

**À faire**Envoyer au modèle la page en image plutôt que le texte, ou passer par un outil qui reconstruit la mise en page : [lecture de documents](https://meridal.fr/apprendre/lecture-de-documents-ocr-vision-pdf/).

**Le modèle remplit un champ que la facture ne porte pas**

**Cause**Le champ est obligatoire dans le format, ou la consigne ne dit pas quoi faire d'un champ absent.

**À faire**Rendre chaque champ facultatif (`Optional`) et le dire dans la consigne ; puis traiter un champ vide comme une raison de faire revoir.

**Le TTC ne tombe pas juste à un centime près**

**Cause**Des montants lus en nombres à virgule flottante, ou une TVA arrondie ligne à ligne par le fournisseur.

**À faire**Garder les montants en `Decimal` ; tolérer un centime sur la TVA, jamais sur l'addition du TTC.

## Correction commentée

**Ouvrir la correction**

**La forme d'abord**Une seule des trois factures a besoin d'un modèle pour sa page de texte, une autre pour son image ; la Factur-X n'en a besoin d'aucun. Lire les données jointes quand elles existent supprime l'erreur de lecture, et le coût.

**La facture b**Le modèle a lu FR92002583196 ; le SIREN est 002583169. Les deux derniers chiffres sont inversés. Les montants sont justes : sans la règle d'identité, la facture partait à l'import avec un numéro de TVA faux.

**La facture c**L'échéance est sous une tache ; le modèle l'a laissée vide, comme demandé. Une personne la lit sur le papier, ou la demande au fournisseur. Un modèle qui aurait mis une date plausible, trente jours après l'émission par exemple, aurait passé toutes les règles.

**La mesure**Le numéro F-2026-0916 au lieu de F-2026-0918 passe les six règles : rien dans la facture ne permet de le contrôler. Seule la comparaison à un corrigé le trouve. Sur un vrai lot, comptez les champs justes, champ par champ, sur les factures dont vous avez les données.

## Extensions

- **Un lot entier**, avec les doublons et la liste des factures mises de côté : [le projet d'extraction de factures](https://meridal.fr/apprendre/projet-extraction-de-factures/).
- **Un seuil de confiance** : certains services rendent une confiance par champ ; Microsoft propose de s'en servir pour accepter ou faire revoir, et AWS suggère 90 % ou plus pour une décision financière.
- **Les scans difficiles** : un OCR spécialisé avant le modèle, et ses limites : [lecture de documents](https://meridal.fr/apprendre/lecture-de-documents-ocr-vision-pdf/).
- **La réception électronique** : depuis le 1er septembre 2026, toute entreprise doit pouvoir recevoir des factures électroniques ; une PME émet les siennes à partir du 1er septembre 2027. Ce que cela change : [la facture électronique](https://meridal.fr/apprendre/facture-electronique/).

## Lectures suivantes

↑ Plus général [Le RAG : une IA qui répond avec les documents de l'entreprise Comprendre · Niveau 1 · 12 minutes](https://meridal.fr/apprendre/rag/)

← Avant [Extraction des chiffres d'un document Comprendre · Niveau 2 · 14 minutes](https://meridal.fr/apprendre/extraction-des-chiffres/)

Vous êtes ici

Extraire une facture dans un format imposé

→ Ensuite [Liste de contrôle : un assistant sur vos documents Vérifier · Niveau 2 · 12 minutes](https://meridal.fr/apprendre/avant-de-brancher-un-assistant/)

↓ Plus concret [Atelier : interroger vos documents sans programmer S'exercer · Niveau 2 · 20 minutes](https://meridal.fr/apprendre/atelier-documents/) [Un modèle financier rempli à partir des documents reçus Réalisation](https://meridal.fr/realisations/modele-financier/)

[Tout le dossier « Vos documents »](https://meridal.fr/apprendre/dossier/documents/)

Les pages qui renvoient ici

- [Lecture de documents : OCR, vision, PDF et tableaux](https://meridal.fr/apprendre/lecture-de-documents-ocr-vision-pdf/)

**Limites et sources**

Les trois factures, les SIREN, les numéros de TVA et les lectures du modèle sont fabriqués. Le fichier a.pdf porte un XML Factur-X, mais n'est pas un PDF/A-3 et n'a pas été validé contre un profil de la norme.

L'appel au modèle suit la documentation d'Anthropic et n'a pas encore été exécuté par Meridal. Les autres programmes ont tourné le 9 octobre 2026 avec Python 3.12.7, pypdf 6.17.0 et reportlab 4.3.1.

La clé de Luhn du SIREN est décrite par l'IREM de Limoges ; l'Insee dit seulement que le neuvième chiffre est une clé de contrôle. Le contrôle du numéro de TVA vérifie qu'il contient le SIREN, pas sa clé.

1. [Service-public.fr, mentions obligatoires sur une facture](https://entreprendre.service-public.gouv.fr/vosdroits/F31808) ; [numéro de TVA intracommunautaire](https://entreprendre.service-public.gouv.fr/vosdroits/F23570) ; [Insee, numéro Siren](https://www.insee.fr/fr/metadonnees/definition/c2047) ; [economie.gouv.fr, taux de TVA](https://www.economie.gouv.fr/entreprises/gerer-sa-fiscalite-et-ses-impots/autres-impots-et-taxes/entreprises-ce-que-vous-devez-savoir-sur-la-tva).
2. [FNFE-MPE, Factur-X](https://fnfe-mpe.org/factur-x/) ; [Akretion, bibliothèque factur-x](https://github.com/akretion/factur-x) ; [IREM de Limoges, codes au quotidien](https://www.irem.unilim.fr/OLD/fileadmin/documents/ERR/arithmetiquueaulycee/codes_au_quotidien.pdf).
3. Le relevé : `apprendre/recherches/2026-10-07-extraction-documents.json`.

Écrit par Meridal, avec l'aide de l'IA ; chaque fait vérifié à sa source et relu avant publication. Texte et schémas sous licence [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.fr) : reprenez-les en citant Meridal. Le code sous licence MIT.

Service correspondant

Préparer les documents qui reviennent

1 650 € à 5 100 €, puis sur devis

[Voir le service](https://meridal.fr/offre/#documents)

## Un premier échange de 30 minutes

En visio et sans frais : vous décrivez votre situation, nous vous indiquons ce qui est réalisable, avec quels outils et pour quel ordre de prix. Vous pouvez aussi nous écrire ; chaque message reçoit une réponse écrite.

[Réserver 30 minutes](https://meridal.fr/contact/#reserver) [Écrire un message](https://meridal.fr/contact/)
