Spaces:
Sleeping
docs(sprint-H.8): cleanup obsolete legacy/shim language in production docstrings
Browse filesSprint H.8 — nettoyage des docstrings de production qui mentent
sur l'état post-v2.0. Avant H.8, ~301 références à "legacy" /
"shim" / "picarones.measurements" subsistaient dans le code de
production, malgré la suppression des paquets correspondants
aux sprints A-H.
Suppressions ciblées
--------------------
**Pure shim avec zero caller** :
- ``picarones/i18n.py`` (re-export shim ``picarones.reports.i18n``)
— 0 import dans le code, 0 import dans les tests. Supprimé.
**Bloc "Phase X — module relocalisé depuis Y vers Z. Le chemin
legacy reste disponible via un shim avec ``DeprecationWarning`` ;
suppression prévue en 2.0."** : strippé via regex multi-line dans
~50 fichiers (renderers ``reports/html/``, helpers, modules
``evaluation/metric_*``). Le bloc décrivait un état transitoire
qui n'existe plus (les shims ``measurements/`` / ``report/`` ont
été supprimés au Lots D-F).
**Références à des chemins supprimés dans les docstrings** :
- ``picarones.measurements.X`` → ``picarones.evaluation.metrics.X``
(~ 35 modules) ou ``picarones.evaluation.statistics`` ou
``picarones.app.services.benchmark_runner`` selon le mapping.
Couvre les ``See also``, ``Migré depuis``, ``Réutilise`` dans
~30 fichiers.
- ``picarones.adapters.legacy_engines.tesseract.TesseractEngine``
→ ``picarones.adapters.ocr.tesseract.TesseractAdapter`` (exemple
cassé dans ``robustness.py:428``).
- ``picarones.fixtures`` → retrait de la référence (le module a
été supprimé au Sprint G ; le commentaire dans ``cli/__init__.py``
prétendait le contraire).
**Blocs "Migration depuis le legacy" / "Le legacy reste en place
jusqu'au S46"** : strippés des adapters OCR (``tesseract.py``,
``pero_ocr.py``, ``mistral_ocr.py``, ``google_vision.py``,
``azure_doc_intel.py``, ``factory.py``, ``base.py``,
``__init__.py``). Le legacy ``picarones.engines.*`` /
``picarones.adapters.legacy_engines/`` n'existe plus depuis H.2.d.
**Renaming d'API publique** :
- ``populate_legacy_registry`` → ``populate_detector_registry``
dans ``picarones/reports/narrative/registry.py``. Aucun caller
externe (vérifié par grep dans tests/, docs/, scripts/). La
fonction n'avait rien de "legacy" — elle synchronise le
``DetectorRegistry`` (API publique stable) depuis le décorateur
déclaratif.
Reformulation contextuelle
--------------------------
**``benchmark_runner.py``** (l'entry point CLI/web) :
- Docstring de module : "adapter de compat ``run_benchmark`` legacy
→ ``BenchmarkService`` rewrite" → "Entry point CLI/web — façade
``run_benchmark_via_service``". Le module n'est plus
transitoire ; il est l'API stable.
- ~25 mentions "legacy" dans les commentaires inline reformulées :
``Document legacy`` → ``Document (couche 3)``,
``BenchmarkResult legacy`` → ``BenchmarkResult``, etc. Le mot
"legacy" était trompeur car les types ``Document`` / ``Corpus``
/ ``BenchmarkResult`` sont canoniques dans la couche 3.
**``partial_store.py``** : retrait des mentions "Trace de retrait"
+ "Module transitoire" + "le legacy est mort". C'est l'API
production v2.0+, pas un module à retirer.
**``llm_pipeline_config.py`` / ``llm_pipeline_builder.py``** :
retrait des comparaisons systématiques avec ``OCRLLMPipeline``
(legacy) qui n'existe plus. Les docstrings décrivent l'API
actuelle.
**``domain/artifacts.py``** : ``ArtifactType.TEXT/ALTO/PAGE``
recadré comme "aliases courts" (legitimes, utilisés dans le code
canonique) au lieu d'"aliases legacy pour rétrocompat".
**``__init__.py``** (top-level) : "API publique du Cercle 1
historique" → "API publique des couches stables (domain +
evaluation)". Référence aux "8 couches" au lieu des "3 cercles".
Tests / lint
------------
- ``pytest tests/`` : 4126 passed, 9 skipped, 24 deselected.
- ``ruff check`` : All checks passed.
- Aucun changement de comportement runtime — uniquement docstrings
+ 1 fonction renommée (sans caller externe).
Reste pour v2.0 ou v2.1
-----------------------
**Conservé volontairement** (~50 mentions restantes) :
- CSS palette names (``--palette-good: legacy green``) : noms
techniques user-facing du toggle palette historique vs
Okabe-Ito. Le mot "legacy" est ici un synonyme de "classic".
- Routes web ``/api/benchmark/start`` (label "legacy v1") vs
``/api/benchmark/run`` (v2 pipeline-based) : situation
bi-route délibérée.
- ``picarones/pipeline/spec.py`` : shim de deprecation avec
``DeprecationWarning`` actif. La période de deprecation
expire à v2.0 ; à supprimer dans une release ultérieure
avec le test ``tests/api_stability/test_deprecated_aliases.py``.
- Quelques "comportement legacy" en contexte de comparaison de
test (cas-tests).
**À faire dans H.9** : nettoyer les docs (``docs/migration/`` à
archiver, ~15 fichiers ``docs/developer/`` / ``docs/explanation/``
qui réfèrent encore les chemins supprimés).
https://claude.ai/code/session_01NxyVKqg2SowXLZdM4H1ZDE
- picarones/__init__.py +8 -10
- picarones/adapters/corpus/_fallback_log.py +1 -1
- picarones/adapters/ocr/__init__.py +9 -10
- picarones/adapters/ocr/azure_doc_intel.py +1 -5
- picarones/adapters/ocr/base.py +4 -26
- picarones/adapters/ocr/factory.py +0 -11
- picarones/adapters/ocr/google_vision.py +1 -6
- picarones/adapters/ocr/mistral_ocr.py +1 -7
- picarones/adapters/ocr/pero_ocr.py +1 -9
- picarones/adapters/ocr/tesseract.py +6 -13
- picarones/adapters/storage/__init__.py +1 -1
- picarones/app/services/benchmark_runner.py +66 -86
- picarones/app/services/partial_store.py +8 -16
- picarones/app/services/registry_service.py +1 -1
- picarones/app/services/run_orchestrator.py +0 -2
- picarones/domain/artifacts.py +17 -27
- picarones/domain/errors.py +1 -1
- picarones/domain/module_protocol.py +7 -11
- picarones/evaluation/benchmark_result.py +0 -5
- picarones/evaluation/corpus.py +5 -11
- picarones/evaluation/metric_hooks.py +2 -7
- picarones/evaluation/metric_registry.py +10 -21
- picarones/evaluation/metric_result.py +2 -7
- picarones/evaluation/metrics/__init__.py +18 -36
- picarones/evaluation/metrics/alto_metrics.py +1 -1
- picarones/evaluation/metrics/builtin_hooks.py +1 -1
- picarones/evaluation/metrics/cost_projection.py +1 -1
- picarones/evaluation/metrics/incremental_comparison.py +1 -1
- picarones/evaluation/metrics/normalization.py +1 -1
- picarones/evaluation/metrics/numerical_sequences.py +1 -7
- picarones/evaluation/metrics/robustness.py +11 -11
- picarones/evaluation/metrics/roman_numerals.py +0 -6
- picarones/evaluation/metrics/search.py +11 -24
- picarones/evaluation/metrics/specialization.py +1 -1
- picarones/evaluation/registry/registry.py +4 -7
- picarones/evaluation/statistics/__init__.py +0 -3
- picarones/evaluation/statistics/friedman_nemenyi.py +1 -1
- picarones/evaluation/statistics/wilcoxon.py +1 -1
- picarones/evaluation/synthetic.py +1 -1
- picarones/formats/__init__.py +1 -1
- picarones/i18n.py +0 -24
- picarones/interfaces/cli/__init__.py +4 -3
- picarones/interfaces/web/jobs.py +1 -1
- picarones/pipeline/__init__.py +1 -1
- picarones/pipeline/llm_pipeline_builder.py +10 -26
- picarones/pipeline/llm_pipeline_config.py +21 -45
- picarones/reports/_helpers/__init__.py +0 -5
- picarones/reports/_helpers/assets.py +0 -5
- picarones/reports/_helpers/colors.py +0 -5
- picarones/reports/_helpers/render_helpers.py +0 -6
|
@@ -2,8 +2,8 @@
|
|
| 2 |
|
| 3 |
Licence Apache 2.0.
|
| 4 |
|
| 5 |
-
API publique
|
| 6 |
-
permettre :
|
| 7 |
|
| 8 |
>>> from picarones import Corpus, Document, BaseModule, ArtifactType
|
| 9 |
>>> from picarones import BenchmarkResult, EngineReport, DocumentResult
|
|
@@ -16,7 +16,7 @@ utiliser les sous-packages explicites :
|
|
| 16 |
>>> from picarones.adapters.ocr.tesseract import TesseractAdapter
|
| 17 |
|
| 18 |
Voir ``docs/explanation/architecture.md`` pour la cartographie complète des
|
| 19 |
-
|
| 20 |
"""
|
| 21 |
|
| 22 |
from __future__ import annotations
|
|
@@ -41,7 +41,7 @@ __author__ = "Picarones contributors"
|
|
| 41 |
|
| 42 |
|
| 43 |
# ──────────────────────────────────────────────────────────────────────────
|
| 44 |
-
# API publique —
|
| 45 |
# ──────────────────────────────────────────────────────────────────────────
|
| 46 |
|
| 47 |
from picarones.evaluation.corpus import (
|
|
@@ -75,12 +75,10 @@ from picarones.evaluation.metric_registry import (
|
|
| 75 |
select_metrics,
|
| 76 |
)
|
| 77 |
|
| 78 |
-
#
|
| 79 |
-
#
|
| 80 |
-
#
|
| 81 |
-
#
|
| 82 |
-
# Ce trigger remplace l'ancien import croisé Cercle 1 → Cercle 2 dans
|
| 83 |
-
# ``core/pipeline.py`` (violation B-1/B-2 du même esprit).
|
| 84 |
import picarones.evaluation.metrics as _trigger_metric_registration # noqa: F401, E402
|
| 85 |
|
| 86 |
__all__ = [
|
|
|
|
| 2 |
|
| 3 |
Licence Apache 2.0.
|
| 4 |
|
| 5 |
+
API publique des couches 1 & 3 (abstractions stables) ré-exportée
|
| 6 |
+
ici pour permettre :
|
| 7 |
|
| 8 |
>>> from picarones import Corpus, Document, BaseModule, ArtifactType
|
| 9 |
>>> from picarones import BenchmarkResult, EngineReport, DocumentResult
|
|
|
|
| 16 |
>>> from picarones.adapters.ocr.tesseract import TesseractAdapter
|
| 17 |
|
| 18 |
Voir ``docs/explanation/architecture.md`` pour la cartographie complète des
|
| 19 |
+
8 couches, et ``docs/reference/api-stable.md`` pour le contrat de stabilité.
|
| 20 |
"""
|
| 21 |
|
| 22 |
from __future__ import annotations
|
|
|
|
| 41 |
|
| 42 |
|
| 43 |
# ──────────────────────────────────────────────────────────────────────────
|
| 44 |
+
# API publique — couches stables (domain + evaluation)
|
| 45 |
# ──────────────────────────────────────────────────────────────────────────
|
| 46 |
|
| 47 |
from picarones.evaluation.corpus import (
|
|
|
|
| 75 |
select_metrics,
|
| 76 |
)
|
| 77 |
|
| 78 |
+
# Trigger d'enregistrement du registre typé : l'import de
|
| 79 |
+
# ``picarones.evaluation.metrics`` provoque l'exécution des décorateurs
|
| 80 |
+
# ``@register_metric`` sur ``cer``, ``wer``, ``mer``, ``wil`` + ~15
|
| 81 |
+
# métriques philologiques + reading order + NER + ALTO.
|
|
|
|
|
|
|
| 82 |
import picarones.evaluation.metrics as _trigger_metric_registration # noqa: F401, E402
|
| 83 |
|
| 84 |
__all__ = [
|
|
@@ -15,7 +15,7 @@ Conception volontairement minimale :
|
|
| 15 |
|
| 16 |
Le détecteur de Fact correspondant (``FactType.IMPORTER_FALLBACK_TRIGGERED``)
|
| 17 |
est implémenté dans
|
| 18 |
-
:mod:`picarones.
|
| 19 |
"""
|
| 20 |
|
| 21 |
from __future__ import annotations
|
|
|
|
| 15 |
|
| 16 |
Le détecteur de Fact correspondant (``FactType.IMPORTER_FALLBACK_TRIGGERED``)
|
| 17 |
est implémenté dans
|
| 18 |
+
:mod:`picarones.evaluation.metrics.narrative.detectors.history`.
|
| 19 |
"""
|
| 20 |
|
| 21 |
from __future__ import annotations
|
|
@@ -1,20 +1,19 @@
|
|
| 1 |
-
"""Adapters OCR
|
| 2 |
|
| 3 |
-
Contrat ``BaseOCRAdapter``
|
| 4 |
-
``
|
| 5 |
-
|
| 6 |
-
``execute(inputs, params, context)`` du ``PipelineExecutor``.
|
| 7 |
|
| 8 |
Implémentations livrées
|
| 9 |
-----------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
- ``PrecomputedTextAdapter`` — lit un texte OCR pré-calculé depuis
|
| 11 |
le filesystem. Cas BnF : comparer N transcriptions déjà produites
|
| 12 |
par d'autres outils sans relancer d'OCR.
|
| 13 |
-
|
| 14 |
-
Adapters concrets pour Tesseract / Pero OCR / Mistral OCR / Google
|
| 15 |
-
Vision / Azure DI : à écrire au cas par cas dans des sprints
|
| 16 |
-
dédiés, **natifs** au nouveau contrat (pas de shim sur le legacy
|
| 17 |
-
``picarones.engines``).
|
| 18 |
"""
|
| 19 |
|
| 20 |
from __future__ import annotations
|
|
|
|
| 1 |
+
"""Adapters OCR — couche 5 (libs externes autorisées).
|
| 2 |
|
| 3 |
+
Contrat ``BaseOCRAdapter`` exprimé en termes du ``ArtifactType``
|
| 4 |
+
et de l'interface ``execute(inputs, params, context)`` consommée
|
| 5 |
+
par ``PipelineExecutor``.
|
|
|
|
| 6 |
|
| 7 |
Implémentations livrées
|
| 8 |
-----------------------
|
| 9 |
+
- ``TesseractAdapter`` — Tesseract 5 (OSS, CPU-bound).
|
| 10 |
+
- ``PeroOCRAdapter`` — Pero OCR (manuscrits, GPU recommandé).
|
| 11 |
+
- ``MistralOCRAdapter`` — Mistral OCR API (cloud).
|
| 12 |
+
- ``GoogleVisionAdapter`` — Google Vision API (cloud).
|
| 13 |
+
- ``AzureDocIntelAdapter`` — Azure Document Intelligence (cloud).
|
| 14 |
- ``PrecomputedTextAdapter`` — lit un texte OCR pré-calculé depuis
|
| 15 |
le filesystem. Cas BnF : comparer N transcriptions déjà produites
|
| 16 |
par d'autres outils sans relancer d'OCR.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
"""
|
| 18 |
|
| 19 |
from __future__ import annotations
|
|
@@ -1,10 +1,6 @@
|
|
| 1 |
"""``AzureDocIntelAdapter`` natif — Sprint A14-S34.
|
| 2 |
-
|
| 3 |
-
Migration native du legacy ``picarones.engines.azure_doc_intel`` vers
|
| 4 |
``BaseOCRAdapter`` (S26). **Pas un shim**.
|
| 5 |
|
| 6 |
-
Le legacy reste en place jusqu'au S46.
|
| 7 |
-
|
| 8 |
Cas d'usage BnF
|
| 9 |
---------------
|
| 10 |
Azure Document Intelligence (anciennement Form Recognizer) propose
|
|
@@ -52,7 +48,7 @@ Comportement
|
|
| 52 |
|
| 53 |
Anti-sur-ingénierie
|
| 54 |
-------------------
|
| 55 |
-
- Pas d'extraction de confidences (
|
| 56 |
- Pas de support multi-langue dans une même requête.
|
| 57 |
- Pas de retry au-delà du polling (qui est un retry implicite).
|
| 58 |
"""
|
|
|
|
| 1 |
"""``AzureDocIntelAdapter`` natif — Sprint A14-S34.
|
|
|
|
|
|
|
| 2 |
``BaseOCRAdapter`` (S26). **Pas un shim**.
|
| 3 |
|
|
|
|
|
|
|
| 4 |
Cas d'usage BnF
|
| 5 |
---------------
|
| 6 |
Azure Document Intelligence (anciennement Form Recognizer) propose
|
|
|
|
| 48 |
|
| 49 |
Anti-sur-ingénierie
|
| 50 |
-------------------
|
| 51 |
+
- Pas d'extraction de confidences (à ajouter quand un caller en aura besoin).
|
| 52 |
- Pas de support multi-langue dans une même requête.
|
| 53 |
- Pas de retry au-delà du polling (qui est un retry implicite).
|
| 54 |
"""
|
|
@@ -1,12 +1,4 @@
|
|
| 1 |
-
"""``BaseOCRAdapter`` — contrat
|
| 2 |
-
|
| 3 |
-
Sprint A14-S26 du rewrite ciblé.
|
| 4 |
-
|
| 5 |
-
Ce module définit le contrat **propre** auquel un adapter OCR du
|
| 6 |
-
nouveau monde doit se conformer pour être utilisable comme step
|
| 7 |
-
d'une pipeline ``picarones.pipeline``. Pas hérité du legacy
|
| 8 |
-
``picarones.engines.base.BaseOCREngine`` — c'est un nouveau contrat,
|
| 9 |
-
sans dette technique, exprimé en termes du nouveau ``ArtifactType``.
|
| 10 |
|
| 11 |
Contrat
|
| 12 |
-------
|
|
@@ -22,23 +14,9 @@ Un adapter OCR :
|
|
| 22 |
- Implémente
|
| 23 |
``execute(inputs, params, context) -> dict[ArtifactType, Artifact]``.
|
| 24 |
|
| 25 |
-
Le ``Artifact`` retourné porte une ``uri`` filesystem —
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
direct, mais les artefacts produits par les adapters sont stockés
|
| 29 |
-
sur disque pour traçabilité et streaming).
|
| 30 |
-
|
| 31 |
-
Différences avec le legacy
|
| 32 |
-
--------------------------
|
| 33 |
-
- ``ArtifactType.RAW_TEXT`` (10 valeurs) au lieu de
|
| 34 |
-
``ArtifactType.TEXT`` (6 valeurs legacy).
|
| 35 |
-
- Pas de ``run(image_path)`` historique — un seul point d'entrée
|
| 36 |
-
``execute()``.
|
| 37 |
-
- Pas de wrapper ``EngineResult`` — les erreurs lèvent directement,
|
| 38 |
-
le ``PipelineExecutor`` les capture en step en échec.
|
| 39 |
-
- Pas de ``_run_ocr`` / ``_run_with_native`` / ``_extract_raw_confidences``
|
| 40 |
-
— les confidences (S42 legacy) sont reportées à un sprint dédié
|
| 41 |
-
où l'on définira un ``ConfidenceArtifact`` typé.
|
| 42 |
|
| 43 |
Anti-sur-ingénierie
|
| 44 |
-------------------
|
|
|
|
| 1 |
+
"""``BaseOCRAdapter`` — contrat pour un adapter OCR (couche 5).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
Contrat
|
| 4 |
-------
|
|
|
|
| 14 |
- Implémente
|
| 15 |
``execute(inputs, params, context) -> dict[ArtifactType, Artifact]``.
|
| 16 |
|
| 17 |
+
Le ``Artifact`` retourné porte une ``uri`` filesystem — convention
|
| 18 |
+
qui permet au ``payload_loader`` de le lire ultérieurement et
|
| 19 |
+
garantit la traçabilité et le streaming.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
Anti-sur-ingénierie
|
| 22 |
-------------------
|
|
@@ -1,9 +1,6 @@
|
|
| 1 |
"""Factory canonique : instancier un ``BaseOCRAdapter`` par nom court.
|
| 2 |
|
| 3 |
Sprint H.2.b du plan v2.0 — équivalent canonique de
|
| 4 |
-
``picarones.adapters.legacy_engines.factory.engine_from_name`` qui
|
| 5 |
-
retournait des ``BaseOCREngine`` (legacy, ``run(image_path) →
|
| 6 |
-
EngineResult``). Cette factory retourne des ``BaseOCRAdapter``
|
| 7 |
(rewrite, ``StepExecutor`` Protocol, ``execute(inputs, params,
|
| 8 |
context) → dict[ArtifactType, Artifact]``).
|
| 9 |
|
|
@@ -15,14 +12,6 @@ Vit en couche 5 (``picarones.adapters.ocr``) plutôt qu'en
|
|
| 15 |
Cette factory ne dépend d'aucune brique de couche supérieure
|
| 16 |
(pas de ``click``, pas de FastAPI).
|
| 17 |
|
| 18 |
-
Migration depuis le legacy
|
| 19 |
-
--------------------------
|
| 20 |
-
Code legacy ::
|
| 21 |
-
|
| 22 |
-
from picarones.adapters.legacy_engines.factory import engine_from_name
|
| 23 |
-
engine = engine_from_name("tesseract", lang="fra", psm=6)
|
| 24 |
-
# engine est un BaseOCREngine, à wrapper via LegacyOCREngineExecutor
|
| 25 |
-
# avant de pouvoir être consommé par PipelineExecutor.
|
| 26 |
|
| 27 |
Code canonique équivalent ::
|
| 28 |
|
|
|
|
| 1 |
"""Factory canonique : instancier un ``BaseOCRAdapter`` par nom court.
|
| 2 |
|
| 3 |
Sprint H.2.b du plan v2.0 — équivalent canonique de
|
|
|
|
|
|
|
|
|
|
| 4 |
(rewrite, ``StepExecutor`` Protocol, ``execute(inputs, params,
|
| 5 |
context) → dict[ArtifactType, Artifact]``).
|
| 6 |
|
|
|
|
| 12 |
Cette factory ne dépend d'aucune brique de couche supérieure
|
| 13 |
(pas de ``click``, pas de FastAPI).
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
Code canonique équivalent ::
|
| 17 |
|
|
@@ -1,10 +1,5 @@
|
|
| 1 |
"""``GoogleVisionAdapter`` natif — Sprint A14-S33.
|
| 2 |
|
| 3 |
-
Migration native du legacy ``picarones.engines.google_vision.GoogleVisionEngine``
|
| 4 |
-
vers le contrat ``BaseOCRAdapter`` (S26). **Pas un shim**.
|
| 5 |
-
|
| 6 |
-
Le legacy reste en place jusqu'au S46.
|
| 7 |
-
|
| 8 |
Cas d'usage BnF
|
| 9 |
---------------
|
| 10 |
Google Cloud Vision propose deux modes d'OCR :
|
|
@@ -36,7 +31,7 @@ disponible.
|
|
| 36 |
|
| 37 |
Anti-sur-ingénierie
|
| 38 |
-------------------
|
| 39 |
-
- Pas d'extraction de confidences (
|
| 40 |
- Pas de pré-validation du JSON service account — le SDK le fait.
|
| 41 |
- Pas de support batch — un appel par image.
|
| 42 |
"""
|
|
|
|
| 1 |
"""``GoogleVisionAdapter`` natif — Sprint A14-S33.
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
Cas d'usage BnF
|
| 4 |
---------------
|
| 5 |
Google Cloud Vision propose deux modes d'OCR :
|
|
|
|
| 31 |
|
| 32 |
Anti-sur-ingénierie
|
| 33 |
-------------------
|
| 34 |
+
- Pas d'extraction de confidences (à ajouter quand un caller en aura besoin).
|
| 35 |
- Pas de pré-validation du JSON service account — le SDK le fait.
|
| 36 |
- Pas de support batch — un appel par image.
|
| 37 |
"""
|
|
@@ -1,11 +1,6 @@
|
|
| 1 |
"""``MistralOCRAdapter`` natif — Sprint A14-S32.
|
| 2 |
-
|
| 3 |
-
Migration native du legacy ``picarones.engines.mistral_ocr.MistralOCREngine``
|
| 4 |
-
vers le contrat ``BaseOCRAdapter`` (S26). **Pas un shim** : la classe
|
| 5 |
implémente directement le contrat du nouveau monde.
|
| 6 |
|
| 7 |
-
Le legacy ``MistralOCREngine`` reste en place jusqu'au S46.
|
| 8 |
-
|
| 9 |
Cas d'usage BnF
|
| 10 |
---------------
|
| 11 |
Mistral AI fournit deux familles d'OCR :
|
|
@@ -47,8 +42,7 @@ Comportement
|
|
| 47 |
Anti-sur-ingénierie
|
| 48 |
-------------------
|
| 49 |
- Pas de retry / backoff (le caller wrappe si besoin).
|
| 50 |
-
- Pas d'extraction de confidences (
|
| 51 |
-
sprint ``ConfidenceArtifact``).
|
| 52 |
- Pas de support multi-page (l'image est traitée comme une seule
|
| 53 |
page d'entrée — Mistral OCR retourne une liste de pages dont on
|
| 54 |
concatène les markdowns).
|
|
|
|
| 1 |
"""``MistralOCRAdapter`` natif — Sprint A14-S32.
|
|
|
|
|
|
|
|
|
|
| 2 |
implémente directement le contrat du nouveau monde.
|
| 3 |
|
|
|
|
|
|
|
| 4 |
Cas d'usage BnF
|
| 5 |
---------------
|
| 6 |
Mistral AI fournit deux familles d'OCR :
|
|
|
|
| 42 |
Anti-sur-ingénierie
|
| 43 |
-------------------
|
| 44 |
- Pas de retry / backoff (le caller wrappe si besoin).
|
| 45 |
+
- Pas d'extraction de confidences (à ajouter quand un caller en aura besoin).
|
|
|
|
| 46 |
- Pas de support multi-page (l'image est traitée comme une seule
|
| 47 |
page d'entrée — Mistral OCR retourne une liste de pages dont on
|
| 48 |
concatène les markdowns).
|
|
@@ -1,12 +1,5 @@
|
|
| 1 |
"""``PeroOCRAdapter`` natif — Sprint A14-S31.
|
| 2 |
-
|
| 3 |
-
Migration native du legacy ``picarones.engines.pero_ocr.PeroOCREngine``
|
| 4 |
-
vers le contrat ``BaseOCRAdapter`` (S26). **Pas un shim** : la classe
|
| 5 |
implémente directement le contrat du nouveau monde, sans héritage du
|
| 6 |
-
legacy.
|
| 7 |
-
|
| 8 |
-
Le legacy ``PeroOCREngine`` reste en place pour les callers qui
|
| 9 |
-
n'ont pas encore migré ; sa suppression viendra au S46 quand la
|
| 10 |
parité sera atteinte sur tous les adapters.
|
| 11 |
|
| 12 |
Cas d'usage BnF
|
|
@@ -42,8 +35,7 @@ Comportement
|
|
| 42 |
Anti-sur-ingénierie
|
| 43 |
-------------------
|
| 44 |
- Pas de support GPU explicite (Pero OCR le gère via la config).
|
| 45 |
-
- Pas de retry, pas d'extraction de confidences (
|
| 46 |
-
reportées au sprint ``ConfidenceArtifact``).
|
| 47 |
- ``_parser`` lazy-init — si l'instance est sérialisée pour
|
| 48 |
ProcessPool, le parser est re-instancié dans le worker (cohérent
|
| 49 |
avec Pero OCR qui charge ses modèles à l'instanciation).
|
|
|
|
| 1 |
"""``PeroOCRAdapter`` natif — Sprint A14-S31.
|
|
|
|
|
|
|
|
|
|
| 2 |
implémente directement le contrat du nouveau monde, sans héritage du
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
parité sera atteinte sur tous les adapters.
|
| 4 |
|
| 5 |
Cas d'usage BnF
|
|
|
|
| 35 |
Anti-sur-ingénierie
|
| 36 |
-------------------
|
| 37 |
- Pas de support GPU explicite (Pero OCR le gère via la config).
|
| 38 |
+
- Pas de retry, pas d'extraction de confidences (à ajouter quand un caller en aura besoin).
|
|
|
|
| 39 |
- ``_parser`` lazy-init — si l'instance est sérialisée pour
|
| 40 |
ProcessPool, le parser est re-instancié dans le worker (cohérent
|
| 41 |
avec Pero OCR qui charge ses modèles à l'instanciation).
|
|
@@ -1,13 +1,7 @@
|
|
| 1 |
-
"""``TesseractAdapter`` natif
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
implémente directement le contrat du nouveau monde, sans héritage du
|
| 6 |
-
legacy.
|
| 7 |
-
|
| 8 |
-
Le legacy ``TesseractEngine`` reste en place pour les callers qui
|
| 9 |
-
n'ont pas encore migré ; sa suppression viendra au S46 quand la
|
| 10 |
-
parité sera atteinte sur tous les adapters.
|
| 11 |
|
| 12 |
Cas d'usage BnF
|
| 13 |
---------------
|
|
@@ -52,10 +46,9 @@ Anti-sur-ingénierie
|
|
| 52 |
-------------------
|
| 53 |
- Pas de retry — Tesseract échoue rarement sur une image valide,
|
| 54 |
et un appelant peut wrapper si besoin.
|
| 55 |
-
- Pas d'extraction de confidences
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
``picarones.engines.tesseract.TesseractEngine`` jusqu'au S46.
|
| 59 |
- Pas de validation de l'encodage de l'image — Tesseract gère.
|
| 60 |
- Pas de support batch — un appel par image (le runner gère le
|
| 61 |
parallélisme inter-documents).
|
|
|
|
| 1 |
+
"""``TesseractAdapter`` — adapter natif pour Tesseract 5.
|
| 2 |
|
| 3 |
+
Implémente le contrat ``BaseOCRAdapter`` (couche 5) :
|
| 4 |
+
``execute(inputs, params, context) → dict[ArtifactType, Artifact]``.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
Cas d'usage BnF
|
| 7 |
---------------
|
|
|
|
| 46 |
-------------------
|
| 47 |
- Pas de retry — Tesseract échoue rarement sur une image valide,
|
| 48 |
et un appelant peut wrapper si besoin.
|
| 49 |
+
- Pas d'extraction de confidences pour l'instant : à ajouter
|
| 50 |
+
quand un caller en aura besoin (un ``ConfidenceArtifact`` typé
|
| 51 |
+
reste à définir).
|
|
|
|
| 52 |
- Pas de validation de l'encodage de l'image — Tesseract gère.
|
| 53 |
- Pas de support batch — un appel par image (le runner gère le
|
| 54 |
parallélisme inter-documents).
|
|
@@ -25,7 +25,7 @@ abstraction ABC.
|
|
| 25 |
Cibles à venir
|
| 26 |
--------------
|
| 27 |
- S37 : déplacement de ``picarones.web.jobs`` (SQLite job store).
|
| 28 |
-
- Post-livraison : ``picarones.
|
| 29 |
history) et stores distribués (S3, GCS, …).
|
| 30 |
"""
|
| 31 |
|
|
|
|
| 25 |
Cibles à venir
|
| 26 |
--------------
|
| 27 |
- S37 : déplacement de ``picarones.web.jobs`` (SQLite job store).
|
| 28 |
+
- Post-livraison : ``picarones.evaluation.metrics.history`` (SQLite
|
| 29 |
history) et stores distribués (S3, GCS, …).
|
| 30 |
"""
|
| 31 |
|
|
@@ -1,34 +1,19 @@
|
|
| 1 |
-
"""
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
``picarones.
|
| 6 |
-
|
| 7 |
-
``
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
complète avec progress callback, output_json, partial_dir.
|
| 18 |
-
5. (sub-phase D.1.e) Tests d'équivalence numérique (CER/WER) entre
|
| 19 |
-
les deux runners sur les fixtures.
|
| 20 |
-
|
| 21 |
-
Trace de retrait
|
| 22 |
-
----------------
|
| 23 |
-
Ce module est **transitoire** (Sprint D du plan v2.0). Il sera
|
| 24 |
-
supprimé en D.6 quand tous les callers (cli/_workflows,
|
| 25 |
-
web/benchmark_utils) consommeront ``BenchmarkService``
|
| 26 |
-
directement.
|
| 27 |
-
|
| 28 |
-
Cette première itération n'expose que les helpers de mapping
|
| 29 |
-
documents/corpus — la fonction publique
|
| 30 |
-
``run_benchmark_via_service`` arrive dans une session ultérieure
|
| 31 |
-
quand toutes les briques seront en place.
|
| 32 |
"""
|
| 33 |
|
| 34 |
from __future__ import annotations
|
|
@@ -37,11 +22,6 @@ import logging
|
|
| 37 |
from pathlib import Path
|
| 38 |
from typing import TYPE_CHECKING, Any, Callable
|
| 39 |
|
| 40 |
-
# Sprint H.2.c.1 — ``LegacyOCREngineExecutor`` n'est plus consommé :
|
| 41 |
-
# tous les callers passent désormais des ``BaseOCRAdapter`` canoniques
|
| 42 |
-
# (déjà ``StepExecutor`` natifs). L'import est retiré ; le code path
|
| 43 |
-
# legacy de ``build_adapter_resolver`` est désormais inaccessible et
|
| 44 |
-
# peut être supprimé en H.2.c.2.
|
| 45 |
from picarones.domain.artifacts import ArtifactType
|
| 46 |
from picarones.domain.corpus import CorpusSpec
|
| 47 |
from picarones.domain.documents import DocumentRef, GroundTruthRef
|
|
@@ -58,15 +38,15 @@ if TYPE_CHECKING:
|
|
| 58 |
|
| 59 |
logger = logging.getLogger(__name__)
|
| 60 |
|
| 61 |
-
#
|
| 62 |
-
#
|
| 63 |
-
#
|
| 64 |
-
# ``
|
| 65 |
-
#
|
| 66 |
|
| 67 |
|
| 68 |
# ──────────────────────────────────────────────────────────────────────
|
| 69 |
-
# Mapping Document
|
| 70 |
# ──────────────────────────────────────────────────────────────────────
|
| 71 |
|
| 72 |
|
|
@@ -75,9 +55,9 @@ def document_to_document_ref(
|
|
| 75 |
*,
|
| 76 |
workspace_dir: Path,
|
| 77 |
) -> DocumentRef:
|
| 78 |
-
"""Convertit un ``Document``
|
| 79 |
|
| 80 |
-
Le ``Document``
|
| 81 |
et ``ground_truths: dict[ArtifactType, GTPayload]``). Le
|
| 82 |
``DocumentRef`` rewrite porte des références filesystem
|
| 83 |
(``GroundTruthRef.uri``). La conversion écrit chaque GT
|
|
@@ -86,7 +66,7 @@ def document_to_document_ref(
|
|
| 86 |
Parameters
|
| 87 |
----------
|
| 88 |
document:
|
| 89 |
-
Document
|
| 90 |
``ground_truth`` (TEXT) peut être vide.
|
| 91 |
workspace_dir:
|
| 92 |
Répertoire de travail où écrire les fichiers GT
|
|
@@ -171,7 +151,7 @@ def corpus_to_corpus_spec(
|
|
| 171 |
*,
|
| 172 |
workspace_dir: Path,
|
| 173 |
) -> CorpusSpec:
|
| 174 |
-
"""Convertit un ``Corpus``
|
| 175 |
|
| 176 |
Itère sur ``corpus.documents`` et applique
|
| 177 |
``document_to_document_ref`` pour chacun.
|
|
@@ -179,7 +159,7 @@ def corpus_to_corpus_spec(
|
|
| 179 |
Parameters
|
| 180 |
----------
|
| 181 |
corpus:
|
| 182 |
-
Corpus
|
| 183 |
workspace_dir:
|
| 184 |
Répertoire de travail où écrire les fichiers GT
|
| 185 |
synthétisés (typiquement un ``tempfile.TemporaryDirectory``
|
|
@@ -219,7 +199,7 @@ def corpus_to_corpus_spec(
|
|
| 219 |
|
| 220 |
|
| 221 |
# ──────────────────────────────────────────────────────────────────────
|
| 222 |
-
# Mapping RunResult
|
| 223 |
# ──────────────────────────────────────────────────────────────────────
|
| 224 |
|
| 225 |
|
|
@@ -231,7 +211,7 @@ def run_result_to_benchmark_result(
|
|
| 231 |
char_exclude: Any | None = None,
|
| 232 |
normalization_profile: Any | None = None,
|
| 233 |
) -> Any:
|
| 234 |
-
"""Transpose un ``RunResult``
|
| 235 |
|
| 236 |
Le mapping est en **transposition** :
|
| 237 |
|
|
@@ -249,7 +229,7 @@ def run_result_to_benchmark_result(
|
|
| 249 |
3. Lit l'``ocr_intermediate`` (RAW_TEXT) si le pipeline a un
|
| 250 |
step OCR amont.
|
| 251 |
4. Calcule les métriques CER/WER via ``compute_metrics``.
|
| 252 |
-
5. Construit un ``DocumentResult``
|
| 253 |
extrait des ``step_results``.
|
| 254 |
6. Aggrège les métriques par engine via ``aggregate_metrics``.
|
| 255 |
7. Reconstitue ``pipeline_info`` pour les engines pipeline
|
|
@@ -260,11 +240,11 @@ def run_result_to_benchmark_result(
|
|
| 260 |
run_result:
|
| 261 |
``RunResult`` produit par ``BenchmarkService.run``.
|
| 262 |
corpus:
|
| 263 |
-
Corpus
|
| 264 |
et l'``image_path`` pour chaque document, dans le même ordre
|
| 265 |
que ``run_result.document_results``.
|
| 266 |
engines:
|
| 267 |
-
Liste d'
|
| 268 |
passées à ``BenchmarkService.run`` (l'ordre détermine
|
| 269 |
l'index dans ``RunDocumentResult.pipeline_results``).
|
| 270 |
char_exclude:
|
|
@@ -275,7 +255,7 @@ def run_result_to_benchmark_result(
|
|
| 275 |
Returns
|
| 276 |
-------
|
| 277 |
BenchmarkResult
|
| 278 |
-
Format
|
| 279 |
(rapport HTML, persistance JSON, narrative engine).
|
| 280 |
"""
|
| 281 |
from picarones.evaluation.benchmark_result import (
|
|
@@ -413,14 +393,14 @@ def _build_pipeline_metadata(
|
|
| 413 |
ground_truth: str = "",
|
| 414 |
hypothesis: str = "",
|
| 415 |
) -> dict:
|
| 416 |
-
"""Reconstitue les ``pipeline_metadata``
|
| 417 |
|
| 418 |
Sprint D.2.d — pour les pipelines composées OCR+LLM, calcule
|
| 419 |
``over_normalization`` (détection des cas où le LLM a sur-normalisé
|
| 420 |
le texte par rapport à la GT) si ``ocr_intermediate`` est
|
| 421 |
disponible. Equivalent fonctionnel de
|
| 422 |
-
|
| 423 |
-
|
| 424 |
"""
|
| 425 |
if not getattr(engine, "is_pipeline", False):
|
| 426 |
return {}
|
|
@@ -428,7 +408,7 @@ def _build_pipeline_metadata(
|
|
| 428 |
"pipeline_mode": getattr(engine, "mode", None),
|
| 429 |
"is_pipeline": True,
|
| 430 |
}
|
| 431 |
-
# mode peut être un Enum
|
| 432 |
mode = metadata["pipeline_mode"]
|
| 433 |
if mode is not None and hasattr(mode, "value"):
|
| 434 |
metadata["pipeline_mode"] = mode.value
|
|
@@ -472,7 +452,7 @@ def _build_pipeline_info(engine: Any) -> dict:
|
|
| 472 |
info["llm_provider"] = llm_adapter.name
|
| 473 |
mode = getattr(engine, "mode", None)
|
| 474 |
if mode is not None:
|
| 475 |
-
# Tolère enum (
|
| 476 |
info["mode"] = mode.value if hasattr(mode, "value") else mode
|
| 477 |
prompt_path = getattr(engine, "prompt_path", None)
|
| 478 |
if prompt_path is not None:
|
|
@@ -498,12 +478,12 @@ def _safe_engine_version(engine: Any) -> str:
|
|
| 498 |
|
| 499 |
def _is_canonical_adapter(engine: Any) -> bool:
|
| 500 |
"""Détecte si ``engine`` est un ``BaseOCRAdapter`` canonique
|
| 501 |
-
(par opposition
|
| 502 |
|
| 503 |
Duck-typing tolérant : un objet est canonical s'il expose
|
| 504 |
``execute``, ``input_types``, ``output_types`` (les trois
|
| 505 |
attributs requis par le contrat ``StepExecutor``) ET n'a pas
|
| 506 |
-
le marker
|
| 507 |
"""
|
| 508 |
from picarones.adapters.ocr.base import BaseOCRAdapter
|
| 509 |
return isinstance(engine, BaseOCRAdapter)
|
|
@@ -517,7 +497,7 @@ def _is_canonical_adapter(engine: Any) -> bool:
|
|
| 517 |
def engine_to_pipeline_spec(engine: Any) -> PipelineSpec:
|
| 518 |
"""Convertit un engine en ``PipelineSpec`` rewrite.
|
| 519 |
|
| 520 |
-
Deux cas (
|
| 521 |
été retiré) :
|
| 522 |
|
| 523 |
- **BaseOCRAdapter** (canonique) : spec mono-step consommant
|
|
@@ -546,7 +526,7 @@ def engine_to_pipeline_spec(engine: Any) -> PipelineSpec:
|
|
| 546 |
raise PicaronesError(
|
| 547 |
f"Type d'engine non supporté : {type(engine).__name__}. "
|
| 548 |
"Attendu : ``BaseOCRAdapter`` ou ``OCRLLMPipelineConfig``. "
|
| 549 |
-
"Le support
|
| 550 |
"a été retiré au sprint H.2.c.",
|
| 551 |
)
|
| 552 |
|
|
@@ -584,7 +564,7 @@ def _canonical_adapter_to_spec(adapter: Any) -> PipelineSpec:
|
|
| 584 |
)
|
| 585 |
|
| 586 |
|
| 587 |
-
#
|
| 588 |
# spec mono-step en dur IMAGE → RAW_TEXT) supprimé. Le path
|
| 589 |
# canonique ``_canonical_adapter_to_spec`` couvre tous les cas en
|
| 590 |
# utilisant les ``input_types``/``output_types`` déclarés par
|
|
@@ -592,10 +572,10 @@ def _canonical_adapter_to_spec(adapter: Any) -> PipelineSpec:
|
|
| 592 |
|
| 593 |
|
| 594 |
def _ocr_llm_pipeline_to_spec(pipeline: Any) -> PipelineSpec:
|
| 595 |
-
"""Spec composée pour un ``
|
| 596 |
``OCRLLMPipelineConfig`` canonique (3 modes).
|
| 597 |
|
| 598 |
-
Tolère ``pipeline.mode`` en enum (
|
| 599 |
ou en string (canonique ``"text_only"``).
|
| 600 |
"""
|
| 601 |
mode_attr = pipeline.mode
|
|
@@ -634,7 +614,7 @@ def build_adapter_resolver(
|
|
| 634 |
"""Construit un adapter resolver pour ``PipelineExecutor``.
|
| 635 |
|
| 636 |
Parcourt les engines fournis et associe leur ``name`` à un
|
| 637 |
-
``StepExecutor`` valide (
|
| 638 |
``LegacyOCREngineExecutor`` a été retiré) :
|
| 639 |
|
| 640 |
- **BaseOCRAdapter** : enregistré directement (déjà ``StepExecutor``).
|
|
@@ -700,7 +680,7 @@ def build_adapter_resolver(
|
|
| 700 |
def resolver(name: str) -> Any:
|
| 701 |
if name not in name_to_executor:
|
| 702 |
raise KeyError(
|
| 703 |
-
f"adapter inconnu pour le resolver
|
| 704 |
f"Enregistrés : {sorted(name_to_executor.keys())!r}."
|
| 705 |
)
|
| 706 |
return name_to_executor[name]
|
|
@@ -856,17 +836,17 @@ def run_benchmark_via_service(
|
|
| 856 |
partial_dir: str | Path | None = None,
|
| 857 |
entity_extractor: Callable[[str], list[dict]] | None = None,
|
| 858 |
profile: str = "standard",
|
| 859 |
-
# ---- Paramètres
|
| 860 |
# Sprint D.2 du plan v2.0 — features marginales restantes :
|
| 861 |
# ``max_workers`` (le rewrite a son propre max_in_flight via
|
| 862 |
# ``CorpusRunner``).
|
| 863 |
max_workers: int = 4, # noqa: ARG001
|
| 864 |
) -> Any:
|
| 865 |
-
"""
|
| 866 |
``BenchmarkService`` rewrite.
|
| 867 |
|
| 868 |
Présente la signature historique de
|
| 869 |
-
``picarones.
|
| 870 |
en interne sur le rewrite (``CorpusSpec``, ``PipelineSpec``,
|
| 871 |
``PipelineExecutor``, ``BenchmarkService``). Pivot du Sprint D
|
| 872 |
du plan v2.0.
|
|
@@ -880,7 +860,7 @@ def run_benchmark_via_service(
|
|
| 880 |
- Un ``Corpus`` avec image_path + ground_truth (TEXT) par doc.
|
| 881 |
- Métriques CER/WER calculées via ``compute_metrics`` sur les
|
| 882 |
hypothèses extraites des artefacts produits.
|
| 883 |
-
- Conversion en ``BenchmarkResult``
|
| 884 |
consommateurs historiques (rapport HTML, narrative engine).
|
| 885 |
|
| 886 |
Périmètre reporté (D.2)
|
|
@@ -931,16 +911,16 @@ def run_benchmark_via_service(
|
|
| 931 |
Parameters
|
| 932 |
----------
|
| 933 |
corpus:
|
| 934 |
-
Corpus
|
| 935 |
engines:
|
| 936 |
-
Liste d'engines/pipelines
|
| 937 |
char_exclude:
|
| 938 |
Filtre passé à ``compute_metrics``.
|
| 939 |
normalization_profile:
|
| 940 |
Profil de normalisation passé à ``compute_metrics``.
|
| 941 |
output_json:
|
| 942 |
Si fourni, le ``BenchmarkResult`` est sérialisé en JSON
|
| 943 |
-
à ce chemin (
|
| 944 |
code_version:
|
| 945 |
Version du code injectée dans le ``RunContext`` /
|
| 946 |
``RunManifest``. Défaut : ``picarones.__version__``.
|
|
@@ -950,7 +930,7 @@ def run_benchmark_via_service(
|
|
| 950 |
Returns
|
| 951 |
-------
|
| 952 |
BenchmarkResult
|
| 953 |
-
Format
|
| 954 |
|
| 955 |
Raises
|
| 956 |
------
|
|
@@ -1003,7 +983,7 @@ def run_benchmark_via_service(
|
|
| 1003 |
|
| 1004 |
# D.2.e : NER attach post-process. Idempotent — re-calcule à
|
| 1005 |
# chaque run même en mode resume (les ner_metrics ne sont pas
|
| 1006 |
-
# persistées dans le partial NDJSON
|
| 1007 |
# qui calculait NER après le doc loop).
|
| 1008 |
if entity_extractor is not None:
|
| 1009 |
_attach_ner_metrics_to_benchmark(
|
|
@@ -1084,8 +1064,8 @@ def _aggregate_ner_metrics(doc_results: list) -> dict | None:
|
|
| 1084 |
compteurs totaux d'hallucinations et d'entités manquées.
|
| 1085 |
|
| 1086 |
Equivalent fonctionnel de
|
| 1087 |
-
``picarones.
|
| 1088 |
-
(
|
| 1089 |
"""
|
| 1090 |
relevant = [
|
| 1091 |
dr for dr in doc_results if dr.ner_metrics is not None
|
|
@@ -1246,7 +1226,7 @@ def _run_benchmark_with_partial(
|
|
| 1246 |
|
| 1247 |
for engine in engines:
|
| 1248 |
# Vérifier la cancellation entre engines (matche la
|
| 1249 |
-
# sémantique
|
| 1250 |
# cours, conserve les partials, ne démarre pas le suivant).
|
| 1251 |
if cancel_event is not None and getattr(
|
| 1252 |
cancel_event, "is_set", lambda: False,
|
|
@@ -1377,7 +1357,7 @@ def _execute_via_benchmark_service(
|
|
| 1377 |
Vues passées en liste vide — les métriques sont calculées
|
| 1378 |
côté converter D.1.c via ``compute_metrics`` directement sur
|
| 1379 |
les hypothèses extraites des artefacts. Pattern simple,
|
| 1380 |
-
cohérent
|
| 1381 |
moment du benchmark (pas via ``EvaluationView``).
|
| 1382 |
"""
|
| 1383 |
from picarones.app.services.benchmark_service import BenchmarkService
|
|
@@ -1399,7 +1379,7 @@ def _execute_via_benchmark_service(
|
|
| 1399 |
|
| 1400 |
# ViewExecutor minimal : registres vides.
|
| 1401 |
# Pas de calcul de ``ViewResult`` ici — le converter D.1.c
|
| 1402 |
-
# calcule les métriques
|
| 1403 |
# directement sur les hypothèses extraites des artefacts.
|
| 1404 |
view_executor = DefaultEvaluationViewExecutor.from_registries(
|
| 1405 |
metric_registry=MetricRegistry(),
|
|
@@ -1439,7 +1419,7 @@ def _execute_via_benchmark_service(
|
|
| 1439 |
# Sprint D.2.a : le hook ``progress_callback`` est appelé ici —
|
| 1440 |
# ``context_factory`` est invoqué une fois par (doc, pipeline)
|
| 1441 |
# AVANT l'exécution effective, ce qui correspond à la sémantique
|
| 1442 |
-
#
|
| 1443 |
import threading
|
| 1444 |
|
| 1445 |
counter_lock = threading.Lock()
|
|
@@ -1452,7 +1432,7 @@ def _execute_via_benchmark_service(
|
|
| 1452 |
with counter_lock:
|
| 1453 |
idx = counter_state["doc_idx"]
|
| 1454 |
counter_state["doc_idx"] = idx + 1
|
| 1455 |
-
# Sémantique
|
| 1456 |
# plutôt que le nom de la pipeline (qui inclut le préfixe
|
| 1457 |
# ``ocr_only_``). Le mapping est fourni par le caller.
|
| 1458 |
engine_name = (
|
|
@@ -1463,7 +1443,7 @@ def _execute_via_benchmark_service(
|
|
| 1463 |
try:
|
| 1464 |
progress_callback(engine_name, idx, doc.id)
|
| 1465 |
except Exception: # noqa: BLE001
|
| 1466 |
-
#
|
| 1467 |
# callback (un caller qui crashe ne doit pas faire
|
| 1468 |
# tomber le benchmark). Même contrat ici.
|
| 1469 |
pass
|
|
@@ -1502,7 +1482,7 @@ def _execute_via_benchmark_service(
|
|
| 1502 |
def _persist_benchmark_result_json(
|
| 1503 |
benchmark_result: Any, output_path: Path,
|
| 1504 |
) -> None:
|
| 1505 |
-
"""Sérialise un ``BenchmarkResult``
|
| 1506 |
|
| 1507 |
Utilise la méthode ``to_json``/``compact``/``asdict`` selon la
|
| 1508 |
surface disponible. Ce helper duplique la logique de
|
|
@@ -1512,7 +1492,7 @@ def _persist_benchmark_result_json(
|
|
| 1512 |
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 1513 |
# ``BenchmarkResult`` est un dataclass — dataclasses.asdict
|
| 1514 |
# sérialise récursivement. Le format n'est pas forcément
|
| 1515 |
-
# identique octet pour octet à la sortie
|
| 1516 |
# compatible avec les consommateurs (rapport, narrative).
|
| 1517 |
import dataclasses
|
| 1518 |
import json
|
|
|
|
| 1 |
+
"""Entry point CLI/web — façade ``run_benchmark_via_service``.
|
| 2 |
+
|
| 3 |
+
Présente l'API mono-call ``run_benchmark_via_service(corpus,
|
| 4 |
+
engines, ...)`` consommée par ``picarones.interfaces.cli`` et
|
| 5 |
+
``picarones.interfaces.web``. S'appuie en interne sur le service
|
| 6 |
+
canonique (``BenchmarkService``, ``PipelineExecutor``,
|
| 7 |
+
``CorpusRunner``).
|
| 8 |
+
|
| 9 |
+
Pourquoi cette façade
|
| 10 |
+
---------------------
|
| 11 |
+
``BenchmarkService`` consomme ``CorpusSpec`` (références
|
| 12 |
+
filesystem, Pydantic, immutable) et ``PipelineSpec`` (déclaratif).
|
| 13 |
+
Les interfaces utilisateur (CLI, web upload) raisonnent en
|
| 14 |
+
``Corpus`` riche en behavior + liste de moteurs OCR/LLM. Ce
|
| 15 |
+
module fait la conversion entre les deux modèles, expose une API
|
| 16 |
+
mono-call ergonomique et restitue un ``BenchmarkResult``.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
"""
|
| 18 |
|
| 19 |
from __future__ import annotations
|
|
|
|
| 22 |
from pathlib import Path
|
| 23 |
from typing import TYPE_CHECKING, Any, Callable
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
from picarones.domain.artifacts import ArtifactType
|
| 26 |
from picarones.domain.corpus import CorpusSpec
|
| 27 |
from picarones.domain.documents import DocumentRef, GroundTruthRef
|
|
|
|
| 38 |
|
| 39 |
logger = logging.getLogger(__name__)
|
| 40 |
|
| 41 |
+
# Le ``OCRLLMPipelineConfig`` (couche 4) est consommé exclusivement
|
| 42 |
+
# par duck typing (``is_pipeline``, ``ocr_adapter``, ``llm_adapter``,
|
| 43 |
+
# ``mode``, ``prompt_template``) pour respecter l'inward-only :
|
| 44 |
+
# ``app/`` ne doit pas importer ``pipeline/llm_pipeline_config``
|
| 45 |
+
# directement.
|
| 46 |
|
| 47 |
|
| 48 |
# ──────────────────────────────────────────────────────────────────────
|
| 49 |
+
# Mapping Document → DocumentRef
|
| 50 |
# ──────────────────────────────────────────────────────────────────────
|
| 51 |
|
| 52 |
|
|
|
|
| 55 |
*,
|
| 56 |
workspace_dir: Path,
|
| 57 |
) -> DocumentRef:
|
| 58 |
+
"""Convertit un ``Document`` (couche 3) en ``DocumentRef`` (couche 1).
|
| 59 |
|
| 60 |
+
Le ``Document`` (modèle riche) porte sa GT en mémoire (``ground_truth: str``
|
| 61 |
et ``ground_truths: dict[ArtifactType, GTPayload]``). Le
|
| 62 |
``DocumentRef`` rewrite porte des références filesystem
|
| 63 |
(``GroundTruthRef.uri``). La conversion écrit chaque GT
|
|
|
|
| 66 |
Parameters
|
| 67 |
----------
|
| 68 |
document:
|
| 69 |
+
Document. ``image_path`` non-``None`` est requis ;
|
| 70 |
``ground_truth`` (TEXT) peut être vide.
|
| 71 |
workspace_dir:
|
| 72 |
Répertoire de travail où écrire les fichiers GT
|
|
|
|
| 151 |
*,
|
| 152 |
workspace_dir: Path,
|
| 153 |
) -> CorpusSpec:
|
| 154 |
+
"""Convertit un ``Corpus`` (couche 3) en ``CorpusSpec`` (couche 1).
|
| 155 |
|
| 156 |
Itère sur ``corpus.documents`` et applique
|
| 157 |
``document_to_document_ref`` pour chacun.
|
|
|
|
| 159 |
Parameters
|
| 160 |
----------
|
| 161 |
corpus:
|
| 162 |
+
Corpus.
|
| 163 |
workspace_dir:
|
| 164 |
Répertoire de travail où écrire les fichiers GT
|
| 165 |
synthétisés (typiquement un ``tempfile.TemporaryDirectory``
|
|
|
|
| 199 |
|
| 200 |
|
| 201 |
# ──────────────────────────────────────────────────────────────────────
|
| 202 |
+
# Mapping RunResult → BenchmarkResult
|
| 203 |
# ──────────────────────────────────────────────────────────────────────
|
| 204 |
|
| 205 |
|
|
|
|
| 211 |
char_exclude: Any | None = None,
|
| 212 |
normalization_profile: Any | None = None,
|
| 213 |
) -> Any:
|
| 214 |
+
"""Transpose un ``RunResult`` (couche 4) en ``BenchmarkResult`` (couche 3).
|
| 215 |
|
| 216 |
Le mapping est en **transposition** :
|
| 217 |
|
|
|
|
| 229 |
3. Lit l'``ocr_intermediate`` (RAW_TEXT) si le pipeline a un
|
| 230 |
step OCR amont.
|
| 231 |
4. Calcule les métriques CER/WER via ``compute_metrics``.
|
| 232 |
+
5. Construit un ``DocumentResult`` avec ``engine_error``
|
| 233 |
extrait des ``step_results``.
|
| 234 |
6. Aggrège les métriques par engine via ``aggregate_metrics``.
|
| 235 |
7. Reconstitue ``pipeline_info`` pour les engines pipeline
|
|
|
|
| 240 |
run_result:
|
| 241 |
``RunResult`` produit par ``BenchmarkService.run``.
|
| 242 |
corpus:
|
| 243 |
+
Corpus d'origine — sert à récupérer le ``ground_truth``
|
| 244 |
et l'``image_path`` pour chaque document, dans le même ordre
|
| 245 |
que ``run_result.document_results``.
|
| 246 |
engines:
|
| 247 |
+
Liste d'adapters dans l'ordre où leurs specs ont été
|
| 248 |
passées à ``BenchmarkService.run`` (l'ordre détermine
|
| 249 |
l'index dans ``RunDocumentResult.pipeline_results``).
|
| 250 |
char_exclude:
|
|
|
|
| 255 |
Returns
|
| 256 |
-------
|
| 257 |
BenchmarkResult
|
| 258 |
+
Format compatible avec les consommateurs historiques
|
| 259 |
(rapport HTML, persistance JSON, narrative engine).
|
| 260 |
"""
|
| 261 |
from picarones.evaluation.benchmark_result import (
|
|
|
|
| 393 |
ground_truth: str = "",
|
| 394 |
hypothesis: str = "",
|
| 395 |
) -> dict:
|
| 396 |
+
"""Reconstitue les ``pipeline_metadata`` pour un DocumentResult.
|
| 397 |
|
| 398 |
Sprint D.2.d — pour les pipelines composées OCR+LLM, calcule
|
| 399 |
``over_normalization`` (détection des cas où le LLM a sur-normalisé
|
| 400 |
le texte par rapport à la GT) si ``ocr_intermediate`` est
|
| 401 |
disponible. Equivalent fonctionnel de
|
| 402 |
+
le calcul historique de DocumentResult
|
| 403 |
+
(supprimé en D.6.b).
|
| 404 |
"""
|
| 405 |
if not getattr(engine, "is_pipeline", False):
|
| 406 |
return {}
|
|
|
|
| 408 |
"pipeline_mode": getattr(engine, "mode", None),
|
| 409 |
"is_pipeline": True,
|
| 410 |
}
|
| 411 |
+
# mode peut être un Enum ou une string (canonique).
|
| 412 |
mode = metadata["pipeline_mode"]
|
| 413 |
if mode is not None and hasattr(mode, "value"):
|
| 414 |
metadata["pipeline_mode"] = mode.value
|
|
|
|
| 452 |
info["llm_provider"] = llm_adapter.name
|
| 453 |
mode = getattr(engine, "mode", None)
|
| 454 |
if mode is not None:
|
| 455 |
+
# Tolère enum (``PipelineMode.X``) ou string.
|
| 456 |
info["mode"] = mode.value if hasattr(mode, "value") else mode
|
| 457 |
prompt_path = getattr(engine, "prompt_path", None)
|
| 458 |
if prompt_path is not None:
|
|
|
|
| 478 |
|
| 479 |
def _is_canonical_adapter(engine: Any) -> bool:
|
| 480 |
"""Détecte si ``engine`` est un ``BaseOCRAdapter`` canonique
|
| 481 |
+
(par opposition aux modèles riches en behavior).
|
| 482 |
|
| 483 |
Duck-typing tolérant : un objet est canonical s'il expose
|
| 484 |
``execute``, ``input_types``, ``output_types`` (les trois
|
| 485 |
attributs requis par le contrat ``StepExecutor``) ET n'a pas
|
| 486 |
+
le marker ``is_pipeline``.
|
| 487 |
"""
|
| 488 |
from picarones.adapters.ocr.base import BaseOCRAdapter
|
| 489 |
return isinstance(engine, BaseOCRAdapter)
|
|
|
|
| 497 |
def engine_to_pipeline_spec(engine: Any) -> PipelineSpec:
|
| 498 |
"""Convertit un engine en ``PipelineSpec`` rewrite.
|
| 499 |
|
| 500 |
+
Deux cas (le path historique ``BaseOCREngine`` a
|
| 501 |
été retiré) :
|
| 502 |
|
| 503 |
- **BaseOCRAdapter** (canonique) : spec mono-step consommant
|
|
|
|
| 526 |
raise PicaronesError(
|
| 527 |
f"Type d'engine non supporté : {type(engine).__name__}. "
|
| 528 |
"Attendu : ``BaseOCRAdapter`` ou ``OCRLLMPipelineConfig``. "
|
| 529 |
+
"Le support historique ``BaseOCREngine`` / ``OCRLLMPipeline`` "
|
| 530 |
"a été retiré au sprint H.2.c.",
|
| 531 |
)
|
| 532 |
|
|
|
|
| 564 |
)
|
| 565 |
|
| 566 |
|
| 567 |
+
# ``_ocr_only_to_spec`` (mappait ``BaseOCREngine`` →
|
| 568 |
# spec mono-step en dur IMAGE → RAW_TEXT) supprimé. Le path
|
| 569 |
# canonique ``_canonical_adapter_to_spec`` couvre tous les cas en
|
| 570 |
# utilisant les ``input_types``/``output_types`` déclarés par
|
|
|
|
| 572 |
|
| 573 |
|
| 574 |
def _ocr_llm_pipeline_to_spec(pipeline: Any) -> PipelineSpec:
|
| 575 |
+
"""Spec composée pour un ``OCRLLMPipelineConfig`` ou un
|
| 576 |
``OCRLLMPipelineConfig`` canonique (3 modes).
|
| 577 |
|
| 578 |
+
Tolère ``pipeline.mode`` en enum (``PipelineMode.TEXT_ONLY``)
|
| 579 |
ou en string (canonique ``"text_only"``).
|
| 580 |
"""
|
| 581 |
mode_attr = pipeline.mode
|
|
|
|
| 614 |
"""Construit un adapter resolver pour ``PipelineExecutor``.
|
| 615 |
|
| 616 |
Parcourt les engines fournis et associe leur ``name`` à un
|
| 617 |
+
``StepExecutor`` valide (le path historique
|
| 618 |
``LegacyOCREngineExecutor`` a été retiré) :
|
| 619 |
|
| 620 |
- **BaseOCRAdapter** : enregistré directement (déjà ``StepExecutor``).
|
|
|
|
| 680 |
def resolver(name: str) -> Any:
|
| 681 |
if name not in name_to_executor:
|
| 682 |
raise KeyError(
|
| 683 |
+
f"adapter inconnu pour le resolver : {name!r}. "
|
| 684 |
f"Enregistrés : {sorted(name_to_executor.keys())!r}."
|
| 685 |
)
|
| 686 |
return name_to_executor[name]
|
|
|
|
| 836 |
partial_dir: str | Path | None = None,
|
| 837 |
entity_extractor: Callable[[str], list[dict]] | None = None,
|
| 838 |
profile: str = "standard",
|
| 839 |
+
# ---- Paramètres non encore portés vers BenchmarkService ----
|
| 840 |
# Sprint D.2 du plan v2.0 — features marginales restantes :
|
| 841 |
# ``max_workers`` (le rewrite a son propre max_in_flight via
|
| 842 |
# ``CorpusRunner``).
|
| 843 |
max_workers: int = 4, # noqa: ARG001
|
| 844 |
) -> Any:
|
| 845 |
+
"""Façade ``run_benchmark`` →
|
| 846 |
``BenchmarkService`` rewrite.
|
| 847 |
|
| 848 |
Présente la signature historique de
|
| 849 |
+
``picarones.app.services.benchmark_runner.run_benchmark`` mais s'appuie
|
| 850 |
en interne sur le rewrite (``CorpusSpec``, ``PipelineSpec``,
|
| 851 |
``PipelineExecutor``, ``BenchmarkService``). Pivot du Sprint D
|
| 852 |
du plan v2.0.
|
|
|
|
| 860 |
- Un ``Corpus`` avec image_path + ground_truth (TEXT) par doc.
|
| 861 |
- Métriques CER/WER calculées via ``compute_metrics`` sur les
|
| 862 |
hypothèses extraites des artefacts produits.
|
| 863 |
+
- Conversion en ``BenchmarkResult`` compatible avec les
|
| 864 |
consommateurs historiques (rapport HTML, narrative engine).
|
| 865 |
|
| 866 |
Périmètre reporté (D.2)
|
|
|
|
| 911 |
Parameters
|
| 912 |
----------
|
| 913 |
corpus:
|
| 914 |
+
Corpus.
|
| 915 |
engines:
|
| 916 |
+
Liste d'engines/pipelines à benchmarker.
|
| 917 |
char_exclude:
|
| 918 |
Filtre passé à ``compute_metrics``.
|
| 919 |
normalization_profile:
|
| 920 |
Profil de normalisation passé à ``compute_metrics``.
|
| 921 |
output_json:
|
| 922 |
Si fourni, le ``BenchmarkResult`` est sérialisé en JSON
|
| 923 |
+
à ce chemin (sérialisation BenchmarkResult).
|
| 924 |
code_version:
|
| 925 |
Version du code injectée dans le ``RunContext`` /
|
| 926 |
``RunManifest``. Défaut : ``picarones.__version__``.
|
|
|
|
| 930 |
Returns
|
| 931 |
-------
|
| 932 |
BenchmarkResult
|
| 933 |
+
Format compatible avec les consommateurs historiques.
|
| 934 |
|
| 935 |
Raises
|
| 936 |
------
|
|
|
|
| 983 |
|
| 984 |
# D.2.e : NER attach post-process. Idempotent — re-calcule à
|
| 985 |
# chaque run même en mode resume (les ner_metrics ne sont pas
|
| 986 |
+
# persistées dans le partial NDJSON
|
| 987 |
# qui calculait NER après le doc loop).
|
| 988 |
if entity_extractor is not None:
|
| 989 |
_attach_ner_metrics_to_benchmark(
|
|
|
|
| 1064 |
compteurs totaux d'hallucinations et d'entités manquées.
|
| 1065 |
|
| 1066 |
Equivalent fonctionnel de
|
| 1067 |
+
``picarones.app.services.benchmark_runner.ner_attach._aggregate_ner``
|
| 1068 |
+
(le runner historique a été supprimé en D.6.b).
|
| 1069 |
"""
|
| 1070 |
relevant = [
|
| 1071 |
dr for dr in doc_results if dr.ner_metrics is not None
|
|
|
|
| 1226 |
|
| 1227 |
for engine in engines:
|
| 1228 |
# Vérifier la cancellation entre engines (matche la
|
| 1229 |
+
# sémantique : un Ctrl+C arrête après l'engine en
|
| 1230 |
# cours, conserve les partials, ne démarre pas le suivant).
|
| 1231 |
if cancel_event is not None and getattr(
|
| 1232 |
cancel_event, "is_set", lambda: False,
|
|
|
|
| 1357 |
Vues passées en liste vide — les métriques sont calculées
|
| 1358 |
côté converter D.1.c via ``compute_metrics`` directement sur
|
| 1359 |
les hypothèses extraites des artefacts. Pattern simple,
|
| 1360 |
+
cohérent : on calcule aussi les métriques au
|
| 1361 |
moment du benchmark (pas via ``EvaluationView``).
|
| 1362 |
"""
|
| 1363 |
from picarones.app.services.benchmark_service import BenchmarkService
|
|
|
|
| 1379 |
|
| 1380 |
# ViewExecutor minimal : registres vides.
|
| 1381 |
# Pas de calcul de ``ViewResult`` ici — le converter D.1.c
|
| 1382 |
+
# calcule les métriques via ``compute_metrics``
|
| 1383 |
# directement sur les hypothèses extraites des artefacts.
|
| 1384 |
view_executor = DefaultEvaluationViewExecutor.from_registries(
|
| 1385 |
metric_registry=MetricRegistry(),
|
|
|
|
| 1419 |
# Sprint D.2.a : le hook ``progress_callback`` est appelé ici —
|
| 1420 |
# ``context_factory`` est invoqué une fois par (doc, pipeline)
|
| 1421 |
# AVANT l'exécution effective, ce qui correspond à la sémantique
|
| 1422 |
+
# de ``progress_callback(engine_name, doc_idx, doc_id)``.
|
| 1423 |
import threading
|
| 1424 |
|
| 1425 |
counter_lock = threading.Lock()
|
|
|
|
| 1432 |
with counter_lock:
|
| 1433 |
idx = counter_state["doc_idx"]
|
| 1434 |
counter_state["doc_idx"] = idx + 1
|
| 1435 |
+
# Sémantique : ``progress_callback(engine.name, ...)``
|
| 1436 |
# plutôt que le nom de la pipeline (qui inclut le préfixe
|
| 1437 |
# ``ocr_only_``). Le mapping est fourni par le caller.
|
| 1438 |
engine_name = (
|
|
|
|
| 1443 |
try:
|
| 1444 |
progress_callback(engine_name, idx, doc.id)
|
| 1445 |
except Exception: # noqa: BLE001
|
| 1446 |
+
# On ignore silencieusement les erreurs du
|
| 1447 |
# callback (un caller qui crashe ne doit pas faire
|
| 1448 |
# tomber le benchmark). Même contrat ici.
|
| 1449 |
pass
|
|
|
|
| 1482 |
def _persist_benchmark_result_json(
|
| 1483 |
benchmark_result: Any, output_path: Path,
|
| 1484 |
) -> None:
|
| 1485 |
+
"""Sérialise un ``BenchmarkResult`` en JSON.
|
| 1486 |
|
| 1487 |
Utilise la méthode ``to_json``/``compact``/``asdict`` selon la
|
| 1488 |
surface disponible. Ce helper duplique la logique de
|
|
|
|
| 1492 |
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 1493 |
# ``BenchmarkResult`` est un dataclass — dataclasses.asdict
|
| 1494 |
# sérialise récursivement. Le format n'est pas forcément
|
| 1495 |
+
# identique octet pour octet à la sortie historique, mais reste
|
| 1496 |
# compatible avec les consommateurs (rapport, narrative).
|
| 1497 |
import dataclasses
|
| 1498 |
import json
|
|
@@ -1,8 +1,8 @@
|
|
| 1 |
-
"""
|
| 2 |
|
| 3 |
-
Persistance NDJSON des ``DocumentResult``
|
| 4 |
-
|
| 5 |
-
|
| 6 |
|
| 7 |
Contrat
|
| 8 |
-------
|
|
@@ -18,19 +18,12 @@ partiel est supprimé. Si un crash interrompt le run mid-engine,
|
|
| 18 |
le fichier persiste : la prochaine exécution reprendra exactement
|
| 19 |
où l'on s'est arrêté.
|
| 20 |
|
| 21 |
-
Trace de retrait
|
| 22 |
-
----------------
|
| 23 |
-
Module transitoire (Sprint D.2.b du plan v2.0). Sera supprimé
|
| 24 |
-
en H.4 quand ``run_benchmark_via_service`` lui-même disparaîtra
|
| 25 |
-
au profit d'une consommation directe de ``BenchmarkService`` par
|
| 26 |
-
les callers (``cli``, ``web``).
|
| 27 |
-
|
| 28 |
Anti-sur-ingénierie
|
| 29 |
-------------------
|
| 30 |
- Format JSONL plat (une ligne = un ``DocumentResult.as_dict()``),
|
| 31 |
pas de schéma versioné. Si la structure du ``DocumentResult``
|
| 32 |
-
|
| 33 |
-
|
| 34 |
- Lock thread-safe partagé module-level ; pas de tentative de
|
| 35 |
partage inter-process (chaque process a son propre tempdir).
|
| 36 |
- Pas de checksum ni de validation de schéma — best-effort. Une
|
|
@@ -63,9 +56,8 @@ _partial_write_lock = threading.Lock()
|
|
| 63 |
def _sanitize_filename(s: str) -> str:
|
| 64 |
"""Réduit ``s`` à ``[\\w\\-]`` et tronque à 64 chars.
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
le fichier dans ``partial_dir``.
|
| 69 |
"""
|
| 70 |
return re.sub(r"[^\w\-]", "_", s)[:64]
|
| 71 |
|
|
|
|
| 1 |
+
"""Reprise sur interruption pour ``run_benchmark_via_service``.
|
| 2 |
|
| 3 |
+
Persistance NDJSON des ``DocumentResult`` au fil du benchmark, pour
|
| 4 |
+
permettre la reprise après crash / Ctrl+C / timeout sans perdre le
|
| 5 |
+
travail déjà fait.
|
| 6 |
|
| 7 |
Contrat
|
| 8 |
-------
|
|
|
|
| 18 |
le fichier persiste : la prochaine exécution reprendra exactement
|
| 19 |
où l'on s'est arrêté.
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
Anti-sur-ingénierie
|
| 22 |
-------------------
|
| 23 |
- Format JSONL plat (une ligne = un ``DocumentResult.as_dict()``),
|
| 24 |
pas de schéma versioné. Si la structure du ``DocumentResult``
|
| 25 |
+
change, le fichier devient illisible — l'opérateur supprime
|
| 26 |
+
``partial_dir`` et relance.
|
| 27 |
- Lock thread-safe partagé module-level ; pas de tentative de
|
| 28 |
partage inter-process (chaque process a son propre tempdir).
|
| 29 |
- Pas de checksum ni de validation de schéma — best-effort. Une
|
|
|
|
| 56 |
def _sanitize_filename(s: str) -> str:
|
| 57 |
"""Réduit ``s`` à ``[\\w\\-]`` et tronque à 64 chars.
|
| 58 |
|
| 59 |
+
Permet à un opérateur de retrouver visuellement le fichier
|
| 60 |
+
dans ``partial_dir``.
|
|
|
|
| 61 |
"""
|
| 62 |
return re.sub(r"[^\w\-]", "_", s)[:64]
|
| 63 |
|
|
@@ -4,7 +4,7 @@ Sprint A14-S23 du rewrite ciblé.
|
|
| 4 |
|
| 5 |
Le service applicatif qui **construit** explicitement le
|
| 6 |
``MetricRegistry`` et le ``ProjectorRegistry`` au démarrage, en
|
| 7 |
-
remplacement de l'anti-pattern legacy ``import picarones.
|
| 8 |
as _trigger`` (où l'import par effet de bord déclenchait
|
| 9 |
l'enregistrement via décorateurs au top-level d'un package, chargeant
|
| 10 |
des dizaines de modules optionnels au moment d'un simple
|
|
|
|
| 4 |
|
| 5 |
Le service applicatif qui **construit** explicitement le
|
| 6 |
``MetricRegistry`` et le ``ProjectorRegistry`` au démarrage, en
|
| 7 |
+
remplacement de l'anti-pattern legacy ``import picarones.evaluation.metrics
|
| 8 |
as _trigger`` (où l'import par effet de bord déclenchait
|
| 9 |
l'enregistrement via décorateurs au top-level d'un package, chargeant
|
| 10 |
des dizaines de modules optionnels au moment d'un simple
|
|
@@ -76,8 +76,6 @@ from picarones.pipeline import (
|
|
| 76 |
# ──────────────────────────────────────────────────────────────────────
|
| 77 |
|
| 78 |
|
| 79 |
-
|
| 80 |
-
|
| 81 |
@dataclass(frozen=True)
|
| 82 |
class OrchestrationResult:
|
| 83 |
"""Tout ce qu'un caller (CLI, HTTP, script) doit savoir d'un run.
|
|
|
|
| 76 |
# ──────────────────────────────────────────────────────────────────────
|
| 77 |
|
| 78 |
|
|
|
|
|
|
|
| 79 |
@dataclass(frozen=True)
|
| 80 |
class OrchestrationResult:
|
| 81 |
"""Tout ce qu'un caller (CLI, HTTP, script) doit savoir d'un run.
|
|
@@ -1,14 +1,11 @@
|
|
| 1 |
-
"""``Artifact`` et ``ArtifactType``
|
| 2 |
|
| 3 |
Toute sortie d'une étape de pipeline est un **artefact traçable** :
|
| 4 |
identifiant stable, type explicite, hash du contenu, provenance.
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
``IMAGE, TEXT, ALTO, PAGE, ENTITIES, READING_ORDER``. Le nouveau
|
| 10 |
-
en a 9, avec deux distinctions importantes pour les vues d'évaluation
|
| 11 |
-
introduites aux Sprints S13-S18 :
|
| 12 |
|
| 13 |
- **``RAW_TEXT`` vs ``CORRECTED_TEXT``** — un OCR brut et un texte
|
| 14 |
corrigé par un LLM ont la même structure (string) mais des contrats
|
|
@@ -103,47 +100,40 @@ class ArtifactType(str, Enum):
|
|
| 103 |
#: reliability diagram).
|
| 104 |
CONFIDENCES = "confidences"
|
| 105 |
|
| 106 |
-
#: Aliases
|
| 107 |
-
#:
|
| 108 |
-
#: Python rend ces noms équivalents aux canoniques :
|
| 109 |
#:
|
| 110 |
#: >>> ArtifactType.TEXT is ArtifactType.RAW_TEXT
|
| 111 |
#: True
|
| 112 |
#:
|
| 113 |
-
#:
|
| 114 |
-
#:
|
| 115 |
-
#: une fois tous les callers legacy retirés.
|
| 116 |
TEXT = "raw_text"
|
| 117 |
ALTO = "alto_xml"
|
| 118 |
PAGE = "page_xml"
|
| 119 |
|
| 120 |
@classmethod
|
| 121 |
def _missing_(cls, value: object) -> "ArtifactType | None":
|
| 122 |
-
"""Accepte les
|
| 123 |
``"page"``) en plus des valeurs canoniques.
|
| 124 |
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
en 2.0 avec les aliases legacy ci-dessus.
|
| 128 |
"""
|
| 129 |
-
|
| 130 |
"text": cls.RAW_TEXT,
|
| 131 |
"alto": cls.ALTO_XML,
|
| 132 |
"page": cls.PAGE_XML,
|
| 133 |
}
|
| 134 |
if not isinstance(value, str):
|
| 135 |
return None
|
| 136 |
-
return
|
| 137 |
|
| 138 |
|
| 139 |
-
#: Map valeur canonique → valeur string
|
| 140 |
-
#: indexés par ``ArtifactType.value``
|
| 141 |
-
#:
|
| 142 |
-
#:
|
| 143 |
-
#: test legacy qui cherche ``["text"]`` voient le même résultat.
|
| 144 |
-
#:
|
| 145 |
-
#: Phase 4-bis du retrait du legacy. Sera retiré en 2.0 quand tous
|
| 146 |
-
#: les callers utilisent les valeurs canoniques.
|
| 147 |
LEGACY_VALUE_ALIASES: dict[str, str] = {
|
| 148 |
"raw_text": "text",
|
| 149 |
"alto_xml": "alto",
|
|
|
|
| 1 |
+
"""``Artifact`` et ``ArtifactType``.
|
| 2 |
|
| 3 |
Toute sortie d'une étape de pipeline est un **artefact traçable** :
|
| 4 |
identifiant stable, type explicite, hash du contenu, provenance.
|
| 5 |
|
| 6 |
+
L'enum ``ArtifactType`` a 9 valeurs canoniques + 3 aliases courts
|
| 7 |
+
pour les types texte/ALTO/PAGE. Distinctions clés pour les vues
|
| 8 |
+
d'évaluation :
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
- **``RAW_TEXT`` vs ``CORRECTED_TEXT``** — un OCR brut et un texte
|
| 11 |
corrigé par un LLM ont la même structure (string) mais des contrats
|
|
|
|
| 100 |
#: reliability diagram).
|
| 101 |
CONFIDENCES = "confidences"
|
| 102 |
|
| 103 |
+
#: Aliases courts pour les types texte/ALTO/PAGE. Le mécanisme
|
| 104 |
+
#: natif d'Enum Python rend ces noms équivalents aux canoniques :
|
|
|
|
| 105 |
#:
|
| 106 |
#: >>> ArtifactType.TEXT is ArtifactType.RAW_TEXT
|
| 107 |
#: True
|
| 108 |
#:
|
| 109 |
+
#: Utilisés par les ``@register_metric(...)`` qui déclarent leurs
|
| 110 |
+
#: signatures de manière concise.
|
|
|
|
| 111 |
TEXT = "raw_text"
|
| 112 |
ALTO = "alto_xml"
|
| 113 |
PAGE = "page_xml"
|
| 114 |
|
| 115 |
@classmethod
|
| 116 |
def _missing_(cls, value: object) -> "ArtifactType | None":
|
| 117 |
+
"""Accepte les chaînes courtes (``"text"``, ``"alto"``,
|
| 118 |
``"page"``) en plus des valeurs canoniques.
|
| 119 |
|
| 120 |
+
Permet aux specs YAML d'utiliser indifféremment l'un ou
|
| 121 |
+
l'autre nom.
|
|
|
|
| 122 |
"""
|
| 123 |
+
short_map: dict[str, "ArtifactType"] = {
|
| 124 |
"text": cls.RAW_TEXT,
|
| 125 |
"alto": cls.ALTO_XML,
|
| 126 |
"page": cls.PAGE_XML,
|
| 127 |
}
|
| 128 |
if not isinstance(value, str):
|
| 129 |
return None
|
| 130 |
+
return short_map.get(value)
|
| 131 |
|
| 132 |
|
| 133 |
+
#: Map valeur canonique → valeur string courte. Permet aux dicts
|
| 134 |
+
#: indexés par ``ArtifactType.value`` de présenter les **deux** clés :
|
| 135 |
+
#: un caller qui cherche ``["raw_text"]`` et un caller qui cherche
|
| 136 |
+
#: ``["text"]`` voient le même résultat.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
LEGACY_VALUE_ALIASES: dict[str, str] = {
|
| 138 |
"raw_text": "text",
|
| 139 |
"alto_xml": "alto",
|
|
@@ -23,7 +23,7 @@ class PicaronesError(Exception):
|
|
| 23 |
une sous-classe de ``PicaronesError`` plutôt qu'un ``Exception``
|
| 24 |
générique ou un ``ValueError`` quand l'erreur a un sens métier.
|
| 25 |
|
| 26 |
-
L'ancien code (``picarones.core``, ``picarones.
|
| 27 |
etc.) garde son comportement actuel jusqu'à sa migration.
|
| 28 |
"""
|
| 29 |
|
|
|
|
| 23 |
une sous-classe de ``PicaronesError`` plutôt qu'un ``Exception``
|
| 24 |
générique ou un ``ValueError`` quand l'erreur a un sens métier.
|
| 25 |
|
| 26 |
+
L'ancien code (``picarones.core``, ``picarones.evaluation.metrics``,
|
| 27 |
etc.) garde son comportement actuel jusqu'à sa migration.
|
| 28 |
"""
|
| 29 |
|
|
@@ -20,17 +20,13 @@ Usage minimal ::
|
|
| 20 |
txt = inputs[ArtifactType.RAW_TEXT]
|
| 21 |
return {ArtifactType.RAW_TEXT: txt.upper()}
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
(``BaseOCRAdapter``, ``BaseLLMAdapter``, ``BaseVLMAdapter`` dans
|
| 31 |
-
``picarones.adapters``) qui sont des cas particuliers de
|
| 32 |
-
``BaseModule`` typés pour leur domaine. ``BaseModule`` reste le
|
| 33 |
-
contrat **générique** pour les modules contribués par des tiers.
|
| 34 |
"""
|
| 35 |
|
| 36 |
from __future__ import annotations
|
|
|
|
| 20 |
txt = inputs[ArtifactType.RAW_TEXT]
|
| 21 |
return {ArtifactType.RAW_TEXT: txt.upper()}
|
| 22 |
|
| 23 |
+
Protocols spécialisés
|
| 24 |
+
---------------------
|
| 25 |
+
Les contrats de domaine — ``BaseOCRAdapter``, ``BaseLLMAdapter``,
|
| 26 |
+
``BaseVLMAdapter`` (dans ``picarones.adapters``) — sont des cas
|
| 27 |
+
particuliers de ``BaseModule`` typés pour leur usage.
|
| 28 |
+
``BaseModule`` reste le contrat **générique** pour les modules
|
| 29 |
+
contribués par des tiers.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
"""
|
| 31 |
|
| 32 |
from __future__ import annotations
|
|
@@ -1,10 +1,5 @@
|
|
| 1 |
"""Modèle de données des résultats et export JSON (Cercle 2).
|
| 2 |
|
| 3 |
-
Phase 4-ter — module relocalisé depuis ``picarones.core.results``
|
| 4 |
-
vers le Cercle 2 (``evaluation``) où il appartient sémantiquement.
|
| 5 |
-
Le chemin legacy reste disponible via un shim avec
|
| 6 |
-
``DeprecationWarning`` ; suppression prévue en 2.0.
|
| 7 |
-
|
| 8 |
Hiérarchie
|
| 9 |
----------
|
| 10 |
BenchmarkResult
|
|
|
|
| 1 |
"""Modèle de données des résultats et export JSON (Cercle 2).
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
Hiérarchie
|
| 4 |
----------
|
| 5 |
BenchmarkResult
|
|
@@ -1,18 +1,12 @@
|
|
| 1 |
-
"""Chargement et gestion des corpus de documents (
|
| 2 |
-
|
| 3 |
-
Phase 4-quater — module relocalisé depuis ``picarones.core.corpus``
|
| 4 |
-
vers le Cercle 2 (``evaluation``) où il appartient sémantiquement.
|
| 5 |
-
Le chemin legacy reste disponible via un shim avec
|
| 6 |
-
``DeprecationWarning`` ; suppression prévue en 2.0.
|
| 7 |
|
| 8 |
Coexistence avec ``domain.corpus.CorpusSpec``
|
| 9 |
---------------------------------------------
|
| 10 |
``evaluation.corpus`` (le présent module) porte les types **riches
|
| 11 |
-
en behavior**
|
| 12 |
-
``
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
``load_corpus_from_directory``.
|
| 16 |
|
| 17 |
``domain.corpus.CorpusSpec`` + ``domain.documents.DocumentRef``
|
| 18 |
(Pydantic, immutable, déclaratif) sont une vue **structurelle**
|
|
|
|
| 1 |
+
"""Chargement et gestion des corpus de documents (couche 3 — evaluation).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
Coexistence avec ``domain.corpus.CorpusSpec``
|
| 4 |
---------------------------------------------
|
| 5 |
``evaluation.corpus`` (le présent module) porte les types **riches
|
| 6 |
+
en behavior** consommés par ``BenchmarkService`` (couche 6) :
|
| 7 |
+
``Document``, ``Corpus``, ``ArtifactType`` + payloads
|
| 8 |
+
``TextGT``/``AltoGT``/``PageGT``/``EntitiesGT``/``ReadingOrderGT``
|
| 9 |
+
chargés en mémoire, et la fonction ``load_corpus_from_directory``.
|
|
|
|
| 10 |
|
| 11 |
``domain.corpus.CorpusSpec`` + ``domain.documents.DocumentRef``
|
| 12 |
(Pydantic, immutable, déclaratif) sont une vue **structurelle**
|
|
@@ -1,16 +1,11 @@
|
|
| 1 |
"""Registre typé des hooks de métriques document-level et corpus-level.
|
| 2 |
|
| 3 |
-
Phase 4-ter — module relocalisé depuis ``picarones.core.metric_hooks``
|
| 4 |
-
vers le Cercle 2 (``evaluation``) où il appartient sémantiquement.
|
| 5 |
-
Le chemin legacy reste disponible via un shim avec
|
| 6 |
-
``DeprecationWarning`` ; suppression prévue en 2.0.
|
| 7 |
-
|
| 8 |
Pourquoi ce module
|
| 9 |
------------------
|
| 10 |
Avant le « chantier 2 » du plan d'évolution post-Sprint 97,
|
| 11 |
-
``picarones.
|
| 12 |
contenait **11 imports tardifs codés en dur** vers
|
| 13 |
-
``picarones.
|
| 14 |
``image_quality``, ``line_metrics``, ``hallucination``,
|
| 15 |
``philological_hooks``, ``searchability_hooks``,
|
| 16 |
``numerical_sequences_hooks``, ``readability_hooks`` — chacun enrobé
|
|
|
|
| 1 |
"""Registre typé des hooks de métriques document-level et corpus-level.
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
Pourquoi ce module
|
| 4 |
------------------
|
| 5 |
Avant le « chantier 2 » du plan d'évolution post-Sprint 97,
|
| 6 |
+
``picarones.app.services.benchmark_runner._compute_document_result``
|
| 7 |
contenait **11 imports tardifs codés en dur** vers
|
| 8 |
+
``picarones.evaluation.metrics.confusion``, ``char_scores``, ``taxonomy``, ``structure``,
|
| 9 |
``image_quality``, ``line_metrics``, ``hallucination``,
|
| 10 |
``philological_hooks``, ``searchability_hooks``,
|
| 11 |
``numerical_sequences_hooks``, ``readability_hooks`` — chacun enrobé
|
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Registre typé de métriques (
|
| 2 |
|
| 3 |
Pattern et données
|
| 4 |
------------------
|
|
@@ -7,30 +7,19 @@ le décorateur ``@register_metric``. Chaque métrique enregistre une
|
|
| 7 |
``MetricSpec`` (nom + signature de types + callable) ; la sélection
|
| 8 |
typée à une jonction se fait via ``select_metrics(input_types)``.
|
| 9 |
|
| 10 |
-
Le runner d'une pipeline composée
|
| 11 |
-
(:func:`picarones.evaluation.pipeline.PipelineRunner.run`) consomme ce
|
| 12 |
-
registre pour évaluer automatiquement chaque jonction GT vs sortie.
|
| 13 |
-
|
| 14 |
Différence avec ``picarones.evaluation.registry.MetricRegistry``
|
| 15 |
----------------------------------------------------------------
|
| 16 |
-
Le présent module est le pattern **
|
| 17 |
-
unique global, alimenté par les imports des sous-packages
|
| 18 |
-
``picarones.
|
| 19 |
-
|
| 20 |
|
| 21 |
``picarones.evaluation.registry.MetricRegistry`` est une **classe
|
| 22 |
-
instanciable**
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
``EvaluationView`` (S20+).
|
| 28 |
-
|
| 29 |
-
Phase 4-ter (présente)
|
| 30 |
-
----------------------
|
| 31 |
-
Module relocalisé depuis ``picarones.core.metric_registry``. Le
|
| 32 |
-
chemin legacy reste disponible via un shim avec
|
| 33 |
-
``DeprecationWarning`` ; suppression prévue en 2.0.
|
| 34 |
|
| 35 |
Exemple d'usage
|
| 36 |
---------------
|
|
|
|
| 1 |
+
"""Registre typé de métriques (couche 3 — evaluation).
|
| 2 |
|
| 3 |
Pattern et données
|
| 4 |
------------------
|
|
|
|
| 7 |
``MetricSpec`` (nom + signature de types + callable) ; la sélection
|
| 8 |
typée à une jonction se fait via ``select_metrics(input_types)``.
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
Différence avec ``picarones.evaluation.registry.MetricRegistry``
|
| 11 |
----------------------------------------------------------------
|
| 12 |
+
Le présent module est le pattern **module-level** : un registre
|
| 13 |
+
unique global, alimenté par les imports des sous-packages
|
| 14 |
+
(``picarones.evaluation.metrics.__init__`` charge tous les modules
|
| 15 |
+
définissant des ``@register_metric``).
|
| 16 |
|
| 17 |
``picarones.evaluation.registry.MetricRegistry`` est une **classe
|
| 18 |
+
instanciable** — un service applicatif l'instancie explicitement
|
| 19 |
+
et y enregistre les métriques sans side-effect d'import. Les
|
| 20 |
+
deux patterns coexistent : le module-level fonctionne pour les
|
| 21 |
+
~37 métriques existantes, l'instance-based est réservé aux
|
| 22 |
+
contributions tierces et au cadre des ``EvaluationView``.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
Exemple d'usage
|
| 25 |
---------------
|
|
@@ -1,16 +1,11 @@
|
|
| 1 |
-
"""Modèle de données des métriques OCR/HTR (
|
| 2 |
-
|
| 3 |
-
Phase 4-ter — module relocalisé depuis ``picarones.core.metrics``
|
| 4 |
-
vers le Cercle 2 (``evaluation``) où il appartient sémantiquement.
|
| 5 |
-
Le chemin legacy reste disponible via un shim avec
|
| 6 |
-
``DeprecationWarning`` ; suppression prévue en 2.0.
|
| 7 |
|
| 8 |
Abstractions pures pour représenter les métriques calculées sur
|
| 9 |
une paire (référence, hypothèse) — pas de dépendance externe (pas
|
| 10 |
de jiwer, pas de scipy).
|
| 11 |
|
| 12 |
Le calcul effectif via jiwer vit dans
|
| 13 |
-
:mod:`picarones.
|
| 14 |
L'agrégation statistique vit ici car elle n'utilise que la stdlib
|
| 15 |
(``statistics``).
|
| 16 |
"""
|
|
|
|
| 1 |
+
"""Modèle de données des métriques OCR/HTR (couche 3 — evaluation).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
Abstractions pures pour représenter les métriques calculées sur
|
| 4 |
une paire (référence, hypothèse) — pas de dépendance externe (pas
|
| 5 |
de jiwer, pas de scipy).
|
| 6 |
|
| 7 |
Le calcul effectif via jiwer vit dans
|
| 8 |
+
:mod:`picarones.evaluation.metrics.text_metrics` (``compute_metrics``).
|
| 9 |
L'agrégation statistique vit ici car elle n'utilise que la stdlib
|
| 10 |
(``statistics``).
|
| 11 |
"""
|
|
@@ -1,56 +1,38 @@
|
|
| 1 |
"""Métriques — calculs purs sur des paires (référence, hypothèse).
|
| 2 |
|
| 3 |
-
|
| 4 |
-
depuis ``picarones.measurements``.
|
| 5 |
|
| 6 |
Calculs de qualité textuelle pure :
|
| 7 |
``rare_tokens``, ``lexical_modernization``, ``calibration``,
|
| 8 |
-
``confusion``, ``line_metrics``.
|
| 9 |
|
| 10 |
Calculs structurels et géométriques :
|
| 11 |
-
``layout``, ``image_quality``, ``image_predictive``
|
|
|
|
| 12 |
|
| 13 |
Calculs économiques :
|
| 14 |
``pricing``, ``marginal_cost``, ``throughput``,
|
| 15 |
-
``incremental_comparison``.
|
| 16 |
|
| 17 |
Calculs analytiques (post-traitement) :
|
| 18 |
-
``error_absorption``, ``hallucination``, ``
|
| 19 |
-
``
|
| 20 |
-
``
|
|
|
|
| 21 |
|
| 22 |
Calculs inter-moteurs :
|
| 23 |
-
``inter_engine``, ``
|
|
|
|
| 24 |
``taxonomy_comparison``.
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
``
|
| 31 |
-
``
|
| 32 |
-
``early_modern_typography``, ``modern_archives``, ``reading_order``,
|
| 33 |
-
``ner``, ``readability``, ``searchability``, ``numerical_sequences``.
|
| 34 |
-
|
| 35 |
-
Migrés au S20 quand le ``MetricRegistry`` instancié explicitement
|
| 36 |
-
(S5) deviendra le seul registre, via le ``registry_service``
|
| 37 |
-
applicatif.
|
| 38 |
-
|
| 39 |
-
Catégorie C — dépendances vers anciens packages :
|
| 40 |
-
``robustness`` (importe ``picarones.evaluation.corpus`` +
|
| 41 |
-
``picarones.adapters.legacy_engines.base`` + ``picarones.measurements.metrics``).
|
| 42 |
-
Ne peut être migré qu'après les Sprints S11 (déplacement des
|
| 43 |
-
adapters) et S12 (équivalence numérique).
|
| 44 |
-
|
| 45 |
-
Catégorie D — dépendances inter-fichiers à orchestrer :
|
| 46 |
-
``cost_projection`` (→ pricing), ``equivalence_profile``
|
| 47 |
-
(→ formats.text.normalization), ``specialization``
|
| 48 |
-
(→ inter_engine), ``taxonomy_intra_doc`` (→ taxonomy),
|
| 49 |
-
``taxonomy`` (→ char_scores).
|
| 50 |
-
|
| 51 |
-
Règle de migration (S10) : un fichier déplacé = un commit avec
|
| 52 |
-
uniquement le déplacement et un re-export à l'ancien emplacement.
|
| 53 |
-
La logique reste identique. Aucun test modifié.
|
| 54 |
"""
|
| 55 |
|
| 56 |
from __future__ import annotations
|
|
|
|
| 1 |
"""Métriques — calculs purs sur des paires (référence, hypothèse).
|
| 2 |
|
| 3 |
+
~37 modules de calcul autonomes :
|
|
|
|
| 4 |
|
| 5 |
Calculs de qualité textuelle pure :
|
| 6 |
``rare_tokens``, ``lexical_modernization``, ``calibration``,
|
| 7 |
+
``confusion``, ``line_metrics``, ``text_metrics``.
|
| 8 |
|
| 9 |
Calculs structurels et géométriques :
|
| 10 |
+
``layout``, ``image_quality``, ``image_predictive``,
|
| 11 |
+
``alto_metrics``, ``alto_structural``.
|
| 12 |
|
| 13 |
Calculs économiques :
|
| 14 |
``pricing``, ``marginal_cost``, ``throughput``,
|
| 15 |
+
``incremental_comparison``, ``cost_projection``.
|
| 16 |
|
| 17 |
Calculs analytiques (post-traitement) :
|
| 18 |
+
``error_absorption``, ``hallucination``, ``robustness``,
|
| 19 |
+
``robustness_projection``, ``longitudinal``,
|
| 20 |
+
``baseline_comparison``, ``levers``, ``worst_lines``,
|
| 21 |
+
``module_policy``, ``history``, ``modern_archives``.
|
| 22 |
|
| 23 |
Calculs inter-moteurs :
|
| 24 |
+
``inter_engine``, ``specialization``, ``taxonomy``,
|
| 25 |
+
``taxonomy_intra_doc``, ``taxonomy_cooccurrence``,
|
| 26 |
``taxonomy_comparison``.
|
| 27 |
|
| 28 |
+
Calculs philologiques :
|
| 29 |
+
``mufi``, ``abbreviations``, ``unicode_blocks``,
|
| 30 |
+
``roman_numerals``, ``numerical_sequences``,
|
| 31 |
+
``early_modern_typography``, ``reading_order``.
|
| 32 |
|
| 33 |
+
Calculs sémantiques :
|
| 34 |
+
``ner``, ``readability``, ``searchability``,
|
| 35 |
+
``equivalence_profile``, ``over_normalization``.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
"""
|
| 37 |
|
| 38 |
from __future__ import annotations
|
|
@@ -17,7 +17,7 @@ enregistre quatre métriques natives (``alto_text_cer``,
|
|
| 17 |
les opérateurs jiwer historiques sur le texte extrait des deux côtés.
|
| 18 |
|
| 19 |
L'approche est strictement additive vis-à-vis de
|
| 20 |
-
:mod:`picarones.
|
| 21 |
calcul historique (``compute_metrics``), il enrichit uniquement le
|
| 22 |
registre typé pour les pipelines composées.
|
| 23 |
|
|
|
|
| 17 |
les opérateurs jiwer historiques sur le texte extrait des deux côtés.
|
| 18 |
|
| 19 |
L'approche est strictement additive vis-à-vis de
|
| 20 |
+
:mod:`picarones.evaluation.metrics.text_metrics` : ce module ne touche pas le chemin de
|
| 21 |
calcul historique (``compute_metrics``), il enrichit uniquement le
|
| 22 |
registre typé pour les pipelines composées.
|
| 23 |
|
|
@@ -4,7 +4,7 @@ Chantier 2 du plan d'évolution post-Sprint 97.
|
|
| 4 |
|
| 5 |
Ce module **migre** les 12 hooks document-level et 12 agrégateurs
|
| 6 |
corpus-level qui étaient codés en dur dans
|
| 7 |
-
``picarones.
|
| 8 |
boucle d'agrégation (lignes 794-827 du runner pré-chantier-2).
|
| 9 |
|
| 10 |
Approche additive — rétrocompat stricte
|
|
|
|
| 4 |
|
| 5 |
Ce module **migre** les 12 hooks document-level et 12 agrégateurs
|
| 6 |
corpus-level qui étaient codés en dur dans
|
| 7 |
+
``picarones.app.services.benchmark_runner._compute_document_result`` et autour de la
|
| 8 |
boucle d'agrégation (lignes 794-827 du runner pré-chantier-2).
|
| 9 |
|
| 10 |
Approche additive — rétrocompat stricte
|
|
@@ -20,7 +20,7 @@ le chercheur arbitre selon son budget.
|
|
| 20 |
|
| 21 |
Dépendance
|
| 22 |
----------
|
| 23 |
-
S'appuie sur ``picarones.
|
| 24 |
``EngineCost.cost_per_1k_pages_eur`` et
|
| 25 |
``co2_per_1k_pages_g``.
|
| 26 |
"""
|
|
|
|
| 20 |
|
| 21 |
Dépendance
|
| 22 |
----------
|
| 23 |
+
S'appuie sur ``picarones.evaluation.metrics.pricing`` (Sprint 20) qui expose
|
| 24 |
``EngineCost.cost_per_1k_pages_eur`` et
|
| 25 |
``co2_per_1k_pages_g``.
|
| 26 |
"""
|
|
@@ -28,7 +28,7 @@ On ne reconstruit pas Friedman/Nemenyi (déjà dans Sprint 18) ;
|
|
| 28 |
on agrège ici les données nécessaires pour qu'un
|
| 29 |
tests statistique externe puisse les consommer. Le rapport
|
| 30 |
existant reste libre de brancher
|
| 31 |
-
``picarones.
|
| 32 |
ce module.
|
| 33 |
|
| 34 |
Sortie
|
|
|
|
| 28 |
on agrège ici les données nécessaires pour qu'un
|
| 29 |
tests statistique externe puisse les consommer. Le rapport
|
| 30 |
existant reste libre de brancher
|
| 31 |
+
``picarones.evaluation.statistics.friedman_test`` sur la sortie de
|
| 32 |
ce module.
|
| 33 |
|
| 34 |
Sortie
|
|
@@ -6,7 +6,7 @@ rewrite ciblé (cf. ``docs/roadmap/rewrite-2026.md``).
|
|
| 6 |
|
| 7 |
Ce fichier est conservé comme re-export pour ne **rien casser**
|
| 8 |
chez les ~50 consommateurs qui font ``from
|
| 9 |
-
picarones.
|
| 10 |
publics ET privés utilisés downstream (``_parse_exclude_chars``,
|
| 11 |
``_apply_diplomatic_table``) sont ré-exposés explicitement.
|
| 12 |
|
|
|
|
| 6 |
|
| 7 |
Ce fichier est conservé comme re-export pour ne **rien casser**
|
| 8 |
chez les ~50 consommateurs qui font ``from
|
| 9 |
+
picarones.formats.text.normalization import X``. Les symboles
|
| 10 |
publics ET privés utilisés downstream (``_parse_exclude_chars``,
|
| 11 |
``_apply_diplomatic_table``) sont ré-exposés explicitement.
|
| 12 |
|
|
@@ -1,11 +1,5 @@
|
|
| 1 |
"""Précision sur séquences numériques — Sprint 85 (A.II.5b).
|
| 2 |
|
| 3 |
-
Phase 5.C.batch7 — module relocalisé depuis
|
| 4 |
-
``picarones.measurements.numerical_sequences`` vers
|
| 5 |
-
``picarones.evaluation.metrics.numerical_sequences``. Le chemin
|
| 6 |
-
legacy reste disponible via un shim avec ``DeprecationWarning`` ;
|
| 7 |
-
suppression prévue en 2.0.
|
| 8 |
-
|
| 9 |
Sprint 85 — A.II.5b du plan d'évolution 2026.
|
| 10 |
|
| 11 |
Pourquoi ce module
|
|
@@ -23,7 +17,7 @@ Catégories couvertes
|
|
| 23 |
(le module détecte les **années** sur 4 chiffres dans la
|
| 24 |
plage [1000-2099]).
|
| 25 |
2. **Numéraux romains** : ``MDCLXVIII``, ``XIV``, ``Tome IV``.
|
| 26 |
-
Réutilise ``picarones.
|
| 27 |
3. **Foliotation** : ``f. 12``, ``f. 12r``, ``fol. 24v``,
|
| 28 |
``p. 5``, ``pp. 12-15``, ``n° 42``.
|
| 29 |
4. **Montants** : ``12 livres``, ``5 sols``, ``8 deniers``,
|
|
|
|
| 1 |
"""Précision sur séquences numériques — Sprint 85 (A.II.5b).
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
Sprint 85 — A.II.5b du plan d'évolution 2026.
|
| 4 |
|
| 5 |
Pourquoi ce module
|
|
|
|
| 17 |
(le module détecte les **années** sur 4 chiffres dans la
|
| 18 |
plage [1000-2099]).
|
| 19 |
2. **Numéraux romains** : ``MDCLXVIII``, ``XIV``, ``Tome IV``.
|
| 20 |
+
Réutilise ``picarones.evaluation.metrics.roman_numerals`` (Sprint 60).
|
| 21 |
3. **Foliotation** : ``f. 12``, ``f. 12r``, ``fol. 24v``,
|
| 22 |
``p. 5``, ``pp. 12-15``, ``n° 42``.
|
| 23 |
4. **Montants** : ``12 livres``, ``5 sols``, ``8 deniers``,
|
|
@@ -33,13 +33,12 @@ from typing import TYPE_CHECKING, Any, Optional
|
|
| 33 |
|
| 34 |
if TYPE_CHECKING:
|
| 35 |
from picarones.evaluation.corpus import Corpus, Document
|
| 36 |
-
#
|
| 37 |
-
#
|
| 38 |
-
#
|
| 39 |
-
# ``Any`` ; le
|
| 40 |
-
#
|
| 41 |
-
|
| 42 |
-
BaseOCREngine = Any # type: ignore[misc,assignment]
|
| 43 |
|
| 44 |
logger = logging.getLogger(__name__)
|
| 45 |
|
|
@@ -413,7 +412,8 @@ class RobustnessAnalyzer:
|
|
| 413 |
Parameters
|
| 414 |
----------
|
| 415 |
engines:
|
| 416 |
-
Un ou plusieurs
|
|
|
|
| 417 |
degradation_types:
|
| 418 |
Liste des types de dégradation à tester.
|
| 419 |
Par défaut : tous (``"noise"``, ``"blur"``, ``"rotation"``,
|
|
@@ -425,16 +425,16 @@ class RobustnessAnalyzer:
|
|
| 425 |
|
| 426 |
Examples
|
| 427 |
--------
|
| 428 |
-
>>> from picarones.adapters.
|
| 429 |
>>> from picarones.evaluation.metrics.robustness import RobustnessAnalyzer
|
| 430 |
-
>>> engine =
|
| 431 |
>>> analyzer = RobustnessAnalyzer([engine], degradation_types=["noise", "blur"])
|
| 432 |
>>> report = analyzer.analyze(corpus)
|
| 433 |
"""
|
| 434 |
|
| 435 |
def __init__(
|
| 436 |
self,
|
| 437 |
-
engines: "list[
|
| 438 |
degradation_types: Optional[list[str]] = None,
|
| 439 |
cer_threshold: float = 0.20,
|
| 440 |
custom_levels: Optional[dict[str, list]] = None,
|
|
|
|
| 33 |
|
| 34 |
if TYPE_CHECKING:
|
| 35 |
from picarones.evaluation.corpus import Corpus, Document
|
| 36 |
+
# Le moteur OCR passé à ``RobustnessAnalyzer`` n'est pas
|
| 37 |
+
# importé statiquement depuis la couche ``evaluation/`` (la
|
| 38 |
+
# règle inward-only interdit les imports vers ``adapters/``).
|
| 39 |
+
# L'annotation utilise donc ``Any`` ; le duck typing suffit :
|
| 40 |
+
# l'objet doit exposer ``.run(image_path) -> EngineResult``.
|
| 41 |
+
OCREngine = Any # type: ignore[misc,assignment]
|
|
|
|
| 42 |
|
| 43 |
logger = logging.getLogger(__name__)
|
| 44 |
|
|
|
|
| 412 |
Parameters
|
| 413 |
----------
|
| 414 |
engines:
|
| 415 |
+
Un ou plusieurs adapters OCR (``BaseOCRAdapter`` — duck typing
|
| 416 |
+
suffit : l'objet doit exposer ``.run(image_path)``).
|
| 417 |
degradation_types:
|
| 418 |
Liste des types de dégradation à tester.
|
| 419 |
Par défaut : tous (``"noise"``, ``"blur"``, ``"rotation"``,
|
|
|
|
| 425 |
|
| 426 |
Examples
|
| 427 |
--------
|
| 428 |
+
>>> from picarones.adapters.ocr.tesseract import TesseractAdapter
|
| 429 |
>>> from picarones.evaluation.metrics.robustness import RobustnessAnalyzer
|
| 430 |
+
>>> engine = TesseractAdapter(config={"lang": "fra"})
|
| 431 |
>>> analyzer = RobustnessAnalyzer([engine], degradation_types=["noise", "blur"])
|
| 432 |
>>> report = analyzer.analyze(corpus)
|
| 433 |
"""
|
| 434 |
|
| 435 |
def __init__(
|
| 436 |
self,
|
| 437 |
+
engines: "list[OCREngine]",
|
| 438 |
degradation_types: Optional[list[str]] = None,
|
| 439 |
cer_threshold: float = 0.20,
|
| 440 |
custom_levels: Optional[dict[str, list]] = None,
|
|
@@ -1,11 +1,5 @@
|
|
| 1 |
"""Numéraux romains — Sprint 60.
|
| 2 |
|
| 3 |
-
Phase 5.C.batch7 — module relocalisé depuis
|
| 4 |
-
``picarones.measurements.roman_numerals`` vers
|
| 5 |
-
``picarones.evaluation.metrics.roman_numerals``. Le chemin legacy
|
| 6 |
-
reste disponible via un shim avec ``DeprecationWarning`` ;
|
| 7 |
-
suppression prévue en 2.0.
|
| 8 |
-
|
| 9 |
Sprint 60 — Étape 3 / extension philologique transversale du plan
|
| 10 |
d'évolution 2026.
|
| 11 |
|
|
|
|
| 1 |
"""Numéraux romains — Sprint 60.
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
Sprint 60 — Étape 3 / extension philologique transversale du plan
|
| 4 |
d'évolution 2026.
|
| 5 |
|
|
@@ -1,15 +1,7 @@
|
|
| 1 |
-
"""Recherchabilité fuzzy + séquences numériques
|
| 2 |
|
| 3 |
-
Fonctions de calcul **pures** (sans ``@register_metric``
|
| 4 |
-
utilisées par ``SearchView``.
|
| 5 |
-
historiques ``picarones.measurements.searchability`` (Sprint 84)
|
| 6 |
-
et ``picarones.measurements.numerical_sequences`` (Sprint 85),
|
| 7 |
-
sans la dépendance vers le singleton global ``core.metric_registry``.
|
| 8 |
-
|
| 9 |
-
Les modules legacy seront supprimés au S20 quand le
|
| 10 |
-
``MetricRegistry`` instancié explicitement (S5) deviendra le seul
|
| 11 |
-
registre. En attendant, ce module fournit la version "couche
|
| 12 |
-
evaluation" propre.
|
| 13 |
|
| 14 |
Métriques livrées
|
| 15 |
-----------------
|
|
@@ -20,11 +12,8 @@ Métriques livrées
|
|
| 20 |
|
| 21 |
- ``numerical_sequence_preservation(reference, hypothesis)`` —
|
| 22 |
fraction des séquences numériques de la GT préservées
|
| 23 |
-
strictement dans l'hypothèse.
|
| 24 |
-
|
| 25 |
-
réaliste pour les corpus patrimoniaux datés). Le cas complet
|
| 26 |
-
(numéraux romains, foliations, monnaies, années régnales) reste
|
| 27 |
-
dans le legacy et sera réintégré au S20 avec le registre.
|
| 28 |
|
| 29 |
Toutes les métriques ∈ [0, 1] avec ``higher_is_better=True``.
|
| 30 |
"""
|
|
@@ -42,8 +31,7 @@ import re
|
|
| 42 |
def levenshtein_distance(a: str, b: str) -> int:
|
| 43 |
"""Distance de Levenshtein (substitution = insertion = suppression = 1).
|
| 44 |
|
| 45 |
-
Implémentation
|
| 46 |
-
(Sprint 84) mais sans le décorateur ``@register_metric``.
|
| 47 |
"""
|
| 48 |
if a == b:
|
| 49 |
return 0
|
|
@@ -93,7 +81,7 @@ def searchability_recall(
|
|
| 93 |
-------
|
| 94 |
float
|
| 95 |
``n_retrouves / n_gt`` ∈ [0, 1]. ``0.0`` si la GT est
|
| 96 |
-
vide
|
| 97 |
"""
|
| 98 |
if max_distance < 0:
|
| 99 |
raise ValueError(f"max_distance doit être ≥ 0, reçu {max_distance}")
|
|
@@ -164,11 +152,10 @@ def numerical_sequence_preservation(
|
|
| 164 |
|
| 165 |
Note méthodologique
|
| 166 |
-------------------
|
| 167 |
-
Volontairement minimaliste
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
(Sprint 85) et sera réintégré dans la couche evaluation au S20.
|
| 172 |
|
| 173 |
Multi-set : si la GT contient ``"1789"`` deux fois et
|
| 174 |
l'hypothèse une fois, seul un est compté préservé.
|
|
|
|
| 1 |
+
"""Recherchabilité fuzzy + séquences numériques.
|
| 2 |
|
| 3 |
+
Fonctions de calcul **pures** (sans décorateur ``@register_metric``)
|
| 4 |
+
utilisées par ``SearchView``.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
Métriques livrées
|
| 7 |
-----------------
|
|
|
|
| 12 |
|
| 13 |
- ``numerical_sequence_preservation(reference, hypothesis)`` —
|
| 14 |
fraction des séquences numériques de la GT préservées
|
| 15 |
+
strictement dans l'hypothèse. Détecte uniquement les **années
|
| 16 |
+
4 chiffres** (proxy réaliste pour les corpus patrimoniaux datés).
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
Toutes les métriques ∈ [0, 1] avec ``higher_is_better=True``.
|
| 19 |
"""
|
|
|
|
| 31 |
def levenshtein_distance(a: str, b: str) -> int:
|
| 32 |
"""Distance de Levenshtein (substitution = insertion = suppression = 1).
|
| 33 |
|
| 34 |
+
Implémentation pure (sans décorateur ``@register_metric``).
|
|
|
|
| 35 |
"""
|
| 36 |
if a == b:
|
| 37 |
return 0
|
|
|
|
| 81 |
-------
|
| 82 |
float
|
| 83 |
``n_retrouves / n_gt`` ∈ [0, 1]. ``0.0`` si la GT est
|
| 84 |
+
vide.
|
| 85 |
"""
|
| 86 |
if max_distance < 0:
|
| 87 |
raise ValueError(f"max_distance doit être ≥ 0, reçu {max_distance}")
|
|
|
|
| 152 |
|
| 153 |
Note méthodologique
|
| 154 |
-------------------
|
| 155 |
+
Volontairement minimaliste : seules les années 4 chiffres sont
|
| 156 |
+
détectées. Le pattern complet (numéraux romains, foliations
|
| 157 |
+
``f. 12r``, monnaies, années régnales ``an III``) n'est pas
|
| 158 |
+
couvert ici.
|
|
|
|
| 159 |
|
| 160 |
Multi-set : si la GT contient ``"1789"`` deux fois et
|
| 161 |
l'hypothèse une fois, seul un est compté préservé.
|
|
@@ -32,7 +32,7 @@ intuitive :
|
|
| 32 |
|
| 33 |
Dépendances
|
| 34 |
-----------
|
| 35 |
-
S'appuie strictement sur ``picarones.
|
| 36 |
35) — pas de double calcul, pas de logique nouvelle de
|
| 37 |
divergence.
|
| 38 |
"""
|
|
|
|
| 32 |
|
| 33 |
Dépendances
|
| 34 |
-----------
|
| 35 |
+
S'appuie strictement sur ``picarones.evaluation.metrics.inter_engine`` (Sprint
|
| 36 |
35) — pas de double calcul, pas de logique nouvelle de
|
| 37 |
divergence.
|
| 38 |
"""
|
|
@@ -8,13 +8,10 @@ pas de décorateur magique.
|
|
| 8 |
|
| 9 |
Différence avec ``picarones.evaluation.metric_registry``
|
| 10 |
--------------------------------------------------------
|
| 11 |
-
L'autre registre
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
``import picarones`` charge ~50 sous-modules pour amorcer le
|
| 16 |
-
registre — anti-pattern documenté dans
|
| 17 |
-
``BACKLOG_POST_LIVRAISON.md`` §2.4.
|
| 18 |
|
| 19 |
Ici, ``MetricRegistry`` est une classe instanciable :
|
| 20 |
|
|
|
|
| 8 |
|
| 9 |
Différence avec ``picarones.evaluation.metric_registry``
|
| 10 |
--------------------------------------------------------
|
| 11 |
+
L'autre registre utilise un dict module-level ``_METRIC_REGISTRY``
|
| 12 |
+
rempli par un décorateur ``@register_metric`` appliqué au top-level
|
| 13 |
+
d'autres modules. Conséquence : un ``import picarones`` charge
|
| 14 |
+
~50 sous-modules pour amorcer le registre.
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
Ici, ``MetricRegistry`` est une classe instanciable :
|
| 17 |
|
|
@@ -19,9 +19,6 @@ Familles
|
|
| 19 |
|
| 20 |
Migration Phase 2
|
| 21 |
-----------------
|
| 22 |
-
|
| 23 |
-
Migré depuis :mod:`picarones.measurements.statistics` qui devient
|
| 24 |
-
un shim re-export avec ``DeprecationWarning``. Comportement
|
| 25 |
identique bit-for-bit (même seed pour le bootstrap, mêmes
|
| 26 |
algorithmes scipy, même rendu SVG). Suppression du shim legacy
|
| 27 |
en version 2.0.
|
|
|
|
| 19 |
|
| 20 |
Migration Phase 2
|
| 21 |
-----------------
|
|
|
|
|
|
|
|
|
|
| 22 |
identique bit-for-bit (même seed pour le bootstrap, mêmes
|
| 23 |
algorithmes scipy, même rendu SVG). Suppression du shim legacy
|
| 24 |
en version 2.0.
|
|
@@ -6,7 +6,7 @@ Standard de facto pour comparer plusieurs systèmes sur plusieurs
|
|
| 6 |
datasets — ici plusieurs moteurs OCR sur plusieurs documents.
|
| 7 |
|
| 8 |
Le rendu visuel canonique (Critical Difference Diagram) vit dans
|
| 9 |
-
:mod:`picarones.
|
| 10 |
calcul (ce module) et présentation (l'autre).
|
| 11 |
"""
|
| 12 |
|
|
|
|
| 6 |
datasets — ici plusieurs moteurs OCR sur plusieurs documents.
|
| 7 |
|
| 8 |
Le rendu visuel canonique (Critical Difference Diagram) vit dans
|
| 9 |
+
:mod:`picarones.evaluation.statistics.cdd_render` pour séparer
|
| 10 |
calcul (ce module) et présentation (l'autre).
|
| 11 |
"""
|
| 12 |
|
|
@@ -215,7 +215,7 @@ def compute_pairwise_stats(
|
|
| 215 |
|
| 216 |
__all__ = [
|
| 217 |
# Symboles publics : signature stable, consommés directement par les
|
| 218 |
-
# tests via le ré-export de ``picarones.
|
| 219 |
"compute_pairwise_stats",
|
| 220 |
"wilcoxon_test",
|
| 221 |
# Symboles privés ré-exportés (consommés par certains tests) :
|
|
|
|
| 215 |
|
| 216 |
__all__ = [
|
| 217 |
# Symboles publics : signature stable, consommés directement par les
|
| 218 |
+
# tests via le ré-export de ``picarones.evaluation.statistics``.
|
| 219 |
"compute_pairwise_stats",
|
| 220 |
"wilcoxon_test",
|
| 221 |
# Symboles privés ré-exportés (consommés par certains tests) :
|
|
@@ -496,7 +496,7 @@ def generate_sample_benchmark(
|
|
| 496 |
document_count=n_docs,
|
| 497 |
engine_reports=engine_reports,
|
| 498 |
metadata={
|
| 499 |
-
"description": "Données de démonstration
|
| 500 |
"script": "gothique textura",
|
| 501 |
"langue": "Français médiéval (XIVe-XVe siècle)",
|
| 502 |
"institution": "Département des manuscrits",
|
|
|
|
| 496 |
document_count=n_docs,
|
| 497 |
engine_reports=engine_reports,
|
| 498 |
metadata={
|
| 499 |
+
"description": "Données de démonstration synthétiques",
|
| 500 |
"script": "gothique textura",
|
| 501 |
"langue": "Français médiéval (XIVe-XVe siècle)",
|
| 502 |
"institution": "Département des manuscrits",
|
|
@@ -13,7 +13,7 @@ Sous-packages :
|
|
| 13 |
- ``pagexml/`` — PAGE XML (PRIMA, transkribus).
|
| 14 |
- ``text/`` — normalisation texte (NFC, casefold, profils
|
| 15 |
diplomatiques, exclusion de caractères). Cible du déplacement
|
| 16 |
-
de ``picarones.
|
| 17 |
|
| 18 |
Règle d'import : ces modules peuvent importer ``lxml`` et
|
| 19 |
``defusedxml``. Ils ne doivent **jamais** importer un moteur OCR
|
|
|
|
| 13 |
- ``pagexml/`` — PAGE XML (PRIMA, transkribus).
|
| 14 |
- ``text/`` — normalisation texte (NFC, casefold, profils
|
| 15 |
diplomatiques, exclusion de caractères). Cible du déplacement
|
| 16 |
+
de ``picarones.formats.text.normalization`` au Sprint S9.
|
| 17 |
|
| 18 |
Règle d'import : ces modules peuvent importer ``lxml`` et
|
| 19 |
``defusedxml``. Ils ne doivent **jamais** importer un moteur OCR
|
|
@@ -1,24 +0,0 @@
|
|
| 1 |
-
"""``picarones.i18n`` — shim re-export (déprécié, suppression 2.0).
|
| 2 |
-
|
| 3 |
-
Canonique : :mod:`picarones.reports.i18n`. Phase 5.E du retrait
|
| 4 |
-
du legacy.
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
from __future__ import annotations
|
| 8 |
-
|
| 9 |
-
import warnings
|
| 10 |
-
|
| 11 |
-
from picarones.reports.i18n import * # noqa: F401, F403
|
| 12 |
-
from picarones.reports.i18n import ( # noqa: F401
|
| 13 |
-
TRANSLATIONS,
|
| 14 |
-
SUPPORTED_LANGS,
|
| 15 |
-
get_labels,
|
| 16 |
-
reload_translations,
|
| 17 |
-
)
|
| 18 |
-
|
| 19 |
-
warnings.warn(
|
| 20 |
-
"picarones.i18n is deprecated and will be removed in 2.0. "
|
| 21 |
-
"Import from picarones.reports.i18n instead.",
|
| 22 |
-
DeprecationWarning,
|
| 23 |
-
stacklevel=2,
|
| 24 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -326,9 +326,10 @@ def demo_cmd(
|
|
| 326 |
picarones demo --with-robustness
|
| 327 |
picarones demo --with-history --with-robustness --docs 8
|
| 328 |
"""
|
| 329 |
-
#
|
| 330 |
-
# top-level
|
| 331 |
-
#
|
|
|
|
| 332 |
import importlib
|
| 333 |
generate_sample_benchmark = importlib.import_module(
|
| 334 |
"picarones.evaluation.synthetic",
|
|
|
|
| 326 |
picarones demo --with-robustness
|
| 327 |
picarones demo --with-history --with-robustness --docs 8
|
| 328 |
"""
|
| 329 |
+
# Import dynamique pour respecter ``test_layer_imports_are_legal``
|
| 330 |
+
# (les imports top-level depuis ``interfaces/`` sont scannés à
|
| 331 |
+
# l'import-time, et l'analyseur s'exécute sans avoir loadé tous
|
| 332 |
+
# les modules).
|
| 333 |
import importlib
|
| 334 |
generate_sample_benchmark = importlib.import_module(
|
| 335 |
"picarones.evaluation.synthetic",
|
|
@@ -12,7 +12,7 @@ Avant le Sprint 26, l'état des benchmarks vivait uniquement en mémoire dans
|
|
| 12 |
au-delà de ce que ``BenchmarkJob.events`` portait en RAM.
|
| 13 |
|
| 14 |
Le Sprint 26 adresse les trois en persistant les jobs et leurs événements
|
| 15 |
-
dans une base SQLite locale (cohérent avec ``picarones.
|
| 16 |
qui utilise déjà SQLite). La base joue trois rôles :
|
| 17 |
|
| 18 |
- **Source de vérité** pour le statut/progression d'un job — ``BenchmarkJob``
|
|
|
|
| 12 |
au-delà de ce que ``BenchmarkJob.events`` portait en RAM.
|
| 13 |
|
| 14 |
Le Sprint 26 adresse les trois en persistant les jobs et leurs événements
|
| 15 |
+
dans une base SQLite locale (cohérent avec ``picarones.evaluation.metrics.history``,
|
| 16 |
qui utilise déjà SQLite). La base joue trois rôles :
|
| 17 |
|
| 18 |
- **Source de vérité** pour le statut/progression d'un job — ``BenchmarkJob``
|
|
@@ -45,7 +45,7 @@ Modules livrés au S8
|
|
| 45 |
Cible du Sprint S12
|
| 46 |
-------------------
|
| 47 |
Équivalence numérique CER/WER avec l'ancien
|
| 48 |
-
``
|
| 49 |
"""
|
| 50 |
|
| 51 |
from __future__ import annotations
|
|
|
|
| 45 |
Cible du Sprint S12
|
| 46 |
-------------------
|
| 47 |
Équivalence numérique CER/WER avec l'ancien
|
| 48 |
+
``BenchmarkService`` à 1e-9 près sur les fixtures.
|
| 49 |
"""
|
| 50 |
|
| 51 |
from __future__ import annotations
|
|
@@ -1,14 +1,13 @@
|
|
| 1 |
-
"""Builder de ``PipelineSpec`` pour les chaînes OCR + LLM
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
``PipelineSpec`` canonique exécutable par ``PipelineExecutor``.
|
| 6 |
|
| 7 |
-
|
| 8 |
-
-----
|
| 9 |
|
| 10 |
================ ============= =========== ================================
|
| 11 |
-
Mode
|
| 12 |
================ ============= =========== ================================
|
| 13 |
``text_only`` IMAGE OCR + LLM ``CORRECTED_TEXT``
|
| 14 |
``text_and_image`` IMAGE OCR + LLM ``CORRECTED_TEXT`` (LLM voit aussi IMAGE)
|
|
@@ -26,22 +25,7 @@ L'adapter OCR amont (Tesseract, Pero, Mistral OCR, Google Vision,
|
|
| 26 |
Azure DI, ou ``precomputed`` quand le corpus porte déjà l'OCR) est
|
| 27 |
quelconque tant qu'il déclare ``output_types ⊇ {RAW_TEXT}``.
|
| 28 |
|
| 29 |
-
|
| 30 |
-
--------------------
|
| 31 |
-
Code legacy ::
|
| 32 |
-
|
| 33 |
-
from picarones.adapters.legacy_pipelines import OCRLLMPipeline, PipelineMode
|
| 34 |
-
from picarones.adapters.legacy_engines.tesseract import TesseractEngine
|
| 35 |
-
from picarones.adapters.llm import OpenAIAdapter
|
| 36 |
-
|
| 37 |
-
pipeline = OCRLLMPipeline(
|
| 38 |
-
ocr_engine=TesseractEngine({"lang": "fra"}),
|
| 39 |
-
llm_adapter=OpenAIAdapter(model="gpt-4o"),
|
| 40 |
-
mode=PipelineMode.TEXT_ONLY,
|
| 41 |
-
)
|
| 42 |
-
result = pipeline.run("scan.jpg") # → EngineResult
|
| 43 |
-
|
| 44 |
-
Code canonique équivalent ::
|
| 45 |
|
| 46 |
from picarones.pipeline import PipelineExecutor
|
| 47 |
from picarones.pipeline.llm_pipeline_builder import (
|
|
@@ -119,10 +103,10 @@ def make_ocr_llm_pipeline_spec(
|
|
| 119 |
Format scalaire (``str``, ``int``, ``float``, ``bool``).
|
| 120 |
llm_params:
|
| 121 |
Paramètres dynamiques passés au step LLM/VLM au runtime.
|
| 122 |
-
Cas typique
|
| 123 |
``{"prompt_template": "Corrige : {ocr_output}"}`` permet à
|
| 124 |
-
un caller de spécifier un template
|
| 125 |
-
|
| 126 |
|
| 127 |
Returns
|
| 128 |
-------
|
|
|
|
| 1 |
+
"""Builder de ``PipelineSpec`` pour les chaînes OCR + LLM.
|
| 2 |
|
| 3 |
+
Construit une ``PipelineSpec`` exécutable par ``PipelineExecutor``
|
| 4 |
+
à partir d'un mode + des noms d'adapters.
|
|
|
|
| 5 |
|
| 6 |
+
Modes
|
| 7 |
+
-----
|
| 8 |
|
| 9 |
================ ============= =========== ================================
|
| 10 |
+
Mode Initial input Steps Output final
|
| 11 |
================ ============= =========== ================================
|
| 12 |
``text_only`` IMAGE OCR + LLM ``CORRECTED_TEXT``
|
| 13 |
``text_and_image`` IMAGE OCR + LLM ``CORRECTED_TEXT`` (LLM voit aussi IMAGE)
|
|
|
|
| 25 |
Azure DI, ou ``precomputed`` quand le corpus porte déjà l'OCR) est
|
| 26 |
quelconque tant qu'il déclare ``output_types ⊇ {RAW_TEXT}``.
|
| 27 |
|
| 28 |
+
Usage ::
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
from picarones.pipeline import PipelineExecutor
|
| 31 |
from picarones.pipeline.llm_pipeline_builder import (
|
|
|
|
| 103 |
Format scalaire (``str``, ``int``, ``float``, ``bool``).
|
| 104 |
llm_params:
|
| 105 |
Paramètres dynamiques passés au step LLM/VLM au runtime.
|
| 106 |
+
Cas typique :
|
| 107 |
``{"prompt_template": "Corrige : {ocr_output}"}`` permet à
|
| 108 |
+
un caller de spécifier un template ad-hoc sans toucher à la
|
| 109 |
+
config de l'adapter.
|
| 110 |
|
| 111 |
Returns
|
| 112 |
-------
|
|
@@ -1,44 +1,20 @@
|
|
| 1 |
-
"""``OCRLLMPipelineConfig`` — container
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
-
|
| 11 |
-
|
| 12 |
-
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
-
|
| 17 |
-
-
|
| 18 |
-
``BaseOCREngine`` (legacy) pour le step OCR amont,
|
| 19 |
-
- ne dépend pas du legacy.
|
| 20 |
-
|
| 21 |
-
L'exécution effective passe par ``PipelineExecutor`` qui consomme
|
| 22 |
-
une ``PipelineSpec`` construite via ``make_ocr_llm_pipeline_spec``.
|
| 23 |
-
|
| 24 |
-
Duck-typing compat
|
| 25 |
-
------------------
|
| 26 |
-
Pour faciliter la migration progressive,
|
| 27 |
-
``OCRLLMPipelineConfig`` expose les mêmes attributs/propriétés
|
| 28 |
-
que ``OCRLLMPipeline`` legacy :
|
| 29 |
-
|
| 30 |
-
- ``is_pipeline = True``,
|
| 31 |
-
- ``ocr_engine`` (alias de ``ocr_adapter`` côté canonique),
|
| 32 |
-
- ``llm_adapter``,
|
| 33 |
-
- ``mode`` (string, pas enum — tolérance ajoutée dans
|
| 34 |
-
``_ocr_llm_pipeline_to_spec``),
|
| 35 |
-
- ``prompt_template``,
|
| 36 |
-
- ``name``.
|
| 37 |
-
|
| 38 |
-
Les helpers
|
| 39 |
-
``picarones.app.services.benchmark_runner.engine_to_pipeline_spec``
|
| 40 |
-
et ``build_adapter_resolver`` traitent donc indifféremment les
|
| 41 |
-
deux types.
|
| 42 |
"""
|
| 43 |
|
| 44 |
from __future__ import annotations
|
|
@@ -128,12 +104,12 @@ class OCRLLMPipelineConfig:
|
|
| 128 |
|
| 129 |
@property
|
| 130 |
def ocr_engine(self) -> Any | None:
|
| 131 |
-
"""
|
| 132 |
|
| 133 |
Les helpers ``_ocr_llm_pipeline_to_spec`` et
|
| 134 |
-
``build_adapter_resolver`` accèdent à ``pipeline.ocr_engine``
|
| 135 |
-
|
| 136 |
-
|
| 137 |
"""
|
| 138 |
return self.ocr_adapter
|
| 139 |
|
|
|
|
| 1 |
+
"""``OCRLLMPipelineConfig`` — container pour pipelines OCR+LLM.
|
| 2 |
+
|
| 3 |
+
Container *pur* (immutable, pas de logique d'exécution) qui décrit
|
| 4 |
+
un pipeline composé OCR amont + LLM aval. L'exécution effective
|
| 5 |
+
passe par ``PipelineExecutor`` qui consomme une ``PipelineSpec``
|
| 6 |
+
construite via ``make_ocr_llm_pipeline_spec``.
|
| 7 |
+
|
| 8 |
+
Attributs exposés
|
| 9 |
+
-----------------
|
| 10 |
+
- ``is_pipeline = True`` — marker consommé par ``benchmark_runner``
|
| 11 |
+
pour distinguer un pipeline composé d'un OCR seul.
|
| 12 |
+
- ``ocr_engine`` (alias de ``ocr_adapter``) — adapter OCR amont.
|
| 13 |
+
- ``llm_adapter`` — adapter LLM aval.
|
| 14 |
+
- ``mode`` — string parmi ``text_only`` / ``text_and_image`` /
|
| 15 |
+
``zero_shot``.
|
| 16 |
+
- ``prompt_template`` — template de prompt pour le LLM.
|
| 17 |
+
- ``name`` — nom du pipeline pour l'identification dans le rapport.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
"""
|
| 19 |
|
| 20 |
from __future__ import annotations
|
|
|
|
| 104 |
|
| 105 |
@property
|
| 106 |
def ocr_engine(self) -> Any | None:
|
| 107 |
+
"""Alias historique de ``ocr_adapter``.
|
| 108 |
|
| 109 |
Les helpers ``_ocr_llm_pipeline_to_spec`` et
|
| 110 |
+
``build_adapter_resolver`` accèdent à ``pipeline.ocr_engine`` ;
|
| 111 |
+
on expose ``ocr_adapter`` sous ce nom pour préserver leur
|
| 112 |
+
wiring.
|
| 113 |
"""
|
| 114 |
return self.ocr_adapter
|
| 115 |
|
|
@@ -7,11 +7,6 @@ Ce sous-package abrite les utilitaires purs et stables :
|
|
| 7 |
- ``render_helpers`` — fonctions de rendu HTML/CSS communes.
|
| 8 |
- ``assets`` — bundling JS + CSS + glossaire dans le rapport
|
| 9 |
autonome.
|
| 10 |
-
|
| 11 |
-
Phase 5 du retrait du legacy. Ces modules viennent de
|
| 12 |
-
``picarones.report.*`` ; les chemins legacy restent disponibles
|
| 13 |
-
via des shims avec ``DeprecationWarning`` jusqu'à ce que tous les
|
| 14 |
-
renderers thématiques aient migré.
|
| 15 |
"""
|
| 16 |
|
| 17 |
from __future__ import annotations
|
|
|
|
| 7 |
- ``render_helpers`` — fonctions de rendu HTML/CSS communes.
|
| 8 |
- ``assets`` — bundling JS + CSS + glossaire dans le rapport
|
| 9 |
autonome.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
"""
|
| 11 |
|
| 12 |
from __future__ import annotations
|
|
@@ -1,10 +1,5 @@
|
|
| 1 |
"""Chargement et préparation des assets du rapport HTML.
|
| 2 |
|
| 3 |
-
Phase 5 — module relocalisé depuis ``picarones.report.assets`` vers
|
| 4 |
-
``picarones.reports._helpers.assets``. Le chemin legacy reste
|
| 5 |
-
disponible via un shim avec ``DeprecationWarning`` ; suppression
|
| 6 |
-
prévue en 2.0.
|
| 7 |
-
|
| 8 |
Ce module concentre tout ce qui touche aux ressources binaires
|
| 9 |
embarquées ou référencées par le rapport :
|
| 10 |
|
|
|
|
| 1 |
"""Chargement et préparation des assets du rapport HTML.
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
Ce module concentre tout ce qui touche aux ressources binaires
|
| 4 |
embarquées ou référencées par le rapport :
|
| 5 |
|
|
@@ -1,10 +1,5 @@
|
|
| 1 |
"""Palettes de couleurs CSS — partagées entre rapport HTML et modules de rendu.
|
| 2 |
|
| 3 |
-
Phase 5 — module relocalisé depuis ``picarones.report.colors`` vers
|
| 4 |
-
``picarones.reports._helpers.colors``. Le chemin legacy reste
|
| 5 |
-
disponible via un shim avec ``DeprecationWarning`` ; suppression
|
| 6 |
-
prévue en 2.0.
|
| 7 |
-
|
| 8 |
Sprint A7 (item m-5 de l'audit institutional-readiness-2026-05) :
|
| 9 |
introduction d'une **palette daltonien-friendly** (Okabe-Ito) qui
|
| 10 |
remplace la palette historique rouge/vert/orange (problématique pour
|
|
|
|
| 1 |
"""Palettes de couleurs CSS — partagées entre rapport HTML et modules de rendu.
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
Sprint A7 (item m-5 de l'audit institutional-readiness-2026-05) :
|
| 4 |
introduction d'une **palette daltonien-friendly** (Okabe-Ito) qui
|
| 5 |
remplace la palette historique rouge/vert/orange (problématique pour
|
|
@@ -1,11 +1,5 @@
|
|
| 1 |
"""Helpers de rendu mutualisés.
|
| 2 |
|
| 3 |
-
Phase 5 — module relocalisé depuis
|
| 4 |
-
``picarones.report.render_helpers`` vers
|
| 5 |
-
``picarones.reports._helpers.render_helpers``. Le chemin legacy
|
| 6 |
-
reste disponible via un shim avec ``DeprecationWarning`` ;
|
| 7 |
-
suppression prévue en 2.0.
|
| 8 |
-
|
| 9 |
Centralise les fonctions de coloration et le builder de grille SVG qui
|
| 10 |
étaient auparavant dupliqués dans chaque ``*_render.py``. Avant cette
|
| 11 |
consolidation, le projet comptait 25 versions différentes de
|
|
|
|
| 1 |
"""Helpers de rendu mutualisés.
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
Centralise les fonctions de coloration et le builder de grille SVG qui
|
| 4 |
étaient auparavant dupliqués dans chaque ``*_render.py``. Avant cette
|
| 5 |
consolidation, le projet comptait 25 versions différentes de
|