Anatomie d'un pipeline : du document à l'écriture comptable

Anatomie d'un pipeline : du document à l'écriture comptable

Suivi détaillé d'une facture fournisseur de la réception à l'écriture validée.*


Pourquoi suivre un document à la trace

Les articles précédents ont décrit l'infrastructure (article #4), les agents (article #5), la couche invisible (article #6), et le cadre de décision entre workflow, RAG et agent (article #7). Tout cela reste abstrait tant qu'on n'a pas montré comment ces couches travaillent ensemble sur un cas concret.

Cet article suit une facture fournisseur de bout en bout. De la réception (un email arrive) à l'écriture comptable validée dans l'ERP. Onze étapes successives, une cascade de composants, un score de confiance qui décide à chaque embranchement, et un humain qui tranche les cas marginaux. C'est l'épine dorsale de notre traitement documentaire.

L'objectif n'est pas l'exhaustivité. C'est de rendre lisible ce qui se passe vraiment, en racontant un parcours qui se reproduit plusieurs centaines de fois par jour.

Étape 1. Réception et normalisation

Une facture peut arriver par trois canaux principaux : email (la majorité du volume), connecteur fournisseur direct (Peppol, EDI), ou dépôt manuel par un comptable depuis l'interface IODD. Chaque canal a son propre adaptateur, qui normalise l'entrée vers un format pivot.

L'adaptateur produit un objet structuré qui contient le binaire du document, ses métadonnées de réception (canal, expéditeur, date, taille, type MIME détecté), et un identifiant de transaction. Le binaire est déposé dans MinIO sous une clé construite à partir de l'identifiant. Un événement « document reçu » est émis sur le bus Redis Streams.

À ce stade, aucune information sensible n'a encore été lue. Le document est un binaire opaque dans un bucket isolé. Le pipeline, à proprement parler, démarre à l'étape suivante.

Étape 2. Détection MIME et segmentation

