Claude commited on
Commit
e407ec0
·
unverified ·
1 Parent(s): 1d36e9e

docs(sprint-H.8): cleanup obsolete legacy/shim language in production docstrings

Browse files

Sprint 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

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. picarones/__init__.py +8 -10
  2. picarones/adapters/corpus/_fallback_log.py +1 -1
  3. picarones/adapters/ocr/__init__.py +9 -10
  4. picarones/adapters/ocr/azure_doc_intel.py +1 -5
  5. picarones/adapters/ocr/base.py +4 -26
  6. picarones/adapters/ocr/factory.py +0 -11
  7. picarones/adapters/ocr/google_vision.py +1 -6
  8. picarones/adapters/ocr/mistral_ocr.py +1 -7
  9. picarones/adapters/ocr/pero_ocr.py +1 -9
  10. picarones/adapters/ocr/tesseract.py +6 -13
  11. picarones/adapters/storage/__init__.py +1 -1
  12. picarones/app/services/benchmark_runner.py +66 -86
  13. picarones/app/services/partial_store.py +8 -16
  14. picarones/app/services/registry_service.py +1 -1
  15. picarones/app/services/run_orchestrator.py +0 -2
  16. picarones/domain/artifacts.py +17 -27
  17. picarones/domain/errors.py +1 -1
  18. picarones/domain/module_protocol.py +7 -11
  19. picarones/evaluation/benchmark_result.py +0 -5
  20. picarones/evaluation/corpus.py +5 -11
  21. picarones/evaluation/metric_hooks.py +2 -7
  22. picarones/evaluation/metric_registry.py +10 -21
  23. picarones/evaluation/metric_result.py +2 -7
  24. picarones/evaluation/metrics/__init__.py +18 -36
  25. picarones/evaluation/metrics/alto_metrics.py +1 -1
  26. picarones/evaluation/metrics/builtin_hooks.py +1 -1
  27. picarones/evaluation/metrics/cost_projection.py +1 -1
  28. picarones/evaluation/metrics/incremental_comparison.py +1 -1
  29. picarones/evaluation/metrics/normalization.py +1 -1
  30. picarones/evaluation/metrics/numerical_sequences.py +1 -7
  31. picarones/evaluation/metrics/robustness.py +11 -11
  32. picarones/evaluation/metrics/roman_numerals.py +0 -6
  33. picarones/evaluation/metrics/search.py +11 -24
  34. picarones/evaluation/metrics/specialization.py +1 -1
  35. picarones/evaluation/registry/registry.py +4 -7
  36. picarones/evaluation/statistics/__init__.py +0 -3
  37. picarones/evaluation/statistics/friedman_nemenyi.py +1 -1
  38. picarones/evaluation/statistics/wilcoxon.py +1 -1
  39. picarones/evaluation/synthetic.py +1 -1
  40. picarones/formats/__init__.py +1 -1
  41. picarones/i18n.py +0 -24
  42. picarones/interfaces/cli/__init__.py +4 -3
  43. picarones/interfaces/web/jobs.py +1 -1
  44. picarones/pipeline/__init__.py +1 -1
  45. picarones/pipeline/llm_pipeline_builder.py +10 -26
  46. picarones/pipeline/llm_pipeline_config.py +21 -45
  47. picarones/reports/_helpers/__init__.py +0 -5
  48. picarones/reports/_helpers/assets.py +0 -5
  49. picarones/reports/_helpers/colors.py +0 -5
  50. picarones/reports/_helpers/render_helpers.py +0 -6
picarones/__init__.py CHANGED
@@ -2,8 +2,8 @@
2
 
3
  Licence Apache 2.0.
4
 
