Géométrie, induction, vérification
Remplir un formulaire officiel sans gabarit
Chaque administration a sa variante du même formulaire. Maintenir un gabarit par variante est une course perdue, et elle échoue en silence. Voici comment on remplit un document qu’on n’a jamais vu — et surtout comment on vérifie qu’on ne l’a pas abîmé.
Le problème n’est pas de savoir quoi écrire
Un formulaire administratif à rendre : déclaration sur l’honneur, acte d’engagement, attestation. Les valeurs à y mettre, on les a déjà — elles sont dans la base. La raison sociale, l’identifiant fiscal, le montant, la date, le nom du signataire.
La difficulté est ailleurs : où les écrire. Chaque donneur d’ordre a sa version. Les mêmes champs, dans un ordre différent, avec des libellés différents, parfois en tableau, parfois en pleine prose, souvent scannés de travers.
On n’a pas un formulaire. On a des centaines de variantes du même formulaire, et on ne les connaît pas à l’avance.
Le défaut : une table tenue à la main, qui échoue sans bruit
La première version faisait ce que tout le monde fait : une table de correspondance entre un libellé canonique et la ligne du document. Seize clés, écrites à la main.
Sur un scan, la détection lisait très bien — 60 champs appariés sur 74 pour un document, 22 sur 36 pour un autre — mais elle ne rendait aucune géométrie : 110 champs sur 110 sans coordonnées. La position venait donc d’un tout autre étage, l’ancrage sur les lignes détectées. Et cet étage s’arrêtait sur la table de seize clés : toute clé absente était jetée par un `continue` muet.
Le résultat mesuré, sur les deux documents réellement choisis par un client ce jour-là : 21 champs dans la variante, 10 posés. 23 champs, 7 posés.
Les treize clés manquantes : l’objet du marché, la qualité du représentant, la forme juridique, la banque, le type de compte, les quatre montants, le taux de TVA, le titulaire du compte, la ville, le régime de prévoyance. Leur valeur était correcte et disponible. Elle mourait sur un `dict.get()`.
C’est le pire type de défaut : il ne lève rien, il ne journalise rien, et il rend un document qui a l’air fini.
Pourquoi agrandir la table est le mauvais réflexe
L’instinct est d’ajouter les treize clés. Ça règle la journée et ça reconduit le défaut : un contrat tenu à la main, qui échoue fermé et sans bruit dès qu’un document sort du lot connu.
Un système dont la couverture dépend d’une liste écrite par un humain a une propriété désagréable : sa qualité se dégrade avec le temps sans que rien ne le signale, parce que le monde produit des variantes plus vite qu’on n’en ajoute.
Le changement de cadre tient en une phrase : cesser de demander « quel est le libellé » et commencer à demander « où est le blanc ». Les libellés varient à l’infini. La forme d’un espace à remplir, beaucoup moins.
Passe A — la géométrie
On rend chaque page en image, on la donne à un modèle de vision, et on lui demande une seule chose : les zones à remplir, avec leurs coordonnées. Pas les valeurs. Pas les libellés. Les blancs.
Ce n’est pas une intuition, c’est un banc. Sur un premier banc réduit — deux documents, trois répétitions, la vérité étant les zones réellement posées en production — un modèle atteignait 96,1 %, un autre 88,2 %. Le second ratait systématiquement le bloc signature de la dernière page.
Sur un banc élargi — huit documents scannés, six familles de formulaires, 280 zones de référence, deux formulations de consigne — la consigne corrigée rattrape le modèle faible : il passe à 95,0 % de rappel et devient à la fois le meilleur et le moins cher. La correction consistait en un balayage page par page, plus une passe explicite sur les blocs de fin de document — ajoutée exactement sur le défaut mesuré, pas sur une intuition.
La leçon n’est pas « les modèles de vision sont bons ». C’est : une consigne se répare sur les défauts mesurés, jamais sur l’idée qu’on se fait des défauts.
Passe B — les blancs qui n’ont pas de forme
La géométrie attrape ce qui a une forme reconnaissable : des pointillés, une cellule, un cadre. Elle rate le reste — un blanc en pleine prose juridique, un espace après un deux-points, une valeur attendue entre deux fragments de phrase.
Une génération libre (« trouve tous les champs de ce document ») est instable : mesuré, le même document a rendu zéro champ, puis treize.
Ce qui la rend stable tient en trois inversions.
Le code énumère, le modèle répond. On découpe le document en unités et on les présente une par une. Le modèle ne choisit pas ce qu’il regarde.
Une réponse par unité présentée, et le code le vérifie. Il ne peut pas en sauter une en silence.
Il copie les ancres, il ne les paraphrase pas, et il n’invente aucune coordonnée.
Et surtout, la question posée n’est pas une liste de mots-clés mais un test contre-factuel : peut-on remplacer cette région par une valeur et obtenir un formulaire complété, sans altérer le texte permanent ? Une table de marqueurs, elle, se déclenche sur du texte courant — mesuré : « Cautionnement provisoire » en en-tête de colonne d’un avis produisait un faux positif. La question contre-factuelle, non.
La métrique qui compte n’est pas celle qu’on regarde
Sur 56 documents natifs et 1 783 zones de référence, la passe géométrique seule couvrait 87,3 % des zones. Un très bon chiffre. Elle terminait 8 documents sur 46.
Avec la seconde passe : 93,7 % de couverture — six points, un gain modeste — et 33 documents sur 46 entièrement couverts. Quatre fois plus.
Un client ne dépose pas 87 % d’un formulaire. La métrique agrégée récompensait un progrès que personne ne pouvait utiliser, et masquait celui qui changeait tout. Depuis, la métrique de tête est le nombre de documents terminés, pas le taux de zones couvertes.
C’est une question à poser à n’importe quel système : est-ce que ma métrique est additive alors que mon livrable est binaire ? Si oui, elle vous ment poliment depuis le début.
Ce qu’on fait du texte autour du blanc
Un blanc n’est presque jamais seul. Il est entouré de mentions entre parenthèses ou entre crochets, et chacune demande un traitement différent. Quatre catégories, rendues par le même appel que celui qui détecte le blanc :
Le blanc est avant, la mention reste imprimée. — La mention est elle-même la zone à remplacer. — La mention décrit la forme attendue, pas la valeur. — C’est du texte permanent, il n’y a rien à remplir.
Sans cette distinction, le programme écrit par-dessus « (localité) » sans l’effacer. Défaut mesuré sur treize documents. Ce n’est pas un détail cosmétique : un document rendu illisible à cet endroit est un document rejeté.
Vérifier le rendu, pas les intentions
Ici est la partie que je referais en premier si je recommençais.
Pendant une semaine, quatre défauts graves sont sortis : un masque blanc qui effaçait des exposants, une raison sociale posée à la place d’un signataire, une valeur écrite dans le bloc d’une autre variante juridique, et une surimpression. Les quatre ont été trouvés à l’œil. Aucun compteur ne les voyait — parce que les compteurs lisaient la géométrie *déclarée par le programme qui écrit*, et héritaient donc exactement de ses angles morts. Mesuré un jour : 482 caractères de prose détruits pendant que les compteurs affichaient zéro.
La correction est brutale et simple : on ne fait plus confiance à ce que le programme déclare. On rend l’original et le livrable en image, et on compare les pixels. Deux mesures : l’érosion — des caractères imprimés dont l’encre a disparu — et l’encre non revendiquée — des régions d’encre ajoutée qu’aucune zone déclarée ne couvre.
Trois pièges de mesure, tous rencontrés pour de vrai.
La résolution. À 150 points par pouce, l’anticrénelage comptait 45 caractères « altérés » là où il y en avait 2 à 300. La mesure se fait à 300, jamais moins.
La boîte n’est pas le glyphe. Le moteur rend la boîte de la *ligne*, pas celle du caractère : un « r » qui déborde sur la ligne du dessous produisait 42 faux positifs, stables à 300, 400 et 600 points par pouce. Stables — donc crédibles, donc dangereux. La comparaison est désormais restreinte aux lignes de pixels où le caractère a réellement de l’encre.
La page tournée. Les coordonnées du texte ne sont pas tournées, l’image l’est. Sans matrice de rotation, tout est décalé. Et une page dont les dimensions ont changé n’est jamais ignorée en silence : le saut muet récompense la casse, alors elle devient une violation nommée.
Enfin une doctrine, qui vaut au-delà de ce module : sur un scan sans couche texte, l’érosion n’est pas mesurable — le vérificateur rend « indisponible », jamais un verdict favorable par défaut. Un test qui ne peut pas tourner ne doit pas passer au vert.
Le juge qui regarde, pour ce que les pixels ne voient pas
Un comptage de pixels ne voit pas un document juridiquement faux. Si la valeur est posée proprement dans le mauvais bloc, l’encre est déclarée, rien n’est effacé, tout est vert. Le document est faux quand même.
D’où un second vérificateur, qui reçoit les pages rendues en images et la liste des placements, et cherche quatre classes de défauts : mauvais bloc, valeur qui ne correspond pas au libellé de sa ligne, surimpression, texte du formulaire effacé.
Un détail de transport qui a coûté cher à comprendre : donner le même PDF en fichier plutôt qu’en image fait chuter le rappel géométrique de 83,5 % à 42,7 %. Le modèle lit alors le texte extrait et ne *voit* pas la page. Quand ce qu’on juge est une mise en page, il faut envoyer une image — le format d’entrée n’est pas un détail d’implémentation, c’est la moitié du résultat.
Détecter qu’on a raté quelque chose, sans savoir quoi
Le défaut le plus dangereux ne se signale pas : il fait disparaître du travail en silence. Trois documents d’une même famille butaient exactement à 24 zones. Il a fallu croiser une référence annotée pour découvrir qu’un tableau entier n’était jamais transporté.
Le signal qui aurait prédit ce défaut sans aucune référence annotée : quatre lignes géométriquement homologues dont aucune ne porte de champ. Peu importe ce qu’elles contiennent, peu importe leurs libellés. Une structure répétée entièrement vide est une anomalie.
Ce module ne rend délibérément pas de score composite. Le précédent en rendait un, il prédisait à l’envers, et il était impossible de dire pourquoi — précisément parce qu’il mélangeait ses composantes. On expose des quantités brutes et comparables : combien de cellules éligibles, combien détectées, la couverture par ligne et par colonne. De quoi distinguer une ligne oubliée d’une colonne oubliée d’une table entière absente.
Un score unique est confortable à afficher et inutile à déboguer.
Ce qui se transpose
- Une table de correspondance tenue à la main est une machine à échouer en silence : si votre couverture dépend d’une liste écrite par un humain, elle se dégrade sans prévenir.
- Remplacez « connaître le libellé » par « voir la forme ». La géométrie se généralise, les libellés non.
- Le code énumère, le modèle répond. C’est ce qui transforme une génération instable en une sortie vérifiable.
- Posez un test contre-factuel plutôt qu’une liste de mots-clés.
- Demandez-vous si votre métrique est additive alors que votre livrable est binaire. 87 % de zones, ce n’est pas 87 % de documents livrables.
- Ne mesurez jamais un rendu avec la géométrie déclarée par le programme qui l’a produit : vous héritez de ses angles morts. Rendez, et comparez des pixels.
- Un saut silencieux récompense la casse. Un cas non mesurable doit rendre « indisponible », jamais « conforme ».
- Une structure répétée entièrement vide est une anomalie, quels que soient ses libellés. C’est le seul signal qui prédit une détection ayant raté un bloc entier.
- Un score composite est confortable à afficher et inutile à déboguer. Exposez les composantes.
Une démo qui marche. Et après ?
Décrivez la situation en trois lignes — ce qui existe déjà, ce qui doit exister, et pour quand. Je vous dis en trente minutes dans laquelle des trois offres ça tombe, ou si ce n’est pas pour moi. Réponse sous 24 h en semaine.