@mostajs/piece-extract
v0.2.0
Published
Lire une PIÈCE commerciale photographiée (ticket de caisse, facture d'achat) et en rendre des données vérifiées.
Maintainers
Readme
@mostajs/piece-extract
Auteur : Dr Hamid MADANI [email protected] Licence : AGPL-3.0-or-later · Couche : N3 (composite) · Dépendances de production : aucune
Lit une pièce commerciale photographiée — ticket de caisse, bientôt facture d'achat — et rend une structure typée accompagnée d'un verdict.
Le module n'extrait pas : il valide. L'état de l'art plafonne autour de 84 à 93 % de F1 sur les référentiels publics : une pièce sur dix est fausse quelque part, chez tout le monde. Dire laquelle est la valeur ajoutée.
⚠️ Trois « ticket » dans l'écosystème : @mostajs/ticketing est une billetterie,
@mostajs/ticket-print imprime des numéros de file d'attente, et ce module lit des reçus
de caisse. Un renommage en @mostajs/piece-extract est proposé — voir
docs/00-PROPOSITION-MODULE.md §4.3.
État
Jalon 1 livré — toutes les fonctions pures : lecture d'un montant, lecture des lignes, découpage en zones, gabarits, et le contrôle. Tout ce qui décide de la justesse d'une pièce est écrit et éprouvé, sans dépendre d'un moteur d'OCR.
| Jalon | Contenu | État |
|---|---|---|
| J1 | Types, montantDepuisTexte, lignesDepuisTexte, verifier(), 2 gabarits | ✅ 9 tests verts |
| J2 | extrairePiece() — pipeline complet sur une image | à venir |
| J3 | extrairePdf(), campagne sur les 791 pièces, rapport chiffré | à venir |
| J4 | Corrections apprises (ocr-learn) | à venir |
| J5 | Factures d'achat, plusieurs taux | à venir |
Le contrôle, en trente secondes
import { verifier, recalculerTotaux } from '@mostajs/piece-extract';
const piece = {
lignes: [
{ quantite: 1, libelle: 'POULET KATSU', ttcLigne: 1050 }, // centimes, jamais 10.50
{ quantite: 1, libelle: 'Mixte roll…', ttcLigne: 1190 },
{ quantite: 1, libelle: 'Riz Nature', ttcLigne: 180 },
],
totaux: { ttcLu: 2420, nbArticlesLu: 3 },
dateHeure: '2026-03-31T21:42:04',
canal: 'emporte',
};
verifier(piece, { taux: 100 }); // taux en POUR MILLE : 100 = 10 %
// → { fiable: true, controles: [...], aRelire: [] }
recalculerTotaux(piece, { taux: 100 });
// → { ttcCalcule: 2420, ht: 2201, tva: 219, ttcLu: 2420, nbArticlesLu: 3 }fiable ne porte que sur l'arithmétique. aRelire nomme ce qu'un humain doit vérifier —
'dateHeure', 'canal', 'lignes[2]'. Les deux questions sont distinctes : les confondre
reviendrait à jeter des pièces saines, ou à valider des pièces incomplètes.
Ajouter un restaurant : un gabarit, pas du code
C'est ce qui rend le module réutilisable d'une enseigne à l'autre. Un nouveau restaurant ne demande aucune ligne du module — seulement la description de son papier :
import { definirModele, registerModele, lignesDepuisTexte } from '@mostajs/piece-extract';
registerModele(definirModele('le-gourmet', {
reperes: {
reference: /Facture\s*n[°o]\s*(\d+)/i, // cette caisse numérote autrement
total: /MONTANT\s*D[ÛU]/i, // et n'écrit pas « TOTAL À PAYER »
},
ignorer: [/merci de votre visite/i],
}));
lignesDepuisTexte(texteOcr, 'le-gourmet');definirModele() part du générique et n'exige que les différences ; les lignes à ignorer
s'ajoutent à celles du générique. Deux gabarits sont livrés :
| Gabarit | Rôle |
|---|---|
| generique-fr | traite une enseigne inconnue au premier passage |
| sushi-tori | écrit d'après le papier réel du corpus |
Les avoir tous les deux n'est pas un luxe : c'est ce qui prouve que l'abstraction tient. Un gabarit qui ne servirait qu'une fois ne serait pas un gabarit, seulement du code particulier déguisé. Un gabarit inconnu est refusé en listant ceux qui existent — jamais un repli silencieux, qui produirait une lecture dégradée sans le dire.
Référence API — jalon 1
| Fonction | Rend | Pure ? |
|---|---|---|
| montantDepuisTexte(txt) | entier de centimes, ou null | ✅ |
| lignesDepuisTexte(texte, modele?) | LigneLue[] | ✅ |
| decouperZones(texte, modele) | { entete, corps, pied } | ✅ |
| verifier(piece, { taux?, tolerance? }) | Verdict | ✅ |
| recalculerTotaux(piece, { taux? }) | Totaux | ✅ |
| definirModele · registerModele · getModele · listModeles | gabarits | ✅ |
Ce que le lecteur de lignes sait faire, et pourquoi
Trois comportements viennent du texte OCR réel, et aucun n'aurait été imaginé depuis un bureau :
- le libellé se coupe sur deux lignes — papier étroit :
1 Mixte roll saumon thon avocatpuisoignons 11,90. Une ligne à quantité sans montant est mise en attente, la suivante la complète ; - du bruit suit le montant —
1 Riz Nature 1.80 2e. On repère le dernier montant et sa position, plutôt que d'espérer une fin de ligne propre ; - la quantité est bornée — sans quoi
60430 NOAILLESdeviendrait 60 430 unités d'un article nommé « NOAILLES », et8 PLACE DE L'HÔTEL DE VILLEun article de la commande.
Les règles qui ne se négocient pas
- La somme des lignes doit égaler le total. Sinon la pièce n'est pas fiable.
- Un seul contrôle ne suffit pas. Deux erreurs qui se compensent passent le premier — d'où le contrôle croisé sur le nombre d'articles imprimé.
- Le HT et la TVA ne se lisent jamais, ils se recalculent. Mesuré : l'OCR rend
HT: 22001là où le papier porte22,01. Le HT lu ne sert que de contrôle. - Absent ≠ zéro. Un montant illisible vaut
null. Une date bruitée n'est pas rendue. - Le canal ne se devine pas.
'inconnu'est une valeur de plein droit.
Tests
npm test # typecheck + build + suite — instantanée, sur du TEXTE OCR GELÉLa suite ne lance aucune reconnaissance : elle tourne sur le texte que tesseract a réellement rendu sur les tickets du corpus, à 80 dpi (bruité) et à 200 dpi. Le texte bruité y est un cas de test à part entière — c'est exactement ce que le module doit savoir écarter.
Documentation
docs/00-PROPOSITION-MODULE.md · 01-ETUDE-ETAT-ART · 02-AUDIT-CHAINE-OCR · 03-PLAN-DEV ·
03bis-PROMPT-IMAGE-OBJECTIF (+ docs/img/) · 04-PLAN-TESTS ·
DEVTEST-PLAN.ticket-extract.json (jumeau qatrax) · llms.txt (fiche LLM).
