Analyse de contrats par IA (OCR & PII)
Extraction de données financières (OCR), pseudonymisation des PII et analyse structurelle de contrats | Stage d'innovation chez Doxallia.
- TypeScript
- Bun
- Next.js
- OCR
- Hugging Face
- PostgreSQL
- Cellar S3
- NER
- Regex
Point de départ
C'était le plus gros projet de mon stage. Un collègue me l'a confié en me demandant de l'améliorer. Le point de départ était son dépôt : un pipeline principalement en Python, dans lequel un LLM local lisait la sortie de Tesseract sur le PDF. Malheureusement, il analysait chaque document isolément et ne pouvait pas traiter un dossier contractuel complet (contrat, avenants, factures), j'ai donc décidé de repartir d'une architecture neuve.
Plutôt que de suivre la même direction que mon collègue, j'ai d'abord dessiné tout le squelette sur papier. Je ne voulais pas écrire une seule ligne de code avant d'avoir toute l'architecture couchée quelque part. J'ai aussi passé en revue son travail et noté chaque point à améliorer. Le plus gros problème était le LLM local, imposé par les données sensibles et les contraintes du RGPD : un LLM local, avec ou sans gros harnais, hallucine beaucoup. L'OCR était aussi trop fragile : Tesseract est ancien, et il nous fallait une extraction fiable pour réduire le risque d'hallucinations du LLM.
Le choix de l'OCR
Ma première tâche a donc été d'évaluer différents moteurs d'OCR. Je voulais aussi une sortie en Markdown, car les LLM donnent généralement de meilleurs résultats avec des entrées en Markdown. J'ai choisi PaddleOCR après l'avoir comparé à Tesseract sur les mêmes documents (EasyOCR était indisponible hors Python, et le seul OCR basé sur un VLM que j'ai testé a été écarté surtout pour des raisons matérielles). Il extrayait un meilleur texte, et ses scores de confiance restaient exploitables, là où Tesseract produisait des fragments à confiance nulle.
Pseudonymisation en trois couches
Ma deuxième tâche a été la pseudonymisation. Je voulais pseudonymiser la sortie de l'OCR avant de l'envoyer au LLM, en remplaçant « M. Alan Toro » par « M. [[PERSONNE_1]] » et en gardant la correspondance dans un coffre éphémère en mémoire, jamais transmis au LLM ni persisté. Pour cela, j'ai d'abord listé toutes les données à pseudonymiser (SIRET, SIREN, IBAN, noms, entreprises, …), ce qui a donné 15 catégories.
Le travail s'est organisé en trois couches : Regex, Ancrage et NER. J'ai choisi trois couches avant tout pour la robustesse : chacune complète les autres, avec peu de recouvrement.
La couche Regex se concentre sur les données personnelles structurées (PII, pour Personally Identifiable Information) et détecte 15 types. Neuf sont validés par une somme de contrôle : numéros de sécurité sociale (clé de contrôle), identifiants d'entreprise (algorithme de Luhn), numéros de TVA, IBAN (modulo 97), RIB, cartes bancaires, identifiants de professionnels de santé et identifiants douaniers. La validation est essentielle, car elle distingue un vrai identifiant d'une suite de chiffres quelconque. Les six autres (BIC, identifiant créancier SEPA, numéro fiscal, courriel, téléphone, adresse) reposent sur le format et, pour certains, sur un mot-clé du contexte. Cette couche est entièrement déterministe, donc reproductible et exécutable en intégration continue.
La couche Ancrage capture les noms de parties grâce aux formules stéréotypées qui les introduisent dans un contrat (« ci-après dénommé », « la société X », « X, société anonyme au capital de »), et grâce à leur emplacement, comme les zones de signature. Elle comble une lacune de la troisième couche : un nom en capitales ou un sigle est systématiquement ignoré par un NER.
La couche NER traite les noms en texte libre. J'ai utilisé CamemBERT, un modèle français, quantifié sur 8 bits au format ONNX et exécuté en local. Trois mécanismes complètent l'ensemble : une passe de normalisation pour les identifiants coupés par un saut de ligne, une règle de précédence pour les recouvrements (Regex, puis Ancrage, puis NER), et une propagation qui masque les autres occurrences d'un nom une fois qu'il a été capturé de façon fiable.
Tout cela découle d'une règle posée au début et jamais modifiée : un faux négatif, c'est-à-dire une donnée personnelle laissée en clair, est un échec catastrophique, alors qu'un masquage appliqué avec la mauvaise étiquette reste acceptable. Pour la même raison, une somme de contrôle qui échoue ne bloque pas le masquage lorsque le contexte signale fortement un identifiant : un identifiant abîmé par l'OCR ne doit pas fuir. Cela implique aussi de mesurer le rappel indépendamment du type attribué.
Extraction et quarantaine
Une fois le texte masqué, le LLM en tire une structure : parties, dates, clauses tarifaires. La sortie attendue suit un schéma strict, et toute réponse non conforme est rejetée puis redemandée. Les cas douteux partent dans une file de quarantaine où un juriste peut intervenir, au lieu d'être approximés. Un montant faux présenté avec assurance coûte plus cher que dix cas non traités.
Calcul déterministe
Le principe central de l'architecture est le suivant : le modèle comprend, le code calcule. Aucun montant calculé n'est jamais produit par le LLM. Sa sortie est un jeu de paramètres (nature de la clause, indice, échéance), et une fonction déterministe calcule le montant. Chaque étape est consignée dans un journal d'audit chaîné par empreinte cryptographique, de sorte que n'importe qui peut refaire le calcul à la main.
Résultats
Résultats, mesurés sur 320 cas fictifs annotés : les couches déterministes atteignent un F1 de 93,8 % (rappel 94,6 %, précision 93,0 %), alors que le NER seul atteint 70,0 % de rappel et 63,6 % de précision (sur seulement 15 cas). Les principaux identifiants (entreprise, bancaires, sécurité sociale, fiscal) sont tous détectés, mais le rappel sur les noms de personnes en texte libre tombe à 50 %. Cet écart n'est pas accidentel : il sépare ce qui obéit à une règle vérifiable de ce qui dépend du contexte. Il montre aussi où porterait le prochain effort : non pas sur les expressions régulières, déjà saturées, mais sur la couche NER. Sur les quatre premiers contrats réels, le nombre d'occurrences laissées en clair est passé de 473 à 246 après une série de corrections.
Limites
Les limites sont claires. Je n'ai eu aucune donnée réelle avant les deux dernières semaines du stage, donc l'essentiel de l'évaluation repose sur un corpus que j'ai écrit moi-même, et l'extraction sémantique n'a pas été mesurée. Tout a aussi tourné sur mon ordinateur portable personnel (8 Go de RAM, pas de GPU), ce qui a contraint certains choix, à commencer par l'OCR. C'est un prototype destiné à lever des incertitudes avant toute industrialisation, pas un produit fini.