Spaces:
Sleeping
Sleeping
| # Équivalence numérique — ancien runner ↔ nouveau pipeline executor | |
| Ce document décrit comment le `CorpusRunner` introduit au Sprint S8 | |
| (combiné au `PipelineExecutor` du S7) reproduit les mêmes chiffres | |
| CER/WER que l'ancien `picarones.measurements.runner.run_benchmark`. | |
| C'est le **critère go/no-go de fin de Phase 2** du rewrite ciblé | |
| (cf. `docs/roadmap/rewrite-2026.md`). Sans cette équivalence, on | |
| ne peut pas basculer la BnF vers le nouveau runner sans surprise. | |
| ## Architecture des deux orchestrations | |
| ### Ancien runner (`picarones.measurements.runner`) | |
| ``` | |
| Corpus[Document(image, GT)] | |
| │ | |
| ▼ | |
| run_benchmark(corpus, [BaseOCREngine]) | |
| │ | |
| ▼ ProcessPoolExecutor / ThreadPoolExecutor | |
| BaseOCREngine.run(image) → EngineResult(text, ...) | |
| │ | |
| ▼ | |
| compute_metrics(GT, text) → MetricsResult(cer, wer, ...) | |
| │ | |
| ▼ | |
| aggregate_metrics([MetricsResult, ...]) → {"cer": {"mean": 0.05}, ...} | |
| │ | |
| ▼ | |
| EngineReport(mean_cer=0.05, ...) | |
| ``` | |
| ### Nouveau pipeline (`picarones.pipeline`) | |
| ``` | |
| [DocumentRef], initial_inputs={IMAGE: Artifact} | |
| │ | |
| ▼ | |
| CorpusRunner.run(spec, docs, factory_inputs, factory_ctx) | |
| │ | |
| ▼ ThreadPoolExecutor avec backpressure | |
| PipelineExecutor.run(spec, doc, inputs, ctx) | |
| │ | |
| ▼ pour chaque step | |
| StepExecutor.execute(inputs, params, ctx) → {RAW_TEXT: Artifact} | |
| │ | |
| ▼ (S13+ : EvaluationViewExecutor) | |
| TextView.evaluate(candidate, ground_truth) → ViewResult(metric_values) | |
| ``` | |
| Le S12 ne livre pas encore l'`EvaluationViewExecutor` — il vérifie | |
| juste que **si on appelle ``compute_metrics`` directement sur les | |
| artefacts produits par le nouveau pipeline**, on obtient les mêmes | |
| valeurs. Le S13-S14 livrera la couche `TextView` qui fera ce | |
| calcul automatiquement. | |
| ## Méthode de vérification (test d'équivalence) | |
| Le test `tests/integration/test_sprint_a14_s12_executor_equivalence.py` | |
| implémente l'équivalence : | |
| 1. **Construit deux orchestrations** consommant exactement le même | |
| corpus : | |
| - `_FakeOCREngine` (héritant de `BaseOCREngine`) pour l'ancien | |
| runner. | |
| - `_FakeStepExecutor` (satisfaisant le protocole `StepExecutor`) | |
| pour le nouveau. | |
| - Les deux retournent **le même texte** par document, indexé par | |
| `doc_id`. | |
| 2. **Lance les deux runners** sur le même corpus. | |
| 3. **Calcule CER/WER avec le même `compute_metrics`** sur les | |
| sorties des deux runners. | |
| 4. **Compare** les moyennes CER et WER. | |
| ## Tolérance : 1e-6, pas 1e-9 | |
| Le plan d'origine prévoyait une tolérance de **1e-9** ("équivalence | |
| numérique stricte"). La réalité du code montre une divergence de | |
| l'ordre de **1e-7** sur certaines fixtures, **uniquement à cause | |
| d'un arrondi à 6 décimales** dans `aggregate_metrics` de l'ancien | |
| runner : | |
| ```python | |
| # picarones/core/metrics.py — _stats() | |
| return { | |
| "mean": round(statistics.mean(values), 6), | |
| "median": round(statistics.median(values), 6), | |
| ... | |
| } | |
| ``` | |
| Les valeurs brutes (avant `round`) sont identiques bit-à-bit | |
| entre les deux runners. La divergence observée provient | |
| strictement du `round(..., 6)`. | |
| Le test S12 utilise donc une tolérance **1e-6** (cohérente avec les | |
| 6 décimales d'arrondi) et documente cette décision. Quand | |
| l'agrégation finale passera par les types non-arrondis du nouveau | |
| code (S22), la tolérance pourra être resserrée à 1e-9. | |
| ## 5 fixtures patrimoniales testées | |
| Le test couvre 5 cas de difficulté croissante : | |
| | Fixture | Description | | |
| |---|---| | |
| | `fixture_1_court` | Mots isolés, hypothèse parfaite | | |
| | `fixture_2_paragraphe` | Phrases avec une coquille | | |
| | `fixture_3_multi_lignes` | Multi-lignes + accents perdus | | |
| | `fixture_4_abreviations` | Bibliographie + date erronée | | |
| | `fixture_5_mix_langues` | Latin + français, multiples coquilles | | |
| Plus deux cas limites : | |
| - `test_equivalence_with_perfect_hypothesis` — CER == WER == 0 | |
| - `test_equivalence_with_empty_hypothesis` — texte produit vide | |
| Total : **7 tests d'équivalence**, tous verts. | |
| ## Conséquences pour la migration BnF | |
| À partir du S12, on peut affirmer que : | |
| - Basculer un benchmark BnF du runner legacy vers le nouveau | |
| `CorpusRunner` ne change pas les chiffres rapportés au-delà de | |
| l'arrondi à 6 décimales. | |
| - Les rapports HTML produits depuis le nouveau pipeline (S22) | |
| afficheront les mêmes CER que les rapports historiques (modulo | |
| arrondi). | |
| - Le nouveau `CorpusRunner` apporte **trois améliorations** non | |
| visibles côté chiffres : | |
| 1. Backpressure (RAM bornée même sur 1000+ docs). | |
| 2. Timeout depuis le **début d'exécution** (pas la queue). | |
| 3. Annulation propre via `threading.Event`. | |
| ## Limites du S12 | |
| L'équivalence vérifiée ici porte uniquement sur : | |
| - Le pipeline OCR seul (un step → un texte → CER/WER). | |
| - Les métriques principales `mean_cer` / `mean_wer`. | |
| Restent à vérifier dans des sprints suivants : | |
| - **S13** : équivalence des projecteurs (ALTO → texte) — couvert | |
| par les tests unitaires de `formats.alto.projector` mais pas | |
| encore comparé à `extract_text_from_alto` legacy. | |
| - **S15** : équivalence des métriques structurelles (Layout F1, | |
| reading order F1) — non testées en S12 car elles vivent dans | |
| des fichiers `measurements/*.py` non encore migrés. | |
| - **S20** : équivalence des métriques philologiques (MUFI, | |
| abbreviations, etc.) — idem. | |
| Quand ces sprints ajouteront leurs tests d'équivalence, le critère | |
| "équivalence numérique fin Phase 3 / Phase 4" sera complet. | |
| ## Statut | |
| - **Fin de Phase 2 (S12)** — équivalence runner OCR ✅ | |
| - **Fin de Phase 3 (S18)** — équivalence views ouverte (S13-S18) | |
| - **Fin de Phase 4 (S22)** — équivalence rapport HTML ouverte | |