L'agent documentaire consomme l'événement « document reçu ». Il charge le binaire et déclenche une détection MIME fiable (basée sur la signature de fichier, pas l'extension). Trois cas se distinguent.

Pour un PDF natif (texte sélectionnable), le pipeline saute l'OCR et passe directement à l'extraction. Pour un PDF scanné ou un fichier image, l'OCR sera nécessaire. Pour un fichier multi-pages contenant plusieurs documents (cas fréquent quand un fournisseur agrège plusieurs factures dans un envoi mensuel), une étape de segmentation découpe le fichier en sous-documents distincts, chacun traité indépendamment.

La segmentation est gérée par un modèle léger qui détecte les pages de garde, les sauts de logique, et les patterns d'en-tête. Sa précision n'est pas parfaite, mais ses erreurs ne sont pas critiques : un document mal segmenté sera détecté en aval (étape de validation arithmétique typiquement) et déclenchera une réintégration.

Étape 3. Cascade OCR

Pour les documents nécessitant l'OCR, nous appliquons une cascade en trois moteurs, chacun appelé en fallback selon un seuil de qualité observée.

PyMuPDF native : si le PDF est partiellement structuré (texte sélectionnable sur certaines zones), nous extrayons d'abord le texte natif. Coût : quasi nul. Couverture : élevée pour les factures émises depuis un ERP propre.

PaddleOCR : moteur OCR open source performant sur les documents avec tables structurées. C'est notre moteur principal pour les factures scannées de qualité moyenne à bonne. Il produit non seulement du texte, mais aussi une structure de mise en page (zones, lignes, cellules) que nous exploitons en aval pour l'extraction.

MinerU : moteur plus lourd, mobilisé en dernière ligne pour les scans complexes (mauvaise qualité, mise en page atypique, langues mélangées). Latence plus élevée, qualité supérieure sur les cas difficiles.

À la sortie de la cascade, l'agent dispose du texte extrait, d'une structure de zones, et d'un score de qualité OCR (proportion du document confirmée par au moins deux moteurs). Si ce score reste sous un seuil critique, l'agent peut déclencher une demande de renvoi (« scan illisible, merci de renvoyer en meilleure qualité ») plutôt que de continuer sur des données incertaines.

Étape 4. Extraction d'entités

À partir du texte extrait, il faut produire des données structurées : montant HT, TVA, total TTC, date de facture, date d'échéance, IBAN du fournisseur, numéro de TVA, référence, lignes de détail.

Pour les factures provenant de fournisseurs connus avec patterns stables, une extraction par règles (regex paramétrées par fournisseur) suffit et est extrêmement rapide. Pour les autres, nous invoquons Qwen3-14B via vLLM, avec un prompt structuré qui demande la liste des champs au format JSON. Le modèle local traite la majorité des cas non patternés. Pour les cas atypiques (multilingue avec libellés inhabituels, structures non standard), une exception encadrée déclenche un appel à OpenAI gpt-4o, après passage par le pipeline de masquage GLiNER (article #6).

L'extraction produit un objet structuré, avec un score de confiance par champ. Le score est composite : il combine la confiance OCR sur la zone source, la cohérence du champ avec sa zone de mise en page, et la confiance déclarée par le modèle d'extraction.

Étape 5. Validation arithmétique

Une facture suit des règles arithmétiques simples qui n'ont rien d'aléatoire : la somme des lignes HT plus la TVA doit égaler le total TTC. Cette validation est faite côté workflow, sans LLM, et c'est l'un des contrôles les plus efficaces du pipeline.

Si l'arithmétique ne tombe pas juste, trois explications sont testées dans l'ordre. Une erreur d'extraction sur un montant : on relance l'extraction sur la zone douteuse en mode « focused ». Une erreur d'OCR sur un caractère : on demande à un modèle local de proposer des corrections plausibles (un 0 mal lu en O, un 1 en l). Un format atypique de facture : on bascule l'agent vers une trajectoire d'analyse plus lente.

Si après ces vérifications l'arithmétique reste fausse, le score de confiance global est plafonné à un niveau qui forcera l'escalade humaine en aval. On ne tente pas de masquer une incohérence par un patch. La transparence sur les incertitudes est plus précieuse que l'apparence d'un succès.

Étape 6. Croisement référentiels

Une facture extraite avec succès n'est pas encore une facture comptablement valide. Il faut la rapprocher du référentiel client (fournisseur connu ? IBAN cohérent avec l'historique ? montant dans les ordres de grandeur habituels ?).

Le croisement interroge plusieurs stores. Supabase pour le référentiel structuré du client (liste des fournisseurs, leurs identifiants, leur classification comptable habituelle). Qdrant pour la recherche par similarité sémantique (« est-ce qu'on a vu une facture qui ressemble à celle-ci dans les six derniers mois pour ce client ? »). Neo4j pour les relations explicites (« est-ce que ce fournisseur est lié à un autre fournisseur déjà classé d'une certaine manière ? »).

Trois cas typiques. Fournisseur connu, montant cohérent, classification habituelle disponible : le pipeline continue avec une forte confiance. Fournisseur connu, mais montant ou type inhabituel : le score de confiance baisse, l'escalade devient probable. Fournisseur inconnu : un mini-workflow d'enrichissement se déclenche (recherche dans le registre du commerce suisse, validation IBAN par algorithme MOD-97, demande de classification au comptable).

Étape 7. Score composite et arbitrage

À ce stade, l'agent a accumulé plusieurs scores partiels : qualité OCR, confiance d'extraction, validation arithmétique, cohérence avec référentiels. Un score composite agrège ces dimensions selon une fonction calibrée empiriquement (article #9 sur la calibration).

Le score composite est comparé à deux seuils.

Au-dessus du seuil haut, la facture est considérée comme automatisable. L'agent enchaîne directement vers la génération d'écriture.

Entre les deux seuils, la facture est qualifiée pour vérification rapide. Elle part dans une queue de revue spécifique, où un comptable confirme ou ajuste en quelques secondes les éléments douteux. Le retour humain alimente la mémoire persistante (un nouveau pattern fournisseur, une correction de classification) qui fera que des cas similaires futurs auront un meilleur score.

Sous le seuil bas, la facture est en escalade pleine. Un comptable reprend le dossier, examine en détail, et tranche. Sa décision est journalisée et exploitée pour ajuster les modèles et les seuils.

Cette gradation à trois niveaux est la clé de la rentabilité du pipeline. On ne cherche pas à automatiser à 100 %. On cherche à automatiser tout ce qu'on peut avec confiance, à demander une vérification rapide pour la zone grise, et à concentrer le jugement humain là où il a vraiment de la valeur.

Étape 8. Génération de l'écriture comptable

Pour les factures qui passent le seuil haut, ou qui ont reçu validation rapide, l'agent comptable prend le relais. Il interroge le plan comptable du client (Supabase), applique les règles de classification (déterministes pour la majorité des cas, modèle LLM local pour les ambiguïtés), et génère la proposition d'écriture.

L'écriture suit le format Business Central : journal cible, comptes débit et crédit, libellé, références (numéro de facture, date, fournisseur), TVA détaillée par ligne si nécessaire. Le format est strict et l'écriture est validée syntaxiquement avant émission.

À ce moment, l'écriture n'est pas encore postée. Elle est mise en attente dans une queue de validation. Pour les comptes clients avec validation automatique configurée (typiquement les fournisseurs récurrents avec classifications stables), l'écriture est postée immédiatement après un dernier contrôle de cohérence (montant total, scope client). Pour les autres, un comptable reçoit l'écriture en proposition, valide ou amende, et la poste.

Étape 9. Injection Business Central

L'injection se fait via l'API Business Central. Elle est idempotente : si pour une raison quelconque l'opération échoue à mi-chemin, elle peut être rejouée sans risque de double comptabilisation. L'idempotence repose sur un identifiant de transaction propagé depuis l'étape 1.

Trois cas d'erreur sont prévus et gérés. Erreur réseau temporaire : retry exponentiel avec timeout. Erreur métier (compte inexistant, format invalide) : remontée immédiate à l'opérateur, écriture mise en quarantaine. Erreur de cohérence (déséquilibre débit-crédit, période fermée) : escalade vers le comptable avec contexte complet.

À la sortie, l'écriture est dans Business Central avec son identifiant. Le pipeline boucle l'événement de complétion sur le bus.

Étape 10. Journalisation finale

Toutes les étapes ont été journalisées au fil de l'eau. À la complétion, un événement de bilan est émis qui agrège : durée totale, durée par étape, scores intermédiaires, décisions prises, identifiants des artefacts produits (écriture comptable, document archivé). Cet événement enrichit Loki, alimente Prometheus pour les métriques agrégées, et est exposé dans Langfuse pour le suivi LLM.

Le binaire original est archivé dans MinIO selon la politique de conservation du client (typiquement 10 ans pour les pièces comptables suisses, par OR art. 958f). Le mapping entre la facture, l'écriture, et les artefacts intermédiaires est conservé dans Supabase pour permettre une reconstruction d'audit ultérieure.

Étape 11. Boucle d'apprentissage

Le pipeline ne finit pas à l'étape 10. À intervalles réguliers (quotidien pour les agrégats, hebdomadaire pour les revues), les corrections humaines (validation rapide ou escalade pleine) sont consolidées et alimentent trois mécanismes.

D'abord, l'enrichissement des référentiels. Un fournisseur nouvellement classifié est ajouté au référentiel actif. Un pattern d'extraction nouvellement documenté est ajouté à la bibliothèque de regex. Une correction d'IBAN est intégrée.

Ensuite, l'ajustement des seuils. Si sur une période donnée le seuil haut a laissé passer trop de cas qui auraient mérité validation rapide (faux positifs d'automatisation), on le remonte. S'il en a bloqué trop (faux négatifs), on le baisse. Cette calibration est revue toutes les deux semaines.

Enfin, l'enrichissement des modèles d'extraction. Les corrections humaines fournissent un signal d'entraînement précieux. À échéance régulière (mensuelle), un re-entraînement des composants spécifiques aux fournisseurs avec un volume suffisant améliore la qualité de l'extraction sans nécessiter d'intervention manuelle ultérieure.

Diagramme synthétique du pipeline

Pour fixer la mémoire, voici la séquence des étapes condensée.

Réception (email, EDI, manuel)
    ↓
Normalisation et dépôt MinIO
    ↓
Détection MIME, segmentation
    ↓
Cascade OCR (PyMuPDF → PaddleOCR → MinerU)
    ↓
Extraction d'entités (regex → Qwen3 → gpt-4o exception)
    ↓
Validation arithmétique
    ↓
Croisement référentiels (Supabase + Qdrant + Neo4j)
    ↓
Score composite et arbitrage
    ↓
[seuil haut] → Génération écriture
[zone grise] → Validation rapide humaine
[seuil bas]  → Escalade pleine
    ↓
Génération écriture comptable
    ↓
Injection Business Central (idempotente)
    ↓
Journalisation finale (Loki, Prometheus, Langfuse)
    ↓
Archivage MinIO + boucle d'apprentissage

Temps de traitement par étape

À titre indicatif, sur des factures normalisées de qualité standard :

  • Réception : moins de 100 ms.
  • Normalisation : moins de 500 ms.
  • Détection MIME et segmentation : 200 ms à 2 s selon la complexité.
  • Cascade OCR : 500 ms à 8 s selon le moteur invoqué.
  • Extraction d'entités : 100 ms (regex) à 3 s (LLM).
  • Validation arithmétique : moins de 100 ms.
  • Croisement référentiels : 200 ms à 1 s.
  • Score composite : moins de 100 ms.
  • Génération écriture : 500 ms à 2 s.
  • Injection Business Central : 500 ms à 3 s.

Total typique : entre 3 et 15 secondes pour une facture qui passe en automatisation directe, contre plusieurs minutes en saisie manuelle. La majorité du gain ne vient pas d'un composant magique : elle vient de l'élimination des frottements entre étapes et de la suppression des saisies redondantes.

Ce que ce pipeline n'est pas

Pour clore, il est utile de dire ce que ce pipeline n'est pas. Il n'est pas une magie LLM qui comprend les factures. Le LLM intervient à des moments précis (extraction sur cas non patternés, raisonnement sur ambiguïtés), et reste minoritaire en proportion d'invocations. Il n'est pas non plus un workflow rigide qui casse à chaque exception. La cascade de scores et les niveaux d'escalade absorbent la variabilité, en payant en jugement humain ce qui ne peut pas raisonnablement être automatisé.

C'est une architecture d'arbitrage. Workflow déterministe partout où c'est possible. Agent quand le jugement est nécessaire. RAG quand on cherche dans une base. LLM en exception encadrée. Humain en dernier ressort sur la zone grise et l'escalade pleine. Chaque composant a son rôle. La rentabilité vient de l'orchestration, pas de la performance individuelle d'un composant.

L'article suivant (#9) plonge dans la question essentielle qu'un pipeline comme celui-ci doit se poser tous les jours : comment évaluer les agents en continu, détecter les drifts, et calibrer les seuils.

Lire la suite