# Plan d'évolution Picarones — 2026 > Synthèse d'une conversation de cadrage (avril 2026) entre l'équipe Picarones > et un LLM externe. Couvre à la fois l'enrichissement du benchmark texte > existant (axe A) et la bascule progressive vers un banc d'essai de pipelines > composées (axe B), pensés comme **un seul produit à deux modes d'usage**. > > Ce document est un plan d'intention, pas une spécification figée. Chaque > sprint réel devra rouvrir et challenger les hypothèses ci-dessous, en > particulier celles de la Phase B qui dépendent du retour terrain BnF. --- ## 1. Principe directeur ### 1.1 Un seul produit, deux modes d'usage Picarones reste **une seule base de code, un seul rapport, un seul runner**. La distinction entre « benchmark texte » (axe A) et « banc d'essai de pipelines composées » (axe B) n'est pas un fork du produit, c'est un continuum : > **Une pipeline à un seul module est un cas particulier d'une pipeline à > N modules.** Cette règle, déjà appliquée avec succès en Sprint 3 quand `OCRLLMPipeline` a hérité de `BaseOCREngine`, doit être poussée un cran plus loin. Le mode benchmark texte devient alors le **cas dégénéré** du banc d'essai pipelines. ### 1.2 Trois modes d'entrée, un seul moteur | Mode | Commande | Pour qui | |---|---|---| | **Legacy** | `picarones run --corpus ./c --engines tesseract,pero` | Chercheur qui n'a jamais entendu parler de pipelines. Aucune nouvelle option à apprendre. | | **YAML composé** | `picarones pipeline run my-pipeline.yaml` | Ingénieur qui compose une chaîne OCR → reconstructeur → post-correcteur. | | **Hybride** | `picarones run --pipeline-yaml post.yaml --engines tesseract,pero` | Qui veut comparer l'effet du choix d'OCR à pipeline post-OCR fixée. | En interne, **les trois construisent la même structure** : un graphe orienté de modules avec, pour le mode legacy, un graphe trivial à un seul nœud par moteur. ### 1.3 Rapport adaptatif par défaut Question à se poser à chaque vue : « si la pipeline a un seul nœud, est-ce que cette vue apporte quelque chose ? ». - **Non** → la vue est **absente** (pas vide, pas désactivée). - **Oui** → la vue est affichée, éventuellement dégradée mais utile. Application directe du principe du panneau « Avancé » du Sprint 21 : l'utilisateur simple ne voit pas ce qu'il n'a pas demandé. La discipline du masquage automatique est ce qui distingue un bon outil d'une usine à gaz. ### 1.4 La règle pratique à tenir À chaque décision de design : *« est-ce que ça oblige l'utilisateur du mode simple à apprendre quelque chose de nouveau ? »*. Si oui, mauvaise voie. Repenser jusqu'à ce que la réponse soit non. ### 1.5 Ce qui ne change pas Le différenciateur qui distingue déjà Picarones reste central : **rigueur méthodologique, traçabilité, absence de prescription**. Tout ce que ce plan ajoute doit servir cette ligne, pas la diluer. En particulier : - Pas de LLM dans le chemin critique du rapport. - Pas de score composite imposé. Tout score agrégé est opt-in et étiqueté comme tel. - Chaque chiffre rendu dans la synthèse factuelle reste traçable au payload du `Fact` source (garde-fou anti-hallucination du Sprint 19). --- ## 2. Phase 0 — Fondation commune Trois chantiers à mener avant le reste, sans lesquels tout le plan construit sur du sable. Ils débloquent à la fois l'axe A (métriques structurelles, philologiques) et l'axe B (pipelines composées). ### 2.1 Modèle de données multi-niveaux **Pourquoi maintenant.** La GT actuelle est mono-niveau (texte plat dans `.gt.txt`). Cette contrainte interdit l'évaluation de tout module qui produit ou consomme une autre représentation : ALTO, PAGE XML, entités nommées, ordre de lecture. C'est le verrou qui empêche tout l'axe B et qui limite déjà des métriques de l'axe A (Layout F1, reading order F1). **Refonte de `picarones/domain/corpus.py`.** La classe `Document` actuelle porte `image_path`, `ground_truth: str`, `ocr_text`. La nouvelle version porte une GT structurée : ```python class GTLevel(str, Enum): TEXT = "text" ALTO = "alto" PAGE = "page" ENTITIES = "entities" READING_ORDER = "reading_order" @dataclass class Document: image_path: Path ground_truths: dict[GTLevel, GTPayload] metadata: dict ``` Chaque payload est typé : `TextGT(str)`, `AltoGT(xml_root)`, `EntitiesGT(list[Entity])`, etc. Le chargeur `load_corpus_from_directory` détecte automatiquement les fichiers présents (`.gt.txt`, `.gt.alto.xml`, `.gt.page.xml`, `.gt.entities.json`) et peuple les niveaux disponibles. **Compatibilité ascendante stricte.** Un corpus avec uniquement `.gt.txt` doit continuer à fonctionner exactement comme avant. Le runner consulte `document.ground_truths[GTLevel.TEXT]`. Une `@property ground_truth` de transition garantit zéro casse côté tests. **Conséquences à anticiper.** Les fixtures (`fixtures.py`) doivent générer des corpus mixtes : text-only et text+alto, pour que la suite de tests valide les deux régimes. Le rapport HTML doit afficher dans la vue Document quels niveaux de GT sont disponibles. **Critère de réussite.** Les 1242 tests actuels passent sans modification. Trois nouveaux tests valident le chargement d'un corpus avec GT ALTO partielle (certains documents ont l'ALTO, d'autres non). **Effort estimé.** Un sprint. Risque : les imports (HuggingFace, IIIF, HTR-United) peuvent nécessiter de petits ajustements pour produire la nouvelle structure. ### 2.2 Interface module générique **Pourquoi maintenant.** Aujourd'hui `BaseOCREngine` est typé `image → texte` de manière implicite. Pour qu'un même runner puisse exécuter un mappeur ALTO ou un rewriter, il faut une interface plus générale dont `BaseOCREngine` devient un cas particulier. **Création de `picarones/domain/module_protocol.py`.** ```python class ArtifactType(str, Enum): IMAGE = "image" TEXT = "text" ALTO = "alto" PAGE = "page" ENTITIES = "entities" READING_ORDER = "reading_order" class BaseModule: name: str input_types: tuple[ArtifactType, ...] output_types: tuple[ArtifactType, ...] execution_mode: Literal["io", "cpu"] def process(self, inputs: dict[ArtifactType, Any]) -> dict[ArtifactType, Any]: raise NotImplementedError def metadata(self) -> dict: ... ``` Un OCR classique déclare `input_types=(IMAGE,)`, `output_types=(TEXT,)`. Un mappeur VLM→ALTO déclare `input_types=(IMAGE,)`, `output_types=(TEXT, ALTO)`. Un rewriter ALTO post-correction déclare `input_types=(ALTO,)`, `output_types=(ALTO,)`. **`BaseOCREngine` devient un alias de compatibilité** dont l'implémentation de `process` wrappe l'ancien `_run_ocr` et retourne `{TEXT: result.text}`. Aucun adaptateur OCR n'est touché à ce stade. **Critère de réussite.** Tous les moteurs existants passent les tests sans modification. Un test ajouté instancie un `MockModule` qui consomme `TEXT` et produit `ALTO`, et vérifie que le runner peut l'invoquer. **Effort estimé.** Un demi-sprint, principalement de la rigueur de typage. ### 2.3 Métriques composables **Pourquoi maintenant.** Aujourd'hui une métrique est calculée une fois, en sortie de moteur, sur la paire `(GT_text, hypothesis_text)`. Dans une pipeline composée, on veut calculer une métrique **à chaque jonction** du DAG, et la métrique change selon les types d'artefacts à la jonction. **Registre de métriques typé.** Chaque métrique déclare ses types d'entrée : ```python @register_metric(input_types=(TEXT, TEXT)) def cer(reference: str, hypothesis: str) -> float: ... @register_metric(input_types=(ALTO, ALTO)) def reading_order_f1(ref_alto, hyp_alto) -> float: ... @register_metric(input_types=(TEXT, ALTO)) def text_preservation_after_reconstruction(ref_text, hyp_alto) -> float: ... ``` Le runner, étant donné une jonction `(artifact_produced, gt_at_this_level)`, sélectionne automatiquement les métriques applicables et les calcule. C'est le mécanisme qui rend l'axe B possible. **Compatibilité.** Tout `compute_metrics` actuel devient un appel orchestré du registre. Le format de sortie de `MetricsResult` reste identique pour les jonctions `text→text`. **Critère de réussite.** Les rapports HTML existants restent strictement identiques (déterminisme bit-à-bit) sur les fixtures de test. Un nouveau test enregistre une métrique factice `(TEXT, ALTO)` et vérifie qu'elle est sélectionnée à la bonne jonction. **Effort estimé.** Un demi-sprint. ### 2.4 Bilan Phase 0 À la fin de cette phase : - Tous les tests existants passent sans modification. - Le rapport HTML est strictement identique sur les fixtures legacy. - L'infrastructure peut accueillir des modules non-OCR, des GT multi-niveaux et des métriques typées par jonction. **Aucune autre étape ne peut commencer tant que la Phase 0 n'est pas stable.** Trois sprints consécutifs, sans dérive de scope. --- ## 3. Phase A — Enrichissement métrique et UX L'axe A travaille sur le périmètre actuel (benchmark texte mono-pipeline) et bénéficie immédiatement à tous les utilisateurs existants. Il se décline en deux familles : **A.I — adresser les 9 critiques structurelles identifiées dans la conversation de cadrage**, et **A.II — combler les métriques absentes**. Une vue transversale (A.III stratification) clôt l'axe. ### A.I — Réponses aux 9 critiques structurelles Chaque sous-section nomme la critique, ce qui existe déjà, et le chantier à mener. #### A.I.1 — La granularité ne s'arrête plus à la page **Critique.** CER/WER agrégés au document, agrégés au moteur. Le niveau ligne existe (`line_metrics.py`, Gini, percentiles depuis Sprint 10) mais n'est que décrit. Le niveau token rare est absent. **Chantier 1 — Vue « Worst lines globale ».** Une nouvelle vue dans le rapport qui transcende les documents : top-N lignes les plus mal transcrites de tout le corpus, classées par CER ligne, avec image de la ligne (extrait ALTO si disponible, sinon crop image), GT et hypothèse en diff coloré, et lien vers le document parent. C'est le complément opérationnel du percentile p95 abstrait. Paramètres : N (défaut 20), filtre par moteur, filtre par strate `script_type`. Les ingrédients existent (`line_metrics.py`, `diff_utils.py`, vue galerie) — il manque la requête transversale et la vue dédiée. **Chantier 2 — Métrique « rare-token recall ».** Calcul du rappel sur les tokens GT de fréquence ≤ 2 dans le corpus (hapax + dis legomena). Implémentation : tokenisation Unicode-aware sur la GT, comptage de fréquence corpus-wide, calcul du taux de tokens rares correctement restitués par chaque moteur. Une variante stratifiée par `pos`-tag ou par catégorie (NOUN, PROPN) est rendue par A.II.1 (NER). Conjecture à tester sur la fixture : cette métrique discrimine plus les moteurs que le CER global. Si confirmée, elle gagne sa place dans le tableau de classement principal à côté du CER. #### A.I.2 — La médiane devient le critère de classement par défaut **Critique.** Le rapport affiche les deux mais classe sur la moyenne. Sur des distributions asymétriques (80 % à 3 %, 20 % à 40 %), le moteur A peut gagner sur la moyenne uniquement parce qu'il rate moins souvent les pages catastrophiques, alors que B est strictement meilleur sur le quotidien. **Chantier.** Le tri par défaut du tableau de classement passe sur la **médiane CER**. La moyenne devient une colonne secondaire. Un **avertissement** s'affiche automatiquement si l'écart relatif `|moyenne − médiane| / médiane` dépasse un seuil (par défaut 30 %), avec un message explicite : *« la distribution des CER est très asymétrique sur ce corpus — la moyenne est tirée par quelques documents catastrophiques. La médiane est plus représentative. »* Cohérence supplémentaire : le test de Friedman travaille déjà sur les rangs (Sprint 18). Trier sur la médiane aligne la lecture humaine sur la lecture statistique. #### A.I.3 — Vue « inhabituel ici » alimentée par l'historique SQLite **Critique.** Le rapport décrit le corpus évalué mais ne le compare jamais à une distribution de référence. L'historique SQLite (Sprint 8) existe mais aucun détecteur narratif ne le lit. **Chantier 1 — Encart « Ce corpus est-il habituel ? ».** En tête du rapport, sous la synthèse factuelle, un mini-graphique qui place le score de difficulté moyen du corpus courant sur la distribution des corpus précédents stockés en SQLite (boxplot + point). Une phrase factuelle : *« Difficulté observée 0,62 — au 88ᵉ percentile des 47 corpus précédents de votre institution. Ce corpus est plus difficile que la moyenne. »* **Chantier 2 — Détecteur narratif `engine_off_baseline`.** Pour chaque moteur, comparer le CER observé à la moyenne historique des derniers benchmarks. Templates FR/EN du type : *« Tesseract a obtenu 5,2 % CER ici, vs 4,1 % en moyenne sur les 12 derniers benchmarks de votre institution. Ce corpus lui est plus difficile que vos corpus habituels. »* Garde-fous : ne déclenche que si N_runs ≥ 5 et même `corpus_signature` (empreinte de strates). Sinon le détecteur reste silencieux. #### A.I.4 — Taxonomie d'erreurs exploitée à 3 niveaux **Critique.** 10 classes, mais le rapport montre un seul histogramme. **Chantier 1 — Co-occurrence pattern.** Matrice de co-occurrence des classes taxonomiques au niveau document. Si `ligature_error` et `abbreviation_error` co-occurrent toujours, c'est un signal de scribe particulier — utile pour stratifier le corpus *a posteriori*. Visualisation : heatmap de Jaccard entre paires de classes. **Chantier 2 — Évolution intra-document.** Étendre la heatmap de CER par tranche de page (déjà disponible) à toutes les classes taxonomiques. Permet de distinguer les erreurs de marge (concentrées en bordure) des erreurs de scribe (uniformes). **Chantier 3 — Taxonomie comparative.** Vue côte-à-côte des profils taxonomiques de deux moteurs avec CER similaire. Diagramme en miroir (barres horizontales) qui montre que A fait surtout des erreurs de casse (récupérables) tandis que B fait des lacunes (irrécupérables). Le détecteur `error_profile_outlier` existe mais ne génère pas cette vue comparative — il faut la créer. #### A.I.5 — Normalisation diplomatique en curseur fin **Critique.** `cer_diplomatic` applique un profil entier. Un éditeur peut vouloir « je tolère ſ=s mais pas u=v ». **Chantier.** Le panneau « Avancé » (Sprint 21) gagne une section **« Équivalences diplomatiques »** : liste de toutes les transformations des profils (`medieval_french`, `early_modern_french`, etc.) sous forme de cases à cocher granulaires. Quand l'utilisateur (dé)coche, le CER se recalcule **côté client** (les hypothèses et GT pré-tokenisées sont déjà embarquées) et le tableau de classement se met à jour en direct. État persisté en URL (`?eq=oe-ae,longs-s,...`) comme les autres options du panneau Avancé. Garde-fou : ne pas recalculer plus d'une fois par seconde (debounce). C'est précisément ce qui distingue un benchmark *outil de mesure* d'un *outil de décision éditoriale*. #### A.I.6 — Coût projeté en volume cible **Critique.** La vue Pareto (Sprint 20) trace CER vs coût mais le coût n'est pas linéaire en valeur — payer 50 € de plus sur 50 pages est trivial, sur 5 millions ça change tout. **Chantier.** Champ « **Volume cible** » dans le panneau Avancé : nombre de pages que l'utilisateur projette de traiter en production. La vue Pareto et le tableau Pareto recalculent le **coût total projeté** : > *« Pour vos 80 000 pages BMS — Tesseract = 3 €, Pero = 0 € (local), > Mistral OCR = 280 €, GPT-4o post-correction = 600 €. »* Trois axes de la vue Pareto restent disponibles (coût, vitesse, carbone) mais le coût se lit en valeur projetée plutôt qu'en valeur unitaire. Le détecteur `cost_outlier` (Sprint 20) gagne un seuil paramétré par volume — ce qui est trivial à 50 pages devient bloquant à 5 M. #### A.I.7 — Sur-normalisation LLM en vue analytique dédiée **Critique.** La classe taxonomique 10, le détecteur d'hallucination, la vue triple-diff document existent. Mais le score agrégé (« 0,05 % ») ne dit rien sur **quoi** corriger dans le prompt. **Chantier.** Nouvelle vue **« Modernisation lexicale »** dédiée aux moteurs LLM/VLM. Tableau de fréquences : | Forme historique GT | Forme « modernisée » par le LLM | Occurrences GT | % de fois modernisé | |---|---|---:|---:| | maistre | maître | 47 | 85 % | | nostre | nostre (préservé) | 92 | 8 % | | veoir | voir | 23 | 100 % | Calcul : alignement caractère par caractère GT/hypothèse, extraction des substitutions sur tokens GT non présents dans un dictionnaire moderne (stop-list paramétrable). Trie les substitutions les plus fréquentes par moteur. C'est exploitable pour ajuster le prompt — bien plus utile qu'un score agrégé. #### A.I.8 — Robustesse synthétique projetée sur le corpus réel **Critique.** Le module `robustness.py` génère des dégradations synthétiques (bruit, flou, rotation). Mais aucun lien avec les caractéristiques réelles des images. **Chantier.** Pour chaque document du corpus, mesurer (via `image_quality.py`) les niveaux de bruit/flou/contraste réels. Projeter ces niveaux sur la courbe de robustesse synthétique pour estimer le **déficit attendu de CER** : > *« 30 % de vos documents ont un bruit équivalent à σ=15 où Tesseract > perd 8 points de CER — soit un déficit attendu global de 2,4 points. »* Visualisation : sur le graphique de robustesse, ajouter un histogramme des qualités réelles observées en arrière-plan. Le détecteur `robustness_fragile` (Sprint 19) est étendu pour intégrer ce déficit projeté dans son texte. #### A.I.9 — Section « Leviers d'amélioration » **Critique.** Le rapport dit « voici les performances » mais jamais « voici ce qui changerait facilement le classement ». **Chantier.** Nouvelle section en pied de rapport, factuelle, qui agrège des observations actionnables à partir des données déjà calculées : - *« 45 % des erreurs de Tesseract sont de la classe `casse` — applicable post-traitement gratuit. »* (source : taxonomie) - *« 12 documents concentrent 60 % du CER total — leur rescan en haute résolution aurait plus d'impact que de changer de moteur. »* (source : distribution par document + `image_quality`) - *« Mistral OCR et Pero OCR sont complémentaires : leurs erreurs ne se chevauchent qu'à 30 %. Un vote majoritaire entre les deux ferait passer le CER de 7 % à 4 %. »* (source : Venn d'erreurs + `cer_oracle`, voir A.II.8) Format : liste de cartes, chacune avec un titre court, un chiffre saillant et un lien vers la vue détaillée qui justifie l'observation. Architecture : un nouveau registre `levers/` parallèle au registre des détecteurs narratifs, avec la même contrainte de traçabilité des chiffres (garde-fou anti-hallucination). ### A.II — Métriques absentes à ajouter Classées par utilité réelle (haut ROI d'abord). Trois métriques marquent le premier livrable parce qu'elles ouvrent une dimension d'utilité nouvelle dans le rapport. #### A.II.1 — Trois métriques prioritaires (premier livrable) **A.II.1.a — Précision sur entités nommées (NER).** Nouveau module `picarones/evaluation/metrics/ner.py`. Backends : spaCy multilingue, Stanza, modèle HIPE pour les corpus historiques. Choix paramétré par profil (`fr_core_news_lg`, `xx_ent_wiki_sm`, `hipe2022`). Métriques calculées sur la paire `(entities_GT, entities_OCR)` après alignement par chevauchement de spans : - Précision, rappel, F1 par catégorie (`PER`, `LOC`, `ORG`, `DATE`, `MISC`) - F1 global pondéré - Taux d'**hallucinations d'entité** : entités en OCR sans correspondance GT Le rapport gagne une vue dédiée : tableau moteur × catégorie, drill-down sur les `PER` ratés. C'est la métrique qui parle directement aux indexeurs et aux prosopographes. **Piège.** Les modèles NER hallucinent eux-mêmes. La métrique mesure conjointement OCR + NER. Documenter explicitement ce biais dans la glossaire (entrée `ner_score`). **A.II.1.b — Score de calibration des moteurs.** Nouveau module `picarones/evaluation/metrics/calibration.py`. Tous les moteurs cibles fournissent une confidence par token ou par ligne (Tesseract `tsv` output, Pero OCR via `PageLayout`, Mistral OCR via `confidence`, Google Vision via `Word.confidence`). Ajout d'un champ `EngineResult.token_confidences: list[float]`. Métriques : - **ECE** (Expected Calibration Error) en 10 bins - **MCE** (Maximum Calibration Error) - **Reliability diagram** en SVG embarqué dans le rapport Vue « Fiabilité de l'auto-évaluation » qui répond à *« quand le moteur dit qu'il est sûr, est-il vraiment sûr ? »*. Pour un workflow de validation humaine, c'est la différence entre vérifier 100 % vs 15 % du corpus. **Piège.** Les confidences VLM/LLM sont moins fiables et moins standardisées (logprobs vs scores arbitraires). Marquer explicitement les moteurs sans calibration calculable. **A.II.1.c — Divergence taxonomique entre moteurs.** Extension de `core/taxonomy.py`. À partir des distributions taxonomiques agrégées (déjà calculées), ajout de la KL-divergence et de la Jensen-Shannon-divergence pour chaque paire de moteurs. Le rapport gagne une **matrice de divergence triangulaire**. Une divergence élevée signale des moteurs spécialisés sur des erreurs différentes — candidats pour un voting ensemble. Faible divergence = mêmes faiblesses, pas de gain attendu. Couplé au score de complémentarité quantifiée (A.II.8.a). Détecteur narratif `ensemble_opportunity` qui remonte automatiquement : *« Pero et Mistral sont fortement complémentaires (KL=0,82) — un voting majoritaire pourrait faire passer le CER de 7 % à 4 %. »* **Effort total A.II.1.** Un sprint et demi pour les trois métriques + leurs vues + la mise à jour du moteur narratif et du glossaire. #### A.II.2 — Métriques structurelles Ces métriques ne sont calculables que quand la GT ALTO/PAGE est disponible (Phase 0.1). **Reading order F1 par région.** Implémentation de la métrique ICDAR 2015 (Antonacopoulos). Comparaison de l'ordre des `TextRegion` dans la GT et dans l'hypothèse ALTO. F1 sur les paires consécutives correctement préservées. Pour les pipelines qui produisent du texte plat, l'ordre est extrait de l'ordre de concaténation (et la métrique signale cette extraction comme approximative). **Layout F1 par type de région.** Précision/rappel sur la détection de `TextRegion`, `MarginNote`, `Header`, `Footer`, `Drop-Cap`. Métrique critique pour les manuscrits glosés et les journaux multi-colonnes. Vue qui répond à *« le moteur sépare-t-il bien le texte principal de la glose ? »*. **Différence de score Flesch.** Calcul du score Flesch (ou Flesch-Kincaid) sur GT et sortie OCR. La différence est un signal d'over-normalisation indépendant de toute classe taxonomique. **N'exige aucun alignement**, donc fiable même quand l'OCR est très dégradé. Particulièrement utile pour repérer les LLM qui « lissent » la langue historique. #### A.II.3 — Métriques philologiques Pour les corpus médiévaux et les éditions critiques. **Précision par bloc Unicode.** À partir de la matrice de confusion existante, agrégation par bloc : *Latin de Base* (U+0000–U+007F), *Latin-1 Supplément*, *Latin Étendu A et B*, *Diacritiques combinants*, *Présentation latine*. Graphe à barres groupées qui dit immédiatement *« ce moteur restitue 95 % du Latin de Base mais 12 % des formes de présentation latines »*. Actionnable en un coup d'œil pour le choix éditorial. **Score d'expansion d'abréviations en deux variantes.** Reconnaissance des abréviations médiévales par leur position Unicode (ꝑ, ꝓ, ꝗ, p̃, q̃) et calcul de : - **Strict** : taux de préservation de la forme abrégée Unicode - **Expansion** : taux de développement correct (ꝑ → per, ꝓ → pro) Le ratio des deux dit beaucoup sur la convention adoptée par le moteur. Crucial pour distinguer un OCR diplomatique d'un OCR modernisant. **Score de couverture MUFI.** Liste de référence : caractères MUFI v4.0 utilisés dans le GT. Mesure : taux de ces caractères correctement préservés dans l'OCR. Pour les médiévistes, c'est un critère éditorial central — déjà mentionné dans le glossaire (Sprint 21) mais non mesuré. #### A.II.4 — Métriques de fiabilité **Inter-annotator agreement.** Quand le corpus a plusieurs GT (par exemple deux paléographes), calcul du Cohen κ ou Krippendorff α. Donne le plafond atteignable. Affiché dans la synthèse factuelle : > *« Le CER moyen de Pero (4,2 %) approche le plafond humain pour ce > corpus (κ = 0,89). »* Nécessite une extension du loader pour accepter `doc_001.gt.A.txt` et `doc_001.gt.B.txt` comme GT multiples — ce qui s'inscrit naturellement dans le modèle multi-niveaux de la Phase 0.1. **Score de stabilité multi-runs.** Le runner gagne une option `--repeats N` qui exécute N fois la même pipeline LLM sur les mêmes documents et mesure : - Variance du CER - Taux de tokens divergents entre runs - Divergence sémantique (BERTScore sur paires de sorties) C'est critique pour la reproductibilité scientifique. Une publication qui rapporte un CER LLM sans stabilité est méthodologiquement faible. Détecteur narratif `engine_unstable` qui remonte les moteurs dont la variance dépasse un seuil. #### A.II.5 — Métriques d'utilisabilité aval **OCR-friendliness pour la recherche plein-texte.** Pourcentage de mots GT dont une variante orthographiquement proche (Levenshtein ≤ 2) existe dans la sortie OCR. C'est ce que recherchent réellement Elastic et Solr en mode fuzzy. Un CER de 8 % peut donner 95 % de findability si les erreurs sont concentrées sur des caractères non-significatifs. Affichée à côté du CER dans le tableau principal sous le nom **« Recherchabilité »**. **Précision sur séquences numériques.** Extraction par regex des dates (formats classiques et historiques : MDCLXVIII, *mil cinq cens*, 1ᵉʳ janvier 1789), foliotation, montants, années régnales. Mesure de précision sur ces séquences. Pour un économiste-historien ou un éditeur de chartes, c'est un proxy direct de la qualité éditoriale. #### A.II.6 — Métriques de processus et économiques **Throughput effectif.** ``` pages_par_heure_utilisable = pages_traitées / (durée_totale + temps_correction_humaine_estimé) ``` Le temps de correction humaine est estimé par `temps_par_erreur × nombre_d_erreurs`. La constante `temps_par_erreur` est paramétrable (défaut 5 secondes par erreur de mot, justifié par les études HTR-United). Discrimine fortement entre un cloud rapide à 30 % de timeouts et un local lent à 100 % de fiabilité. **Coût marginal par erreur évitée.** Extension naturelle de la vue Pareto. Pour chaque paire de moteurs : ``` coût_marginal = (coût_B − coût_A) / (errors_A − errors_B) # quand B est meilleur ``` Nouvelle colonne dans le tableau Pareto : *« Passer de Tesseract à Mistral OCR : 0,83 € par erreur évitée »*. Couplé au volume cible (chantier A.I.6), devient un outil de décision business. #### A.II.7 — Métriques d'image prédictives **Score de complexité paléographique.** Trois features simples combinées : - Densité de glyphes par cm² (à partir d'OCR amont approximatif) - Variabilité de hauteur de ligne (écart-type sur les lignes détectées) - Ratio encre/papier (Otsu) Combinaison pondérée en un score [0, 1]. Corrélé avec le CER attendu sur le corpus. Permet d'expliquer une partie de la difficulté observée. **Score d'homogénéité du corpus.** Variance des features image entre documents. Score bas = corpus uniforme = moyenne fiable. Score haut = corpus hétérogène = la moyenne ment, il faut stratifier. Avertissement automatique en haut de la vue Classement quand le score d'homogénéité est bas : *« ⚠ Ce corpus est hétérogène (score 0,34) — la moyenne globale masque des disparités importantes. Voir la vue stratifiée. »* (renvoie vers A.III.) #### A.II.8 — Métriques inter-moteurs **A.II.8.a — Score de complémentarité quantifié.** ``` cer_oracle = tokens_GT_correctement_transcrits_par_AU_MOINS_un_moteur / total_tokens_GT ``` Borne inférieure atteignable par voting ensemble. Si `cer_oracle` est très inférieur au meilleur moteur seul, ça justifie l'effort d'un pipeline d'ensemble. Sinon non. Affiché à côté du meilleur CER observé dans la synthèse factuelle. **A.II.8.b — Score de spécialisation.** Pour chaque paire de moteurs, la KL-divergence sur les distributions taxonomiques (déjà calculée en A.II.1.c) sert de score de spécialisation. Moteurs spécialisés différemment → candidats pour ensemble. Moteurs spécialisés identiquement → pas de gain attendu. #### A.II.9 — Métriques longitudinales L'historique SQLite (`core/history.py`, Sprint 8) existe mais aucune métrique n'en sort dans le rapport. C'est complémentaire du chantier A.I.3. **Pente de progression.** Régression linéaire sur les CER successifs d'un moteur dans le temps. Trois nombres : pente, R², `n_runs`. Nouvelle vue **« Évolution dans le temps »** du rapport, un graphe par moteur avec zone de confiance bootstrap. **Détection de point de rupture.** Algorithmes de change-point detection (PELT, Bayesian via `ruptures` ou implémentation Python pure pour les petits N) sur la série temporelle des CER. Identifie automatiquement la version où un modèle a changé de comportement. Particulièrement utile pour relier une régression à un changement de pipeline d'entraînement. Détecteur narratif `regression_in_history` qui remonte *« Tesseract montre une régression depuis le run du 12/02 (CER moyen +1,8 points sur les 3 derniers benchmarks) »*. ### A.III — Stratification par `script_type` (vue transversale) C'est probablement **la plus haute valeur ajoutée transversale** du plan A. Aujourd'hui les détecteurs `stratum_winner` et `stratum_collapse` (Sprint 19) calculent l'information par strate mais elle n'est pas remontée dans le tableau de classement principal. **Chantier.** Toggle dans la nav du rapport : *« Stratifier par script_type »*. Quand activé, le tableau de classement se dédouble : - Un sous-tableau par strate avec son propre top-3 - Ses propres tests statistiques (Wilcoxon, Friedman) - Son propre Pareto Le moteur narratif adapte sa synthèse : > *« Sur les manuscrits gothiques (n=43), Pero domine ; sur les imprimés > modernes (n=87), Tesseract suffit. Le classement global agrégé masque > cette divergence. »* **Architecture.** Le runner stocke déjà `script_type` par document (metadata). L'agrégation actuelle est plate ; il faut introduire un `StratifiedBenchmarkResults` qui contient une `BenchmarkResults` par strate plus l'agrégat global. Le rapport HTML en mode stratifié remplace les vues principales par des onglets de strates, avec un onglet « Tous » qui reprend la vue actuelle. **Contrainte UX.** Le toggle est désactivé automatiquement si une seule strate est présente. Il est mis en évidence (badge `Recommandé`) si le score d'homogénéité (A.II.7) est bas. C'est le passage d'un rapport qui dit *« voici la performance moyenne »* à un rapport qui dit *« voici quel moteur choisir pour quel type de document »*. **Effort A.III.** Un sprint complet, parce qu'il faut adapter toute la chaîne de visualisation et plusieurs détecteurs narratifs. --- ## 4. Phase B — Banc d'essai de pipelines (mode optionnel) Saut conceptuel qui répond à la question scientifique non-résolue soulevée par le contexte BnF : **le classement des moteurs survit-il à l'introduction d'une étape de reconstruction de structure ?**. Ce mode reste **optionnel et masqué par défaut**. Un utilisateur du mode benchmark texte simple ne voit jamais les vues B tant qu'il ne charge pas une pipeline composée. Cf. § 1.3 (rapport adaptatif). ### B.1 — Acte 1 : premier cas BnF concret **Pourquoi ne pas généraliser tout de suite.** L'erreur classique sur ce type de projet : coder un framework avant d'avoir vu un cas d'usage réel. On finit avec une abstraction élégante qui ne colle à aucun workflow. Un cas BnF concret va révéler ce qui manque dans le modèle de données, comment associer la GT par étape, quelles visualisations ont du sens. **Ce qu'il faut faire.** Choisir avec l'équipe BnF un corpus qui réunit trois conditions : - GT ALTO disponible et stable - Volume modeste (50 à 200 pages) pour itérer vite - Représentatif d'un cas réel (registres, presse, manuscrits) Construire une première pipeline en YAML : ```yaml pipeline: name: "pero_to_alto_v1" steps: - module: pero_ocr input: image output: text - module: alto_reconstructor_naive input: [image, text] output: alto evaluation: junctions: - after: pero_ocr compare_to: gt.text metrics: [cer, wer] - after: alto_reconstructor_naive compare_to: gt.alto metrics: [reading_order_f1, layout_f1, text_preservation] ``` **Pas d'UI graphique à ce stade.** CLI uniquement : `picarones pipeline run my-pipeline.yaml --corpus path/to/corpus`. **Ce qui doit sortir.** Un rapport HTML qui montre, document par document, ce que produit chaque étape de la pipeline et comment ça se compare à la GT correspondante. Minimum viable. **Critères de réussite.** 1. La pipeline tourne de bout en bout sur le corpus. 2. Au moins une métrique est calculée à chaque jonction. 3. On identifie au moins trois choses qui manquent dans le modèle de données ou dans l'interface module (entrée pour l'acte 2). 4. Le rapport est interprétable par un collègue BnF qui n'a pas codé la pipeline. **Décision critique en fin d'acte 1.** Faire le bilan **avant** de passer à l'acte 2. Si l'acte 1 a révélé des manques majeurs dans la Phase 0, refactoriser maintenant. Si tout est solide, continuer. **Effort B.1.** Deux à trois sprints — la marge couvre les découvertes. ### B.2 — Acte 2 : pipeline composée comme concept À ce stade on a un cas qui marche. On peut généraliser. **Extension du concept de « concurrent ».** Aujourd'hui un concurrent est un moteur OCR ou un OCR+LLM. Demain c'est une chaîne de N modules. Le runner traite uniformément les deux : un moteur OCR seul est une pipeline à un seul nœud (cf. § 1.1). **Métriques par jonction.** À chaque arête du DAG, calculer la métrique adéquate selon les types d'artefacts. Le rapport décompose la performance par étape : > *« La pipeline A (Pero → reconstructor_v2) bat la pipeline B (Tesseract > → reconstructor_v2) globalement (CER 4,2 % vs 7,8 %). Mais > reconstructor_v2 produit un meilleur reading-order F1 quand il part de > Tesseract (0,89 vs 0,82). La différence finale vient entièrement de la > qualité OCR amont. »* C'est cette analyse par étape qui transforme un benchmark en outil de diagnostic. **Synthèse factuelle adaptée.** Les détecteurs narratifs existants gagnent des frères pour les pipelines : - `pipeline_stage_collapse` — une étape effondre la performance - `module_introduces_more_than_corrects` — voir B.3 - `pipeline_dominated_by_single_stage` — une étape porte tout le gain Le moteur narratif compare des pipelines au lieu de comparer des moteurs quand le mode pipeline est actif. ### B.3 — Acte 3 : métrique d'absorption d'erreur **Pourquoi c'est critique.** Quand un module post-correction LLM aplatit les différences entre OCR amont, ce n'est pas qu'il « améliore » tous les moteurs — c'est qu'il introduit ses propres biais qui dominent ceux de l'OCR. Mesurer la dégradation par étape ne suffit pas. Sans cette métrique, on confond correction et écrasement, et la communauté scientifique ne peut pas faire confiance aux conclusions. **Ce qu'il faut faire.** À chaque jonction où un module transforme un artefact, mesurer **deux flux séparément** : - **Taux de correction** : parmi les erreurs présentes en entrée du module, combien sont corrigées en sortie ? - **Taux d'introduction** : parmi les erreurs présentes en sortie, combien sont nouvelles (absentes en entrée) ? C'est la généralisation du score de sur-normalisation existant (chantier A.I.7) à toute jonction. La formule s'applique uniformément à OCR→LLM, OCR→reconstructor, VLM→ALTO_mapper. **Visualisation.** Un graphe **Sankey** par pipeline : à chaque jonction, deux flux (corrections et introductions) avec leurs volumes. Permet de voir au premier coup d'œil quels modules « ajoutent du bruit » sous couvert d'amélioration globale. **Détecteur narratif `module_writes_more_than_reads`.** > *« Le module GPT-4o post-corrector introduit 1,3 erreur nouvelle pour > chaque erreur corrigée — il écrit son propre texte plutôt que de > corriger Tesseract. »* C'est précisément la métrique qui légitimise une publication ICDAR/TPDL sur le sujet. ### B.4 — Acte 4 : visualisation du DAG **Outil d'inspection, pas de construction.** Le YAML reste source de vérité. **Ce qu'il faut faire.** Un nouveau module dans le rapport HTML : **« Pipeline DAG »**. Affiche le graphe orienté de la pipeline. Chaque nœud est un module. Chaque arête est annotée avec : - Le type d'artefact transmis - La métrique calculée à cette jonction (la plus pertinente) - Une indication visuelle de qualité (vert/jaune/rouge selon seuils) Clic sur une arête → drill-down par document : *« À cette étape, document 47, voici ce qui sort du module en amont, voici ce qui sort en aval, voici la GT correspondante. »*. Diff coloré façon GitHub à trois colonnes. **Pas de drag-and-drop, pas de notebook.** Le visuel sert à inspecter et déboguer, pas à construire. Une institution sérieuse versionne ses pipelines en YAML dans Git, pas en JSON exporté d'une UI. ### B.5 — Acte 5 : comparaison incrémentale **Pourquoi c'est indispensable.** Avec 5 OCR × 3 reconstructeurs × 4 post-correcteurs × 3 mappeurs = 180 pipelines à comparer, le rapport noie l'information. Il faut un mécanisme de comparaison contrôlée type design d'expérience. **Ce qu'il faut faire.** Une nouvelle vue **« Comparaison contrôlée »** : - Fixer N−1 modules de la pipeline - Faire varier le N-ième - Voir l'effet isolé du module qui varie Présentation : un tableau ANOVA-like qui isole l'effet du module variable en contrôlant les autres. Tests statistiques associés (Friedman généralisé sur les rangs des pipelines, Nemenyi pour les comparaisons par paires). C'est presque un Latin square automatisé. Sans ça, le rapport sur 180 pipelines est inutilisable. ### B.6 — Acte 6 : politique de modules **Avant d'ouvrir aux contributions externes.** **Cadre de qualité pour les modules contribués.** - Test unitaire obligatoire sur un corpus de référence (versionné dans le repo) - Contrat d'entrée/sortie strict avec validation automatique au chargement - Métadonnées obligatoires : licence, auteur, version, citation académique si applicable - Score de qualité affiché dans le rapport pour chaque module utilisé Un module qui ne passe pas l'audit n'est **pas exécutable**. Pas exécutable = pas dans le rapport, pas dans la pipeline. **Stratégie d'ouverture en deux temps.** 1. **Phase fermée.** Modules officiels uniquement, contributions via PR sur le repo principal. Tant que l'interface module n'est pas stable, accepter des contributions externes force à maintenir une compatibilité qu'on n'est pas prêt à offrir. 2. **Phase ouverte.** Une fois 5–6 modules officiels stables et un guide de contribution éprouvé, ouvrir via plugins (`picarones-module-X` sur PyPI avec mécanisme `entry_points`). --- ## 5. Trajectoire intégrée et calendrier Le plan se déroule en **six étapes**, certaines en parallèle. Les axes A et B partagent la même base de code et le même rapport — ils ne s'opposent jamais. ### Étape 1 — Phase 0 dans son intégralité **Durée.** 3 sprints (≈ 6 semaines). Modèle de données multi-niveaux, interface module générique, métriques composables. Non-négociable. **Aucune autre étape ne peut commencer tant que ce n'est pas stable.** À la fin : tous les tests existants passent, la rétrocompatibilité est garantie, la base est posée pour les axes A et B. ### Étape 2 — Premier livrable de l'axe A **Durée.** 1,5 sprint (≈ 3 semaines). Trois métriques prioritaires (A.II.1 : NER, calibration, divergence taxonomique) + **stratification par script_type (A.III)**, qui est l'amélioration la plus visible pour les utilisateurs actuels. Inclure aussi **A.I.2 (médiane par défaut)** parce qu'il s'agit d'un changement à un seul endroit avec impact immédiat sur la lecture du rapport. À la fin : le rapport actuel a gagné une dimension d'utilité aval (NER), une dimension de fiabilité (calibration), un classement plus honnête (médiane), et une lecture stratifiée qui change la nature des recommandations. ### Étape 3 — Acte 1 de l'axe B + suite de l'axe A en parallèle **Durée.** 3 sprints (≈ 6 semaines). - **Axe B** : cas BnF concret (B.1) — démarrage de la collaboration formalisée avec l'équipe BnF. - **Axe A en parallèle** : critiques structurelles à fort ROI : - A.I.1 (worst lines + rare-token recall) - A.I.4 (taxonomie 3 niveaux) - A.I.9 (section « Leviers d'amélioration ») - A.II.2 (métriques structurelles, qui consommeront la GT ALTO du cas BnF) Les deux axes ne se gênent pas : l'axe A travaille sur l'évaluation texte+structure mono-pipeline, l'axe B sur l'évaluation pipeline composée. **Décision critique en fin d'étape 3.** Bilan du cas BnF avant de passer à l'étape suivante. Refactoriser la Phase 0 si besoin. ### Étape 4 — Actes 2 et 3 de l'axe B + suite de l'axe A **Durée.** 3,5 sprints (≈ 7 semaines). - **Axe B** : pipeline composée comme concept (B.2) + métrique d'absorption d'erreur (B.3). C'est ici que la dimension publication ICDAR/TPDL devient réelle. - **Axe A en parallèle** : - A.I.3 (vue « inhabituel ici » + détecteur `engine_off_baseline`) - A.I.5 (curseur normalisation diplomatique) - A.I.6 (volume cible projeté) - A.II.4 (fiabilité : IAA + stabilité multi-runs) À la fin de l'étape 4, **un papier scientifique est rédactible** sur les résultats du cas BnF avec absorption d'erreur quantifiée. ### Étape 5 — Visualisation DAG + métriques longitudinales et avancées **Durée.** 3 sprints (≈ 6 semaines). - **Axe B** : visualisation DAG (B.4) - **Axe A** : - A.I.7 (sur-normalisation LLM en vue analytique) - A.I.8 (robustesse projetée) - A.II.3 (philologiques : Unicode/abbrev/MUFI) - A.II.5 (utilisabilité aval) - A.II.6 (économique) - A.II.7 (image prédictives) - A.II.8 (inter-moteurs : oracle + spécialisation) - A.II.9 (longitudinales) À ce stade, l'outil est mature et commence à attirer l'attention de la communauté patrimoniale. ### Étape 6 — Maturité institutionnelle **Durée.** 3 sprints (≈ 6 semaines). - B.5 (comparaison incrémentale) - B.6 (politique de modules) - Ouverture aux contributions externes en phase fermée → ouverte - Documentation institutionnelle (guide d'écriture de modules, audit-bench) - Premiers retours d'autres institutions ### Synthèse temporelle | Étape | Durée | Cumul | Livrable saillant | |---|---:|---:|---| | 1 — Phase 0 | 3 sprints | 6 sem | Fondations multi-niveaux | | 2 — A premier livrable | 1,5 sprint | 9 sem | NER + calibration + médiane + stratif | | 3 — B acte 1 + A suite | 3 sprints | 15 sem | Premier cas BnF + worst lines + leviers | | 4 — B actes 2-3 + A | 3,5 sprints | 22 sem | Absorption d'erreur (publication possible) | | 5 — DAG + métriques restantes | 3 sprints | 28 sem | Outil mature | | 6 — Maturité | 3 sprints | 34 sem | Ouverture externe | **Total estimé.** 17 sprints, soit **8–9 mois** à un sprint de deux semaines. Ambitieux mais cohérent avec ce qui a déjà été construit (22 sprints terminés à date). --- ## 6. Décisions structurantes à trancher avant le premier sprint Trois choix qui contraignent tout le reste. Mieux vaut les fixer maintenant que les découvrir au sprint 8. ### 6.1 Périmètre de la marque « Picarones » **Question.** Dans le mode legacy, Picarones reste un benchmark. Dans le mode pipeline, Picarones devient un banc d'essai. Faut-il un seul produit ou deux noms ? **Recommandation.** **Un seul nom.** Les deux dimensions partagent la même philosophie (rigueur, traçabilité, absence de prescription) et le même socle technique. Documenter clairement les deux modes d'usage dans le `README.md` mis à jour, avec une section *« À qui ça s'adresse »* qui distingue les deux profils. ### 6.2 Partenariat BnF formalisé **Question.** Le plan B est intenable sans un cas BnF en condition réelle. **Recommandation.** Co-écrire dès maintenant un document court (1 page) avec l'équipe BnF qui définit : - Quel corpus (référence stable, accord d'utilisation) - Quelle GT disponible et à quel niveau (texte ? ALTO ? les deux ?) - Quelle pipeline cible (que veut-on évaluer ?) - Quel critère de succès (que doit produire le rapport ?) - Quel calendrier (qui livre quoi à quelle date ?) Sans ce document, on construit sur des hypothèses internes qui peuvent diverger des besoins réels. ### 6.3 Politique de contribution externe **Question.** Accepter ou non des modules tiers dès le départ ? **Recommandation.** **Commencer fermé**, pour deux raisons : 1. Tant que l'interface module n'est pas stable, accepter des contributions externes force à maintenir une compatibilité qu'on n'est pas prêt à offrir. 2. Avant 5–6 modules officiels stables et bien testés, on n'a pas le recul pour juger de la qualité d'un module externe. Ouvrir au sprint 17 (étape 6), pas avant. --- ## 7. Le cap à tenir Le **différenciateur de fond** reste celui qui distingue déjà Picarones : **rigueur méthodologique, traçabilité, absence de prescription**. Tout ce qui s'ajoute doit servir cette ligne, pas la diluer. ### 7.1 Pièges identifiés à ne pas commettre **Piège 1 — Coder le framework de l'axe B avant le cas d'usage.** La Phase 0 prépare l'infrastructure, mais l'acte 1 de l'axe B doit absolument commencer par un cas BnF concret avant toute généralisation. Si on code `BaseModule`, le runner pipeline et la visualisation DAG en partant d'intuitions, on finira avec une abstraction qui ne colle à aucun workflow. **Piège 2 — Le mode hybride en patchwork.** Tentation : ajouter des options à `picarones run` au fil du temps (`--with-pipeline`, `--enable-junction-metrics`, `--show-dag`). Au bout de six mois, quinze flags qui interagissent mal. Mauvaise voie. La bonne voie : un seul concept central — la pipeline — qui peut avoir un seul nœud (cas legacy invisible) ou plusieurs (cas avancé explicite). La CLI legacy `picarones run` construit en interne une pipeline triviale. L'utilisateur ne voit jamais cette construction. **Piège 3 — La progressivité comme concession.** Tentation inverse : afficher tout dans le rapport, « au cas où l'utilisateur en aurait besoin ». Si une vue n'apporte rien quand la pipeline est simple, elle doit être **absente** — pas juste vide ou désactivée. La discipline du masquage automatique est ce qui distingue un bon produit d'un produit qui veut tout faire. **Piège 4 — Aplatir les jonctions.** Une métrique calculée à une jonction text→text (CER) et une métrique à une jonction image→ALTO (Layout F1) ne sont pas comparables. Le rapport doit clairement séparer les deux dans les vues. Le typage strict de la Phase 0.3 garantit cette séparation, **à condition de ne jamais contourner le système**. **Piège 5 — Régressions silencieuses.** À chaque sprint, garder un set de tests qui exécute des configurations purement legacy (pas de YAML, pas de pipeline composée, GT mono-niveau) et vérifie que le rapport produit est strictement identique octet par octet à ce qui est produit aujourd'hui. C'est le seul garde-fou contre les régressions silencieuses quand on enrichit le runner. ### 7.2 Question à se poser à chaque PR À chaque décision de design, à chaque PR, une seule question : > *« Est-ce que ce changement oblige l'utilisateur du mode simple à > apprendre quelque chose de nouveau ? »* Si oui, **mauvais design**. Repenser jusqu'à ce que la réponse soit non. C'est la règle qui garantit que les axes A et B coexistent vraiment au lieu de cohabiter difficilement. --- ## 8. Annexe — Mapping critiques → chantiers Vérification que chaque point soulevé dans la conversation de cadrage trouve sa réponse dans ce plan. ### 8.1 Critiques structurelles | # | Critique | Chantier(s) | |---:|---|---| | 1 | Granularité s'arrête à la page | A.I.1 (worst lines + rare-token recall) | | 2 | CER moyen au lieu de médian | A.I.2 (médiane par défaut) | | 3 | Pas de vue « inhabituel ici » | A.I.3 (encart historique + détecteur off-baseline) | | 4 | Taxonomie sous-exploitée | A.I.4 (co-occurrence + intra-doc + comparative) | | 5 | Normalisation diplomatique binaire | A.I.5 (curseur dans panneau Avancé) | | 6 | Coût décorrélé de la valeur | A.I.6 (volume cible projeté) + A.II.6 (coût marginal) | | 7 | Sur-normalisation LLM en colonne | A.I.7 (vue analytique + table fréquences) | | 8 | Robustesse déconnectée du benchmark | A.I.8 (projection qualité réelle sur courbe) | | 9 | Pas de signal sur leviers | A.I.9 (section dédiée + registre `levers/`) | ### 8.2 Métriques additionnelles | Métrique | Chantier | |---|---| | NER (précision/rappel/F1 par catégorie) | A.II.1.a | | Calibration des moteurs (ECE, MCE, reliability) | A.II.1.b | | Divergence taxonomique inter-moteurs (KL/JS) | A.II.1.c | | Reading order F1 (ICDAR 2015) | A.II.2 | | Layout F1 par type de région | A.II.2 | | Différence Flesch GT vs OCR | A.II.2 | | Précision par bloc Unicode | A.II.3 | | Score d'expansion d'abréviations (strict/expansion) | A.II.3 | | Couverture MUFI | A.II.3 | | Inter-annotator agreement (Cohen κ / Krippendorff α) | A.II.4 | | Stabilité multi-runs | A.II.4 | | Recherchabilité (Levenshtein ≤ 2) | A.II.5 | | Précision sur séquences numériques | A.II.5 | | Throughput effectif | A.II.6 | | Coût marginal par erreur évitée | A.II.6 | | Complexité paléographique | A.II.7 | | Homogénéité du corpus | A.II.7 | | CER oracle / complémentarité | A.II.8.a | | Score de spécialisation (KL) | A.II.8.b | | Pente de progression (régression linéaire) | A.II.9 | | Détection de point de rupture (PELT/Bayesian) | A.II.9 | ### 8.3 Banc d'essai pipelines (contexte BnF) | Question | Acte | |---|---| | GT multi-niveaux (texte + ALTO + entités) | Phase 0.1 | | Module générique typé I/O | Phase 0.2 | | Métriques par jonction | Phase 0.3 | | Premier cas BnF (Pero → reconstructeur ALTO) | B.1 | | Pipeline composée comme concurrent | B.2 | | Absorption vs introduction d'erreur | B.3 | | Visualisation DAG inspectable | B.4 | | Comparaison contrôlée (Latin square) | B.5 | | Politique de modules contribués | B.6 | ### 8.4 Synthèse de l'unification produit | Préoccupation soulevée | Réponse architecturale | |---|---| | Coexister sans gêner les chercheurs actuels | § 1.1 (pipeline 1-nœud = cas dégénéré) | | Trois modes d'entrée sans complexifier la CLI | § 1.2 (legacy / YAML / hybride) | | Vues B masquées en mode simple | § 1.3 (rapport adaptatif) | | Pas obliger à apprendre du nouveau | § 1.4 + § 7.2 (règle pratique) | | Une seule base de code, pas deux produits | § 6.1 (un seul nom Picarones) | --- ## 9. Pour démarrer Ordre concret des trois prochaines actions, dans cet ordre, sans négociation : 1. **Co-écrire le mémo BnF (§ 6.2)** — 1 page, signée des deux côtés, définissant corpus / GT / pipeline cible / critère / calendrier. 2. **Ouvrir une branche `phase-0/multi-level-gt`** et coder la refonte du modèle `Document` (chantier 2.1) en gardant la rétrocompatibilité stricte (test legacy bit-à-bit dès le premier commit). 3. **Lister les modules officiels candidats pour la phase fermée (§ 6.3)** : OCR existants, premier reconstructeur ALTO BnF, premier post-correcteur LLM. Cette liste devient le périmètre de stabilisation de l'interface module pour les sprints 1 à 17. Tout le reste découle.