5
- API publique du Cercle 1 (abstractions stables) ré-exportée ici pour
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
- 3 cercles, et ``docs/reference/api-stable.md`` pour le contrat de stabilité.
20
  """
21
 
22
  from __future__ import annotations
@@ -41,7 +41,7 @@ __author__ = "Picarones contributors"
41
 
42
 
43
  # ──────────────────────────────────────────────────────────────────────────
44
- # API publique — Cercle 1 uniquement
45
  # ──────────────────────────────────────────────────────────────────────────
46
 
47
  from picarones.evaluation.corpus import (
@@ -75,12 +75,10 @@ from picarones.evaluation.metric_registry import (
75
  select_metrics,
76
  )
77
 
78
- # Sprint A3 — trigger d'enregistrement du registre typé (Sprint 34).
79
- # L'import de ``picarones.measurements`` provoque l'exécution des
80
- # décorateurs ``@register_metric`` sur ``cer``, ``wer``, ``mer``,
81
- # ``wil`` + ~15 métriques philologiques + reading order + NER + ALTO.
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__ = [
picarones/adapters/corpus/_fallback_log.py CHANGED
@@ -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.measurements.narrative.detectors.history`.
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
picarones/adapters/ocr/__init__.py CHANGED
@@ -1,20 +1,19 @@
1
- """Adapters OCR du nouveau monde Sprint A14-S26.
2
 
