Picarones / docs /roadmap /evolution-2026.md
Claude
docs(sprint-H.9): archive migration plans + cleanup stale doc paths
2b782d0 unverified
# 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.