3
- Contrat ``BaseOCRAdapter`` natif au rewrite : pas hérité du legacy
4
- ``picarones.engines.base.BaseOCREngine``, exprimé directement en
5
- termes du nouveau ``ArtifactType`` et de l'interface
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
picarones/adapters/ocr/azure_doc_intel.py CHANGED
@@ -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 (legacy S51 reportée).
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
  """
picarones/adapters/ocr/base.py CHANGED
@@ -1,12 +1,4 @@
1
- """``BaseOCRAdapter`` — contrat natif du nouveau monde pour un adapter OCR.
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 — c'est la
26
- convention du nouveau monde pour permettre au ``payload_loader`` de
27
- le lire ultérieurement (Sprint S25 — la projection a un payload
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
  -------------------
picarones/adapters/ocr/factory.py CHANGED
@@ -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
 
picarones/adapters/ocr/google_vision.py CHANGED
@@ -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 (legacy S50 reportée).
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
  """
picarones/adapters/ocr/mistral_ocr.py CHANGED
@@ -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 (legacy S49 reportées au
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).
picarones/adapters/ocr/pero_ocr.py CHANGED
@@ -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 (legacy S48
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).
picarones/adapters/ocr/tesseract.py CHANGED
@@ -1,13 +1,7 @@
1
- """``TesseractAdapter`` natif Sprint A14-S30.
2
 
3
- Migration native du legacy ``picarones.engines.tesseract.TesseractEngine``
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 ``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 (legacy S47) reporté à un
56
- sprint dédié qui définira ``ConfidenceArtifact`` typé. La
57
- fonctionnalité reste disponible via le legacy
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).
picarones/adapters/storage/__init__.py CHANGED
@@ -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.measurements.history`` (SQLite
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
 
picarones/app/services/benchmark_runner.py CHANGED
@@ -1,34 +1,19 @@
1
- """Sprint D.1 du plan v2.0 adapter de compat ``run_benchmark`` legacy
2
- → ``BenchmarkService`` rewrite.
3
-
4
- Ce module présente l'API mono-call historique de
5
- ``picarones.measurements.runner.run_benchmark`` mais s'appuie en
6
- interne sur le rewrite (``BenchmarkService``,
7
- ``PipelineExecutor``, ``CorpusRunner``). Il sert de pont
8
- transitoire pour faciliter la migration des callers en plusieurs
9
- étapes :
10
-
11
- 1. (cette session) Helpers de mapping ``Corpus`` ``CorpusSpec``
12
- et ``Document`` ``DocumentRef`` — testables indépendamment.
13
- 2. (sub-phase D.1.b) Mapping ``BaseOCREngine`` ``PipelineSpec``
14
- + adapter resolver.
15
- 3. (sub-phase D.1.c) Conversion ``RunResult`` ``BenchmarkResult``.
16
- 4. (sub-phase D.1.d) Fonction ``run_benchmark_via_service``
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
- # Pas d'import direct de ``picarones.pipelines.base.OCRLLMPipeline`` ici
62
- # l'invariant architectural ``test_layer_imports_are_legal[layer-app]``
63
- # interdit à ``app/`` de dépendre du legacy. On consomme un
64
- # ``OCRLLMPipeline`` exclusivement par duck typing (``is_pipeline``,
65
- # ``ocr_engine``, ``llm_adapter``, ``mode``, ``prompt_template``).
66
 
67
 
68
  # ──────────────────────────────────────────────────────────────────────
69
- # Mapping Document (legacy) → DocumentRef (rewrite)
70
  # ──────────────────────────────────────────────────────────────────────
71
 
72
 
@@ -75,9 +55,9 @@ def document_to_document_ref(
75
  *,
76
  workspace_dir: Path,
77
  ) -> DocumentRef:
78
- """Convertit un ``Document`` legacy en ``DocumentRef`` rewrite.
79
 
80
- Le ``Document`` legacy porte sa GT en mémoire (``ground_truth: str``
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 legacy. ``image_path`` non-``None`` est requis ;
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`` legacy en ``CorpusSpec`` rewrite.
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 legacy.
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 (rewrite) → BenchmarkResult (legacy)
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`` rewrite en ``BenchmarkResult`` legacy.
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`` legacy avec ``engine_error``
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 legacy d'origine — sert à récupérer le ``ground_truth``
264
  et l'``image_path`` pour chaque document, dans le même ordre
265
  que ``run_result.document_results``.
266
  engines:
267
- Liste d'engines legacy dans l'ordre où leurs specs ont été
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 legacy compatible avec les consommateurs historiques
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`` legacy pour un DocumentResult.
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
- ``picarones.measurements.runner.document._compute_doc_result``
423
- lignes 102-112 (legacy supprimé en D.6.b).
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 (legacy) ou une string (canonique).
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 (legacy ``PipelineMode.X``) ou string (canonique).
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 à ``BaseOCREngine`` legacy ou ``OCRLLMPipeline``).
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 legacy ``is_pipeline``.
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 (Sprint H.2.c — le path legacy ``BaseOCREngine`` a
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 legacy ``BaseOCREngine`` / ``OCRLLMPipeline`` "
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
- # Sprint H.2.c — ``_ocr_only_to_spec`` (legacy ``BaseOCREngine`` →
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 ``OCRLLMPipeline`` legacy ou un
596
  ``OCRLLMPipelineConfig`` canonique (3 modes).
597
 
598
- Tolère ``pipeline.mode`` en enum (legacy ``PipelineMode.TEXT_ONLY``)
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 (Sprint H.2.c — le path legacy
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 legacy : {name!r}. "
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 legacy non encore portés vers BenchmarkService ----
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
- """Adapter de compatibilité ``run_benchmark`` legacy
866
  ``BenchmarkService`` rewrite.
867
 
868
  Présente la signature historique de
869
- ``picarones.measurements.runner.run_benchmark`` mais s'appuie
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`` legacy compatible avec les
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 legacy.
935
  engines:
936
- Liste d'engines/pipelines legacy à benchmarker.
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 (via la sérialisation legacy).
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 legacy compatible.
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, cohérent avec le legacy
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.measurements.runner.ner_attach._aggregate_ner``
1088
- (legacy supprimé en D.6.b).
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 legacy : un Ctrl+C arrête après l'engine en
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 avec le legacy qui calcule aussi les métriques au
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 côté legacy via ``compute_metrics``
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
- # legacy de ``progress_callback(engine_name, doc_idx, doc_id)``.
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 legacy : ``progress_callback(engine.name, ...)``
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
- # Le legacy ignore silencieusement les erreurs du
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`` legacy en JSON.
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 legacy, mais reste
1516
  # compatible avec les consommateurs (rapport, narrative).
1517
  import dataclasses
1518
  import json
 
1
+ """Entry point CLI/webfaç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
picarones/app/services/partial_store.py CHANGED
@@ -1,8 +1,8 @@
1
- """Sprint D.2.b — reprise sur interruption pour ``run_benchmark_via_service``.
2
 
3
- Persistance NDJSON des ``DocumentResult`` legacy au fil du
4
- benchmark, pour permettre la reprise après crash / Ctrl+C / timeout
5
- sans perdre le travail déjà fait.
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
- legacy change, le fichier devient illisible — mais à ce stade
33
- on est déjà en post-rewrite v2.0+ et le legacy est mort.
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
- Cohérent avec le format historique du fichier partiel
67
- legacy ; permet à un opérateur de retrouver visuellement
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
 
picarones/app/services/registry_service.py CHANGED
@@ -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.measurements
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
picarones/app/services/run_orchestrator.py CHANGED
@@ -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.
picarones/domain/artifacts.py CHANGED
@@ -1,14 +1,11 @@
1
- """``Artifact`` et ``ArtifactType`` — Sprint A14-S4.
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
- Différences avec ``picarones.core.modules.ArtifactType`` (Sprint 33)
7
- -------------------------------------------------------------------
8
- L'ancien ``ArtifactType`` historique a 6 valeurs :
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 legacy pour rétrocompat avec ``picarones.core.modules``
107
- #: (Phase 4-bis du retrait du legacy). Le mécanisme natif d'Enum
108
- #: Python rend ces noms équivalents aux canoniques :
109
  #:
110
  #: >>> ArtifactType.TEXT is ArtifactType.RAW_TEXT
111
  #: True
112
  #:
113
- #: Le mapping sémantique TEXT RAW_TEXT est documenté dans
114
- #: ``docs/migration/regression-tolerances.md``. À supprimer en 2.0
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 valeurs string legacy (``"text"``, ``"alto"``,
123
  ``"page"``) en plus des valeurs canoniques.
124
 
125
- Ce hook est invoqué par ``ArtifactType("text")`` (lecture YAML
126
- legacy par exemple) — sans lui, ``ValueError``. À supprimer
127
- en 2.0 avec les aliases legacy ci-dessus.
128
  """
129
- legacy_map: dict[str, "ArtifactType"] = {
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 legacy_map.get(value)
137
 
138
 
139
- #: Map valeur canonique → valeur string legacy. Permet aux dicts
140
- #: indexés par ``ArtifactType.value`` (junction_metrics du runner
141
- #: legacy, etc.) de présenter les **deux** clés pendant la phase de
142
- #: migration : un caller rewrite qui cherche ``["raw_text"]`` et un
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",
picarones/domain/errors.py CHANGED
@@ -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.measurements``,
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
 
picarones/domain/module_protocol.py CHANGED
@@ -20,17 +20,13 @@ Usage minimal ::
20
  txt = inputs[ArtifactType.RAW_TEXT]
21
  return {ArtifactType.RAW_TEXT: txt.upper()}
22
 
23
- Ce module canonique (Phase 4-bis du retrait du legacy) est le
24
- remplacement de ``picarones.core.modules.BaseModule``. Le shim
25
- legacy ``core/modules.py`` le ré-exporte pour la rétrocompat des
26
- ~25 callers (engines, measurements, modules officiels, cli, web,
27
- report) qui le consomment.
28
-
29
- Le rewrite a aussi des protocols spécialisés
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
picarones/evaluation/benchmark_result.py CHANGED
@@ -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
picarones/evaluation/corpus.py CHANGED
@@ -1,18 +1,12 @@
1
- """Chargement et gestion des corpus de documents (Cercle 2).
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** historiquement utilisés par le runner de
12
- ``measurements/`` : ``Document``, ``Corpus``, ``ArtifactType`` +
13
- payloads ``TextGT``/``AltoGT``/``PageGT``/``EntitiesGT``/
14
- ``ReadingOrderGT`` chargés en mémoire, et la fonction
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**
picarones/evaluation/metric_hooks.py CHANGED
@@ -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.measurements.runner._compute_document_result``
12
  contenait **11 imports tardifs codés en dur** vers
13
- ``picarones.measurements.confusion``, ``char_scores``, ``taxonomy``, ``structure``,
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é
picarones/evaluation/metric_registry.py CHANGED
@@ -1,4 +1,4 @@
1
- """Registre typé de métriques (Phase 4-terrelocalisation Cercle 2).
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 **historique** : un registre
17
- unique global, alimenté par les imports des sous-packages (le
18
- ``picarones.measurements.__init__`` est l'amorce qui s'occupe de
19
- charger tous les modules définissant des ``@register_metric``).
20
 
21
  ``picarones.evaluation.registry.MetricRegistry`` est une **classe
22
- instanciable** (Sprint A14-S5) — un service applicatif l'instancie
23
- explicitement et y enregistre les métriques sans side-effect
24
- d'import. Les deux patterns coexistent volontairement : le legacy
25
- fonctionne pour les ~30 métriques existantes, l'instance-based est
26
- réservé aux contributions tierces et au cadre des
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 3evaluation).
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
  ---------------
picarones/evaluation/metric_result.py CHANGED
@@ -1,16 +1,11 @@
1
- """Modèle de données des métriques OCR/HTR (Cercle 2).
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.measurements.metrics` (``compute_metrics``).
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
  """
picarones/evaluation/metrics/__init__.py CHANGED
@@ -1,56 +1,38 @@
1
  """Métriques — calculs purs sur des paires (référence, hypothèse).
2
 
3
- Sprint A14-S10 : déplacement de **23 fichiers de calcul autonomes**
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``, ``robustness_projection``,
19
- ``longitudinal``, ``baseline_comparison``, ``levers``,
20
- ``worst_lines``, ``module_policy``.
 
21
 
22
  Calculs inter-moteurs :
23
- ``inter_engine``, ``taxonomy_cooccurrence``,
 
24
  ``taxonomy_comparison``.
25
 
26
- Reste à migrer (différé)
27
- ------------------------
 
 
28
 
29
- Catégorie B — utilisent ``@register_metric`` du registre global
30
- ``core.metric_registry`` (singleton avec side-effect d'import) :
31
- ``mufi``, ``abbreviations``, ``unicode_blocks``, ``roman_numerals``,
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
picarones/evaluation/metrics/alto_metrics.py CHANGED
@@ -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.measurements.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
 
 
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
 
picarones/evaluation/metrics/builtin_hooks.py CHANGED
@@ -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.measurements.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
 
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
picarones/evaluation/metrics/cost_projection.py CHANGED
@@ -20,7 +20,7 @@ le chercheur arbitre selon son budget.
20
 
21
  Dépendance
22
  ----------
23
- S'appuie sur ``picarones.measurements.pricing`` (Sprint 20) qui expose
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
  """
picarones/evaluation/metrics/incremental_comparison.py CHANGED
@@ -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.measurements.statistics.friedman_test`` sur la sortie de
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
picarones/evaluation/metrics/normalization.py CHANGED
@@ -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.measurements.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
 
 
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
 
picarones/evaluation/metrics/numerical_sequences.py CHANGED
@@ -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.measurements.roman_numerals`` (Sprint 60).
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``,
picarones/evaluation/metrics/robustness.py CHANGED
@@ -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
- # ``BaseOCREngine`` (legacy ``adapters/legacy_engines/``) ne peut
37
- # pas être importé statiquement depuis la couche ``evaluation/``
38
- # (test_layer_imports_are_legal). L'annotation utilise donc
39
- # ``Any`` ; le check ``isinstance`` est fait dynamiquement par
40
- # ``importlib`` si besoin (cas réel : duck typing suffit, l'objet
41
- # passé doit juste avoir ``.run(image_path) -> EngineResult``).
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 moteurs OCR (``BaseOCREngine``).
 
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.legacy_engines.tesseract import TesseractEngine
429
  >>> from picarones.evaluation.metrics.robustness import RobustnessAnalyzer
430
- >>> engine = TesseractEngine(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[BaseOCREngine]",
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,
picarones/evaluation/metrics/roman_numerals.py CHANGED
@@ -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
 
picarones/evaluation/metrics/search.py CHANGED
@@ -1,15 +1,7 @@
1
- """Recherchabilité fuzzy + séquences numériques — Sprint A14-S16.
2
 
3
- Fonctions de calcul **pures** (sans ``@register_metric`` legacy)
4
- utilisées par ``SearchView``. Réimplémente la logique des modules
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. Volontairement minimaliste pour
24
- S16 : détecte uniquement les **années 4 chiffres** (proxy
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 identique à ``picarones.measurements.searchability``
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 (convention identique au legacy Sprint 84).
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 pour S16 : seules les années 4
168
- chiffres sont détectées. Le pattern complet (numéraux romains,
169
- foliations ``f. 12r``, monnaies, années régnales ``an III``)
170
- reste dans ``picarones.measurements.numerical_sequences``
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é.
picarones/evaluation/metrics/specialization.py CHANGED
@@ -32,7 +32,7 @@ intuitive :
32
 
33
  Dépendances
34
  -----------
35
- S'appuie strictement sur ``picarones.measurements.inter_engine`` (Sprint
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
  """
picarones/evaluation/registry/registry.py CHANGED
@@ -8,13 +8,10 @@ pas de décorateur magique.
8
 
9
  Différence avec ``picarones.evaluation.metric_registry``
10
  --------------------------------------------------------
11
- L'autre registre (relocalisé depuis ``picarones.core.metric_registry``
12
- en Phase 4-ter) utilise un dict module-level
13
- ``_METRIC_REGISTRY`` rempli par un décorateur ``@register_metric``
14
- appliqué au top-level d'autres modules. Conséquence : un
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
 
picarones/evaluation/statistics/__init__.py CHANGED
@@ -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.
picarones/evaluation/statistics/friedman_nemenyi.py CHANGED
@@ -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.measurements.statistics.cdd_render` pour séparer
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
 
picarones/evaluation/statistics/wilcoxon.py CHANGED
@@ -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.measurements.statistics``.
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) :
picarones/evaluation/synthetic.py CHANGED
@@ -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 générées par picarones.fixtures",
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",
picarones/formats/__init__.py CHANGED
@@ -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.measurements.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
 
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
picarones/i18n.py DELETED
@@ -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
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
picarones/interfaces/cli/__init__.py CHANGED
@@ -326,9 +326,10 @@ def demo_cmd(
326
  picarones demo --with-robustness
327
  picarones demo --with-history --with-robustness --docs 8
328
  """
329
- # Sprint G du plan v2.0 — ``picarones.fixtures`` reste legacy
330
- # top-level ; import dynamique pour respecter
331
- # ``test_layer_imports_are_legal[layer-interfaces]``.
 
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",
picarones/interfaces/web/jobs.py CHANGED
@@ -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.measurements.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``
 
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``
picarones/pipeline/__init__.py CHANGED
@@ -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
- ``measurements.runner`` à 1e-9 près sur les fixtures.
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
picarones/pipeline/llm_pipeline_builder.py CHANGED
@@ -1,14 +1,13 @@
1
- """Builder de ``PipelineSpec`` pour les chaînes OCR + LLM (Phase 6 volet 2).
2
 
3
- Ce module fournit la convergence entre les 3 modes historiques de
4
- ``picarones.pipelines.base.OCRLLMPipeline`` (legacy) et la
5
- ``PipelineSpec`` canonique exécutable par ``PipelineExecutor``.
6
 
7
- Mapping mode legacy → spec canonique
8
- ------------------------------------
9
 
10
  ================ ============= =========== ================================
11
- Mode legacy Initial input Steps Output final
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
- Exemple de migration
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 (Sprint B du plan v2.0) :
123
  ``{"prompt_template": "Corrige : {ocr_output}"}`` permet à
124
- un caller de spécifier un template legacy ou rewrite sans
125
- toucher à la config de l'adapter.
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
  -------
picarones/pipeline/llm_pipeline_config.py CHANGED
@@ -1,44 +1,20 @@
1
- """``OCRLLMPipelineConfig`` — container canonique pour pipelines OCR+LLM.
2
-
3
- Sprint H.2.b/c du plan v2.0 équivalent canonique de
4
- ``picarones.adapters.legacy_pipelines.base.OCRLLMPipeline``.
5
-
6
- Pourquoi
7
- --------
8
- ``OCRLLMPipeline`` (legacy) :
9
-
10
- - hérite de ``BaseOCREngine`` (legacy),
11
- - expose une méthode ``run(image_path) EngineResult``,
12
- - mélange contrat d'exécution et configuration.
13
-
14
- Cette config canonique :
15
-
16
- - est un container *pur* (immutable, pas de logique d'exécution),
17
- - accepte un ``BaseOCRAdapter`` (canonique) au lieu d'un
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
- """Compat duck-typing avec ``OCRLLMPipeline`` legacy.
132
 
133
  Les helpers ``_ocr_llm_pipeline_to_spec`` et
134
- ``build_adapter_resolver`` accèdent à ``pipeline.ocr_engine``
135
- on expose ``ocr_adapter`` sous ce nom pour la
136
- rétro-compatibilité du wiring existant.
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
 
picarones/reports/_helpers/__init__.py CHANGED
@@ -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
picarones/reports/_helpers/assets.py CHANGED
@@ -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
 
picarones/reports/_helpers/colors.py CHANGED
@@ -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
picarones/reports/_helpers/render_helpers.py CHANGED
@@ -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