diff --git a/docs/user/writing-a-pipeline-module.md b/docs/user/writing-a-pipeline-module.md
index c3c29066de37c2568c00e316fbef9c16760edd84..cf069824aa879c82c81a66a17028088fdb2d2d9f 100644
--- a/docs/user/writing-a-pipeline-module.md
+++ b/docs/user/writing-a-pipeline-module.md
@@ -18,7 +18,7 @@
```python
from picarones.core.modules import BaseModule, ArtifactType
-from picarones.core.pipeline_runner import (
+from picarones.core.pipeline import (
PipelineRunner, PipelineSpec, PipelineStep,
)
@@ -150,7 +150,7 @@ class NERExtractor(BaseModule):
### 3.a Mono-document (Sprint 63)
```python
-from picarones.core.pipeline_runner import (
+from picarones.core.pipeline import (
PipelineRunner, PipelineSpec, PipelineStep,
)
@@ -178,7 +178,7 @@ que `Document.ground_truths` porte une `TextGT` (ou `AltoGT`,
### 3.b Corpus complet (Sprint 64)
```python
-from picarones.core.pipeline_benchmark import run_pipeline_benchmark
+from picarones.measurements.pipeline_benchmark import run_pipeline_benchmark
bench = run_pipeline_benchmark(spec, my_corpus)
print(bench.n_pipelines_succeeded, "/", bench.n_docs)
@@ -203,7 +203,7 @@ bench = run_pipeline_benchmark(spec, corpus, initial_inputs_factory=my_factory)
### 3.c Comparer N pipelines (Sprint 65)
```python
-from picarones.core.pipeline_comparison import compare_pipelines
+from picarones.measurements.pipeline_comparison import compare_pipelines
comparison = compare_pipelines(
[spec_baseline, spec_with_correcteur_a, spec_with_correcteur_b],
diff --git a/picarones/cli/__init__.py b/picarones/cli/__init__.py
index 39d99f44910db162ea5824f85758710f0649b468..e474f91f5eddaf979fe3c42576d95aa78c1047e2 100644
--- a/picarones/cli/__init__.py
+++ b/picarones/cli/__init__.py
@@ -103,7 +103,7 @@ def cli() -> None:
@click.option("--json-output", is_flag=True, default=False, help="Sortie en JSON")
def metrics_cmd(reference: str, hypothesis: str, json_output: bool) -> None:
"""Calcule CER et WER entre deux fichiers texte."""
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
ref_text = Path(reference).read_text(encoding="utf-8").strip()
hyp_text = Path(hypothesis).read_text(encoding="utf-8").strip()
@@ -309,7 +309,7 @@ def demo_cmd(
# Suivi longitudinal
if with_history:
click.echo("\n── Démonstration suivi longitudinal ──────────────")
- from picarones.core.history import BenchmarkHistory, generate_demo_history
+ from picarones.measurements.history import BenchmarkHistory, generate_demo_history
history = BenchmarkHistory(":memory:")
generate_demo_history(history, n_runs=8)
entries = history.query(engine="tesseract")
@@ -333,7 +333,7 @@ def demo_cmd(
# Analyse de robustesse
if with_robustness:
click.echo("\n── Démonstration analyse de robustesse ───────────")
- from picarones.core.robustness import generate_demo_robustness_report
+ from picarones.measurements.robustness import generate_demo_robustness_report
report = generate_demo_robustness_report(
engine_names=["tesseract", "pero_ocr"]
)
diff --git a/picarones/cli/_history.py b/picarones/cli/_history.py
index debe68201c4aff56fa0d142446a597a1efd7ce83..d053a668218b6335784e9141633be41ce5c27505 100644
--- a/picarones/cli/_history.py
+++ b/picarones/cli/_history.py
@@ -103,7 +103,7 @@ def history_cmd(
"""
_setup_logging(verbose)
- from picarones.core.history import BenchmarkHistory, generate_demo_history
+ from picarones.measurements.history import BenchmarkHistory, generate_demo_history
history = BenchmarkHistory(db)
diff --git a/picarones/cli/_imports.py b/picarones/cli/_imports.py
index 8a825f79a37f298531bc468d89d6052e88a8792d..407a3e16b353c862059c6fbcdd6b9c5364ac3447 100644
--- a/picarones/cli/_imports.py
+++ b/picarones/cli/_imports.py
@@ -76,7 +76,7 @@ def import_iiif_cmd(
"""
_setup_logging(verbose)
- from picarones.importers.iiif import IIIFImporter
+ from picarones.extras.importers.iiif import IIIFImporter
click.echo(f"Manifeste IIIF : {manifest_url}")
diff --git a/picarones/cli/_pipeline.py b/picarones/cli/_pipeline.py
index 02d3fe07b0d1c6dfc547bc0b33be0f59fbeb580c..556b3f07b0165ae13bcacce7254c0272a1023dcd 100644
--- a/picarones/cli/_pipeline.py
+++ b/picarones/cli/_pipeline.py
@@ -66,8 +66,8 @@ def pipeline_run_cmd(
import json as _json
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.pipeline_benchmark import run_pipeline_benchmark
- from picarones.core.pipeline_spec_loader import load_pipeline_spec_from_yaml
+ from picarones.measurements.pipeline_benchmark import run_pipeline_benchmark
+ from picarones.measurements.pipeline_spec_loader import load_pipeline_spec_from_yaml
spec = load_pipeline_spec_from_yaml(spec_path)
corpus = load_corpus_from_directory(str(corpus_dir))
@@ -163,8 +163,8 @@ def pipeline_compare_cmd(
"""Compare N pipelines décrites dans SPECS_PATH sur le même corpus."""
from picarones.core.corpus import load_corpus_from_directory
from picarones.core.modules import ArtifactType
- from picarones.core.pipeline_comparison import compare_pipelines
- from picarones.core.pipeline_spec_loader import (
+ from picarones.measurements.pipeline_comparison import compare_pipelines
+ from picarones.measurements.pipeline_spec_loader import (
load_comparison_specs_from_yaml,
)
diff --git a/picarones/cli/_robustness.py b/picarones/cli/_robustness.py
index fea5dbf0d15e7f2a19bffb5a04ff6cd9316f738f..af7739d9fe7c32ccc41346bc84ec55b34201d6ea 100644
--- a/picarones/cli/_robustness.py
+++ b/picarones/cli/_robustness.py
@@ -99,7 +99,7 @@ def robustness_cmd(
deg_types = [d.strip() for d in degradations.split(",") if d.strip()]
- from picarones.core.robustness import (
+ from picarones.measurements.robustness import (
RobustnessAnalyzer, ALL_DEGRADATION_TYPES, generate_demo_robustness_report
)
@@ -139,7 +139,7 @@ def robustness_cmd(
click.echo(f"Erreur moteur : {exc}", err=True)
sys.exit(1)
- from picarones.core.robustness import RobustnessAnalyzer
+ from picarones.measurements.robustness import RobustnessAnalyzer
analyzer = RobustnessAnalyzer(
engines=[ocr_engine],
degradation_types=deg_types,
diff --git a/picarones/cli/_workflows.py b/picarones/cli/_workflows.py
index 0041360efe00c6877c7c49381193e30b1ef2c6f1..60057b0844db73592dbe07b4e87d80fc9dcdc9ba 100644
--- a/picarones/cli/_workflows.py
+++ b/picarones/cli/_workflows.py
@@ -90,7 +90,7 @@ def run_cmd(
_setup_logging(verbose)
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
# Chargement du corpus
try:
@@ -183,7 +183,7 @@ def _run_workflow(
_setup_logging(verbose)
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
try:
corp = load_corpus_from_directory(corpus)
diff --git a/picarones/core/abbreviations.py b/picarones/core/abbreviations.py
deleted file mode 100644
index 22c7c0ba74f796db976327ed0f1069bfd0217ca2..0000000000000000000000000000000000000000
--- a/picarones/core/abbreviations.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.historical.abbreviations`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.historical.abbreviations import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.historical.abbreviations as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/baseline_comparison.py b/picarones/core/baseline_comparison.py
deleted file mode 100644
index 96db6d2480877c36ac466559fcc345faed14aa58..0000000000000000000000000000000000000000
--- a/picarones/core/baseline_comparison.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.baseline_comparison`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.baseline_comparison import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.baseline_comparison as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/builtin_hooks.py b/picarones/core/builtin_hooks.py
deleted file mode 100644
index 9d7b012892736db0281d5e64fe7781521116eee7..0000000000000000000000000000000000000000
--- a/picarones/core/builtin_hooks.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.builtin_hooks`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.builtin_hooks import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.builtin_hooks as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/calibration.py b/picarones/core/calibration.py
deleted file mode 100644
index 7f70c0471244025c064bd0cd4620bce994f6ac14..0000000000000000000000000000000000000000
--- a/picarones/core/calibration.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.calibration`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.calibration import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.calibration as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/char_scores.py b/picarones/core/char_scores.py
deleted file mode 100644
index 18597ae29732e42f46d5ba68fd7f85ab68eac9a0..0000000000000000000000000000000000000000
--- a/picarones/core/char_scores.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.char_scores`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.char_scores import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.char_scores as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/confusion.py b/picarones/core/confusion.py
deleted file mode 100644
index ad64e881ddb8c148bd3ecb6172d38b474e2b1380..0000000000000000000000000000000000000000
--- a/picarones/core/confusion.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.confusion`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.confusion import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.confusion as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/cost_projection.py b/picarones/core/cost_projection.py
deleted file mode 100644
index 709571da9d1a4a0374b1b8beea5c6074a762c8fc..0000000000000000000000000000000000000000
--- a/picarones/core/cost_projection.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.cost_projection`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.cost_projection import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.cost_projection as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/difficulty.py b/picarones/core/difficulty.py
deleted file mode 100644
index 8926945c05f1cd898466071322ace5f72af205db..0000000000000000000000000000000000000000
--- a/picarones/core/difficulty.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.difficulty`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.difficulty import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.difficulty as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/early_modern_typography.py b/picarones/core/early_modern_typography.py
deleted file mode 100644
index c7b04840e4fff6825ab955123e0f8e1919e266cd..0000000000000000000000000000000000000000
--- a/picarones/core/early_modern_typography.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.historical.early_modern_typography`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.historical.early_modern_typography import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.historical.early_modern_typography as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/equivalence_profile.py b/picarones/core/equivalence_profile.py
deleted file mode 100644
index 6101ae37f28db04bb0eb178477aa3eec51f7de6c..0000000000000000000000000000000000000000
--- a/picarones/core/equivalence_profile.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.equivalence_profile`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.equivalence_profile import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.equivalence_profile as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/error_absorption.py b/picarones/core/error_absorption.py
deleted file mode 100644
index 5f7874b9a70f652f8fb4d2041d3b83303ff5b67c..0000000000000000000000000000000000000000
--- a/picarones/core/error_absorption.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.error_absorption`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.error_absorption import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.error_absorption as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/measurements/narrative/facts.py b/picarones/core/facts.py
similarity index 100%
rename from picarones/measurements/narrative/facts.py
rename to picarones/core/facts.py
diff --git a/picarones/core/hallucination.py b/picarones/core/hallucination.py
deleted file mode 100644
index 389092e6d153abb3bba5a7925c37861b097a0730..0000000000000000000000000000000000000000
--- a/picarones/core/hallucination.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.hallucination`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.hallucination import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.hallucination as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/history.py b/picarones/core/history.py
deleted file mode 100644
index 5a6a77460ec44fa356eb3292d2e0e2cbec37b45f..0000000000000000000000000000000000000000
--- a/picarones/core/history.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.history`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.history import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.history as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/image_predictive.py b/picarones/core/image_predictive.py
deleted file mode 100644
index 7c99e9990057682f8a3cb0dd470fcdd3ffccf5cf..0000000000000000000000000000000000000000
--- a/picarones/core/image_predictive.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.academic.image_predictive`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.academic.image_predictive import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.academic.image_predictive as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/image_quality.py b/picarones/core/image_quality.py
deleted file mode 100644
index f265735b0044a73273d918a03794cc50eb17b887..0000000000000000000000000000000000000000
--- a/picarones/core/image_quality.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.image_quality`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.image_quality import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.image_quality as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/incremental_comparison.py b/picarones/core/incremental_comparison.py
deleted file mode 100644
index 355cfacae59a5a0c5d5444ed5996b997e93e55d4..0000000000000000000000000000000000000000
--- a/picarones/core/incremental_comparison.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.incremental_comparison`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.incremental_comparison import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.incremental_comparison as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/inter_engine.py b/picarones/core/inter_engine.py
deleted file mode 100644
index 7c9011670191defbb979878181761a448d955c75..0000000000000000000000000000000000000000
--- a/picarones/core/inter_engine.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.inter_engine`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.inter_engine import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.inter_engine as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/layout.py b/picarones/core/layout.py
deleted file mode 100644
index 5a2eb6a965e1c6958510201e32efdaad3005a420..0000000000000000000000000000000000000000
--- a/picarones/core/layout.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.layout`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.layout import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.layout as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/levers.py b/picarones/core/levers.py
deleted file mode 100644
index cce14722ab9e9ab126bb1619612f3841958ec446..0000000000000000000000000000000000000000
--- a/picarones/core/levers.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.levers`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.levers import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.levers as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/lexical_modernization.py b/picarones/core/lexical_modernization.py
deleted file mode 100644
index e03585f890b46593598255f7c0269b2c5786aa2a..0000000000000000000000000000000000000000
--- a/picarones/core/lexical_modernization.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.historical.lexical_modernization`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.historical.lexical_modernization import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.historical.lexical_modernization as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/line_metrics.py b/picarones/core/line_metrics.py
deleted file mode 100644
index d38ff0cb623e7473a8d914aa6f2c9d42f48ac526..0000000000000000000000000000000000000000
--- a/picarones/core/line_metrics.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.line_metrics`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.line_metrics import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.line_metrics as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/longitudinal.py b/picarones/core/longitudinal.py
deleted file mode 100644
index 4e5732fe852320a8ebfc369421d100ab8504ccf8..0000000000000000000000000000000000000000
--- a/picarones/core/longitudinal.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.longitudinal`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.longitudinal import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.longitudinal as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/marginal_cost.py b/picarones/core/marginal_cost.py
deleted file mode 100644
index 5d2fd947f6ffdf26efb1068e4d1f153a9283153c..0000000000000000000000000000000000000000
--- a/picarones/core/marginal_cost.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.marginal_cost`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.marginal_cost import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.marginal_cost as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/metric_hooks.py b/picarones/core/metric_hooks.py
index 3f185f9d7eba032a2f2d70748da810231ab82069..05fbb7807527d5a419fc8bc0435470f0ddf05ac9 100644
--- a/picarones/core/metric_hooks.py
+++ b/picarones/core/metric_hooks.py
@@ -4,9 +4,9 @@ Chantier 2 du plan d'évolution post-Sprint 97.
Pourquoi ce module
------------------
-Avant ce chantier, ``picarones.core.runner._compute_document_result``
+Avant ce chantier, ``picarones.measurements.runner._compute_document_result``
contenait **11 imports tardifs codés en dur** vers
-``picarones.core.confusion``, ``char_scores``, ``taxonomy``, ``structure``,
+``picarones.measurements.confusion``, ``char_scores``, ``taxonomy``, ``structure``,
``image_quality``, ``line_metrics``, ``hallucination``,
``philological_runner``, ``searchability_runner``,
``numerical_sequences_runner``, ``readability_runner`` — chacun enrobé
diff --git a/picarones/core/modern_archives.py b/picarones/core/modern_archives.py
deleted file mode 100644
index 775a50883a3a9e8c754aee4844c8385364bf7828..0000000000000000000000000000000000000000
--- a/picarones/core/modern_archives.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.historical.modern_archives`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.historical.modern_archives import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.historical.modern_archives as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/module_policy.py b/picarones/core/module_policy.py
deleted file mode 100644
index 9f7814b9e67232037a2e4488cb35728aa3e9f58b..0000000000000000000000000000000000000000
--- a/picarones/core/module_policy.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.governance.module_policy`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.governance.module_policy import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.governance.module_policy as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/mufi.py b/picarones/core/mufi.py
deleted file mode 100644
index 00ec90afc70ed1a522bf739e38a16e8a464c3669..0000000000000000000000000000000000000000
--- a/picarones/core/mufi.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.historical.mufi`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.historical.mufi import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.historical.mufi as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/__init__.py b/picarones/core/narrative/__init__.py
deleted file mode 100644
index 72f3dcce154bdef95ce5bfa1baac36fb4edf5afc..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/__init__.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""Alias rétrocompat — package déplacé dans :mod:`picarones.measurements.narrative`.
-
-Phase E du chantier de refonte en 3 cercles. Le moteur narratif
-(Cercle 2) vit désormais dans ``picarones.measurements.narrative``.
-Cet alias maintient la rétrocompat des imports historiques :
-``from picarones.core.narrative import build_synthesis``,
-``from picarones.core.narrative.facts import Fact``, etc.
-"""
-
-from picarones.measurements.narrative import * # noqa: F401, F403
-
-import picarones.measurements.narrative as _module
-# Réexport explicite des noms privés (préfixe ``_``) que ``import *``
-# ne propage pas — rétrocompat des tests Sprints qui importent
-# directement ``_DEFAULT_REGISTRY`` (test_sprint19_narrative_engine).
-for _shim_name in dir(_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_module, _shim_name)
-del _shim_name
-__all__ = getattr(_module, "__all__", [
- nm for nm in dir(_module) if not nm.startswith("_")
-])
diff --git a/picarones/core/narrative/arbiter.py b/picarones/core/narrative/arbiter.py
deleted file mode 100644
index 537b8ca4d86b9c72607e04d2a2f2c0ea86ee540b..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/arbiter.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.arbiter`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.arbiter import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.arbiter as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/detectors/__init__.py b/picarones/core/narrative/detectors/__init__.py
deleted file mode 100644
index 9d677e61151ff05be9f2d9e5bd0942a2fc7d883e..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/detectors/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""Alias rétrocompat — package déplacé dans :mod:`picarones.measurements.narrative.detectors`.
-
-Phase E du chantier de refonte. Les 18 détecteurs en 6 familles
-(ranking, pareto, stratum, quality, history, ensemble) vivent
-désormais dans ``picarones.measurements.narrative.detectors/``.
-"""
-
-from picarones.measurements.narrative.detectors import * # noqa: F401, F403
-
-import picarones.measurements.narrative.detectors as _module
-__all__ = getattr(_module, "__all__", [
- nm for nm in dir(_module) if not nm.startswith("_")
-])
diff --git a/picarones/core/narrative/detectors/_helpers.py b/picarones/core/narrative/detectors/_helpers.py
deleted file mode 100644
index 66ab9bc648a744714e81ad9caf7023dcb3aeb4fd..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/detectors/_helpers.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.detectors._helpers`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.detectors._helpers import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.detectors._helpers as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/detectors/ensemble.py b/picarones/core/narrative/detectors/ensemble.py
deleted file mode 100644
index 866a1fcf29ce002dbba8d789e45623bec8de9553..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/detectors/ensemble.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.detectors.ensemble`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.detectors.ensemble import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.detectors.ensemble as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/detectors/history.py b/picarones/core/narrative/detectors/history.py
deleted file mode 100644
index 11da8aeb8066eb448043e39a167a66ae713f6b5c..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/detectors/history.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.detectors.history`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.detectors.history import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.detectors.history as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/detectors/pareto.py b/picarones/core/narrative/detectors/pareto.py
deleted file mode 100644
index 766efc7a5014013cf27aa86031e8f0ae5f0dc694..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/detectors/pareto.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.detectors.pareto`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.detectors.pareto import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.detectors.pareto as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/detectors/quality.py b/picarones/core/narrative/detectors/quality.py
deleted file mode 100644
index d53a8adfab3e0f7167cd1964e32b83ac845ca429..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/detectors/quality.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.detectors.quality`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.detectors.quality import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.detectors.quality as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/detectors/ranking.py b/picarones/core/narrative/detectors/ranking.py
deleted file mode 100644
index af9ba4275488bfb58baa518a0e19ade6c34a397e..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/detectors/ranking.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.detectors.ranking`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.detectors.ranking import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.detectors.ranking as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/detectors/stratum.py b/picarones/core/narrative/detectors/stratum.py
deleted file mode 100644
index b24f8f2789fb266292b0ca2f4bd8f150760f1e33..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/detectors/stratum.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.detectors.stratum`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.detectors.stratum import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.detectors.stratum as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/facts.py b/picarones/core/narrative/facts.py
deleted file mode 100644
index dbe7af96580e009f5f0e483e571b165f222d9b35..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/facts.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.facts`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.facts import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.facts as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/registry.py b/picarones/core/narrative/registry.py
deleted file mode 100644
index b51183dc7a145bdb269d3920e78e9df15bee8264..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/registry.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.registry`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.registry import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.registry as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/renderer.py b/picarones/core/narrative/renderer.py
deleted file mode 100644
index 5f3ccef7fad62388a86ee55f7fcbc7fa7fa34f45..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/renderer.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.narrative.renderer`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.narrative.renderer import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.narrative.renderer as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/narrative/templates/en.yaml b/picarones/core/narrative/templates/en.yaml
deleted file mode 100644
index 40e2af6c48583a04243034dd742cc2568f2133e5..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/templates/en.yaml
+++ /dev/null
@@ -1,96 +0,0 @@
-# Narrative rendering templates — English.
-# Anti-hallucination rule: never introduce a number or entity name that is not
-# already in the Fact ``payload``. Tests verify traceability of every number
-# appearing in the rendered synthesis.
-
-global_leader_cer: >-
- On this corpus of {n_docs} documents, {engine} achieves the lowest mean CER
- ({cer_pct} %).
-
-statistical_tie: >-
- Engines {engines_list} are not statistically distinguishable
- (Friedman-Nemenyi, α = {alpha}, n = {n_blocks} documents, CD = {critical_distance}).
-
-significant_gap: >-
- The gap between {leader} and {runner_up} is statistically significant
- (Wilcoxon, p = {p_value:.4f}, Δ CER = {delta_cer_pct} points over {n_pairs} pairs).
-
-stratum_winner: >-
- On stratum "{stratum}" ({n_docs_stratum} documents), {engine} achieves
- the lowest CER ({cer_pct} % vs. {second_cer_pct} % for {second_engine}).
-
-stratum_collapse: >-
- {engine} is globally competitive ({global_cer_pct} %) but collapses on
- stratum "{stratum}" ({local_cer_pct} % over {n_docs_stratum} documents,
- i.e. {delta_cer_pct} points above its own average).
-
-error_profile_outlier: >-
- {engine} has an atypical error profile: {proportion_pct} % of errors fall
- into class "{error_class}", vs. a median of {median_proportion_pct} % across
- other engines (×{ratio_to_median} the median).
-
-llm_hallucination_flag: >-
- Hallucination signal on {engine} ({reasons_list}) —
- {hallucinating_rate_pct} % of documents above alert thresholds.
-
-robustness_fragile: >-
- {engine} is fragile under "{degradation}" degradation: its CER rises from
- {cer_baseline_pct} % to {cer_degraded_pct} % at maximum level (×{ratio}).
-
-speed_winner: >-
- {engine} is the fastest ({mean_duration} s/doc, ×{speedup} faster than the
- median) for comparable quality (CER {cer_pct} %).
-
-confidence_warning: >-
- High statistical uncertainty: the {confidence_level} % confidence interval of
- {engine} spans {ci_width_pct} CER points, compared with a gap of
- {gap_to_runner_up_pct} points to the runner-up.
-
-pareto_alternative: >-
- At much lower cost, {engine} offers an interesting trade-off ({cer_pct} %
- CER for {cost} €/{cost_unit_pages} pages, vs {leader_cer_pct} % / {leader_cost} € for
- {leader}, i.e. ×{cost_saving_ratio} cheaper).
-
-cost_outlier: >-
- Disproportionate cost for {engine} ({cost} €/{cost_unit_pages} pages, ×{ratio_to_median}
- the median) without a compensating quality advantage (CER {cer_pct} %).
-
-ensemble_opportunity: >-
- Engines {pair_a} and {pair_b} have divergent error profiles
- ({divergence_metric}={divergence}). On this corpus of {doc_count} documents,
- {best_engine} preserves {best_recall_pct} % of tokens; a majority vote
- among the engines would preserve {oracle_recall_pct} % — i.e.
- {absolute_gap_pct} points recoverable ({relative_gap_pct} % of the best
- engine's errors).
-
-median_mean_gap_warning: >-
- Asymmetric distribution for {engine}: median CER {median_cer_pct} %
- vs mean {mean_cer_pct} % across {n_docs} documents (relative gap
- {relative_gap_pct} %). The mean is pulled by a few catastrophic
- documents — the median (now used for default ranking) is more
- representative.
-
-stratification_recommended: >-
- Heterogeneous corpus ({n_strata} strata): {leader} performs very
- differently depending on document type — median CER
- {min_stratum_cer_pct} % on "{min_stratum}" vs
- {max_stratum_cer_pct} % on "{max_stratum}", a gap of {gap_pct}
- points. The global ranking hides this disparity; consult the
- stratified view.
-
-engine_off_baseline: >-
- {engine} achieved {cer_current_pct} % CER here, vs {cer_historical_mean_pct} %
- on average over the last {n_runs} runs of your institution on this
- same corpus (relative delta {relative_delta_pct} %). This corpus is
- harder for it than usual.
-
-engine_unstable: >-
- Over {n_runs} successive runs, {engine} produces variable outputs
- (CER CV {cer_cv_pct} %, identical-run pair rate {identical_run_rate_pct} %).
- Reproducibility is limited — interpret the average CER with caution.
-
-regression_in_history: >-
- Over the {n_runs} historical runs for {engine}, the average CER
- moved from {first_cer_pct} % to {last_cer_pct} %
- (cumulative change {absolute_delta_pct} points). Investigate what
- changed in the pipeline or the models.
diff --git a/picarones/core/narrative/templates/fr.yaml b/picarones/core/narrative/templates/fr.yaml
deleted file mode 100644
index d3a858abfd5dcb5462007859458a8a59d43fafce..0000000000000000000000000000000000000000
--- a/picarones/core/narrative/templates/fr.yaml
+++ /dev/null
@@ -1,101 +0,0 @@
-# Templates de rendu narratif — français.
-#
-# Chaque clé correspond à une valeur de ``FactType``. La valeur est un template
-# Python ``.format()`` qui consomme les champs du ``Fact.payload``.
-#
-# Règle anti-hallucination : n'introduire aucune valeur numérique ou nom
-# d'entité qui ne soit pas dans le ``payload``. Les tests parsent la synthèse
-# rendue et vérifient la traçabilité.
-
-global_leader_cer: >-
- Sur ce corpus de {n_docs} documents, {engine} obtient le CER moyen le plus
- bas ({cer_pct} %).
-
-statistical_tie: >-
- Les moteurs {engines_list} ne sont pas statistiquement distinguables
- (Friedman-Nemenyi, α = {alpha}, n = {n_blocks} documents, CD = {critical_distance}).
-
-significant_gap: >-
- L'écart entre {leader} et {runner_up} est statistiquement significatif
- (Wilcoxon, p = {p_value:.4f}, Δ CER = {delta_cer_pct} points sur {n_pairs} paires).
-
-stratum_winner: >-
- Sur la strate « {stratum} » ({n_docs_stratum} documents), {engine}
- obtient le CER le plus bas ({cer_pct} % contre {second_cer_pct} %
- pour {second_engine}).
-
-stratum_collapse: >-
- {engine} est globalement compétitif ({global_cer_pct} %) mais s'effondre sur
- la strate « {stratum} » ({local_cer_pct} % sur {n_docs_stratum} documents,
- soit {delta_cer_pct} points au-dessus de sa moyenne).
-
-error_profile_outlier: >-
- Le profil d'erreurs de {engine} est atypique : {proportion_pct} % de la
- classe « {error_class} », contre une médiane de {median_proportion_pct} %
- sur les autres moteurs (ratio ×{ratio_to_median}).
-
-llm_hallucination_flag: >-
- Signal d'hallucination sur {engine} ({reasons_list}) —
- {hallucinating_rate_pct} % de documents au-dessus des seuils d'alerte.
-
-robustness_fragile: >-
- {engine} est fragile à la dégradation « {degradation} » : son CER passe de
- {cer_baseline_pct} % à {cer_degraded_pct} % au niveau maximal (ratio ×{ratio}).
-
-speed_winner: >-
- {engine} est le plus rapide ({mean_duration} s / doc, ×{speedup} plus vite
- que la médiane) pour un CER comparable ({cer_pct} %).
-
-confidence_warning: >-
- Incertitude statistique élevée : l'intervalle de confiance à {confidence_level} %
- de {engine} s'étend sur {ci_width_pct} points de CER, à comparer à l'écart de
- {gap_to_runner_up_pct} points avec le second.
-
-pareto_alternative: >-
- À coût sensiblement inférieur, {engine} offre un compromis intéressant
- ({cer_pct} % de CER pour {cost} €/{cost_unit_pages} pages, contre {leader_cer_pct} % /
- {leader_cost} € pour {leader}, soit ×{cost_saving_ratio} moins cher).
-
-cost_outlier: >-
- Coût disproportionné pour {engine} ({cost} €/{cost_unit_pages} pages, ×{ratio_to_median}
- la médiane) sans avantage de qualité compensatoire (CER {cer_pct} %).
-
-ensemble_opportunity: >-
- Les moteurs {pair_a} et {pair_b} ont des profils d'erreurs divergents
- ({divergence_metric}={divergence}). Sur ce corpus de {doc_count} documents,
- {best_engine} préserve {best_recall_pct} % des tokens ; un voting majoritaire
- entre les moteurs en préserverait {oracle_recall_pct} %, soit
- {absolute_gap_pct} points récupérables ({relative_gap_pct} % des erreurs
- du meilleur moteur).
-
-median_mean_gap_warning: >-
- Distribution asymétrique pour {engine} : médiane CER {median_cer_pct} %
- vs moyenne {mean_cer_pct} % sur {n_docs} documents (écart relatif
- {relative_gap_pct} %). La moyenne est tirée par quelques documents
- catastrophiques — la médiane (utilisée pour le tri par défaut) est
- plus représentative.
-
-stratification_recommended: >-
- Corpus hétérogène ({n_strata} strates) : {leader} performe très
- différemment selon le type de document — médiane CER
- {min_stratum_cer_pct} % sur « {min_stratum} » contre
- {max_stratum_cer_pct} % sur « {max_stratum} », soit {gap_pct} points
- d'écart. Le classement global masque cette disparité ; consulter la
- vue stratifiée.
-
-engine_off_baseline: >-
- {engine} a obtenu {cer_current_pct} % CER ici, vs {cer_historical_mean_pct} %
- en moyenne sur les {n_runs} runs précédents de votre institution sur
- ce même corpus (écart relatif {relative_delta_pct} %). Ce corpus lui
- est plus difficile que d'habitude.
-
-engine_unstable: >-
- Sur {n_runs} runs successifs, {engine} produit des sorties variables
- (CV CER {cer_cv_pct} %, paires de runs identiques {identical_run_rate_pct} %).
- La reproductibilité est limitée — interpréter le CER moyen avec prudence.
-
-regression_in_history: >-
- Sur les {n_runs} runs historiques pour {engine}, le CER moyen
- est passé de {first_cer_pct} % à {last_cer_pct} %
- (variation cumulée {absolute_delta_pct} points). Vérifier ce qui
- a changé dans le pipeline ou les modèles.
diff --git a/picarones/core/ner.py b/picarones/core/ner.py
deleted file mode 100644
index fbe1636fd6f17410174d301cd4c5a5c5f0869977..0000000000000000000000000000000000000000
--- a/picarones/core/ner.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.ner`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.ner import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.ner as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/ner_backends.py b/picarones/core/ner_backends.py
deleted file mode 100644
index 44f96cd6b909d1796f0a51caecf0fed5b412a462..0000000000000000000000000000000000000000
--- a/picarones/core/ner_backends.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.ner_backends`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.ner_backends import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.ner_backends as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/normalization.py b/picarones/core/normalization.py
deleted file mode 100644
index 3c918f13020ad8929570b09b3fdc0f3d92bba2c3..0000000000000000000000000000000000000000
--- a/picarones/core/normalization.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.normalization`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.normalization import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.normalization as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/numerical_sequences.py b/picarones/core/numerical_sequences.py
deleted file mode 100644
index 8e25033fbfa6cf75cb83ce3e95226ce6f760172f..0000000000000000000000000000000000000000
--- a/picarones/core/numerical_sequences.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.numerical_sequences`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.numerical_sequences import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.numerical_sequences as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/numerical_sequences_runner.py b/picarones/core/numerical_sequences_runner.py
deleted file mode 100644
index 0cf26886e75ae57f78571087c86a9fa6129fb50e..0000000000000000000000000000000000000000
--- a/picarones/core/numerical_sequences_runner.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.numerical_sequences_runner`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.numerical_sequences_runner import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.numerical_sequences_runner as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/philological_runner.py b/picarones/core/philological_runner.py
deleted file mode 100644
index 4483f7df1ae1c8b2fb1639eed30ca52168fb66d1..0000000000000000000000000000000000000000
--- a/picarones/core/philological_runner.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.historical.philological_runner`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.historical.philological_runner import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.historical.philological_runner as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/pipeline_runner.py b/picarones/core/pipeline.py
similarity index 97%
rename from picarones/core/pipeline_runner.py
rename to picarones/core/pipeline.py
index b3b29bea2de2095a9977e13b2bf1cc7c50e8f65c..3c58191ebfd774463a24c05f4ca6ec1bfb003708 100644
--- a/picarones/core/pipeline_runner.py
+++ b/picarones/core/pipeline.py
@@ -61,21 +61,21 @@ from picarones.core.modules import ArtifactType, BaseModule
# registre typé (Sprint 34) — sans ces imports, ``compute_at_junction``
# trouverait un registre vide et ne calculerait rien aux jonctions.
# Sprint 34 : cer / wer / mer / wil + stub TEXT→ALTO
-import picarones.core.builtin_metrics # noqa: F401
+import picarones.measurements.builtin_metrics # noqa: F401
# Sprints 55-60 : métriques philologiques.
-import picarones.core.unicode_blocks # noqa: F401
-import picarones.core.abbreviations # noqa: F401
-import picarones.core.mufi # noqa: F401
-import picarones.core.early_modern_typography # noqa: F401
-import picarones.core.modern_archives # noqa: F401
-import picarones.core.roman_numerals # noqa: F401
+import picarones.measurements.unicode_blocks # noqa: F401
+import picarones.measurements.abbreviations # noqa: F401
+import picarones.measurements.mufi # noqa: F401
+import picarones.measurements.early_modern_typography # noqa: F401
+import picarones.measurements.modern_archives # noqa: F401
+import picarones.measurements.roman_numerals # noqa: F401
# Sprint 53 : reading order F1. Sprints 38, 52 : NER, readability.
-import picarones.core.reading_order # noqa: F401
-import picarones.core.readability # noqa: F401
-import picarones.core.ner # noqa: F401
+import picarones.measurements.reading_order # noqa: F401
+import picarones.measurements.readability # noqa: F401
+import picarones.measurements.ner # noqa: F401
# Chantier 1 (post-Sprint 97) : métriques (ALTO, ALTO) pour évaluer
# les reconstructeurs ALTO contre une GT ALTO du document.
-import picarones.core.alto_metrics # noqa: F401
+import picarones.measurements.alto_metrics # noqa: F401
logger = logging.getLogger(__name__)
diff --git a/picarones/core/pricing.py b/picarones/core/pricing.py
deleted file mode 100644
index b9431ed6431ffe713c56252a03d9d7fd105eadff..0000000000000000000000000000000000000000
--- a/picarones/core/pricing.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.pricing`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.pricing import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.pricing as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/rare_tokens.py b/picarones/core/rare_tokens.py
deleted file mode 100644
index 17a9285a41f42100915b669b6a0912fd97bbdbb6..0000000000000000000000000000000000000000
--- a/picarones/core/rare_tokens.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.rare_tokens`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.rare_tokens import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.rare_tokens as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/readability.py b/picarones/core/readability.py
deleted file mode 100644
index 9c0a96ef15c99e4bc1767b1459cce52de421d4e8..0000000000000000000000000000000000000000
--- a/picarones/core/readability.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.readability`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.readability import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.readability as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/readability_runner.py b/picarones/core/readability_runner.py
deleted file mode 100644
index ab5d818bda6a3b7b32d08c9b26b092431ae1bf28..0000000000000000000000000000000000000000
--- a/picarones/core/readability_runner.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.readability_runner`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.readability_runner import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.readability_runner as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/reading_order.py b/picarones/core/reading_order.py
deleted file mode 100644
index 59d5c5129d510390368f714f8d8d88812cb57dcb..0000000000000000000000000000000000000000
--- a/picarones/core/reading_order.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.reading_order`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.reading_order import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.reading_order as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/reliability.py b/picarones/core/reliability.py
deleted file mode 100644
index 4d5a5115614536b70a21b2b3e93d23bdf5fc0aae..0000000000000000000000000000000000000000
--- a/picarones/core/reliability.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.reliability`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.reliability import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.reliability as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/results.py b/picarones/core/results.py
index 20e4d01a7479f62a5cc7dcd7f93ec0519e57c01a..83a60694ad04381e921c36b57daf12b7641daa0d 100644
--- a/picarones/core/results.py
+++ b/picarones/core/results.py
@@ -16,7 +16,7 @@ from pathlib import Path
from typing import Optional
from picarones import __version__
-from picarones.core.metrics import MetricsResult, aggregate_metrics
+from picarones.measurements.metrics import MetricsResult, aggregate_metrics
@dataclass
diff --git a/picarones/core/robustness.py b/picarones/core/robustness.py
deleted file mode 100644
index 5d1d852de91ed93ab00345bd36cb1941c1a2de50..0000000000000000000000000000000000000000
--- a/picarones/core/robustness.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.robustness`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.robustness import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.robustness as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/robustness_projection.py b/picarones/core/robustness_projection.py
deleted file mode 100644
index 2452a6742bff74c9df429536838ae9bc7dcf0cf6..0000000000000000000000000000000000000000
--- a/picarones/core/robustness_projection.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.robustness_projection`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.robustness_projection import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.robustness_projection as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/roman_numerals.py b/picarones/core/roman_numerals.py
deleted file mode 100644
index b269775f2b32b5c5289911b21b2da0e749393ddd..0000000000000000000000000000000000000000
--- a/picarones/core/roman_numerals.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.historical.roman_numerals`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.historical.roman_numerals import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.historical.roman_numerals as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/searchability.py b/picarones/core/searchability.py
deleted file mode 100644
index 7b5566bf4a1bcc768e7719d23cbaf73a84a0397b..0000000000000000000000000000000000000000
--- a/picarones/core/searchability.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.searchability`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.searchability import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.searchability as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/searchability_runner.py b/picarones/core/searchability_runner.py
deleted file mode 100644
index 7dcbd0cbd815fb42289ab1e40226e26d2afdcb96..0000000000000000000000000000000000000000
--- a/picarones/core/searchability_runner.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.searchability_runner`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.searchability_runner import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.searchability_runner as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/specialization.py b/picarones/core/specialization.py
deleted file mode 100644
index f67cdab273dc61116e505c3dc11d021060cb741a..0000000000000000000000000000000000000000
--- a/picarones/core/specialization.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.specialization`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.specialization import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.specialization as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/statistics.py b/picarones/core/statistics.py
deleted file mode 100644
index 689cac5cc9e48a1993728558dde901e8c47418d9..0000000000000000000000000000000000000000
--- a/picarones/core/statistics.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.statistics`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.statistics import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.statistics as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/structure.py b/picarones/core/structure.py
deleted file mode 100644
index 7b703e560670ba26e0f5f4fff230348b264b5d64..0000000000000000000000000000000000000000
--- a/picarones/core/structure.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.structure`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.structure import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.structure as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/taxonomy.py b/picarones/core/taxonomy.py
deleted file mode 100644
index c2dec846c4362e6ec7dcc9b3917b64c52da940c8..0000000000000000000000000000000000000000
--- a/picarones/core/taxonomy.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.taxonomy`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.taxonomy import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.taxonomy as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/taxonomy_comparison.py b/picarones/core/taxonomy_comparison.py
deleted file mode 100644
index 62d5e031af58c724aa1f0d57431a12abb9050d59..0000000000000000000000000000000000000000
--- a/picarones/core/taxonomy_comparison.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.taxonomy_comparison`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.taxonomy_comparison import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.taxonomy_comparison as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/taxonomy_cooccurrence.py b/picarones/core/taxonomy_cooccurrence.py
deleted file mode 100644
index f1d499873b24b2d418c36477e1fa7ee9ad19931f..0000000000000000000000000000000000000000
--- a/picarones/core/taxonomy_cooccurrence.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.academic.taxonomy_cooccurrence`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.academic.taxonomy_cooccurrence import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.academic.taxonomy_cooccurrence as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/taxonomy_intra_doc.py b/picarones/core/taxonomy_intra_doc.py
deleted file mode 100644
index 68e4f9c74dc62b8629e120284399922760269a81..0000000000000000000000000000000000000000
--- a/picarones/core/taxonomy_intra_doc.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.academic.taxonomy_intra_doc`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.academic.taxonomy_intra_doc import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.academic.taxonomy_intra_doc as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/throughput.py b/picarones/core/throughput.py
deleted file mode 100644
index e84ffeaeb65e0d13ece2a8683d8646d6d606a716..0000000000000000000000000000000000000000
--- a/picarones/core/throughput.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.throughput`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.throughput import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.throughput as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/unicode_blocks.py b/picarones/core/unicode_blocks.py
deleted file mode 100644
index 6c5906a4bff39377b3092bfc768303a6f8567bd4..0000000000000000000000000000000000000000
--- a/picarones/core/unicode_blocks.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.extras.historical.unicode_blocks`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.extras.historical.unicode_blocks import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.extras.historical.unicode_blocks as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/core/worst_lines.py b/picarones/core/worst_lines.py
deleted file mode 100644
index b8d4ec6dbd31ba302e71f9e71c62b962538b4ed2..0000000000000000000000000000000000000000
--- a/picarones/core/worst_lines.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""Alias rétrocompat — module déplacé dans :mod:`picarones.measurements.worst_lines`.
-
-Le contenu vit désormais dans son cercle d'origine. Cet alias permet
-aux imports historiques (y compris les noms privés ``_*``) de
-continuer à fonctionner sans modification.
-
-Voir :doc:`docs/architecture-cercles.md` pour la cartographie.
-"""
-
-from picarones.measurements.worst_lines import * # noqa: F401, F403
-
-# Réexport explicite de TOUS les noms (privés inclus) pour la
-# rétrocompatibilité des tests Sprints qui importent ``_helper``,
-# ``_compute_X``, ``_SCIPY_AVAILABLE``, etc. Sans cette boucle, ``import *``
-# ne propage que les noms publics et casse les imports historiques.
-import picarones.measurements.worst_lines as _shim_module
-for _shim_name in dir(_shim_module):
- if _shim_name == "__builtins__":
- continue
- if _shim_name not in globals():
- globals()[_shim_name] = getattr(_shim_module, _shim_name)
-del _shim_module, _shim_name
-
-__all__ = [
- _n for _n in dir() if not _n.startswith("__")
-]
diff --git a/picarones/fixtures.py b/picarones/fixtures.py
index a3d4b957a17bcfe67f317e342cd3136f680744d4..288a75e1a762c74662612ccce43ce73d77aebc99 100644
--- a/picarones/fixtures.py
+++ b/picarones/fixtures.py
@@ -13,19 +13,19 @@ import random
import struct
import zlib
-from picarones.core.metrics import MetricsResult
+from picarones.measurements.metrics import MetricsResult
from picarones.core.results import BenchmarkResult, DocumentResult, EngineReport
from picarones.pipelines.over_normalization import detect_over_normalization
# Sprint 5 — métriques avancées
-from picarones.core.confusion import build_confusion_matrix
-from picarones.core.char_scores import compute_ligature_score, compute_diacritic_score
-from picarones.core.taxonomy import classify_errors, aggregate_taxonomy
-from picarones.core.structure import analyze_structure, aggregate_structure
-from picarones.core.image_quality import generate_mock_quality_scores, aggregate_image_quality
-from picarones.core.char_scores import aggregate_ligature_scores, aggregate_diacritic_scores
+from picarones.measurements.confusion import build_confusion_matrix
+from picarones.measurements.char_scores import compute_ligature_score, compute_diacritic_score
+from picarones.measurements.taxonomy import classify_errors, aggregate_taxonomy
+from picarones.measurements.structure import analyze_structure, aggregate_structure
+from picarones.measurements.image_quality import generate_mock_quality_scores, aggregate_image_quality
+from picarones.measurements.char_scores import aggregate_ligature_scores, aggregate_diacritic_scores
# Sprint 10 — distribution des erreurs + hallucinations VLM
-from picarones.core.line_metrics import compute_line_metrics, aggregate_line_metrics, LineMetrics
-from picarones.core.hallucination import compute_hallucination_metrics, aggregate_hallucination_metrics
+from picarones.measurements.line_metrics import compute_line_metrics, aggregate_line_metrics, LineMetrics
+from picarones.measurements.hallucination import compute_hallucination_metrics, aggregate_hallucination_metrics
# ---------------------------------------------------------------------------
# Textes GT réalistes (documents patrimoniaux)
@@ -240,7 +240,7 @@ def _png_to_data_uri(png_bytes: bytes) -> str:
# ---------------------------------------------------------------------------
def _make_metrics(reference: str, hypothesis: str) -> MetricsResult:
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
return compute_metrics(reference, hypothesis)
@@ -427,11 +427,11 @@ def generate_sample_benchmark(
}
# Agrégation Sprint 5
- from picarones.core.confusion import aggregate_confusion_matrices, ConfusionMatrix
- from picarones.core.char_scores import LigatureScore, DiacriticScore
- from picarones.core.taxonomy import TaxonomyResult
- from picarones.core.structure import StructureResult
- from picarones.core.image_quality import ImageQualityResult
+ from picarones.measurements.confusion import aggregate_confusion_matrices, ConfusionMatrix
+ from picarones.measurements.char_scores import LigatureScore, DiacriticScore
+ from picarones.measurements.taxonomy import TaxonomyResult
+ from picarones.measurements.structure import StructureResult
+ from picarones.measurements.image_quality import ImageQualityResult
agg_confusion = aggregate_confusion_matrices([
ConfusionMatrix(**dr.confusion_matrix)
@@ -468,7 +468,7 @@ def generate_sample_benchmark(
LineMetrics.from_dict(dr.line_metrics)
for dr in doc_results if dr.line_metrics
])
- from picarones.core.hallucination import HallucinationMetrics as _HM
+ from picarones.measurements.hallucination import HallucinationMetrics as _HM
agg_hallucination = aggregate_hallucination_metrics([
_HM.from_dict(dr.hallucination_metrics)
for dr in doc_results if dr.hallucination_metrics
diff --git a/picarones/core/alto_metrics.py b/picarones/measurements/alto_metrics.py
similarity index 99%
rename from picarones/core/alto_metrics.py
rename to picarones/measurements/alto_metrics.py
index a72f8b42614dcacaab09ceb62f43fafee0ed05dd..cf6420a6902a192fd723211d1d9600b2bbe40a89 100644
--- a/picarones/core/alto_metrics.py
+++ b/picarones/measurements/alto_metrics.py
@@ -17,7 +17,7 @@ enregistre quatre métriques natives (``alto_text_cer``,
les opérateurs jiwer historiques sur le texte extrait des deux côtés.
L'approche est strictement additive vis-à-vis de
-:mod:`picarones.core.metrics` : ce module ne touche pas le chemin de
+:mod:`picarones.measurements.metrics` : ce module ne touche pas le chemin de
calcul historique (``compute_metrics``), il enrichit uniquement le
registre typé pour les pipelines composées.
diff --git a/picarones/measurements/builtin_hooks.py b/picarones/measurements/builtin_hooks.py
index 0b3888966a8890f9cf4aeaeea80ee74dbc9631a4..db3b42d0ff725bc9a966260f4bf10940cc2cc1a2 100644
--- a/picarones/measurements/builtin_hooks.py
+++ b/picarones/measurements/builtin_hooks.py
@@ -4,7 +4,7 @@ Chantier 2 du plan d'évolution post-Sprint 97.
Ce module **migre** les 12 hooks document-level et 12 agrégateurs
corpus-level qui étaient codés en dur dans
-``picarones.core.runner._compute_document_result`` et autour de la
+``picarones.measurements.runner._compute_document_result`` et autour de la
boucle d'agrégation (lignes 794-827 du runner pré-chantier-2).
Approche additive — rétrocompat stricte
@@ -97,7 +97,7 @@ def calibration_from_engine_result(
normalisées à ``[0, 1]``. Les confidences négatives (Tesseract met
-1 pour les non-mots) sont ignorées.
"""
- from picarones.core.calibration import compute_calibration_metrics
+ from picarones.measurements.calibration import compute_calibration_metrics
if not token_confidences:
return None
@@ -146,7 +146,7 @@ def calibration_from_engine_result(
requires_success=True,
)
def _confusion_hook(*, ground_truth, hypothesis, **_):
- from picarones.core.confusion import build_confusion_matrix
+ from picarones.measurements.confusion import build_confusion_matrix
return build_confusion_matrix(ground_truth, hypothesis).as_dict()
@@ -157,7 +157,7 @@ def _confusion_hook(*, ground_truth, hypothesis, **_):
requires_success=True,
)
def _char_scores_hook(*, ground_truth, hypothesis, **_):
- from picarones.core.char_scores import (
+ from picarones.measurements.char_scores import (
compute_diacritic_score,
compute_ligature_score,
)
@@ -173,7 +173,7 @@ def _char_scores_hook(*, ground_truth, hypothesis, **_):
requires_success=True,
)
def _taxonomy_hook(*, ground_truth, hypothesis, **_):
- from picarones.core.taxonomy import classify_errors
+ from picarones.measurements.taxonomy import classify_errors
return classify_errors(ground_truth, hypothesis).as_dict()
@@ -184,7 +184,7 @@ def _taxonomy_hook(*, ground_truth, hypothesis, **_):
requires_success=True,
)
def _structure_hook(*, ground_truth, hypothesis, **_):
- from picarones.core.structure import analyze_structure
+ from picarones.measurements.structure import analyze_structure
return analyze_structure(ground_truth, hypothesis).as_dict()
@@ -195,7 +195,7 @@ def _structure_hook(*, ground_truth, hypothesis, **_):
requires_success=True,
)
def _line_metrics_hook(*, ground_truth, hypothesis, **_):
- from picarones.core.line_metrics import compute_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics
return compute_line_metrics(ground_truth, hypothesis).as_dict()
@@ -206,7 +206,7 @@ def _line_metrics_hook(*, ground_truth, hypothesis, **_):
requires_success=True,
)
def _hallucination_hook(*, ground_truth, hypothesis, **_):
- from picarones.core.hallucination import compute_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics
return compute_hallucination_metrics(ground_truth, hypothesis).as_dict()
@@ -230,7 +230,7 @@ def _calibration_hook(*, ground_truth, ocr_result, **_):
# résultat OCR (pour comparer un échec OCR à la qualité image).
)
def _image_quality_hook(*, image_path, **_):
- from picarones.core.image_quality import analyze_image_quality
+ from picarones.measurements.image_quality import analyze_image_quality
iq = analyze_image_quality(image_path)
if iq.error is not None:
return None
@@ -247,7 +247,7 @@ def _image_quality_hook(*, image_path, **_):
# — comportement adaptive intact.
)
def _philological_hook(*, ground_truth, hypothesis, **_):
- from picarones.core.philological_runner import compute_philological_metrics
+ from picarones.measurements.philological_runner import compute_philological_metrics
return compute_philological_metrics(ground_truth, hypothesis)
@@ -257,7 +257,7 @@ def _philological_hook(*, ground_truth, hypothesis, **_):
profiles=_STANDARD_PROFILES,
)
def _searchability_hook(*, ground_truth, hypothesis, **_):
- from picarones.core.searchability_runner import compute_searchability_metrics
+ from picarones.measurements.searchability_runner import compute_searchability_metrics
return compute_searchability_metrics(ground_truth, hypothesis)
@@ -267,7 +267,7 @@ def _searchability_hook(*, ground_truth, hypothesis, **_):
profiles=_STANDARD_PROFILES,
)
def _numerical_sequences_hook(*, ground_truth, hypothesis, **_):
- from picarones.core.numerical_sequences_runner import (
+ from picarones.measurements.numerical_sequences_runner import (
compute_numerical_sequence_metrics_adaptive,
)
return compute_numerical_sequence_metrics_adaptive(ground_truth, hypothesis)
@@ -279,7 +279,7 @@ def _numerical_sequences_hook(*, ground_truth, hypothesis, **_):
profiles=_STANDARD_PROFILES,
)
def _readability_hook(*, ground_truth, hypothesis, corpus_lang, **_):
- from picarones.core.readability_runner import compute_readability_metrics
+ from picarones.measurements.readability_runner import compute_readability_metrics
return compute_readability_metrics(ground_truth, hypothesis, lang=corpus_lang)
@@ -294,7 +294,7 @@ def _readability_hook(*, ground_truth, hypothesis, corpus_lang, **_):
profiles=_STANDARD_PROFILES,
)
def _aggregate_confusion(doc_results: list) -> Optional[dict]:
- from picarones.core.confusion import (
+ from picarones.measurements.confusion import (
ConfusionMatrix, aggregate_confusion_matrices,
)
try:
@@ -321,7 +321,7 @@ def _aggregate_confusion(doc_results: list) -> Optional[dict]:
profiles=_STANDARD_PROFILES,
)
def _aggregate_char_scores(doc_results: list) -> Optional[dict]:
- from picarones.core.char_scores import (
+ from picarones.measurements.char_scores import (
DiacriticScore,
LigatureScore,
aggregate_diacritic_scores,
@@ -351,7 +351,7 @@ def _aggregate_char_scores(doc_results: list) -> Optional[dict]:
profiles=_STANDARD_PROFILES,
)
def _aggregate_taxonomy(doc_results: list) -> Optional[dict]:
- from picarones.core.taxonomy import TaxonomyResult, aggregate_taxonomy
+ from picarones.measurements.taxonomy import TaxonomyResult, aggregate_taxonomy
results = [
TaxonomyResult.from_dict(dr.taxonomy)
for dr in doc_results
@@ -368,7 +368,7 @@ def _aggregate_taxonomy(doc_results: list) -> Optional[dict]:
profiles=_STANDARD_PROFILES,
)
def _aggregate_structure(doc_results: list) -> Optional[dict]:
- from picarones.core.structure import StructureResult, aggregate_structure
+ from picarones.measurements.structure import StructureResult, aggregate_structure
results = [
StructureResult.from_dict(dr.structure)
for dr in doc_results
@@ -385,7 +385,7 @@ def _aggregate_structure(doc_results: list) -> Optional[dict]:
profiles=_STANDARD_PROFILES,
)
def _aggregate_image_quality(doc_results: list) -> Optional[dict]:
- from picarones.core.image_quality import (
+ from picarones.measurements.image_quality import (
ImageQualityResult, aggregate_image_quality,
)
results = [
@@ -404,7 +404,7 @@ def _aggregate_image_quality(doc_results: list) -> Optional[dict]:
profiles=_STANDARD_PROFILES,
)
def _aggregate_line_metrics(doc_results: list) -> Optional[dict]:
- from picarones.core.line_metrics import (
+ from picarones.measurements.line_metrics import (
LineMetrics, aggregate_line_metrics,
)
results = [
@@ -423,7 +423,7 @@ def _aggregate_line_metrics(doc_results: list) -> Optional[dict]:
profiles=_STANDARD_PROFILES,
)
def _aggregate_hallucination(doc_results: list) -> Optional[dict]:
- from picarones.core.hallucination import (
+ from picarones.measurements.hallucination import (
HallucinationMetrics, aggregate_hallucination_metrics,
)
results = [
@@ -543,7 +543,7 @@ def _aggregate_calibration(doc_results: list) -> Optional[dict]:
profiles=_STANDARD_PROFILES,
)
def _aggregate_philological(doc_results: list) -> Optional[dict]:
- from picarones.core.philological_runner import aggregate_philological_metrics
+ from picarones.measurements.philological_runner import aggregate_philological_metrics
return aggregate_philological_metrics(
[dr.philological_metrics for dr in doc_results],
)
@@ -555,7 +555,7 @@ def _aggregate_philological(doc_results: list) -> Optional[dict]:
profiles=_STANDARD_PROFILES,
)
def _aggregate_searchability(doc_results: list) -> Optional[dict]:
- from picarones.core.searchability_runner import aggregate_searchability_metrics
+ from picarones.measurements.searchability_runner import aggregate_searchability_metrics
return aggregate_searchability_metrics(
[dr.searchability_metrics for dr in doc_results],
)
@@ -567,7 +567,7 @@ def _aggregate_searchability(doc_results: list) -> Optional[dict]:
profiles=_STANDARD_PROFILES,
)
def _aggregate_numerical_sequences(doc_results: list) -> Optional[dict]:
- from picarones.core.numerical_sequences_runner import (
+ from picarones.measurements.numerical_sequences_runner import (
aggregate_numerical_sequence_metrics,
)
return aggregate_numerical_sequence_metrics(
@@ -581,7 +581,7 @@ def _aggregate_numerical_sequences(doc_results: list) -> Optional[dict]:
profiles=_STANDARD_PROFILES,
)
def _aggregate_readability(doc_results: list) -> Optional[dict]:
- from picarones.core.readability_runner import aggregate_readability_metrics
+ from picarones.measurements.readability_runner import aggregate_readability_metrics
return aggregate_readability_metrics(
[dr.readability_metrics for dr in doc_results],
)
diff --git a/picarones/core/builtin_metrics.py b/picarones/measurements/builtin_metrics.py
similarity index 100%
rename from picarones/core/builtin_metrics.py
rename to picarones/measurements/builtin_metrics.py
diff --git a/picarones/measurements/cost_projection.py b/picarones/measurements/cost_projection.py
index f9eab7a6d47731e7b6b0722cb86ad9b1aae0729b..f0fc9baca084ed9b92697636001dcac84c5254ef 100644
--- a/picarones/measurements/cost_projection.py
+++ b/picarones/measurements/cost_projection.py
@@ -20,7 +20,7 @@ le chercheur arbitre selon son budget.
Dépendance
----------
-S'appuie sur ``picarones.core.pricing`` (Sprint 20) qui expose
+S'appuie sur ``picarones.measurements.pricing`` (Sprint 20) qui expose
``EngineCost.cost_per_1k_pages_eur`` et
``co2_per_1k_pages_g``.
"""
@@ -31,7 +31,7 @@ import logging
from dataclasses import dataclass
from typing import Optional
-from picarones.core.pricing import EngineCost
+from picarones.measurements.pricing import EngineCost
logger = logging.getLogger(__name__)
diff --git a/picarones/measurements/difficulty.py b/picarones/measurements/difficulty.py
index 7f037a48d4f67d06e7162473b901fc261681373a..10c5b72a32020f6b843774a68d6f1672e79e8b25 100644
--- a/picarones/measurements/difficulty.py
+++ b/picarones/measurements/difficulty.py
@@ -192,7 +192,7 @@ def difficulty_label(score: float) -> str:
def difficulty_color(score: float) -> str:
"""Retourne une couleur CSS pour un score de difficulté."""
- from picarones.core.colors import COLOR_GREEN, COLOR_YELLOW, COLOR_ORANGE, COLOR_RED
+ from picarones.report.colors import COLOR_GREEN, COLOR_YELLOW, COLOR_ORANGE, COLOR_RED
if score < 0.25:
return COLOR_GREEN
if score < 0.50:
diff --git a/picarones/measurements/equivalence_profile.py b/picarones/measurements/equivalence_profile.py
index e3c9adf1cb24c3e73567733ac16c883ba8396e81..cb8c001f0bb01baf48aac621b482e2cec4872701 100644
--- a/picarones/measurements/equivalence_profile.py
+++ b/picarones/measurements/equivalence_profile.py
@@ -42,7 +42,7 @@ import logging
from dataclasses import dataclass
from typing import Iterable, Optional
-from picarones.core.normalization import (
+from picarones.measurements.normalization import (
DIPLOMATIC_EN_EARLY_MODERN,
DIPLOMATIC_FR_EARLY_MODERN,
DIPLOMATIC_LATIN_MEDIEVAL,
@@ -178,10 +178,10 @@ def compute_cer_with_equivalences(
"""Calcule le CER après application des équivalences sélectionnées
sur les **deux** côtés (GT et hypothèse).
- Utilise ``picarones.core.metrics.compute_metrics`` et extrait
+ Utilise ``picarones.measurements.metrics.compute_metrics`` et extrait
le champ ``cer`` du résultat.
"""
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
selected_list = list(selected_names)
ref = apply_selected_equivalences(reference or "", selected_list)
diff --git a/picarones/measurements/history.py b/picarones/measurements/history.py
index 9a360851ae767a99635a18eadae3fa0dcd9fb411..39aa55efed7a827af50c4c08387aebd607d60baf 100644
--- a/picarones/measurements/history.py
+++ b/picarones/measurements/history.py
@@ -21,7 +21,7 @@ Table ``runs`` :
Usage
-----
->>> from picarones.core.history import BenchmarkHistory
+>>> from picarones.measurements.history import BenchmarkHistory
>>> history = BenchmarkHistory("~/.picarones/history.db")
>>> history.record(benchmark_result)
>>> df = history.query(engine="tesseract", corpus="chroniques")
diff --git a/picarones/measurements/incremental_comparison.py b/picarones/measurements/incremental_comparison.py
index 03f3ea0ee8a3b3eb7ed90af59349f49cb95386e1..8dcd0f6d95b85d94472aa99fffab926755e89be3 100644
--- a/picarones/measurements/incremental_comparison.py
+++ b/picarones/measurements/incremental_comparison.py
@@ -28,7 +28,7 @@ On ne reconstruit pas Friedman/Nemenyi (déjà dans Sprint 18) ;
on agrège ici les données nécessaires pour qu'un
tests statistique externe puisse les consommer. Le rapport
existant reste libre de brancher
-``picarones.core.statistics.friedman_test`` sur la sortie de
+``picarones.measurements.statistics.friedman_test`` sur la sortie de
ce module.
Sortie
diff --git a/picarones/core/metrics.py b/picarones/measurements/metrics.py
similarity index 98%
rename from picarones/core/metrics.py
rename to picarones/measurements/metrics.py
index 1ab3e5355f578ac72049735647931a418bbf12cf..66228fb095d230c74bcf930d99a381b7c1adddb0 100644
--- a/picarones/core/metrics.py
+++ b/picarones/measurements/metrics.py
@@ -195,7 +195,7 @@ def compute_metrics(
cer_diplomatic: Optional[float] = None
diplomatic_profile_name: Optional[str] = None
try:
- from picarones.core.normalization import DEFAULT_DIPLOMATIC_PROFILE
+ from picarones.measurements.normalization import DEFAULT_DIPLOMATIC_PROFILE
profile = normalization_profile or DEFAULT_DIPLOMATIC_PROFILE
ref_diplo = profile.normalize(reference)
hyp_diplo = profile.normalize(hypothesis)
@@ -288,4 +288,4 @@ def aggregate_metrics(results: list[MetricsResult]) -> dict:
# Import paresseux pour éviter les imports circulaires
from typing import TYPE_CHECKING
if TYPE_CHECKING:
- from picarones.core.normalization import NormalizationProfile
+ from picarones.measurements.normalization import NormalizationProfile
diff --git a/picarones/measurements/narrative/__init__.py b/picarones/measurements/narrative/__init__.py
index 12b7ce32122e4d51748a79c79e99220951cdcc81..54c5500c474cccd71139309260000abf67bda840 100644
--- a/picarones/measurements/narrative/__init__.py
+++ b/picarones/measurements/narrative/__init__.py
@@ -14,7 +14,7 @@ API publique
- ``build_synthesis(data, lang="fr")`` : pipeline complet (Sprint 4)
"""
-from picarones.measurements.narrative.facts import (
+from picarones.core.facts import (
Fact,
FactType,
FactImportance,
diff --git a/picarones/measurements/narrative/arbiter.py b/picarones/measurements/narrative/arbiter.py
index 1b0625d3afcf7e0ff670ea28667344b3d867060e..9a5413005a07e5dbffb8112edfa634162113be33 100644
--- a/picarones/measurements/narrative/arbiter.py
+++ b/picarones/measurements/narrative/arbiter.py
@@ -21,7 +21,7 @@ from __future__ import annotations
from typing import Iterable, Sequence
-from picarones.measurements.narrative.facts import Fact, FactImportance, FactType
+from picarones.core.facts import Fact, FactImportance, FactType
# Ordre canonique des types pour départager les ex-aequo à l'importance égale.
diff --git a/picarones/measurements/narrative/detectors/__init__.py b/picarones/measurements/narrative/detectors/__init__.py
index cc71abb34614697b6e11e8af7eba2ecd1503b1c1..ed5dda3fc009448ca4eefe7d7e1e9405bf23809f 100644
--- a/picarones/measurements/narrative/detectors/__init__.py
+++ b/picarones/measurements/narrative/detectors/__init__.py
@@ -69,7 +69,7 @@ from picarones.measurements.narrative.detectors.ensemble import (
# Snapshot du registre + helper d'enregistrement legacy — déplacés
# verbatim depuis l'ancien ``detectors.py`` (lignes 1193-1229).
-from picarones.measurements.narrative.facts import DetectorFn, FactType
+from picarones.core.facts import DetectorFn, FactType
from picarones.measurements.narrative.registry import (
iter_detectors as _iter_detectors,
populate_legacy_registry as _populate_legacy_registry,
diff --git a/picarones/measurements/narrative/detectors/_helpers.py b/picarones/measurements/narrative/detectors/_helpers.py
index 6b55645c418a456617b052fb41a1d52d755203fb..a5e05c694972f3a2c6abc444d2e44ba2e011e52f 100644
--- a/picarones/measurements/narrative/detectors/_helpers.py
+++ b/picarones/measurements/narrative/detectors/_helpers.py
@@ -1,13 +1,4 @@
-"""Helpers internes partagés par les détecteurs narratifs.
-
-Chantier 5 du plan d'évolution post-Sprint 97 — découpage de
-``picarones/core/narrative/detectors.py`` (1229 lignes, 18 détecteurs)
-en 6 sous-modules thématiques + ce module d'helpers communs.
-
-Ces fonctions étaient privées (préfixe ``_``) au module historique.
-Elles sont conservées telles quelles ici ; les sous-modules les
-importent.
-"""
+"""Helpers internes partagés par les détecteurs narratifs."""
from __future__ import annotations
@@ -34,10 +25,8 @@ def _n_docs(data: dict) -> int:
def _mean_duration_per_engine(data: dict) -> dict[str, float]:
"""Durée moyenne d'exécution par moteur (en secondes par document).
- Source primaire : ``benchmark_data["documents"][i]["engine_results"][j]["duration"]``
- (format historique du runner). Fallback secondaire :
- ``benchmark_data["engines"][i]["mean_duration"]`` (champ agrégé
- quand fourni). Filtre les durées non-numériques.
+ Lit ``benchmark_data["documents"][i]["engine_results"][j]["duration"]``
+ (format runner). Filtre les durées non-numériques.
"""
durations: dict[str, list[float]] = {}
for doc in data.get("documents") or []:
@@ -51,21 +40,4 @@ def _mean_duration_per_engine(data: dict) -> dict[str, float]:
except (TypeError, ValueError):
continue
durations.setdefault(engine_name, []).append(d_f)
- if durations:
- return {k: sum(v) / len(v) for k, v in durations.items() if v}
- # Fallback : champ agrégé sur le résumé moteur
- out: dict[str, float] = {}
- for e in _engines_summary(data):
- name = e.get("name")
- if not name:
- continue
- dur = e.get("mean_duration")
- if dur is None:
- continue
- try:
- dur_f = float(dur)
- except (TypeError, ValueError):
- continue
- if dur_f > 0:
- out[name] = dur_f
- return out
+ return {k: sum(v) / len(v) for k, v in durations.items() if v}
diff --git a/picarones/measurements/narrative/detectors/ensemble.py b/picarones/measurements/narrative/detectors/ensemble.py
index 2d2f8a9c3c6463dc4c3561b171bf67076e46f0e7..37f1465469f5bc10bf51984988ea192e0b752807 100644
--- a/picarones/measurements/narrative/detectors/ensemble.py
+++ b/picarones/measurements/narrative/detectors/ensemble.py
@@ -9,7 +9,7 @@ from __future__ import annotations
from typing import Optional
-from picarones.measurements.narrative.facts import Fact, FactImportance, FactType
+from picarones.core.facts import Fact, FactImportance, FactType
from picarones.measurements.narrative.registry import register_detector
diff --git a/picarones/measurements/narrative/detectors/history.py b/picarones/measurements/narrative/detectors/history.py
index 0d08e9b54f927816e898917cf79c500cb0e6d5be..385a99e0b2378a112881f72497c5bde86a84eb71 100644
--- a/picarones/measurements/narrative/detectors/history.py
+++ b/picarones/measurements/narrative/detectors/history.py
@@ -10,7 +10,7 @@
from __future__ import annotations
-from picarones.measurements.narrative.facts import Fact, FactImportance, FactType
+from picarones.core.facts import Fact, FactImportance, FactType
from picarones.measurements.narrative.registry import register_detector
diff --git a/picarones/measurements/narrative/detectors/pareto.py b/picarones/measurements/narrative/detectors/pareto.py
index 8735e0536e78562fda1a2bdead2df40e15ddfa31..e5f744ef22f63e21fb9716d1902e19a658f9294c 100644
--- a/picarones/measurements/narrative/detectors/pareto.py
+++ b/picarones/measurements/narrative/detectors/pareto.py
@@ -11,7 +11,7 @@ from __future__ import annotations
import statistics as _stats
from typing import Optional
-from picarones.measurements.narrative.facts import Fact, FactImportance, FactType
+from picarones.core.facts import Fact, FactImportance, FactType
from picarones.measurements.narrative.registry import register_detector
diff --git a/picarones/measurements/narrative/detectors/quality.py b/picarones/measurements/narrative/detectors/quality.py
index e3180e254de8b65b6f34302ba28425b2ac193937..c7e42ff751d26183d0fe39321a055073405089ed 100644
--- a/picarones/measurements/narrative/detectors/quality.py
+++ b/picarones/measurements/narrative/detectors/quality.py
@@ -12,7 +12,7 @@ from __future__ import annotations
import statistics as _stats
-from picarones.measurements.narrative.facts import Fact, FactImportance, FactType
+from picarones.core.facts import Fact, FactImportance, FactType
from picarones.measurements.narrative.registry import register_detector
from picarones.measurements.narrative.detectors._helpers import (
diff --git a/picarones/measurements/narrative/detectors/ranking.py b/picarones/measurements/narrative/detectors/ranking.py
index b747b51401bb038cb6b0b60af12058ea3843f31a..4c52b599ab57e6d45b84713f6f19d98b6d47cf3f 100644
--- a/picarones/measurements/narrative/detectors/ranking.py
+++ b/picarones/measurements/narrative/detectors/ranking.py
@@ -16,7 +16,7 @@ from __future__ import annotations
import statistics as _stats
-from picarones.measurements.narrative.facts import Fact, FactImportance, FactType
+from picarones.core.facts import Fact, FactImportance, FactType
from picarones.measurements.narrative.registry import register_detector
from picarones.measurements.narrative.detectors._helpers import (
diff --git a/picarones/measurements/narrative/detectors/stratum.py b/picarones/measurements/narrative/detectors/stratum.py
index d58eae9119ed4ea47aeb73a197fb9bed41e2beb9..5db5b795b78fe53b6e557cc0444fde89d784d429 100644
--- a/picarones/measurements/narrative/detectors/stratum.py
+++ b/picarones/measurements/narrative/detectors/stratum.py
@@ -11,7 +11,7 @@
from __future__ import annotations
-from picarones.measurements.narrative.facts import Fact, FactImportance, FactType
+from picarones.core.facts import Fact, FactImportance, FactType
from picarones.measurements.narrative.registry import register_detector
from picarones.measurements.narrative.detectors._helpers import (
diff --git a/picarones/measurements/narrative/registry.py b/picarones/measurements/narrative/registry.py
index 511ed2ed360a178b407183c05ab6ca7adf46dd8b..9415dc6a3cabb24b4acfae8b250811a378ef2227 100644
--- a/picarones/measurements/narrative/registry.py
+++ b/picarones/measurements/narrative/registry.py
@@ -51,7 +51,7 @@ import threading
from dataclasses import dataclass
from typing import Callable, Optional
-from picarones.measurements.narrative.facts import (
+from picarones.core.facts import (
DetectorFn,
DetectorRegistry,
FactImportance,
diff --git a/picarones/measurements/narrative/renderer.py b/picarones/measurements/narrative/renderer.py
index cbb5d3da3f91db658339c78eb6be946177dfb0f9..c710e8687b5c598d4a33cd8796f63bce8618c3a4 100644
--- a/picarones/measurements/narrative/renderer.py
+++ b/picarones/measurements/narrative/renderer.py
@@ -15,7 +15,7 @@ from typing import Iterable
import yaml
-from picarones.measurements.narrative.facts import Fact
+from picarones.core.facts import Fact
logger = logging.getLogger(__name__)
diff --git a/picarones/measurements/numerical_sequences.py b/picarones/measurements/numerical_sequences.py
index 5698b4017fa693cec17a6dd1671bfed7d1cab38c..2e99e8f6de60f45bd0bbb60b6e659c5af01b3eff 100644
--- a/picarones/measurements/numerical_sequences.py
+++ b/picarones/measurements/numerical_sequences.py
@@ -17,7 +17,7 @@ Catégories couvertes
(le module détecte les **années** sur 4 chiffres dans la
plage [1000-2099]).
2. **Numéraux romains** : ``MDCLXVIII``, ``XIV``, ``Tome IV``.
- Réutilise ``picarones.core.roman_numerals`` (Sprint 60).
+ Réutilise ``picarones.measurements.roman_numerals`` (Sprint 60).
3. **Foliotation** : ``f. 12``, ``f. 12r``, ``fol. 24v``,
``p. 5``, ``pp. 12-15``, ``n° 42``.
4. **Montants** : ``12 livres``, ``5 sols``, ``8 deniers``,
@@ -86,7 +86,7 @@ from typing import Optional
from picarones.core.metric_registry import register_metric
from picarones.core.modules import ArtifactType
-from picarones.core.roman_numerals import (
+from picarones.measurements.roman_numerals import (
detect_roman_numerals,
roman_to_int,
)
diff --git a/picarones/measurements/numerical_sequences_runner.py b/picarones/measurements/numerical_sequences_runner.py
index c1d6d1f429e85c11efa6a2a5a6db632c0566c976..405f68109ce3486a82a9d21849ac1426a2b2b82c 100644
--- a/picarones/measurements/numerical_sequences_runner.py
+++ b/picarones/measurements/numerical_sequences_runner.py
@@ -18,7 +18,7 @@ from __future__ import annotations
import logging
from typing import Iterable, Optional
-from picarones.core.numerical_sequences import (
+from picarones.measurements.numerical_sequences import (
CATEGORIES,
compute_numerical_sequence_metrics,
)
diff --git a/picarones/core/pipeline_benchmark.py b/picarones/measurements/pipeline_benchmark.py
similarity index 99%
rename from picarones/core/pipeline_benchmark.py
rename to picarones/measurements/pipeline_benchmark.py
index 40f3afc9b46ff598e8788e6a1c1ba7001c071c02..ef597c329b1a4ccca09be799507db22199445695 100644
--- a/picarones/core/pipeline_benchmark.py
+++ b/picarones/measurements/pipeline_benchmark.py
@@ -47,7 +47,7 @@ from typing import Any, Callable, Optional
from picarones.core.corpus import Corpus, Document
from picarones.core.modules import ArtifactType
-from picarones.core.pipeline_runner import (
+from picarones.core.pipeline import (
PipelineResult,
PipelineRunner,
PipelineSpec,
diff --git a/picarones/core/pipeline_comparison.py b/picarones/measurements/pipeline_comparison.py
similarity index 99%
rename from picarones/core/pipeline_comparison.py
rename to picarones/measurements/pipeline_comparison.py
index bf7f775bda2e1707d488c43e629b3adcd0b86b9e..5d960c9516c0a3dfd04da762568f68e948270b58 100644
--- a/picarones/core/pipeline_comparison.py
+++ b/picarones/measurements/pipeline_comparison.py
@@ -52,13 +52,13 @@ from typing import Optional
from picarones.core.corpus import Corpus
from picarones.core.modules import ArtifactType
-from picarones.core.pipeline_benchmark import (
+from picarones.measurements.pipeline_benchmark import (
InitialInputsFactory,
PipelineBenchmarkResult,
default_initial_inputs,
run_pipeline_benchmark,
)
-from picarones.core.pipeline_runner import PipelineSpec
+from picarones.core.pipeline import PipelineSpec
logger = logging.getLogger(__name__)
diff --git a/picarones/core/pipeline_spec_loader.py b/picarones/measurements/pipeline_spec_loader.py
similarity index 99%
rename from picarones/core/pipeline_spec_loader.py
rename to picarones/measurements/pipeline_spec_loader.py
index 0166b0e99ba128379c7fbf29ade75a4c1c390abd..9b76674f65a4352c76592af1790b2b3edbd43d22 100644
--- a/picarones/core/pipeline_spec_loader.py
+++ b/picarones/measurements/pipeline_spec_loader.py
@@ -68,7 +68,7 @@ from pathlib import Path
from typing import Any
from picarones.core.modules import ArtifactType, BaseModule
-from picarones.core.pipeline_runner import PipelineSpec, PipelineStep
+from picarones.core.pipeline import PipelineSpec, PipelineStep
logger = logging.getLogger(__name__)
diff --git a/picarones/measurements/readability_runner.py b/picarones/measurements/readability_runner.py
index 073b5f16fca2a59993e08453c191f915d8f75405..2ad1a0129fb672457f3de6a8c976ae55b7d612ed 100644
--- a/picarones/measurements/readability_runner.py
+++ b/picarones/measurements/readability_runner.py
@@ -33,7 +33,7 @@ import logging
import statistics
from typing import Iterable, Optional
-from picarones.core.readability import (
+from picarones.measurements.readability import (
Language,
count_words,
flesch_delta,
diff --git a/picarones/measurements/reliability.py b/picarones/measurements/reliability.py
index 116bdc28d8312cc1a702d6d0caf156d2db78e0b9..8a179ffe604fa7a4f0eb5d8483edcacaa14a4773 100644
--- a/picarones/measurements/reliability.py
+++ b/picarones/measurements/reliability.py
@@ -333,7 +333,7 @@ def compute_multirun_stability(
cer_stdev: Optional[float] = None
cer_cv: Optional[float] = None
if reference is not None:
- from picarones.core.metrics import _cer_from_strings
+ from picarones.measurements.metrics import _cer_from_strings
cer_per_run = [_cer_from_strings(reference, r) for r in runs_list]
cer_per_run = [v for v in cer_per_run if v is not None]
if cer_per_run:
diff --git a/picarones/measurements/robustness.py b/picarones/measurements/robustness.py
index 27f407234709718dab9752114a00250c0fc2db60..bb455d6dbe303bf3c8e8102c05444ce7dd7e90f1 100644
--- a/picarones/measurements/robustness.py
+++ b/picarones/measurements/robustness.py
@@ -15,7 +15,7 @@ Fonctionnement
Usage
-----
->>> from picarones.core.robustness import RobustnessAnalyzer
+>>> from picarones.measurements.robustness import RobustnessAnalyzer
>>> analyzer = RobustnessAnalyzer(engine, degradation_types=["noise", "blur"])
>>> report = analyzer.analyze(corpus)
>>> print(report.critical_thresholds)
@@ -420,7 +420,7 @@ class RobustnessAnalyzer:
Examples
--------
>>> from picarones.engines.tesseract import TesseractEngine
- >>> from picarones.core.robustness import RobustnessAnalyzer
+ >>> from picarones.measurements.robustness import RobustnessAnalyzer
>>> engine = TesseractEngine(config={"lang": "fra"})
>>> analyzer = RobustnessAnalyzer([engine], degradation_types=["noise", "blur"])
>>> report = analyzer.analyze(corpus)
@@ -463,7 +463,7 @@ class RobustnessAnalyzer:
-------
RobustnessReport
"""
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
docs = corpus.documents[:max_docs]
curves: list[DegradationCurve] = []
diff --git a/picarones/core/runner.py b/picarones/measurements/runner.py
similarity index 95%
rename from picarones/core/runner.py
rename to picarones/measurements/runner.py
index 9caca766da2bb3894ef099497768b5e578c2726f..2f89a9f9327ef4e14c8eadae6898b57f500c3661 100644
--- a/picarones/core/runner.py
+++ b/picarones/measurements/runner.py
@@ -24,7 +24,7 @@ from typing import Optional
from tqdm import tqdm
from picarones.core.corpus import Corpus
-from picarones.core.metrics import MetricsResult, compute_metrics
+from picarones.measurements.metrics import MetricsResult, compute_metrics
from picarones.core.results import BenchmarkResult, DocumentResult, EngineReport
from picarones.engines.base import BaseOCREngine, EngineResult
@@ -126,19 +126,19 @@ def _io_doc_worker(
# Chantier 2 (post-Sprint 97) — la logique du helper calibration vit
-# désormais dans :mod:`picarones.core.builtin_hooks`. Ce nom reste exposé
+# désormais dans :mod:`picarones.measurements.builtin_hooks`. Ce nom reste exposé
# ici pour la rétrocompat des tests Sprint 42 qui font
-# ``from picarones.core.runner import _calibration_from_engine_result``.
+# ``from picarones.measurements.runner import _calibration_from_engine_result``.
def _calibration_from_engine_result(
ground_truth: str,
token_confidences: list,
) -> Optional[dict]:
- """Délégation vers :func:`picarones.core.builtin_hooks.calibration_from_engine_result`.
+ """Délégation vers :func:`picarones.measurements.builtin_hooks.calibration_from_engine_result`.
Conservé pour la rétrocompat des tests existants ; toute évolution
du calcul doit se faire dans ``builtin_hooks``.
"""
- from picarones.core.builtin_hooks import calibration_from_engine_result
+ from picarones.measurements.builtin_hooks import calibration_from_engine_result
return calibration_from_engine_result(ground_truth, token_confidences)
@@ -162,7 +162,7 @@ def _compute_document_result(
Chantier 2 (post-Sprint 97) — refonte
------------------------------------
Les 11 ``try/except`` codés en dur (Sprints 5+10+39+42+61+86+87) sont
- désormais centralisés dans ``picarones.core.builtin_hooks`` et
+ désormais centralisés dans ``picarones.measurements.builtin_hooks`` et
sélectionnés via ``run_document_hooks(profile)``. Le profil
``"standard"`` (défaut) reproduit strictement le comportement
pré-chantier-2. Les profils ``"minimal"``, ``"philological"``,
@@ -175,7 +175,7 @@ def _compute_document_result(
# Eager-load des hooks natifs pour peupler le registre dans les
# sous-processus du pool (le top-level ``import`` du runner ne le fait
# pas pour ne pas pénaliser le démarrage des moteurs minimaux).
- import picarones.core.builtin_hooks # noqa: F401
+ import picarones.measurements.builtin_hooks # noqa: F401
from picarones.core.metric_hooks import run_document_hooks
if ocr_result.success:
@@ -476,7 +476,7 @@ def run_benchmark(
# aux pools. Eager-load des hooks natifs pour peupler le registre
# dans le main process (les sous-processus du pool feront leur
# propre import dans ``_compute_document_result``).
- import picarones.core.builtin_hooks # noqa: F401
+ import picarones.measurements.builtin_hooks # noqa: F401
from picarones.core.metric_hooks import (
run_corpus_aggregators, validate_profile,
)
@@ -754,7 +754,7 @@ def run_benchmark(
inter_engine_payload: Optional[dict] = None
if len(engine_reports) >= 2:
try:
- from picarones.core.inter_engine import compute_inter_engine_analysis
+ from picarones.measurements.inter_engine import compute_inter_engine_analysis
taxonomy_distros = {
report.engine_name: (
@@ -842,50 +842,50 @@ def _build_pipeline_info(engine: BaseOCREngine, doc_results: list[DocumentResult
# Helpers d'agrégation — délégations rétrocompat
# ---------------------------------------------------------------------------
# Chantier 2 (post-Sprint 97) : les implémentations vivent désormais dans
-# :mod:`picarones.core.builtin_hooks` (single source of truth, exposé via
+# :mod:`picarones.measurements.builtin_hooks` (single source of truth, exposé via
# le registre :mod:`picarones.core.metric_hooks`). Les noms ci-dessous
-# restent disponibles depuis ``picarones.core.runner`` pour la rétrocompat
+# restent disponibles depuis ``picarones.measurements.runner`` pour la rétrocompat
# des tests Sprint 13 / 42 qui les importent directement.
def _aggregate_confusion(doc_results: list) -> Optional[dict]:
"""Délégation vers :func:`builtin_hooks._aggregate_confusion`."""
- from picarones.core.builtin_hooks import _aggregate_confusion as _impl
+ from picarones.measurements.builtin_hooks import _aggregate_confusion as _impl
return _impl(doc_results)
def _aggregate_char_scores(doc_results: list) -> Optional[dict]:
"""Délégation vers :func:`builtin_hooks._aggregate_char_scores`."""
- from picarones.core.builtin_hooks import _aggregate_char_scores as _impl
+ from picarones.measurements.builtin_hooks import _aggregate_char_scores as _impl
return _impl(doc_results)
def _aggregate_taxonomy(doc_results: list) -> Optional[dict]:
"""Délégation vers :func:`builtin_hooks._aggregate_taxonomy`."""
- from picarones.core.builtin_hooks import _aggregate_taxonomy as _impl
+ from picarones.measurements.builtin_hooks import _aggregate_taxonomy as _impl
return _impl(doc_results)
def _aggregate_structure(doc_results: list) -> Optional[dict]:
"""Délégation vers :func:`builtin_hooks._aggregate_structure`."""
- from picarones.core.builtin_hooks import _aggregate_structure as _impl
+ from picarones.measurements.builtin_hooks import _aggregate_structure as _impl
return _impl(doc_results)
def _aggregate_image_quality(doc_results: list) -> Optional[dict]:
"""Délégation vers :func:`builtin_hooks._aggregate_image_quality`."""
- from picarones.core.builtin_hooks import _aggregate_image_quality as _impl
+ from picarones.measurements.builtin_hooks import _aggregate_image_quality as _impl
return _impl(doc_results)
def _aggregate_line_metrics(doc_results: list) -> Optional[dict]:
"""Délégation vers :func:`builtin_hooks._aggregate_line_metrics`."""
- from picarones.core.builtin_hooks import _aggregate_line_metrics as _impl
+ from picarones.measurements.builtin_hooks import _aggregate_line_metrics as _impl
return _impl(doc_results)
def _aggregate_hallucination(doc_results: list) -> Optional[dict]:
"""Délégation vers :func:`builtin_hooks._aggregate_hallucination`."""
- from picarones.core.builtin_hooks import _aggregate_hallucination as _impl
+ from picarones.measurements.builtin_hooks import _aggregate_hallucination as _impl
return _impl(doc_results)
@@ -909,7 +909,7 @@ def _attach_ner_metrics(
"""
try:
from picarones.core.corpus import GTLevel
- from picarones.core.ner import compute_ner_metrics
+ from picarones.measurements.ner import compute_ner_metrics
except ImportError as exc:
logger.warning("[ner.attach] imports indisponibles : %s", exc)
return
@@ -942,11 +942,11 @@ def _aggregate_calibration(doc_results: list) -> Optional[dict]:
"""Délégation vers :func:`builtin_hooks._aggregate_calibration`.
Conservé pour la rétrocompat du test ``test_sprint42_calibration_runner``
- qui importe directement depuis ``picarones.core.runner``. La logique
- réelle vit dans :mod:`picarones.core.builtin_hooks` (chantier 2
+ qui importe directement depuis ``picarones.measurements.runner``. La logique
+ réelle vit dans :mod:`picarones.measurements.builtin_hooks` (chantier 2
post-Sprint 97).
"""
- from picarones.core.builtin_hooks import _aggregate_calibration as _impl
+ from picarones.measurements.builtin_hooks import _aggregate_calibration as _impl
return _impl(doc_results)
diff --git a/picarones/measurements/searchability_runner.py b/picarones/measurements/searchability_runner.py
index cf822338cd0fb86c4a58301f136568a77080a346..d4bd7d82340c4655dc58f7a27da2baa6ad5d1cd8 100644
--- a/picarones/measurements/searchability_runner.py
+++ b/picarones/measurements/searchability_runner.py
@@ -18,7 +18,7 @@ from __future__ import annotations
import logging
from typing import Iterable, Optional
-from picarones.core.searchability import (
+from picarones.measurements.searchability import (
_split_words,
compute_searchability,
)
diff --git a/picarones/measurements/specialization.py b/picarones/measurements/specialization.py
index a3f251c56c578701544d9b57aea1f9d21554f033..27e00a834594b081ccb770e0e068306f10d97e8d 100644
--- a/picarones/measurements/specialization.py
+++ b/picarones/measurements/specialization.py
@@ -32,7 +32,7 @@ intuitive :
Dépendances
-----------
-S'appuie strictement sur ``picarones.core.inter_engine`` (Sprint
+S'appuie strictement sur ``picarones.measurements.inter_engine`` (Sprint
35) — pas de double calcul, pas de logique nouvelle de
divergence.
"""
@@ -42,7 +42,7 @@ from __future__ import annotations
import logging
from typing import Optional
-from picarones.core.inter_engine import jensen_shannon_divergence
+from picarones.measurements.inter_engine import jensen_shannon_divergence
logger = logging.getLogger(__name__)
diff --git a/picarones/measurements/taxonomy.py b/picarones/measurements/taxonomy.py
index a8d36076528d81c781e1ccbf4dd18c9341032237..fad325415384b32804931deff59ac3c8270d5eaa 100644
--- a/picarones/measurements/taxonomy.py
+++ b/picarones/measurements/taxonomy.py
@@ -53,7 +53,7 @@ for _a, _b in _VISUAL_PAIRS:
VISUAL_CONFUSIONS[frozenset({_a, _b})] = f"{_a}/{_b}"
#: Couples de ligatures pour la détection des erreurs de ligatures
-from picarones.core.char_scores import LIGATURE_TABLE, DIACRITIC_MAP # noqa: E402
+from picarones.measurements.char_scores import LIGATURE_TABLE, DIACRITIC_MAP # noqa: E402
# Caractères hors-ASCII présumés hors-vocabulaire (alphabet non latin de base)
_LATIN_BASIC = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
diff --git a/picarones/modules/alto_text_to_mono_region.py b/picarones/modules/alto_text_to_mono_region.py
index f599cdd2018a42db7cbbbf885956b6f5a5589f39..e49d2cc2bda1282ea5879fc6b145ac0c723455e2 100644
--- a/picarones/modules/alto_text_to_mono_region.py
+++ b/picarones/modules/alto_text_to_mono_region.py
@@ -21,8 +21,8 @@ Ce reconstructeur est volontairement **primitif** :
Cette baseline n'a pas vocation à être un bon reconstructeur — elle a
vocation à être un **point de comparaison stable**. Un VLM produisant un
ALTO doit faire mieux qu'elle ; c'est mesurable via Layout F1
-(:mod:`picarones.core.layout`) et via les métriques
-``alto_text_cer``/``alto_text_wer`` (:mod:`picarones.core.alto_metrics`).
+(:mod:`picarones.measurements.layout`) et via les métriques
+``alto_text_cer``/``alto_text_wer`` (:mod:`picarones.measurements.alto_metrics`).
Conformité ALTO 4.2
-------------------
diff --git a/picarones/report/baseline_render.py b/picarones/report/baseline_render.py
index 19d7adda8b93da17ce240b2d9a6a068ca68171f1..abe5fe44f734cded914c8e677232f5fa06123663 100644
--- a/picarones/report/baseline_render.py
+++ b/picarones/report/baseline_render.py
@@ -161,7 +161,7 @@ def build_corpus_difficulty_baseline_html(
----------
percentile_data:
Sortie de
- ``picarones.core.baseline_comparison.compute_corpus_difficulty_percentile``.
+ ``picarones.measurements.baseline_comparison.compute_corpus_difficulty_percentile``.
Si ``None``, retourne ``""`` (rapport adaptatif —
historique trop court ou difficulté absente).
historical_values:
diff --git a/picarones/core/colors.py b/picarones/report/colors.py
similarity index 100%
rename from picarones/core/colors.py
rename to picarones/report/colors.py
diff --git a/picarones/report/comparison.py b/picarones/report/comparison.py
index 9dcb22ca61eaad07e9b7be6bce8e47f75f13536f..a89f02137eab1c5fb6d739489783069e02e1087f 100644
--- a/picarones/report/comparison.py
+++ b/picarones/report/comparison.py
@@ -1,7 +1,7 @@
"""Comparaison de deux runs de benchmark (Sprint 28).
Le Sprint 8 a livré la persistance longitudinale via SQLite
-(``picarones.core.history``) et un détecteur de régression CLI. Mais
+(``picarones.measurements.history``) et un détecteur de régression CLI. Mais
aucun outil n'exposait la **comparaison** de deux runs côté rapport :
un chercheur qui itère sur 8 prompts ne pouvait pas voir d'un coup
*« Tesseract → GPT-4o version V2 a régressé de 0,8 pp en CER moyen
diff --git a/picarones/report/error_absorption_render.py b/picarones/report/error_absorption_render.py
index 838d5528453c381b4321a3bf9e8128813e5e29f0..4280a21d207f13387abcd55e2828fee6f6b04529 100644
--- a/picarones/report/error_absorption_render.py
+++ b/picarones/report/error_absorption_render.py
@@ -25,7 +25,7 @@ l'utilisateur depuis son benchmark de pipeline composée :
.. code-block:: python
- from picarones.core.error_absorption import (
+ from picarones.measurements.error_absorption import (
compute_error_absorption, aggregate_error_absorption,
)
from picarones.report.error_absorption_render import (
diff --git a/picarones/report/generator.py b/picarones/report/generator.py
index 4175a8f329de4b47ee567c1cc2e13ff9af58bb60..a5ef37d68728568854e1567184ae5a59551019ac 100644
--- a/picarones/report/generator.py
+++ b/picarones/report/generator.py
@@ -37,7 +37,7 @@ def _load_vendor_js(name: str) -> str:
from picarones.core.results import BenchmarkResult
from picarones.report.diff_utils import compute_char_diff, compute_word_diff
-from picarones.core.statistics import (
+from picarones.measurements.statistics import (
compute_pairwise_stats,
compute_reliability_curve,
compute_correlation_matrix,
@@ -49,8 +49,8 @@ from picarones.core.statistics import (
build_critical_difference_svg,
compute_pareto_front,
)
-from picarones.core.pricing import build_costs_for_benchmark, load_pricing_database
-from picarones.core.difficulty import compute_all_difficulties, difficulty_label
+from picarones.measurements.pricing import build_costs_for_benchmark, load_pricing_database
+from picarones.measurements.difficulty import compute_all_difficulties, difficulty_label
# ---------------------------------------------------------------------------
@@ -103,7 +103,7 @@ def _encode_images_b64_from_result(benchmark: "BenchmarkResult", max_width: int
def _cer_color(cer: float) -> str:
"""Retourne une couleur CSS pour un score CER donné (0→vert, 1→rouge)."""
- from picarones.core.colors import COLOR_GREEN, COLOR_YELLOW, COLOR_ORANGE, COLOR_RED
+ from picarones.report.colors import COLOR_GREEN, COLOR_YELLOW, COLOR_ORANGE, COLOR_RED
if cer < 0.05:
return COLOR_GREEN
if cer < 0.15:
@@ -114,7 +114,7 @@ def _cer_color(cer: float) -> str:
def _cer_bg(cer: float) -> str:
- from picarones.core.colors import BG_GREEN, BG_YELLOW, BG_ORANGE, BG_RED
+ from picarones.report.colors import BG_GREEN, BG_YELLOW, BG_ORANGE, BG_RED
if cer < 0.05:
return BG_GREEN
if cer < 0.15:
@@ -718,7 +718,7 @@ class ReportGenerator:
)
# Sprint 18 — synthèse factuelle narrative (déterministe, sans LLM)
- from picarones.core.narrative import build_synthesis
+ from picarones.measurements.narrative import build_synthesis
synthesis = build_synthesis(report_data, lang=self.lang)
# Sprint 20 — glossaire contextuel chargé depuis YAML
@@ -908,7 +908,7 @@ class ReportGenerator:
data = _json.loads(Path(json_path).read_text(encoding="utf-8"))
# Reconstruction minimale d'un BenchmarkResult depuis le dict
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
from picarones.core.results import DocumentResult, EngineReport
engine_reports = []
diff --git a/picarones/report/incremental_comparison_render.py b/picarones/report/incremental_comparison_render.py
index 841e07a084d8957704c6c35eefc5e6c58b0148cc..2c179e898d04e63938ffce9309386a21eb5b7549 100644
--- a/picarones/report/incremental_comparison_render.py
+++ b/picarones/report/incremental_comparison_render.py
@@ -19,7 +19,7 @@ Module pur — l'utilisateur compose :
.. code-block:: python
- from picarones.core.incremental_comparison import (
+ from picarones.measurements.incremental_comparison import (
PipelineRun, compare_isolated_effect,
)
from picarones.report.incremental_comparison_render import (
diff --git a/picarones/report/longitudinal_render.py b/picarones/report/longitudinal_render.py
index 02a4abcea88dbf71ba93113cb85a56094f7187c1..b49fc375a44213db34bd9bc296b21325e9fbd26a 100644
--- a/picarones/report/longitudinal_render.py
+++ b/picarones/report/longitudinal_render.py
@@ -18,8 +18,8 @@ Module pur — l'utilisateur compose :
.. code-block:: python
- from picarones.core.history import BenchmarkHistory
- from picarones.core.longitudinal import compute_corpus_longitudinal
+ from picarones.measurements.history import BenchmarkHistory
+ from picarones.measurements.longitudinal import compute_corpus_longitudinal
from picarones.report.longitudinal_render import build_longitudinal_html
hist = BenchmarkHistory(db_path)
diff --git a/picarones/report/multirun_stability_render.py b/picarones/report/multirun_stability_render.py
index dc996af1f5b263fb7101918e0485cee503881671..e0344fb0637553c15a898664beada886491fd572 100644
--- a/picarones/report/multirun_stability_render.py
+++ b/picarones/report/multirun_stability_render.py
@@ -15,7 +15,7 @@ l'utilisateur compose :
.. code-block:: python
- from picarones.core.reliability import compute_multirun_stability
+ from picarones.measurements.reliability import compute_multirun_stability
from picarones.report.multirun_stability_render import (
build_multirun_stability_html,
)
diff --git a/picarones/report/numerical_sequences_render.py b/picarones/report/numerical_sequences_render.py
index ddc3592ea22a1b86327b9aced98c1055ff35b174..cab6883556abf18855afe0cdb8e26bf7e8d7fddd 100644
--- a/picarones/report/numerical_sequences_render.py
+++ b/picarones/report/numerical_sequences_render.py
@@ -23,7 +23,7 @@ from __future__ import annotations
from html import escape as _e
from typing import Optional
-from picarones.core.numerical_sequences import CATEGORIES
+from picarones.measurements.numerical_sequences import CATEGORIES
def _color_for_score(score: float) -> str:
diff --git a/picarones/report/pipeline_render.py b/picarones/report/pipeline_render.py
index d2bccc66052ad883c08527463733b57a9397652f..723b477c53345b7cfc199b80cb5ac7a326f637b5 100644
--- a/picarones/report/pipeline_render.py
+++ b/picarones/report/pipeline_render.py
@@ -48,8 +48,8 @@ from html import escape as _e
from typing import Optional
from picarones.core.modules import ArtifactType
-from picarones.core.pipeline_benchmark import PipelineBenchmarkResult
-from picarones.core.pipeline_comparison import PipelineComparisonResult
+from picarones.measurements.pipeline_benchmark import PipelineBenchmarkResult
+from picarones.measurements.pipeline_comparison import PipelineComparisonResult
# ──────────────────────────────────────────────────────────────────────────
diff --git a/picarones/report/robustness_projection_render.py b/picarones/report/robustness_projection_render.py
index 65febef413c3ef172860dd8666d2b0237fd604cc..5c700b84eebd34d6471c2d50a1c557a8a9eafb79 100644
--- a/picarones/report/robustness_projection_render.py
+++ b/picarones/report/robustness_projection_render.py
@@ -7,15 +7,15 @@ side, pas de JS, anti-injection systématique.
Note d'intégration
------------------
-La robustesse synthétique (``picarones.core.robustness``) est
+La robustesse synthétique (``picarones.measurements.robustness``) est
exécutée par la CLI ``picarones robustness`` indépendamment du
benchmark principal. Pour produire la vue de projection,
l'utilisateur compose :
.. code-block:: python
- from picarones.core.robustness import analyze_robustness
- from picarones.core.robustness_projection import (
+ from picarones.measurements.robustness import analyze_robustness
+ from picarones.measurements.robustness_projection import (
project_robustness_on_corpus,
aggregate_projection_per_engine,
)
@@ -219,7 +219,7 @@ def build_robustness_projection_html(
if not projection:
return ""
if aggregated is None:
- from picarones.core.robustness_projection import (
+ from picarones.measurements.robustness_projection import (
aggregate_projection_per_engine,
)
aggregated = aggregate_projection_per_engine(projection)
diff --git a/picarones/report/snapshot.py b/picarones/report/snapshot.py
index bcba8fb721a6a564bae23b8fb280076872e1e534..e878eac9bc4226c1a796f378c19fefba3ede4159 100644
--- a/picarones/report/snapshot.py
+++ b/picarones/report/snapshot.py
@@ -56,11 +56,11 @@ def pricing_snapshot(pricing_path: Optional[Path] = None) -> dict[str, Any]:
"""Retourne le YAML brut + dict parsé de la table de prix utilisée.
Si ``pricing_path`` n'est pas fourni, utilise le chemin par défaut
- de ``picarones.core.pricing._DEFAULT_PRICING_PATH``.
+ de ``picarones.measurements.pricing._DEFAULT_PRICING_PATH``.
"""
if pricing_path is None:
try:
- from picarones.core.pricing import _DEFAULT_PRICING_PATH
+ from picarones.measurements.pricing import _DEFAULT_PRICING_PATH
pricing_path = _DEFAULT_PRICING_PATH
except ImportError:
return {"available": False, "reason": "module pricing introuvable"}
diff --git a/picarones/report/specialization_render.py b/picarones/report/specialization_render.py
index 326d18ca337e86b2ef58356b2fe2bd167f7a2e74..976d6bc5cd05bc34ee2a37d3f56b5fc2b8638089 100644
--- a/picarones/report/specialization_render.py
+++ b/picarones/report/specialization_render.py
@@ -14,7 +14,7 @@ from __future__ import annotations
from html import escape as _e
from typing import Optional
-from picarones.core.specialization import (
+from picarones.measurements.specialization import (
compute_specialization_matrix,
top_specialized_pairs,
)
diff --git a/picarones/report/throughput_render.py b/picarones/report/throughput_render.py
index 3c3099ae77b9e8437517cfb36f84122c2e25e054..d7bdb7cde72f63ce0a2b064be6474ded8bcfc47f 100644
--- a/picarones/report/throughput_render.py
+++ b/picarones/report/throughput_render.py
@@ -20,7 +20,7 @@ Cette vue est un **module pur** — l'utilisateur compose :
.. code-block:: python
- from picarones.core.throughput import (
+ from picarones.measurements.throughput import (
aggregate_effective_throughput,
)
from picarones.report.throughput_render import (
diff --git a/picarones/report/views/advanced_taxonomy.py b/picarones/report/views/advanced_taxonomy.py
index cf479f30c81c92d1675ac471a3962ad38994d372..0f30df95acd7579bf8274085ad5f111a4be1c274 100644
--- a/picarones/report/views/advanced_taxonomy.py
+++ b/picarones/report/views/advanced_taxonomy.py
@@ -25,11 +25,11 @@ Sources de données automatiques
Sources de données opt-in (via ``opts``)
----------------------------------------
- ``opts["cooccurrence"]`` : sortie de
- :func:`picarones.core.taxonomy_cooccurrence.compute_taxonomy_cooccurrence`.
+ :func:`picarones.measurements.taxonomy_cooccurrence.compute_taxonomy_cooccurrence`.
- ``opts["intra_doc"]`` : sortie de
- :func:`picarones.core.taxonomy_intra_doc.compute_taxonomy_position_heatmap`.
+ :func:`picarones.measurements.taxonomy_intra_doc.compute_taxonomy_position_heatmap`.
- ``opts["lexical_modernization"]`` : sortie de
- :func:`picarones.core.lexical_modernization.compute_lexical_modernization`
+ :func:`picarones.measurements.lexical_modernization.compute_lexical_modernization`
agrégée corpus-wide.
Ces calculs ne sont pas faits automatiquement par le runner standard
@@ -110,15 +110,15 @@ def build_advanced_taxonomy_view_html(
Dict i18n complet.
cooccurrence:
Sortie pré-calculée de
- :func:`picarones.core.taxonomy_cooccurrence.compute_taxonomy_cooccurrence`.
+ :func:`picarones.measurements.taxonomy_cooccurrence.compute_taxonomy_cooccurrence`.
Optionnel — la sous-section est masquée si non fourni.
intra_doc:
Sortie pré-calculée de
- :func:`picarones.core.taxonomy_intra_doc.compute_taxonomy_position_heatmap`.
+ :func:`picarones.measurements.taxonomy_intra_doc.compute_taxonomy_position_heatmap`.
Optionnel.
lexical_modernization:
Sortie pré-calculée de
- :func:`picarones.core.lexical_modernization.aggregate_lexical_modernization`.
+ :func:`picarones.measurements.lexical_modernization.aggregate_lexical_modernization`.
Optionnel.
Returns
@@ -135,7 +135,7 @@ def build_advanced_taxonomy_view_html(
engines_summary = report_data.get("engines") or []
pair = _select_two_engines_for_comparison(engines_summary)
if pair is not None:
- from picarones.core.taxonomy_comparison import compare_taxonomies
+ from picarones.measurements.taxonomy_comparison import compare_taxonomies
from picarones.report.taxonomy_comparison_render import (
build_taxonomy_comparison_html,
)
diff --git a/picarones/report/views/diagnostics.py b/picarones/report/views/diagnostics.py
index 0dc47643badbc022e80a8fb66e3e57b3bd7ce49b..6b227e3941495d8dcfccd88b12a0c322f3a09ea0 100644
--- a/picarones/report/views/diagnostics.py
+++ b/picarones/report/views/diagnostics.py
@@ -21,7 +21,7 @@ résultats »* :
Sources de données automatiques
-------------------------------
-- *Leviers* : :func:`picarones.core.levers.detect_levers` est appelée
+- *Leviers* : :func:`picarones.measurements.levers.detect_levers` est appelée
sur ``report_data``. Couvre :
``dominant_recoverable_class``, ``pareto_concentration``,
``complementarity_observation``, ``lexical_modernization_observation``,
@@ -32,10 +32,10 @@ Sources de données opt-in (via ``opts``)
- ``opts["benchmark"]`` : ``BenchmarkResult`` non compacté (worst lines).
- ``opts["image_qualities"]`` : liste de dicts image_quality par doc.
- ``opts["baseline_data"]`` : sortie de
- :func:`picarones.core.baseline_comparison.compute_corpus_difficulty_percentile`.
+ :func:`picarones.measurements.baseline_comparison.compute_corpus_difficulty_percentile`.
- ``opts["longitudinal"]`` : map ``{engine: longitudinal_data}``.
- ``opts["stability"]`` : sortie de
- :func:`picarones.core.reliability.compute_multirun_stability`.
+ :func:`picarones.measurements.reliability.compute_multirun_stability`.
"""
from __future__ import annotations
@@ -76,16 +76,16 @@ def build_diagnostics_view_html(
depuis les ``EngineReport.document_results`` avant compact).
baseline_data:
Sortie de
- :func:`picarones.core.baseline_comparison.compute_corpus_difficulty_percentile`.
+ :func:`picarones.measurements.baseline_comparison.compute_corpus_difficulty_percentile`.
Active l'encart « ce corpus est-il habituel ? ».
longitudinal:
Sortie de
- :func:`picarones.core.longitudinal.compute_corpus_longitudinal`.
+ :func:`picarones.measurements.longitudinal.compute_corpus_longitudinal`.
Active la table d'évolution.
stability:
Liste enrichie de ``{engine_name, ...stability_data}`` par
moteur, sortie de
- :func:`picarones.core.reliability.compute_multirun_stability`.
+ :func:`picarones.measurements.reliability.compute_multirun_stability`.
Active la table de stabilité multi-runs.
history_values:
Valeurs historiques de difficulté du corpus, utilisées pour
@@ -102,7 +102,7 @@ def build_diagnostics_view_html(
# Sous-section 1 : leviers (calculés automatiquement)
try:
- from picarones.core.levers import detect_levers
+ from picarones.measurements.levers import detect_levers
from picarones.report.levers_render import build_levers_section_html
levers = detect_levers(report_data)
html = build_levers_section_html(levers, labels=labels)
@@ -141,7 +141,7 @@ def build_diagnostics_view_html(
# Sous-section 3 : profil d'image du corpus (opt-in)
if image_qualities:
try:
- from picarones.core.image_predictive import (
+ from picarones.measurements.image_predictive import (
aggregate_corpus_predictive,
)
from picarones.report.image_predictive_render import (
@@ -205,7 +205,7 @@ def build_diagnostics_view_html(
# Sous-section 6 : worst lines (opt-in via benchmark non compacté)
if benchmark is not None:
try:
- from picarones.core.worst_lines import extract_worst_lines
+ from picarones.measurements.worst_lines import extract_worst_lines
from picarones.report.worst_lines_render import (
build_worst_lines_table_html,
)
diff --git a/picarones/report/views/economics.py b/picarones/report/views/economics.py
index 1e6ea377c04c89a1ad0ad98071dd92caaa0e3bdf..a72f83aba9b7330d1f3c010336ccfbd08308072f 100644
--- a/picarones/report/views/economics.py
+++ b/picarones/report/views/economics.py
@@ -31,7 +31,7 @@ def _estimate_engine_throughput_inputs(
engine_reports: list,
) -> list[dict]:
"""Construit les entrées attendues par
- :func:`picarones.core.throughput.aggregate_effective_throughput`
+ :func:`picarones.measurements.throughput.aggregate_effective_throughput`
à partir des ``EngineReport`` du benchmark.
Pour chaque moteur :
@@ -131,7 +131,7 @@ def build_economics_view_html(
# Sous-section 1 : throughput effectif
if engine_reports:
try:
- from picarones.core.throughput import (
+ from picarones.measurements.throughput import (
aggregate_effective_throughput,
)
from picarones.report.throughput_render import (
diff --git a/picarones/report/views/pipeline.py b/picarones/report/views/pipeline.py
index b63f969658f28e73170b25dc48fe4e5ad3fb7f54..e7fbc9971234041add2563c81b43b5044a16a9f8 100644
--- a/picarones/report/views/pipeline.py
+++ b/picarones/report/views/pipeline.py
@@ -168,7 +168,7 @@ def build_pipeline_view_html(
# Sous-section 4 : comparaison incrémentale (effet d'un slot)
if incremental_runs and incremental_varying_slot:
try:
- from picarones.core.incremental_comparison import (
+ from picarones.measurements.incremental_comparison import (
compare_isolated_effect,
)
from picarones.report.incremental_comparison_render import (
diff --git a/picarones/report/views/robustness.py b/picarones/report/views/robustness.py
index fc03d458f8a680656eaaf3c9299c297ff8e6826d..14989451cab208e6a9f3fb174163673971bcfeab 100644
--- a/picarones/report/views/robustness.py
+++ b/picarones/report/views/robustness.py
@@ -12,9 +12,9 @@ de la CLI puisse composer un mini-rapport HTML autonome.
Sources de données
------------------
- ``opts["projection"]`` : sortie de
- :func:`picarones.core.robustness_projection.project_robustness_on_corpus`.
+ :func:`picarones.measurements.robustness_projection.project_robustness_on_corpus`.
- ``opts["aggregated"]`` : sortie de
- :func:`picarones.core.robustness_projection.aggregate_projection_per_engine`.
+ :func:`picarones.measurements.robustness_projection.aggregate_projection_per_engine`.
"""
from __future__ import annotations
@@ -43,10 +43,10 @@ def build_robustness_view_html(
Dict i18n complet.
projection:
Sortie de
- :func:`picarones.core.robustness_projection.project_robustness_on_corpus`.
+ :func:`picarones.measurements.robustness_projection.project_robustness_on_corpus`.
aggregated:
Sortie de
- :func:`picarones.core.robustness_projection.aggregate_projection_per_engine`.
+ :func:`picarones.measurements.robustness_projection.aggregate_projection_per_engine`.
Si ``None`` mais ``projection`` fourni, recalculé.
Returns
diff --git a/picarones/report/worst_lines_render.py b/picarones/report/worst_lines_render.py
index 16de39e7bf95cd036d78725b1e6943c50d044cee..10349e3c4d30264ca39824b77dfa74db59d60c07 100644
--- a/picarones/report/worst_lines_render.py
+++ b/picarones/report/worst_lines_render.py
@@ -18,7 +18,7 @@ from __future__ import annotations
from html import escape as _e
from typing import Optional
-from picarones.core.worst_lines import WorstLineEntry
+from picarones.measurements.worst_lines import WorstLineEntry
from picarones.report.diff_utils import compute_char_diff
diff --git a/picarones/web/app.py b/picarones/web/app.py
index 99ada2dc2c1657a1c9575d1f2d39a19eef0717ba..b1a1ea62eee5f60a2cf58678518b8600d7693a34 100644
--- a/picarones/web/app.py
+++ b/picarones/web/app.py
@@ -47,7 +47,7 @@ from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
from pydantic import BaseModel
from picarones import __version__
-from picarones.core.jobs import JobStore, get_default_store
+from picarones.web.jobs import JobStore, get_default_store
from picarones.web.security import (
RateLimiter,
assert_engines_allowed,
@@ -1095,7 +1095,7 @@ async def api_corpus_delete(corpus_id: str) -> dict:
@app.get("/api/normalization/profiles")
async def api_normalization_profiles() -> dict:
- from picarones.core.normalization import NORMALIZATION_PROFILES
+ from picarones.measurements.normalization import NORMALIZATION_PROFILES
profiles = [
{
@@ -1283,7 +1283,7 @@ async def api_benchmark_synthesis_preview(job_id: str, lang: str = "fr") -> dict
except (OSError, json.JSONDecodeError) as exc:
raise HTTPException(status_code=422, detail=f"Lecture JSON échouée : {exc}")
- from picarones.core.narrative import build_synthesis
+ from picarones.measurements.narrative import build_synthesis
synthesis = build_synthesis(report_json, lang=lang)
return {
@@ -1313,7 +1313,7 @@ async def api_history_regressions(
un encart *« ⚠ Tesseract a régressé de 0,8 pp depuis le 12 janvier »*
en tête de page.
"""
- from picarones.core.history import BenchmarkHistory
+ from picarones.measurements.history import BenchmarkHistory
try:
history = BenchmarkHistory(db_path) if db_path else BenchmarkHistory()
@@ -1412,7 +1412,7 @@ async def api_htr_united_catalogue(
language: str = Query(default="", description="Filtre langue"),
script: str = Query(default="", description="Filtre type d'écriture"),
) -> dict:
- from picarones.importers.htr_united import HTRUnitedCatalogue
+ from picarones.extras.importers.htr_united import HTRUnitedCatalogue
cat = HTRUnitedCatalogue.from_demo()
results = cat.search(
@@ -1431,7 +1431,7 @@ async def api_htr_united_catalogue(
@app.post("/api/htr-united/import")
async def api_htr_united_import(req: HTRUnitedImportRequest) -> dict:
- from picarones.importers.htr_united import HTRUnitedCatalogue, import_htr_united_corpus
+ from picarones.extras.importers.htr_united import HTRUnitedCatalogue, import_htr_united_corpus
cat = HTRUnitedCatalogue.from_demo()
entry = cat.get_by_id(req.entry_id)
@@ -1457,7 +1457,7 @@ async def api_huggingface_search(
tags: str = Query(default="", description="Tags séparés par des virgules"),
limit: int = Query(default=20, ge=1, le=50),
) -> dict:
- from picarones.importers.huggingface import HuggingFaceImporter
+ from picarones.extras.importers.huggingface import HuggingFaceImporter
tag_list = [t.strip() for t in tags.split(",") if t.strip()] if tags else None
importer = HuggingFaceImporter()
@@ -1475,7 +1475,7 @@ async def api_huggingface_search(
@app.post("/api/huggingface/import")
async def api_huggingface_import(req: HuggingFaceImportRequest) -> dict:
- from picarones.importers.huggingface import HuggingFaceImporter
+ from picarones.extras.importers.huggingface import HuggingFaceImporter
importer = HuggingFaceImporter()
result = importer.import_dataset(
@@ -1823,7 +1823,7 @@ def _run_benchmark_thread_v2(job: BenchmarkJob, req: BenchmarkRunRequest) -> Non
try:
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
corpus = load_corpus_from_directory(req.corpus_path)
job.total_docs = len(corpus)
@@ -1872,7 +1872,7 @@ def _run_benchmark_thread_v2(job: BenchmarkJob, req: BenchmarkRunRequest) -> Non
"total": total_steps,
})
- from picarones.core.normalization import _parse_exclude_chars
+ from picarones.measurements.normalization import _parse_exclude_chars
char_excl = _parse_exclude_chars(req.char_exclude) if req.char_exclude else None
result = run_benchmark(
@@ -1919,7 +1919,7 @@ def _run_benchmark_thread(job: BenchmarkJob, req: BenchmarkRequest) -> None:
try:
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
# Charger le corpus
job.add_event("log", {"message": f"Chargement du corpus : {req.corpus_path}"})
@@ -1975,7 +1975,7 @@ def _run_benchmark_thread(job: BenchmarkJob, req: BenchmarkRequest) -> None:
"total": total_steps,
})
- from picarones.core.normalization import _parse_exclude_chars
+ from picarones.measurements.normalization import _parse_exclude_chars
char_excl = _parse_exclude_chars(req.char_exclude) if req.char_exclude else None
# Lancer le benchmark
diff --git a/picarones/core/jobs.py b/picarones/web/jobs.py
similarity index 99%
rename from picarones/core/jobs.py
rename to picarones/web/jobs.py
index 6f3d84469e533bff108e237e435d2da58269c556..c4543472aca8a59835f37475c2ec9a6fc6d0e852 100644
--- a/picarones/core/jobs.py
+++ b/picarones/web/jobs.py
@@ -12,7 +12,7 @@ Avant le Sprint 26, l'état des benchmarks vivait uniquement en mémoire dans
au-delà de ce que ``BenchmarkJob.events`` portait en RAM.
Le Sprint 26 adresse les trois en persistant les jobs et leurs événements
-dans une base SQLite locale (cohérent avec ``picarones.core.history``,
+dans une base SQLite locale (cohérent avec ``picarones.measurements.history``,
qui utilise déjà SQLite). La base joue trois rôles :
- **Source de vérité** pour le statut/progression d'un job — ``BenchmarkJob``
diff --git a/tests/features/test_narrative_and_views.py b/tests/features/test_narrative_and_views.py
index 900657e817202927c5abd1e90963389d4cbf1982..ed99e8c8bf9a794643471248cdf56a028866583d 100644
--- a/tests/features/test_narrative_and_views.py
+++ b/tests/features/test_narrative_and_views.py
@@ -8,7 +8,7 @@ Tests couvrant cette feature
- :mod:`tests.test_chantier5` (classe ``TestDetectorsPackage``) —
package thématique des détecteurs (chantier 5).
- :mod:`tests.test_views` (chantier 3) — vue diagnostics qui consomme
- les leviers calculés depuis ``picarones.core.levers``.
+ les leviers calculés depuis ``picarones.measurements.levers``.
Sprints d'origine du moteur narratif
------------------------------------
diff --git a/tests/features/test_pipeline_ocr_to_alto.py b/tests/features/test_pipeline_ocr_to_alto.py
index 285cb26264786a2e27e0248024e5244c14efca4d..6c19091cd48bf53c9500d8ccea0c4c8445b8f8c2 100644
--- a/tests/features/test_pipeline_ocr_to_alto.py
+++ b/tests/features/test_pipeline_ocr_to_alto.py
@@ -34,7 +34,7 @@ import pytest
from picarones.core.corpus import AltoGT, Document, GTLevel, TextGT
from picarones.core.metric_registry import select_metrics
from picarones.core.modules import ArtifactType, BaseModule
-from picarones.core.pipeline_runner import (
+from picarones.core.pipeline import (
PipelineRunner,
PipelineSpec,
PipelineStep,
@@ -193,7 +193,7 @@ class TestYamlSpec:
import yaml # noqa: F401
except ImportError:
pytest.skip("PyYAML absent")
- from picarones.core.pipeline_spec_loader import load_pipeline_spec_from_yaml
+ from picarones.measurements.pipeline_spec_loader import load_pipeline_spec_from_yaml
repo_root = Path(__file__).resolve().parents[2]
yaml_path = repo_root / "examples" / "pipelines" / "ocr_to_alto.yaml"
@@ -206,7 +206,7 @@ class TestYamlSpec:
# quand pytesseract ou pero-ocr ne sont pas installés
# (CI minimaliste). Le test vérifie que la structure
# YAML est syntaxiquement valide.
- from picarones.core.pipeline_spec_loader import PipelineSpecLoadError
+ from picarones.measurements.pipeline_spec_loader import PipelineSpecLoadError
if isinstance(exc, PipelineSpecLoadError) and (
"tesseract" in str(exc).lower() or "pytesseract" in str(exc).lower()
):
diff --git a/tests/test_alto_baseline.py b/tests/test_alto_baseline.py
index b29b5b81549b3338745190ab12bd4e7ba20738ad..9a9a9ad867f38edc1b5a08a297cec1425a20a5c9 100644
--- a/tests/test_alto_baseline.py
+++ b/tests/test_alto_baseline.py
@@ -5,7 +5,7 @@ Couvre :
- :class:`picarones.modules.TextToAltoMonoRegion` : produit un ALTO 4.2
conforme, déterministe, qui tolère absence d'image / image
introuvable / dimensions invalides.
-- :func:`picarones.core.alto_metrics.extract_text_from_alto` : parsing
+- :func:`picarones.measurements.alto_metrics.extract_text_from_alto` : parsing
tolérant (avec/sans namespace, ALTO partiel, GT ``AltoGT`` ou ``str``).
- Métriques ``alto_text_cer`` / ``alto_text_wer`` enregistrées sur
``(ALTO, ALTO)`` et découvrables via ``compute_at_junction``.
@@ -22,14 +22,14 @@ from xml.etree import ElementTree as ET
import pytest
-from picarones.core.alto_metrics import (
+from picarones.measurements.alto_metrics import (
alto_text_cer,
extract_text_from_alto,
)
from picarones.core.corpus import AltoGT, Document, GTLevel, TextGT
from picarones.core.metric_registry import compute_at_junction, select_metrics
from picarones.core.modules import ArtifactType, BaseModule
-from picarones.core.pipeline_runner import (
+from picarones.core.pipeline import (
PipelineRunner,
PipelineSpec,
PipelineStep,
@@ -237,7 +237,7 @@ class TestExtractTextFromAlto:
class TestAltoMetricsRegistration:
def test_alto_metrics_are_registered(self):
# L'import du module doit avoir peuplé le registre.
- import picarones.core.alto_metrics # noqa: F401
+ import picarones.measurements.alto_metrics # noqa: F401
applicable = select_metrics(
(ArtifactType.ALTO, ArtifactType.ALTO),
@@ -249,7 +249,7 @@ class TestAltoMetricsRegistration:
assert "alto_text_wil" in names
def test_compute_at_junction_runs_alto_metrics(self):
- import picarones.core.alto_metrics # noqa: F401
+ import picarones.measurements.alto_metrics # noqa: F401
ref = ''
hyp = ''
results = compute_at_junction(
diff --git a/tests/test_chantier4.py b/tests/test_chantier4.py
index 4684ce8b577b40737f538c01fa7af8bcb2ee5f0c..698b240776227b40f2afa749ee845877846529cb 100644
--- a/tests/test_chantier4.py
+++ b/tests/test_chantier4.py
@@ -5,7 +5,7 @@ Couvre :
- Sous-chantier 4.A : ``normalize_llm_content`` + ``log_http_error``
factorisés dans :mod:`picarones.llm.base`, propagés aux 4 adapters.
- Sous-chantier 4.B : helpers HTTP factorisés dans
- :mod:`picarones.importers._http`, Gallica et IIIF y délèguent.
+ :mod:`picarones.extras.importers._http`, Gallica et IIIF y délèguent.
- Sous-chantier 4.C : 3 nouvelles sous-commandes CLI ``diagnose``,
``economics``, ``edition`` qui mappent un profil de calcul
(chantier 2) à un workflow.
@@ -160,40 +160,40 @@ class TestLlmAdaptersInheritEnvVar:
class TestHttpHelpers:
def test_validate_http_url_accepts_https(self):
- from picarones.importers._http import validate_http_url
+ from picarones.extras.importers._http import validate_http_url
validate_http_url("https://gallica.bnf.fr/test") # ne lève pas
def test_validate_http_url_accepts_http(self):
- from picarones.importers._http import validate_http_url
+ from picarones.extras.importers._http import validate_http_url
validate_http_url("http://localhost:8080/x")
@pytest.mark.parametrize("scheme", ["file", "ftp", "data", "javascript", "ssh"])
def test_validate_http_url_rejects_other_schemes(self, scheme):
- from picarones.importers._http import validate_http_url
+ from picarones.extras.importers._http import validate_http_url
with pytest.raises(ValueError, match="non autorisé"):
validate_http_url(f"{scheme}://example.com/x")
class TestIiifAliasesDelegateToHttp:
"""Les noms ``_validate_url`` et ``_download_url`` exposés depuis
- :mod:`picarones.importers.iiif` doivent rester disponibles
+ :mod:`picarones.extras.importers.iiif` doivent rester disponibles
(rétrocompat des tests Sprint 4) — ils délèguent aux helpers
factorisés."""
def test_iiif_validate_url_is_alias(self):
- from picarones.importers import iiif
- from picarones.importers._http import validate_http_url
+ from picarones.extras.importers import iiif
+ from picarones.extras.importers._http import validate_http_url
assert iiif._validate_url is validate_http_url
def test_iiif_download_url_is_alias(self):
- from picarones.importers import iiif
- from picarones.importers._http import download_url
+ from picarones.extras.importers import iiif
+ from picarones.extras.importers._http import download_url
assert iiif._download_url is download_url
class TestGallicaDelegatesToHttp:
def test_gallica_validate_url_delegates(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient()
# Doit accepter https
client._validate_url("https://gallica.bnf.fr/x")
@@ -206,7 +206,7 @@ class TestGallicaDelegatesToHttp:
# Lecture statique du source — pas d'appel réseau.
# Chantier 5 (3 cercles) : le contenu vit désormais dans
# ``picarones/extras/importers/gallica.py`` ; le module
- # historique ``picarones.importers.gallica`` est un alias.
+ # historique ``picarones.extras.importers.gallica`` est un alias.
from pathlib import Path
gallica_src = (
Path(__file__).parent.parent
@@ -215,7 +215,7 @@ class TestGallicaDelegatesToHttp:
# Confirme que Gallica importe IIIFImporter
assert "IIIFImporter" in gallica_src
assert "from picarones.extras.importers.iiif" in gallica_src or \
- "from picarones.importers.iiif" in gallica_src
+ "from picarones.extras.importers.iiif" in gallica_src
# ──────────────────────────────────────────────────────────────────────────
diff --git a/tests/test_chantier5.py b/tests/test_chantier5.py
index 19434864072885898e693f6e7d1edf0ad432ae63..b3d850fa5515f280a87d83e27e4b84b4e3fa612e 100644
--- a/tests/test_chantier5.py
+++ b/tests/test_chantier5.py
@@ -2,7 +2,7 @@
Couvre :
-- 5.A : :mod:`picarones.core.narrative.detectors` est désormais un
+- 5.A : :mod:`picarones.measurements.narrative.detectors` est désormais un
package thématique de 6 sous-modules (1229 lignes → 6 fichiers).
Tous les imports historiques restent accessibles.
- 5.B : :mod:`picarones.cli` est désormais un package avec 6
@@ -23,7 +23,7 @@ import pytest
class TestDetectorsPackage:
def test_detectors_is_now_a_package(self):
"""``detectors.py`` est devenu ``detectors/`` (package)."""
- from picarones.core.narrative import detectors
+ from picarones.measurements.narrative import detectors
# Un package a __path__, un module simple ne l'a pas
assert hasattr(detectors, "__path__"), (
"detectors devrait être un package depuis le chantier 5"
@@ -52,19 +52,19 @@ class TestDetectorsPackage:
def test_all_18_detectors_importable_from_root(self, name):
"""Rétrocompat : les 18 détecteurs s'importent depuis le package
comme avant le chantier 5 (tests Sprints 20, 23, 29, 36, 44, 46, 73)."""
- from picarones.core.narrative import detectors
+ from picarones.measurements.narrative import detectors
assert hasattr(detectors, name), f"{name} disparu après chantier 5"
assert callable(getattr(detectors, name))
def test_DETECTORS_BY_TYPE_still_exposed(self):
- from picarones.core.narrative.detectors import DETECTORS_BY_TYPE
+ from picarones.measurements.narrative.detectors import DETECTORS_BY_TYPE
assert isinstance(DETECTORS_BY_TYPE, dict)
assert len(DETECTORS_BY_TYPE) == 18, (
f"DETECTORS_BY_TYPE doit contenir 18 entrées, en a {len(DETECTORS_BY_TYPE)}"
)
def test_register_default_detectors_still_callable(self):
- from picarones.core.narrative.detectors import register_default_detectors
+ from picarones.measurements.narrative.detectors import register_default_detectors
assert callable(register_default_detectors)
@pytest.mark.parametrize("submodule, detector_count", [
@@ -80,7 +80,7 @@ class TestDetectorsPackage:
import importlib
mod = importlib.import_module(
- f"picarones.core.narrative.detectors.{submodule}"
+ f"picarones.measurements.narrative.detectors.{submodule}"
)
detectors_in_sub = [
n for n in dir(mod)
@@ -94,15 +94,15 @@ class TestDetectorsPackage:
def test_identity_through_submodule_and_root(self):
"""Le détecteur exposé depuis __init__.py et depuis son sous-module
est la même fonction (pas de redéfinition)."""
- from picarones.core.narrative.detectors import detect_global_leader_cer
- from picarones.core.narrative.detectors.ranking import (
+ from picarones.measurements.narrative.detectors import detect_global_leader_cer
+ from picarones.measurements.narrative.detectors.ranking import (
detect_global_leader_cer as via_submodule,
)
assert detect_global_leader_cer is via_submodule
def test_detector_smoke_via_root(self):
"""Smoke test : un détecteur fonctionne via l'import root."""
- from picarones.core.narrative.detectors import detect_global_leader_cer
+ from picarones.measurements.narrative.detectors import detect_global_leader_cer
result = detect_global_leader_cer({
"ranking": [
{"engine": "tess", "mean_cer": 0.05},
@@ -115,7 +115,7 @@ class TestDetectorsPackage:
def test_helpers_are_in_dedicated_module(self):
"""Les helpers internes (_engines_summary, etc.) vivent dans
``_helpers.py`` (pattern modulaire propre)."""
- from picarones.core.narrative.detectors import _helpers
+ from picarones.measurements.narrative.detectors import _helpers
assert hasattr(_helpers, "_engines_summary")
assert hasattr(_helpers, "_engine_by_name")
assert hasattr(_helpers, "_n_docs")
@@ -227,12 +227,12 @@ class TestRunnerStillReachable:
])
def test_function_still_in_runner(self, name):
try:
- from picarones.core import runner
+ from picarones.measurements import runner
except ImportError as exc:
if "tqdm" in str(exc):
pytest.skip("tqdm non installé")
raise
assert hasattr(runner, name), (
- f"runner.{name} a disparu — chantier 5 n'aurait pas dû y toucher"
+ f"runner.{name} a disparu"
)
assert callable(getattr(runner, name))
diff --git a/tests/test_char_scores.py b/tests/test_char_scores.py
index f232c8c77d6d31aa8356d7eaf53436b1d9585100..4d8d09f1e819e7488c3deec7b34d65ac2db6c5d6 100644
--- a/tests/test_char_scores.py
+++ b/tests/test_char_scores.py
@@ -20,7 +20,7 @@ from __future__ import annotations
import pytest
-from picarones.core.char_scores import (
+from picarones.measurements.char_scores import (
DiacriticScore,
LigatureScore,
aggregate_diacritic_scores,
diff --git a/tests/test_metric_hooks.py b/tests/test_metric_hooks.py
index 35ff3bd564cc8e8a98e48a184dd35944ca81912a..788e582736d32ab21816e385cab7ba70baabc6af 100644
--- a/tests/test_metric_hooks.py
+++ b/tests/test_metric_hooks.py
@@ -4,11 +4,11 @@ Couvre :
- :mod:`picarones.core.metric_hooks` : profils, registre, décorateurs,
sélection par profil, exécution avec gestion d'erreurs.
-- :mod:`picarones.core.builtin_hooks` : enregistre les 12+12 hooks
+- :mod:`picarones.measurements.builtin_hooks` : enregistre les 12+12 hooks
historiques sur le profil ``standard``.
- Rétrocompat : les fonctions privées ``_aggregate_*`` et
``_calibration_from_engine_result`` restent accessibles depuis
- ``picarones.core.runner`` (tests Sprint 13/42).
+ ``picarones.measurements.runner`` (tests Sprint 13/42).
- Le profil ``standard`` (défaut) couvre **exactement** les 12 hooks
documentaires et 12 agrégateurs historiques.
- Le profil ``minimal`` n'active aucun hook (bench rapide).
@@ -63,7 +63,7 @@ class TestProfiles:
class TestBuiltinHooksRegistration:
def test_twelve_document_hooks_registered(self):
# Import déclenche l'enregistrement via décorateurs.
- import picarones.core.builtin_hooks # noqa: F401
+ import picarones.measurements.builtin_hooks # noqa: F401
from picarones.core.metric_hooks import _all_document_hook_names
names = set(_all_document_hook_names())
@@ -76,7 +76,7 @@ class TestBuiltinHooksRegistration:
assert expected.issubset(names), f"manquants : {expected - names}"
def test_twelve_corpus_aggregators_registered(self):
- import picarones.core.builtin_hooks # noqa: F401
+ import picarones.measurements.builtin_hooks # noqa: F401
from picarones.core.metric_hooks import _all_corpus_aggregator_names
names = set(_all_corpus_aggregator_names())
@@ -89,7 +89,7 @@ class TestBuiltinHooksRegistration:
assert expected.issubset(names), f"manquants : {expected - names}"
def test_standard_profile_activates_all_hooks(self):
- import picarones.core.builtin_hooks # noqa: F401
+ import picarones.measurements.builtin_hooks # noqa: F401
from picarones.core.metric_hooks import (
select_corpus_aggregators, select_document_hooks,
)
@@ -100,7 +100,7 @@ class TestBuiltinHooksRegistration:
assert len(agg_hooks) == 12, [a.name for a in agg_hooks]
def test_minimal_profile_activates_zero_hooks(self):
- import picarones.core.builtin_hooks # noqa: F401
+ import picarones.measurements.builtin_hooks # noqa: F401
from picarones.core.metric_hooks import (
select_corpus_aggregators, select_document_hooks,
)
@@ -112,7 +112,7 @@ class TestBuiltinHooksRegistration:
"""Les attributs déclarés par les hooks doivent correspondre aux
champs réels du DocumentResult — sinon le runner planterait à
l'instanciation du dataclass."""
- import picarones.core.builtin_hooks # noqa: F401
+ import picarones.measurements.builtin_hooks # noqa: F401
from dataclasses import fields
from picarones.core.metric_hooks import select_document_hooks
@@ -126,7 +126,7 @@ class TestBuiltinHooksRegistration:
)
def test_aggregator_attribute_names_match_enginereport(self):
- import picarones.core.builtin_hooks # noqa: F401
+ import picarones.measurements.builtin_hooks # noqa: F401
from dataclasses import fields
from picarones.core.metric_hooks import select_corpus_aggregators
@@ -264,7 +264,7 @@ class TestRunDocumentHooks:
class TestRunnerBackwardCompat:
"""Les tests Sprint 13 et Sprint 42 importent directement depuis
- ``picarones.core.runner``. Ces noms doivent rester disponibles
+ ``picarones.measurements.runner``. Ces noms doivent rester disponibles
après le chantier 2."""
@pytest.mark.parametrize("name", [
@@ -281,11 +281,11 @@ class TestRunnerBackwardCompat:
def test_helper_still_exported_from_runner(self, name):
# Skip si tqdm ou autres deps absents (sandbox minimaliste).
pytest.importorskip("tqdm")
- from picarones.core import runner
+ from picarones.measurements import runner
assert hasattr(runner, name), (
f"runner.{name} a disparu — casse les tests Sprint 13/42 "
- "qui font ``from picarones.core.runner import {name}``"
+ "qui font ``from picarones.measurements.runner import {name}``"
)
assert callable(getattr(runner, name))
diff --git a/tests/test_metrics.py b/tests/test_metrics.py
index 479541b4cf1e448e6ab589db09825bca2d1dcabb..54c864eb8cecb332a99b1fd702368865c81d2b52 100644
--- a/tests/test_metrics.py
+++ b/tests/test_metrics.py
@@ -1,8 +1,8 @@
-"""Tests unitaires pour le module picarones.core.metrics."""
+"""Tests unitaires pour le module picarones.measurements.metrics."""
import pytest
-from picarones.core.metrics import aggregate_metrics, compute_metrics, MetricsResult
+from picarones.measurements.metrics import aggregate_metrics, compute_metrics, MetricsResult
class TestComputeMetrics:
diff --git a/tests/test_phaseE_migration.py b/tests/test_phaseE_migration.py
deleted file mode 100644
index f3082b4ab2d92fe3c34f070d631e9403ccf6a977..0000000000000000000000000000000000000000
--- a/tests/test_phaseE_migration.py
+++ /dev/null
@@ -1,273 +0,0 @@
-"""Tests de la phase E — séparation core/ + measurements/.
-
-Couvre :
-
-- ~41 modules métriques déplacés vers ``picarones/measurements/``.
-- Sous-package ``narrative/`` (4 modules + 6 familles de détecteurs +
- helper) déplacé vers ``picarones/measurements/narrative/``.
-- Identité préservée à travers les shims.
-- Le ``core/`` strict ne contient plus que ~13 fichiers (Cercle 1).
-- Les hooks builtin restent enregistrés.
-- Le moteur narratif fonctionne (détection + arbitre + rendu).
-- Les vues du chantier 3 fonctionnent.
-- Document ``docs/architecture-cercles.md`` mis à jour avec critère DDD.
-"""
-
-from __future__ import annotations
-
-import importlib
-import warnings
-from pathlib import Path
-
-import pytest
-
-
-# ──────────────────────────────────────────────────────────────────────────
-# 1. Imports historiques rétrocompat des mesures
-# ──────────────────────────────────────────────────────────────────────────
-
-
-class TestMeasurementsRetrocompat:
- @pytest.mark.parametrize("module_path, attribute", [
- ("picarones.core.confusion", "build_confusion_matrix"),
- ("picarones.core.taxonomy", "classify_errors"),
- ("picarones.core.calibration", "compute_calibration_metrics"),
- ("picarones.core.layout", "compute_layout_metrics"),
- ("picarones.core.reading_order", "compute_reading_order_metrics"),
- ("picarones.core.error_absorption", "compute_error_absorption"),
- ("picarones.core.searchability", "compute_searchability"),
- ("picarones.core.numerical_sequences",
- "compute_numerical_sequence_metrics"),
- ("picarones.core.readability", "flesch_score"),
- ("picarones.core.specialization", "compute_specialization_score"),
- ("picarones.core.throughput", "compute_effective_throughput"),
- ("picarones.core.cost_projection", "project_engine"),
- ("picarones.core.statistics", "bootstrap_ci"),
- ("picarones.core.history", "BenchmarkHistory"),
- ("picarones.core.builtin_hooks", "calibration_from_engine_result"),
- ("picarones.core.line_metrics", "compute_line_metrics"),
- ("picarones.core.hallucination", "compute_hallucination_metrics"),
- ("picarones.core.image_quality", "analyze_image_quality"),
- ("picarones.core.normalization", "NORMALIZATION_PROFILES"),
- ("picarones.core.rare_tokens", "extract_rare_tokens"),
- ])
- def test_legacy_path_works(self, module_path: str, attribute: str):
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- mod = importlib.import_module(module_path)
- assert hasattr(mod, attribute), f"{module_path}.{attribute}"
-
-
-# ──────────────────────────────────────────────────────────────────────────
-# 2. Sous-package narrative/ déplacé
-# ──────────────────────────────────────────────────────────────────────────
-
-
-class TestNarrativePackageMigration:
- def test_narrative_root_import(self):
- from picarones.core.narrative import build_synthesis
- assert callable(build_synthesis)
-
- def test_narrative_facts_via_shim(self):
- from picarones.core.narrative.facts import Fact, FactType
- assert Fact is not None
- assert FactType is not None
-
- def test_narrative_registry_via_shim(self):
- from picarones.core.narrative.registry import register_detector
- assert callable(register_detector)
-
- def test_narrative_detectors_via_shim(self):
- from picarones.core.narrative.detectors import (
- DETECTORS_BY_TYPE,
- detect_global_leader_cer,
- )
- assert callable(detect_global_leader_cer)
- assert len(DETECTORS_BY_TYPE) == 18
-
- def test_narrative_detector_family_modules(self):
- # Les 6 familles restent accessibles via leur ancien chemin
- from picarones.core.narrative.detectors.ranking import (
- detect_global_leader_cer,
- )
- from picarones.core.narrative.detectors.pareto import (
- detect_pareto_alternative,
- )
- from picarones.core.narrative.detectors.history import (
- detect_engine_unstable,
- )
- from picarones.core.narrative.detectors.quality import (
- detect_confidence_warning,
- )
- from picarones.core.narrative.detectors.stratum import (
- detect_stratum_winner,
- )
- from picarones.core.narrative.detectors.ensemble import (
- detect_ensemble_opportunity,
- )
- assert all(callable(f) for f in [
- detect_global_leader_cer,
- detect_pareto_alternative,
- detect_engine_unstable,
- detect_confidence_warning,
- detect_stratum_winner,
- detect_ensemble_opportunity,
- ])
-
-
-# ──────────────────────────────────────────────────────────────────────────
-# 3. Identité préservée
-# ──────────────────────────────────────────────────────────────────────────
-
-
-class TestIdentityThroughShim:
- def test_confusion_identity(self):
- from picarones.core.confusion import build_confusion_matrix as via_old
- from picarones.measurements.confusion import (
- build_confusion_matrix as via_new,
- )
- assert via_old is via_new
-
- def test_narrative_facts_identity(self):
- from picarones.core.narrative.facts import Fact as via_old
- from picarones.measurements.narrative.facts import Fact as via_new
- assert via_old is via_new
-
- def test_narrative_detector_identity(self):
- from picarones.core.narrative.detectors.ranking import (
- detect_speed_winner as via_old,
- )
- from picarones.measurements.narrative.detectors.ranking import (
- detect_speed_winner as via_new,
- )
- assert via_old is via_new
-
-
-# ──────────────────────────────────────────────────────────────────────────
-# 4. core/ Cercle 1 strict — ne contient plus que ~13 modules
-# ──────────────────────────────────────────────────────────────────────────
-
-
-class TestCoreIsLean:
- """Le ``core/`` post-phase E ne contient plus que les modules
- Cercle 1 (abstractions + orchestration). Tout le reste est shim."""
-
- @pytest.mark.parametrize("name", [
- "corpus", "modules", "results", "metrics",
- "runner", "pipeline_runner", "pipeline_benchmark",
- "pipeline_comparison", "pipeline_spec_loader",
- "metric_registry", "metric_hooks",
- "builtin_metrics", "alto_metrics",
- ])
- def test_cercle1_module_present(self, name):
- """Les modules Cercle 1 doivent rester dans ``core/`` (pas de shim)."""
- repo = Path(__file__).parent.parent
- path = repo / "picarones" / "core" / f"{name}.py"
- assert path.exists()
- content = path.read_text(encoding="utf-8")
- # Un module Cercle 1 a > 30 lignes (vraie logique, pas shim)
- n_lines = len([line for line in content.splitlines() if line.strip()])
- assert n_lines > 30, (
- f"core/{name}.py fait {n_lines} lignes — ne devrait pas être "
- "un shim, c'est un module Cercle 1"
- )
-
-
-# ──────────────────────────────────────────────────────────────────────────
-# 5. Hooks builtin enregistrés (12 doc + 12 corpus)
-# ──────────────────────────────────────────────────────────────────────────
-
-
-class TestHooksStillRegistered:
- def test_12_doc_hooks(self):
- # Eager-load des hooks via builtin_hooks (qui est maintenant un shim
- # vers measurements/builtin_hooks).
- import picarones.core.builtin_hooks # noqa: F401
- from picarones.core.metric_hooks import _all_document_hook_names
-
- hooks = _all_document_hook_names()
- # Le compte exact dépend des hooks expérimentaux qui tests
- # pourraient ajouter, donc on vérifie >= 12 et la présence des
- # 12 attendus.
- expected = {
- "confusion", "char_scores", "taxonomy", "structure",
- "image_quality", "line_metrics", "hallucination",
- "calibration", "philological", "searchability",
- "numerical_sequences", "readability",
- }
- assert expected.issubset(set(hooks))
-
- def test_alto_metrics_registered(self):
- from picarones.core.metric_registry import select_metrics
- from picarones.core.modules import ArtifactType
-
- metrics = select_metrics((ArtifactType.ALTO, ArtifactType.ALTO))
- names = {s.name for s in metrics}
- assert "alto_text_cer" in names
- assert "alto_text_wer" in names
-
-
-# ──────────────────────────────────────────────────────────────────────────
-# 6. build_synthesis fonctionne (intégration narrative complète)
-# ──────────────────────────────────────────────────────────────────────────
-
-
-class TestNarrativeIntegration:
- def test_build_synthesis_works(self):
- from picarones.core.narrative import build_synthesis
-
- synth = build_synthesis({
- "ranking": [
- {"engine": "tess", "mean_cer": 0.05},
- {"engine": "pero", "mean_cer": 0.08},
- ],
- }, lang="fr")
- assert "sentences" in synth
- assert "facts" in synth
- assert len(synth["sentences"]) >= 1
-
-
-# ──────────────────────────────────────────────────────────────────────────
-# 7. Vues du chantier 3 fonctionnent
-# ──────────────────────────────────────────────────────────────────────────
-
-
-class TestChantier3ViewsAfterPhaseE:
- def test_views_still_work(self):
- from picarones.report.views import (
- build_advanced_taxonomy_view_html,
- )
- report_data = {"engines": [
- {"name": "tess", "cer": 0.05,
- "aggregated_taxonomy": {"class_distribution": {"x": 5}}},
- {"name": "pero", "cer": 0.08,
- "aggregated_taxonomy": {"class_distribution": {"x": 8}}},
- ]}
- # Au moins advanced_taxonomy doit produire du HTML
- html = build_advanced_taxonomy_view_html(report_data, {})
- assert isinstance(html, str)
-
-
-# ──────────────────────────────────────────────────────────────────────────
-# 8. Documentation cercles mise à jour
-# ──────────────────────────────────────────────────────────────────────────
-
-
-class TestArchitectureCerclesDocUpdated:
- @pytest.fixture
- def doc(self) -> str:
- path = Path(__file__).parent.parent / "docs" / "architecture-cercles.md"
- return path.read_text(encoding="utf-8")
-
- def test_critere_corrige(self, doc):
- """Le critère DDD remplace l'ancien critère ambigu."""
- assert "abstractions et logique métier du domaine" in doc
- assert "indépendantes de l'interface utilisateur" in doc
-
- def test_mention_phase_e(self, doc):
- """Le doc référence le sous-package measurements/."""
- # Au moins une mention du nouveau dossier
- # (chemin physique du Cercle 2)
- # NB : le doc parle de ``measurements/`` mais la lettre exacte
- # dépend de la formulation. On accepte plusieurs variantes.
- assert "measurements" in doc.lower() or "Cercle 2" in doc
diff --git a/tests/test_pricing_degenerate_cases.py b/tests/test_pricing_degenerate_cases.py
index 98e17b1a869c6d96173d6149a6d7f83a203141f2..88d5fd439b42e2baf4cde1c449dc4220063bb8b4 100644
--- a/tests/test_pricing_degenerate_cases.py
+++ b/tests/test_pricing_degenerate_cases.py
@@ -19,14 +19,14 @@ from __future__ import annotations
import pytest
-from picarones.core.pricing import (
+from picarones.measurements.pricing import (
EngineCost,
PricingDefaults,
build_costs_for_benchmark,
estimate_cost,
load_pricing_database,
)
-from picarones.core.statistics import compute_pareto_front
+from picarones.measurements.statistics import compute_pareto_front
# ---------------------------------------------------------------------------
diff --git a/tests/test_public_api.py b/tests/test_public_api.py
index aa30e2cba7f23e51258eaa0c2b634ea06887869e..5f71a2508cc6a45c94b8d661f8886102c67c1e8a 100644
--- a/tests/test_public_api.py
+++ b/tests/test_public_api.py
@@ -151,24 +151,24 @@ class TestResultsApi:
# ──────────────────────────────────────────────────────────────────────────
-# 4. picarones.core.metrics — métriques de base
+# 4. picarones.measurements.metrics — métriques de base
# ──────────────────────────────────────────────────────────────────────────
class TestMetricsApi:
def test_metrics_result_class(self):
- _assert_class("picarones.core.metrics", "MetricsResult")
+ _assert_class("picarones.measurements.metrics", "MetricsResult")
@pytest.mark.parametrize("name", [
"compute_metrics", "aggregate_metrics",
])
def test_function_exists(self, name):
- _assert_function("picarones.core.metrics", name)
+ _assert_function("picarones.measurements.metrics", name)
def test_compute_metrics_signature(self):
"""``compute_metrics(reference, hypothesis, char_exclude=None)`` est
contractuel — les 2 premiers args sont positionnels, le 3ᵉ keyword."""
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
sig = inspect.signature(compute_metrics)
params = list(sig.parameters.values())
# Au moins 2 paramètres positionnels (reference, hypothesis)
@@ -182,14 +182,14 @@ class TestMetricsApi:
# ──────────────────────────────────────────────────────────────────────────
-# 5. picarones.core.runner — run_benchmark
+# 5. picarones.measurements.runner — run_benchmark
# ──────────────────────────────────────────────────────────────────────────
class TestRunnerApi:
def test_run_benchmark_exists(self):
try:
- _assert_function("picarones.core.runner", "run_benchmark")
+ _assert_function("picarones.measurements.runner", "run_benchmark")
except ImportError as exc:
if "tqdm" in str(exc):
pytest.skip("tqdm non installé en sandbox")
@@ -199,7 +199,7 @@ class TestRunnerApi:
"""Les paramètres clés (corpus, engines, profile…) doivent rester
accessibles. Ajout d'un argument requis = breaking change."""
try:
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
except ImportError as exc:
if "tqdm" in str(exc):
pytest.skip("tqdm non installé")
@@ -218,7 +218,7 @@ class TestRunnerApi:
# ──────────────────────────────────────────────────────────────────────────
-# 6. picarones.core.pipeline_runner — banc d'essai pipelines
+# 6. picarones.core.pipeline — banc d'essai pipelines
# ──────────────────────────────────────────────────────────────────────────
@@ -228,7 +228,7 @@ class TestPipelineRunnerApi:
"StepResult", "PipelineResult", "PipelineRunner",
])
def test_class_exists(self, name):
- _assert_class("picarones.core.pipeline_runner", name)
+ _assert_class("picarones.core.pipeline", name)
class TestPipelineBenchmarkApi:
@@ -236,31 +236,31 @@ class TestPipelineBenchmarkApi:
"StepAggregate", "PipelineBenchmarkResult",
])
def test_class_exists(self, name):
- _assert_class("picarones.core.pipeline_benchmark", name)
+ _assert_class("picarones.measurements.pipeline_benchmark", name)
@pytest.mark.parametrize("name", [
"default_initial_inputs", "run_pipeline_benchmark",
])
def test_function_exists(self, name):
- _assert_function("picarones.core.pipeline_benchmark", name)
+ _assert_function("picarones.measurements.pipeline_benchmark", name)
class TestPipelineComparisonApi:
def test_pipeline_comparison_result(self):
_assert_class(
- "picarones.core.pipeline_comparison", "PipelineComparisonResult",
+ "picarones.measurements.pipeline_comparison", "PipelineComparisonResult",
)
def test_compare_pipelines(self):
_assert_function(
- "picarones.core.pipeline_comparison", "compare_pipelines",
+ "picarones.measurements.pipeline_comparison", "compare_pipelines",
)
class TestPipelineSpecLoaderApi:
def test_pipeline_spec_load_error(self):
cls = _assert_class(
- "picarones.core.pipeline_spec_loader", "PipelineSpecLoadError",
+ "picarones.measurements.pipeline_spec_loader", "PipelineSpecLoadError",
)
assert issubclass(cls, ValueError)
@@ -271,7 +271,7 @@ class TestPipelineSpecLoaderApi:
"load_comparison_specs_from_dict",
])
def test_function_exists(self, name):
- _assert_function("picarones.core.pipeline_spec_loader", name)
+ _assert_function("picarones.measurements.pipeline_spec_loader", name)
# ──────────────────────────────────────────────────────────────────────────
@@ -343,7 +343,7 @@ class TestMetricHooksApi:
# ──────────────────────────────────────────────────────────────────────────
-# 9. picarones.core.builtin_metrics — CER/WER/MER/WIL natifs
+# 9. picarones.measurements.builtin_metrics — CER/WER/MER/WIL natifs
# ──────────────────────────────────────────────────────────────────────────
@@ -353,40 +353,40 @@ class TestBuiltinMetricsApi:
"text_preservation_after_reconstruction",
])
def test_function_exists(self, name):
- _assert_function("picarones.core.builtin_metrics", name)
+ _assert_function("picarones.measurements.builtin_metrics", name)
# ──────────────────────────────────────────────────────────────────────────
-# 10. picarones.core.alto_metrics — métriques (ALTO, ALTO)
+# 10. picarones.measurements.alto_metrics — métriques (ALTO, ALTO)
# ──────────────────────────────────────────────────────────────────────────
class TestAltoMetricsApi:
def test_extract_text_from_alto(self):
- _assert_function("picarones.core.alto_metrics", "extract_text_from_alto")
+ _assert_function("picarones.measurements.alto_metrics", "extract_text_from_alto")
@pytest.mark.parametrize("name", [
"alto_text_cer", "alto_text_wer",
"alto_text_mer", "alto_text_wil",
])
def test_alto_metric_function(self, name):
- _assert_function("picarones.core.alto_metrics", name)
+ _assert_function("picarones.measurements.alto_metrics", name)
# ──────────────────────────────────────────────────────────────────────────
-# 11. picarones.core.jobs — JobStore (utilisé par web/)
+# 11. picarones.web.jobs — JobStore (utilisé par web/)
# ──────────────────────────────────────────────────────────────────────────
class TestJobsApi:
def test_job_store(self):
- _assert_class("picarones.core.jobs", "JobStore")
+ _assert_class("picarones.web.jobs", "JobStore")
@pytest.mark.parametrize("name", [
"get_default_store", "reset_default_store",
])
def test_function_exists(self, name):
- _assert_function("picarones.core.jobs", name)
+ _assert_function("picarones.web.jobs", name)
# ──────────────────────────────────────────────────────────────────────────
@@ -399,17 +399,13 @@ class TestCercle1IsLean:
(les autres sont des shims). Ce test garde-fou empêche un module
métrique d'être réintroduit dans le cœur sans RFC."""
- # Modules Cercle 1 « gros » (> 30 lignes de logique). ``colors.py``
- # est un fichier utilitaire de constantes (13 lignes) co-localisé
- # dans ``core/`` pour éviter le churn — il n'est pas dans cette
- # liste car le seuil 30 lignes ne le détecte pas comme « réel »,
- # mais sa présence dans le dossier est tolérée.
+ # Modules Cercle 1 — abstractions pures (corpus, contrats, registres).
+ # Tout module avec de la logique métier (calcul, orchestration)
+ # appartient au Cercle 2 (``measurements/``) ou au Cercle 3
+ # (``extras/``, ``report/``).
EXPECTED_CERCLE1 = {
- "alto_metrics.py", "builtin_metrics.py", "corpus.py", "jobs.py",
- "metric_hooks.py", "metric_registry.py", "metrics.py", "modules.py",
- "pipeline_benchmark.py", "pipeline_comparison.py",
- "pipeline_runner.py", "pipeline_spec_loader.py",
- "results.py", "runner.py",
+ "corpus.py", "facts.py", "metric_hooks.py", "metric_registry.py",
+ "modules.py", "pipeline.py", "results.py",
}
def test_cercle1_files_lean(self):
@@ -460,17 +456,17 @@ class TestApiStableDoc:
"picarones.core.corpus",
"picarones.core.modules",
"picarones.core.results",
- "picarones.core.metrics",
- "picarones.core.runner",
- "picarones.core.pipeline_runner",
- "picarones.core.pipeline_benchmark",
- "picarones.core.pipeline_comparison",
- "picarones.core.pipeline_spec_loader",
+ "picarones.measurements.metrics",
+ "picarones.measurements.runner",
+ "picarones.core.pipeline",
+ "picarones.measurements.pipeline_benchmark",
+ "picarones.measurements.pipeline_comparison",
+ "picarones.measurements.pipeline_spec_loader",
"picarones.core.metric_registry",
"picarones.core.metric_hooks",
- "picarones.core.builtin_metrics",
- "picarones.core.alto_metrics",
- "picarones.core.jobs",
+ "picarones.measurements.builtin_metrics",
+ "picarones.measurements.alto_metrics",
+ "picarones.web.jobs",
]:
assert module in content, (
f"docs/api-stable.md ne mentionne pas {module}"
diff --git a/tests/test_results.py b/tests/test_results.py
index f4b24beb34364a398aa9750b6144000d4d19378b..f16f78d1c7955b3637832c0c4f64904cc4d479d4 100644
--- a/tests/test_results.py
+++ b/tests/test_results.py
@@ -3,7 +3,7 @@
import json
import pytest
-from picarones.core.metrics import MetricsResult
+from picarones.measurements.metrics import MetricsResult
from picarones.core.results import BenchmarkResult, DocumentResult, EngineReport
diff --git a/tests/test_sprint10_error_distribution.py b/tests/test_sprint10_error_distribution.py
index c7d383fc7fe617e9434c9552c6b75eedcc70ea24..0070eaa0514578c7a1ce9818fa335dbbca58d50a 100644
--- a/tests/test_sprint10_error_distribution.py
+++ b/tests/test_sprint10_error_distribution.py
@@ -41,31 +41,31 @@ HYP_HALLUCINATED = (
# ===========================================================================
class TestLineMetrics:
- """Tests pour picarones.core.line_metrics.compute_line_metrics."""
+ """Tests pour picarones.measurements.line_metrics.compute_line_metrics."""
def test_import(self):
- from picarones.core.line_metrics import compute_line_metrics, LineMetrics
+ from picarones.measurements.line_metrics import compute_line_metrics, LineMetrics
assert callable(compute_line_metrics)
assert LineMetrics is not None
def test_perfect_match_cer_zero(self):
- from picarones.core.line_metrics import compute_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics
result = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_PERFECT)
assert result.mean_cer == pytest.approx(0.0, abs=1e-9)
assert all(v == pytest.approx(0.0, abs=1e-9) for v in result.cer_per_line)
def test_line_count(self):
- from picarones.core.line_metrics import compute_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics
result = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_ERRORS)
assert result.line_count == 3
def test_cer_per_line_length(self):
- from picarones.core.line_metrics import compute_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics
result = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_ERRORS)
assert len(result.cer_per_line) == 3
def test_percentiles_keys(self):
- from picarones.core.line_metrics import compute_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics
result = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_ERRORS)
for key in ("p50", "p75", "p90", "p95", "p99"):
assert key in result.percentiles
@@ -73,23 +73,23 @@ class TestLineMetrics:
def test_percentile_ordering(self):
"""p50 ≤ p75 ≤ p90 ≤ p95 ≤ p99."""
- from picarones.core.line_metrics import compute_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics
result = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_ERRORS)
p = result.percentiles
assert p["p50"] <= p["p75"] <= p["p90"] <= p["p95"] <= p["p99"]
def test_gini_zero_for_perfect(self):
- from picarones.core.line_metrics import compute_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics
result = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_PERFECT)
assert result.gini == pytest.approx(0.0, abs=1e-9)
def test_gini_range(self):
- from picarones.core.line_metrics import compute_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics
result = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_ERRORS)
assert 0.0 <= result.gini <= 1.0
def test_catastrophic_rate_keys(self):
- from picarones.core.line_metrics import compute_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics
result = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_ERRORS,
thresholds=[0.30, 0.50, 1.00])
for t in (0.30, 0.50, 1.00):
@@ -97,12 +97,12 @@ class TestLineMetrics:
assert 0.0 <= result.catastrophic_rate[t] <= 1.0
def test_heatmap_length(self):
- from picarones.core.line_metrics import compute_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics
result = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_ERRORS, heatmap_bins=5)
assert len(result.heatmap) == 5
def test_as_dict_and_from_dict_roundtrip(self):
- from picarones.core.line_metrics import compute_line_metrics, LineMetrics
+ from picarones.measurements.line_metrics import compute_line_metrics, LineMetrics
result = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_ERRORS)
d = result.as_dict()
restored = LineMetrics.from_dict(d)
@@ -111,7 +111,7 @@ class TestLineMetrics:
assert len(restored.cer_per_line) == len(result.cer_per_line)
def test_aggregate_line_metrics(self):
- from picarones.core.line_metrics import compute_line_metrics, aggregate_line_metrics
+ from picarones.measurements.line_metrics import compute_line_metrics, aggregate_line_metrics
r1 = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_PERFECT)
r2 = compute_line_metrics(GT_MULTILINE, HYP_MULTILINE_ERRORS)
agg = aggregate_line_metrics([r1, r2])
@@ -128,64 +128,64 @@ class TestLineMetrics:
# ===========================================================================
class TestHallucinationMetrics:
- """Tests pour picarones.core.hallucination.compute_hallucination_metrics."""
+ """Tests pour picarones.measurements.hallucination.compute_hallucination_metrics."""
def test_import(self):
- from picarones.core.hallucination import compute_hallucination_metrics, HallucinationMetrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics, HallucinationMetrics
assert callable(compute_hallucination_metrics)
assert HallucinationMetrics is not None
def test_perfect_match_anchor_one(self):
- from picarones.core.hallucination import compute_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics
result = compute_hallucination_metrics(GT_SIMPLE, HYP_PERFECT)
# Ancrage parfait → score proche de 1.0
assert result.anchor_score == pytest.approx(1.0, abs=0.05)
assert result.is_hallucinating is False
def test_length_ratio_perfect(self):
- from picarones.core.hallucination import compute_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics
result = compute_hallucination_metrics(GT_SIMPLE, HYP_PERFECT)
assert result.length_ratio == pytest.approx(1.0, abs=0.05)
def test_hallucination_detected(self):
- from picarones.core.hallucination import compute_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics
result = compute_hallucination_metrics(GT_MEDIEVAL, HYP_HALLUCINATED)
# L'hypothèse est beaucoup plus longue
assert result.length_ratio > 1.0
assert result.is_hallucinating is True
def test_hallucinated_blocks_detected(self):
- from picarones.core.hallucination import compute_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics
result = compute_hallucination_metrics(GT_MEDIEVAL, HYP_HALLUCINATED,
anchor_threshold=0.5, min_block_length=3)
# Des blocs hallucinés doivent être détectés
assert len(result.hallucinated_blocks) > 0
def test_net_insertion_rate_range(self):
- from picarones.core.hallucination import compute_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics
result = compute_hallucination_metrics(GT_MEDIEVAL, HYP_HALLUCINATED)
assert 0.0 <= result.net_insertion_rate <= 1.0
def test_word_counts(self):
- from picarones.core.hallucination import compute_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics
result = compute_hallucination_metrics(GT_SIMPLE, HYP_PERFECT)
assert result.gt_word_count > 0
assert result.hyp_word_count > 0
def test_empty_reference(self):
- from picarones.core.hallucination import compute_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics
result = compute_hallucination_metrics("", "some text here added by model")
# Référence vide : insertion nette maximale
assert result.net_insertion_rate == pytest.approx(1.0, abs=0.05)
def test_empty_hypothesis(self):
- from picarones.core.hallucination import compute_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics
result = compute_hallucination_metrics(GT_SIMPLE, "")
assert result.hyp_word_count == 0
assert result.net_insertion_rate == pytest.approx(0.0)
def test_as_dict_and_from_dict_roundtrip(self):
- from picarones.core.hallucination import compute_hallucination_metrics, HallucinationMetrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics, HallucinationMetrics
result = compute_hallucination_metrics(GT_MEDIEVAL, HYP_HALLUCINATED)
d = result.as_dict()
restored = HallucinationMetrics.from_dict(d)
@@ -194,7 +194,7 @@ class TestHallucinationMetrics:
assert len(restored.hallucinated_blocks) == len(result.hallucinated_blocks)
def test_aggregate_hallucination_metrics(self):
- from picarones.core.hallucination import compute_hallucination_metrics, aggregate_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics, aggregate_hallucination_metrics
r1 = compute_hallucination_metrics(GT_SIMPLE, HYP_PERFECT)
r2 = compute_hallucination_metrics(GT_MEDIEVAL, HYP_HALLUCINATED)
agg = aggregate_hallucination_metrics([r1, r2])
@@ -207,7 +207,7 @@ class TestHallucinationMetrics:
def test_anchor_threshold_respected(self):
"""Un ancrage très bas déclenche le badge hallucination."""
- from picarones.core.hallucination import compute_hallucination_metrics
+ from picarones.measurements.hallucination import compute_hallucination_metrics
result = compute_hallucination_metrics(
"abc def ghi", "xyz uvw rst opq lmn",
anchor_threshold=0.5
@@ -225,7 +225,7 @@ class TestLineMetricsInResults:
def test_document_result_has_line_metrics_field(self):
from picarones.core.results import DocumentResult
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
dr = DocumentResult(
doc_id="test_001",
image_path="/test/img.jpg",
@@ -245,7 +245,7 @@ class TestLineMetricsInResults:
def test_document_result_has_hallucination_metrics_field(self):
from picarones.core.results import DocumentResult
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
dr = DocumentResult(
doc_id="test_002",
image_path="/test/img.jpg",
@@ -265,7 +265,7 @@ class TestLineMetricsInResults:
def test_document_result_as_dict_includes_sprint10_fields(self):
from picarones.core.results import DocumentResult
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
dr = DocumentResult(
doc_id="test_003",
image_path="/test/img.jpg",
@@ -287,7 +287,7 @@ class TestLineMetricsInResults:
def test_engine_report_has_aggregated_sprint10_fields(self):
from picarones.core.results import EngineReport, DocumentResult
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
dr = DocumentResult(
doc_id="test_004",
image_path="/test/img.jpg",
diff --git a/tests/test_sprint11_i18n_english.py b/tests/test_sprint11_i18n_english.py
index 439ca23cf142cb59f8bf8f4401ae2dfc76795e60..31929fa93474a8c6f86679e2c1a139b2f0494287 100644
--- a/tests/test_sprint11_i18n_english.py
+++ b/tests/test_sprint11_i18n_english.py
@@ -26,7 +26,7 @@ class TestEarlyModernEnglish:
@pytest.fixture
def profile(self):
- from picarones.core.normalization import get_builtin_profile
+ from picarones.measurements.normalization import get_builtin_profile
return get_builtin_profile("early_modern_english")
def test_profile_exists(self, profile):
@@ -89,7 +89,7 @@ class TestMedievalEnglish:
@pytest.fixture
def profile(self):
- from picarones.core.normalization import get_builtin_profile
+ from picarones.measurements.normalization import get_builtin_profile
return get_builtin_profile("medieval_english")
def test_profile_exists(self, profile):
@@ -129,7 +129,7 @@ class TestSecretaryHand:
@pytest.fixture
def profile(self):
- from picarones.core.normalization import get_builtin_profile
+ from picarones.measurements.normalization import get_builtin_profile
return get_builtin_profile("secretary_hand")
def test_profile_exists(self, profile):
@@ -160,18 +160,18 @@ class TestBuiltinProfilesListing:
"""Vérifie que les 3 nouveaux profils sont bien accessibles."""
def test_all_english_profiles_accessible(self):
- from picarones.core.normalization import get_builtin_profile
+ from picarones.measurements.normalization import get_builtin_profile
for name in ("early_modern_english", "medieval_english", "secretary_hand"):
p = get_builtin_profile(name)
assert p.name == name
def test_unknown_profile_raises_key_error(self):
- from picarones.core.normalization import get_builtin_profile
+ from picarones.measurements.normalization import get_builtin_profile
with pytest.raises(KeyError):
get_builtin_profile("unknown_lang_profile_xyz")
def test_existing_profiles_still_work(self):
- from picarones.core.normalization import get_builtin_profile
+ from picarones.measurements.normalization import get_builtin_profile
for name in ("medieval_french", "early_modern_french", "medieval_latin", "nfc", "caseless", "minimal"):
p = get_builtin_profile(name)
assert p.name == name
diff --git a/tests/test_sprint12_nouvelles_fonctionnalites.py b/tests/test_sprint12_nouvelles_fonctionnalites.py
index e2cf636ea2c293ba92893a9fe3c9b85e0ef6225c..958cc1076a7fa01e2bb25f820ea364abb42fe47f 100644
--- a/tests/test_sprint12_nouvelles_fonctionnalites.py
+++ b/tests/test_sprint12_nouvelles_fonctionnalites.py
@@ -71,7 +71,7 @@ class TestMacOSHiddenFilesFiltering:
class TestExcludeCharsNormalization:
def test_parse_exclude_chars_from_comma_string(self):
- from picarones.core.normalization import _parse_exclude_chars
+ from picarones.measurements.normalization import _parse_exclude_chars
result = _parse_exclude_chars("', -, –")
assert "'" in result
@@ -79,7 +79,7 @@ class TestExcludeCharsNormalization:
assert "–" in result
def test_parse_exclude_chars_from_plain_string(self):
- from picarones.core.normalization import _parse_exclude_chars
+ from picarones.measurements.normalization import _parse_exclude_chars
result = _parse_exclude_chars(".,;:!?")
assert "." in result
@@ -87,13 +87,13 @@ class TestExcludeCharsNormalization:
assert "?" in result
def test_parse_exclude_chars_empty(self):
- from picarones.core.normalization import _parse_exclude_chars
+ from picarones.measurements.normalization import _parse_exclude_chars
assert _parse_exclude_chars("") == frozenset()
assert _parse_exclude_chars(None) == frozenset()
def test_normalize_strips_excluded_chars(self):
- from picarones.core.normalization import NormalizationProfile
+ from picarones.measurements.normalization import NormalizationProfile
profile = NormalizationProfile(
name="test",
@@ -102,7 +102,7 @@ class TestExcludeCharsNormalization:
assert profile.normalize("Bonjour, monde.") == "Bonjour monde"
def test_sans_ponctuation_profile_exists(self):
- from picarones.core.normalization import NORMALIZATION_PROFILES
+ from picarones.measurements.normalization import NORMALIZATION_PROFILES
assert "sans_ponctuation" in NORMALIZATION_PROFILES
p = NORMALIZATION_PROFILES["sans_ponctuation"]
@@ -111,7 +111,7 @@ class TestExcludeCharsNormalization:
assert "?" in p.exclude_chars
def test_sans_apostrophes_profile_exists(self):
- from picarones.core.normalization import NORMALIZATION_PROFILES
+ from picarones.measurements.normalization import NORMALIZATION_PROFILES
assert "sans_apostrophes" in NORMALIZATION_PROFILES
p = NORMALIZATION_PROFILES["sans_apostrophes"]
@@ -119,7 +119,7 @@ class TestExcludeCharsNormalization:
assert "\u2019" in p.exclude_chars # apostrophe typographique
def test_compute_metrics_with_char_exclude(self):
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
ref = "Bonjour, monde!"
hyp = "Bonjour monde"
@@ -135,7 +135,7 @@ class TestExcludeCharsNormalization:
def test_char_exclude_propagated_in_run_benchmark(self, tmp_path):
"""char_exclude doit être transmis à run_benchmark et réduire le CER."""
from picarones.core.corpus import Corpus, Document
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine, EngineResult
class MockEngine(BaseOCREngine):
@@ -195,7 +195,7 @@ def sample_generator():
"""Fixture partagée : crée un ReportGenerator avec des données fictives."""
from picarones.report.generator import ReportGenerator
from picarones.core.results import BenchmarkResult, DocumentResult, EngineReport
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
def _make_metric(cer=0.1):
return MetricsResult(
diff --git a/tests/test_sprint13_parallelisation_stats.py b/tests/test_sprint13_parallelisation_stats.py
index 80b9fbcdcae430d6680f3e907a2eed5bacd32184..25b589bf74f64b444decebe6e2531864b9a39a85 100644
--- a/tests/test_sprint13_parallelisation_stats.py
+++ b/tests/test_sprint13_parallelisation_stats.py
@@ -133,31 +133,31 @@ class TestRunnerParallelParams:
def test_max_workers_param_exists(self):
"""run_benchmark doit accepter max_workers."""
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
sig = inspect.signature(run_benchmark)
assert "max_workers" in sig.parameters
def test_max_workers_default_is_4(self):
"""max_workers doit avoir 4 comme valeur par défaut."""
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
sig = inspect.signature(run_benchmark)
assert sig.parameters["max_workers"].default == 4
def test_timeout_seconds_param_exists(self):
"""run_benchmark doit accepter timeout_seconds."""
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
sig = inspect.signature(run_benchmark)
assert "timeout_seconds" in sig.parameters
def test_timeout_seconds_default_is_60(self):
"""timeout_seconds doit avoir 60.0 comme valeur par défaut."""
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
sig = inspect.signature(run_benchmark)
assert sig.parameters["timeout_seconds"].default == 60.0
def test_partial_dir_param_exists(self):
"""run_benchmark doit accepter partial_dir (None par défaut)."""
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
sig = inspect.signature(run_benchmark)
assert "partial_dir" in sig.parameters
assert sig.parameters["partial_dir"].default is None
@@ -172,7 +172,7 @@ class TestRunnerTimeout:
def test_timeout_doc_result_has_error(self, tmp_corpus):
"""Un document ayant dépassé le timeout doit avoir engine_error contenant 'timeout'."""
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine
import time
@@ -201,7 +201,7 @@ class TestRunnerTimeout:
def test_timeout_doc_result_cer_is_one(self, tmp_corpus):
"""Un document timeout doit avoir CER = 1.0."""
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine
import time
@@ -227,7 +227,7 @@ class TestRunnerTimeout:
def test_fast_docs_not_affected_by_timeout(self, tmp_corpus):
"""Des documents rapides ne doivent pas être touchés par un timeout généreux."""
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine
class FastEngine(BaseOCREngine):
@@ -258,9 +258,9 @@ class TestRunnerPartialResults:
def test_partial_file_created_during_run(self, tmp_corpus, tmp_path):
"""_save_partial_line doit être appelée pour chaque document traité."""
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine
- import picarones.core.runner as runner_mod
+ import picarones.measurements.runner as runner_mod
save_calls: list[str] = []
original_save = runner_mod._save_partial_line
@@ -289,7 +289,7 @@ class TestRunnerPartialResults:
def test_partial_file_deleted_after_success(self, tmp_corpus, tmp_path):
"""Le fichier .partial.json doit être supprimé après un benchmark réussi."""
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine
class MockEngine(BaseOCREngine):
@@ -310,7 +310,7 @@ class TestRunnerPartialResults:
def test_partial_load_skips_already_done_docs(self, tmp_corpus, tmp_path):
"""La reprise depuis un fichier partiel doit sauter les documents déjà traités."""
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import _load_partial, _partial_path
+ from picarones.measurements.runner import _load_partial, _partial_path
corpus = load_corpus_from_directory(str(tmp_corpus))
corpus_name = corpus.name
@@ -338,7 +338,7 @@ class TestRunnerPartialResults:
def test_partial_load_returns_empty_for_missing_file(self, tmp_path):
"""Si aucun fichier partiel n'existe, la liste doit être vide."""
- from picarones.core.runner import _load_partial
+ from picarones.measurements.runner import _load_partial
_, loaded = _load_partial("corpus_inexistant", "moteur_inexistant", tmp_path)
assert loaded == []
@@ -353,7 +353,7 @@ class TestRunnerSilentExceptions:
"""Une erreur dans build_confusion_matrix doit être loguée, pas ignorée."""
import logging
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine
class MockEngine(BaseOCREngine):
@@ -364,10 +364,10 @@ class TestRunnerSilentExceptions:
corpus = load_corpus_from_directory(str(tmp_corpus))
with patch(
- "picarones.core.runner._compute_document_result",
- wraps=__import__("picarones.core.runner", fromlist=["_compute_document_result"])._compute_document_result,
+ "picarones.measurements.runner._compute_document_result",
+ wraps=__import__("picarones.measurements.runner", fromlist=["_compute_document_result"])._compute_document_result,
):
- with patch("picarones.core.confusion.build_confusion_matrix", side_effect=RuntimeError("crash test")):
+ with patch("picarones.measurements.confusion.build_confusion_matrix", side_effect=RuntimeError("crash test")):
with caplog.at_level(logging.WARNING):
result = run_benchmark(corpus, [MockEngine()], show_progress=False)
@@ -379,7 +379,7 @@ class TestRunnerSilentExceptions:
"""Une exception dans le progress_callback doit être loguée, pas propagée."""
import logging
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine
class MockEngine(BaseOCREngine):
@@ -406,11 +406,11 @@ class TestRunnerSilentExceptions:
def test_aggregate_helpers_log_on_failure(self, caplog):
"""Les helpers _aggregate_* doivent logger en WARNING et retourner None."""
import logging
- from picarones.core.runner import _aggregate_confusion
+ from picarones.measurements.runner import _aggregate_confusion
# Créer un doc_result avec des données de confusion corrompues
from picarones.core.results import DocumentResult
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
bad_dr = DocumentResult(
doc_id="x", image_path="x.png", ground_truth="gt", hypothesis="hyp",
metrics=MetricsResult(cer=0.1, cer_nfc=0.1, cer_caseless=0.1,
@@ -433,7 +433,7 @@ class TestWilcoxonValidation:
def test_identical_sequences_not_significant(self):
"""Séquences identiques → pas de différence, p = 1.0, significant = False."""
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
a = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
r = wilcoxon_test(a, a)
assert r["significant"] is False
@@ -442,7 +442,7 @@ class TestWilcoxonValidation:
def test_all_positive_diffs_w_minus_is_zero(self):
"""Si toutes les différences a−b sont positives : W⁻ = 0, W⁺ = n(n+1)/2."""
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
n = 10
a = [float(i) for i in range(1, n + 1)]
b = [0.0] * n
@@ -453,7 +453,7 @@ class TestWilcoxonValidation:
def test_w_plus_w_minus_sum_invariant(self):
"""W⁺ + W⁻ doit toujours être égal à n(n+1)/2 (n = nombre de paires non nulles)."""
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
a = [0.10, 0.25, 0.05, 0.40, 0.30, 0.15, 0.20, 0.35, 0.08, 0.18]
b = [0.12, 0.20, 0.08, 0.35, 0.28, 0.18, 0.15, 0.40, 0.10, 0.20]
r = wilcoxon_test(a, b)
@@ -466,7 +466,7 @@ class TestWilcoxonValidation:
def test_clearly_different_sequences_significant(self):
"""Deux séquences très différentes (n=15) doivent donner p < 0.05."""
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
a = [0.05] * 15 # moteur A très performant
b = [0.60] * 15 # moteur B peu performant — toutes diff = −0.55
# Diffs a−b = −0.55 pour tous → W⁺ = 0 → devrait être significatif
@@ -476,7 +476,7 @@ class TestWilcoxonValidation:
def test_large_n_normal_approximation_reasonable(self):
"""Pour n = 20, l'approximation normale doit donner une p-value dans [0, 1]."""
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
import random
rng = random.Random(42)
a = [rng.uniform(0.1, 0.5) for _ in range(20)]
@@ -487,7 +487,7 @@ class TestWilcoxonValidation:
def test_small_n_returns_conservative_p(self):
"""Pour n < 10, la p-value doit être 0.04 (significatif) ou 0.20 (non sign.)."""
- from picarones.core.statistics import wilcoxon_test, _SCIPY_AVAILABLE
+ from picarones.measurements.statistics import wilcoxon_test, _SCIPY_AVAILABLE
if _SCIPY_AVAILABLE:
pytest.skip("scipy disponible — la table exacte n'est pas utilisée")
a = [0.1, 0.2, 0.3]
@@ -498,7 +498,7 @@ class TestWilcoxonValidation:
def test_result_keys_complete(self):
"""Le dict retourné doit contenir toutes les clés documentées."""
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
r = wilcoxon_test([0.1, 0.3, 0.2, 0.4, 0.15, 0.35, 0.25, 0.5, 0.45, 0.05],
[0.2, 0.2, 0.3, 0.3, 0.25, 0.25, 0.35, 0.35, 0.40, 0.15])
for key in ("statistic", "p_value", "significant", "interpretation", "n_pairs", "W_plus", "W_minus"):
@@ -513,12 +513,12 @@ class TestWilcoxonScipyIntegration:
def test_scipy_available_flag_is_bool(self):
"""_SCIPY_AVAILABLE doit être un booléen."""
- from picarones.core.statistics import _SCIPY_AVAILABLE
+ from picarones.measurements.statistics import _SCIPY_AVAILABLE
assert isinstance(_SCIPY_AVAILABLE, bool)
def test_scipy_and_native_agree_on_significance(self):
"""Scipy et l'implémentation native doivent s'accorder sur la significativité."""
- from picarones.core.statistics import wilcoxon_test, _SCIPY_AVAILABLE
+ from picarones.measurements.statistics import wilcoxon_test, _SCIPY_AVAILABLE
if not _SCIPY_AVAILABLE:
pytest.skip("scipy non disponible")
@@ -534,7 +534,7 @@ class TestWilcoxonScipyIntegration:
def test_scipy_p_value_in_valid_range(self):
"""La p-value fournie par scipy doit être dans [0, 1]."""
- from picarones.core.statistics import wilcoxon_test, _SCIPY_AVAILABLE
+ from picarones.measurements.statistics import wilcoxon_test, _SCIPY_AVAILABLE
if not _SCIPY_AVAILABLE:
pytest.skip("scipy non disponible")
diff --git a/tests/test_sprint14_robust_filtering.py b/tests/test_sprint14_robust_filtering.py
index 05b17b92054c5003e35595667bc6bd2a93939dc8..965ea9b0a2ec72b00f23fd75ffbca2360e9ce1d8 100644
--- a/tests/test_sprint14_robust_filtering.py
+++ b/tests/test_sprint14_robust_filtering.py
@@ -23,7 +23,7 @@ import pytest
def _make_fake_benchmark():
"""Retourne un BenchmarkResult minimal pour tester le générateur."""
from picarones.core.results import BenchmarkResult, EngineReport, DocumentResult
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
def _metrics(cer, wer=0.2):
return MetricsResult(
diff --git a/tests/test_sprint15_llm_pipeline_bugs.py b/tests/test_sprint15_llm_pipeline_bugs.py
index d73a6ddc5fb0d2afc64a345145c8738f05e548de..34237954721b7b3c21e0f653c0f1ce0f1bdffe17 100644
--- a/tests/test_sprint15_llm_pipeline_bugs.py
+++ b/tests/test_sprint15_llm_pipeline_bugs.py
@@ -20,13 +20,13 @@ class TestEmptyHypothesisMetrics:
"""compute_metrics doit retourner CER=1.0, pas 0.0, pour hypothèse vide."""
def test_empty_hypothesis_cer_is_one(self):
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
result = compute_metrics("Bonjour le monde", "")
assert result.cer == pytest.approx(1.0)
assert result.error is None
def test_empty_hypothesis_all_metrics_are_one(self):
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
result = compute_metrics("hello world", "")
assert result.cer == pytest.approx(1.0)
assert result.wer == pytest.approx(1.0)
@@ -34,13 +34,13 @@ class TestEmptyHypothesisMetrics:
assert result.wil == pytest.approx(1.0)
def test_whitespace_only_hypothesis_cer_is_one(self):
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
result = compute_metrics("Bonjour", " \t\n")
assert result.cer == pytest.approx(1.0)
def test_none_hypothesis_guarded(self):
"""compute_metrics ne doit pas planter si hypothesis=None."""
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
# None ne sera jamais passé en pratique, mais on teste la robustesse
# via une chaîne vide (le runner convertit None → "")
result = compute_metrics("test", "")
@@ -48,19 +48,19 @@ class TestEmptyHypothesisMetrics:
def test_both_empty_cer_is_zero(self):
"""Référence ET hypothèse vides → CER=0.0 (pas d'erreur à mesurer)."""
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
result = compute_metrics("", "")
assert result.cer == pytest.approx(0.0)
def test_empty_reference_nonempty_hypothesis(self):
"""Référence vide avec hypothèse non vide → CER=1.0 (comportement existant)."""
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
result = compute_metrics("", "something")
assert result.cer == pytest.approx(1.0)
def test_normal_case_unchanged(self):
"""Un cas normal ne doit pas être affecté par le guard."""
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
result = compute_metrics("abcd", "abce")
assert result.cer == pytest.approx(0.25)
assert result.error is None
@@ -238,7 +238,7 @@ class TestRunnerDocumentResultCohérence:
def test_empty_hypothesis_stored_as_cer_one(self):
"""_compute_document_result avec text="" → metrics.cer = 1.0."""
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
from picarones.engines.base import EngineResult
ocr_result = EngineResult(
@@ -266,7 +266,7 @@ class TestRunnerDocumentResultCohérence:
def test_engine_error_also_gives_cer_one(self):
"""EngineResult avec error → metrics.cer = 1.0 (comportement existant)."""
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
from picarones.engines.base import EngineResult
ocr_result = EngineResult(
diff --git a/tests/test_sprint16_narrative_foundations.py b/tests/test_sprint16_narrative_foundations.py
index 5defda5ad72b221a6ec3e6a33a3baefeed535d80..1e92501586105e33c2a68ddf3aea120a067a620a 100644
--- a/tests/test_sprint16_narrative_foundations.py
+++ b/tests/test_sprint16_narrative_foundations.py
@@ -16,14 +16,14 @@ import json
from pathlib import Path
from picarones.core.corpus import Corpus, Document
-from picarones.core.narrative import (
+from picarones.measurements.narrative import (
DetectorRegistry,
Fact,
FactImportance,
FactType,
detect_all,
)
-from picarones.core.runner import (
+from picarones.measurements.runner import (
_aggregate_hallucination,
_aggregate_line_metrics,
_compute_document_result,
diff --git a/tests/test_sprint18_friedman_nemenyi_cdd.py b/tests/test_sprint18_friedman_nemenyi_cdd.py
index 6c167ffe56210743c00fb4bfc6b972da3c02cd58..9df4871d30aab043a96226fb09bcd020c59a969f 100644
--- a/tests/test_sprint18_friedman_nemenyi_cdd.py
+++ b/tests/test_sprint18_friedman_nemenyi_cdd.py
@@ -14,7 +14,7 @@ import re
import pytest
-from picarones.core.statistics import (
+from picarones.measurements.statistics import (
build_critical_difference_svg,
friedman_test,
nemenyi_posthoc,
@@ -361,8 +361,8 @@ class TestReportIntegration:
class TestStatisticalTieDetector:
def test_detector_emits_fact_when_engines_are_tied(self):
- from picarones.core.narrative.detectors import detect_statistical_tie
- from picarones.core.narrative.facts import FactType
+ from picarones.measurements.narrative.detectors import detect_statistical_tie
+ from picarones.core.facts import FactType
benchmark_data = {
"statistics": {
@@ -384,7 +384,7 @@ class TestStatisticalTieDetector:
assert f.payload["critical_distance"] == 0.9
def test_detector_ignores_singletons(self):
- from picarones.core.narrative.detectors import detect_statistical_tie
+ from picarones.measurements.narrative.detectors import detect_statistical_tie
benchmark_data = {
"statistics": {
@@ -401,14 +401,14 @@ class TestStatisticalTieDetector:
assert facts == []
def test_detector_returns_empty_on_missing_data(self):
- from picarones.core.narrative.detectors import detect_statistical_tie
+ from picarones.measurements.narrative.detectors import detect_statistical_tie
assert detect_statistical_tie({}) == []
assert detect_statistical_tie({"statistics": {}}) == []
assert detect_statistical_tie({"statistics": {"nemenyi": {"error": "no_data"}}}) == []
def test_non_leader_tie_is_high_not_critical(self):
- from picarones.core.narrative.detectors import detect_statistical_tie
- from picarones.core.narrative.facts import FactImportance
+ from picarones.measurements.narrative.detectors import detect_statistical_tie
+ from picarones.core.facts import FactImportance
benchmark_data = {
"statistics": {
diff --git a/tests/test_sprint19_narrative_engine.py b/tests/test_sprint19_narrative_engine.py
index ce0d502f1c7b3dbb6da4d45254d4f9e25f67f041..a0749c13f3766a6ba49470b1bfd98a49e23d169d 100644
--- a/tests/test_sprint19_narrative_engine.py
+++ b/tests/test_sprint19_narrative_engine.py
@@ -15,7 +15,7 @@ import re
import pytest
-from picarones.core.narrative import (
+from picarones.measurements.narrative import (
Fact,
FactImportance,
FactType,
@@ -25,7 +25,7 @@ from picarones.core.narrative import (
render_synthesis,
select_facts,
)
-from picarones.core.narrative.detectors import (
+from picarones.measurements.narrative.detectors import (
detect_confidence_warning,
detect_error_profile_outlier,
detect_global_leader_cer,
@@ -579,8 +579,8 @@ class TestReportIntegration:
hashlib.sha256(s2.group().encode()).hexdigest()
def test_default_registry_has_all_types_registered(self):
- from picarones.core.narrative import _DEFAULT_REGISTRY
- from picarones.core.narrative.facts import FactType
+ from picarones.measurements.narrative import _DEFAULT_REGISTRY
+ from picarones.core.facts import FactType
registered = set(_DEFAULT_REGISTRY.registered_types())
# Tous les types de FactType doivent avoir un détecteur enregistré.
diff --git a/tests/test_sprint20_pareto_pricing.py b/tests/test_sprint20_pareto_pricing.py
index 50ca2d1c348de943546fc7c045a6b2b94fb8a08d..bd80d6f09262ba4a4ad8ee4d11573b86433f6501 100644
--- a/tests/test_sprint20_pareto_pricing.py
+++ b/tests/test_sprint20_pareto_pricing.py
@@ -15,18 +15,18 @@ from pathlib import Path
import pytest
-from picarones.core.narrative import build_synthesis
-from picarones.core.narrative.detectors import (
+from picarones.measurements.narrative import build_synthesis
+from picarones.measurements.narrative.detectors import (
detect_cost_outlier,
detect_pareto_alternative,
)
-from picarones.core.narrative.facts import FactType
-from picarones.core.pricing import (
+from picarones.core.facts import FactType
+from picarones.measurements.pricing import (
build_costs_for_benchmark,
estimate_cost,
load_pricing_database,
)
-from picarones.core.statistics import compute_pareto_front
+from picarones.measurements.statistics import compute_pareto_front
# ---------------------------------------------------------------------------
@@ -306,7 +306,7 @@ class TestReportIntegration:
def test_pricing_yaml_is_packaged(self):
"""Garde-fou : le YAML doit être accessible depuis le package."""
- from picarones.core.pricing import _DEFAULT_PRICING_PATH
+ from picarones.measurements.pricing import _DEFAULT_PRICING_PATH
assert Path(_DEFAULT_PRICING_PATH).exists()
def test_english_locale_renders_pareto_labels(self, benchmark_result, tmp_path):
diff --git a/tests/test_sprint23_anti_hallucination.py b/tests/test_sprint23_anti_hallucination.py
index e3f6b76307519d65a558432195e1163bc044aa63..fa3c73a033f5d20dce48629a6271cdf1c753a787 100644
--- a/tests/test_sprint23_anti_hallucination.py
+++ b/tests/test_sprint23_anti_hallucination.py
@@ -30,18 +30,18 @@ from pathlib import Path
import pytest
-from picarones.core.narrative import (
+from picarones.measurements.narrative import (
Fact,
FactImportance,
FactType,
build_synthesis,
select_facts,
)
-from picarones.core.narrative.arbiter import DEFAULT_TYPE_ORDER
-from picarones.core.statistics import bootstrap_ci
+from picarones.measurements.narrative.arbiter import DEFAULT_TYPE_ORDER
+from picarones.measurements.statistics import bootstrap_ci
ROOT = Path(__file__).parent.parent
-TEMPLATES_DIR = ROOT / "picarones" / "core" / "narrative" / "templates"
+TEMPLATES_DIR = ROOT / "picarones" / "measurements" / "narrative" / "templates"
# ---------------------------------------------------------------------------
@@ -84,7 +84,7 @@ def _full_data() -> dict:
class TestPayloadsCarryFormerlyHardcodedConstants:
def test_confidence_warning_payload_carries_confidence_level(self):
- from picarones.core.narrative.detectors import detect_confidence_warning
+ from picarones.measurements.narrative.detectors import detect_confidence_warning
facts = detect_confidence_warning(_full_data())
assert facts, "fixture devrait déclencher au moins un confidence_warning"
@@ -95,7 +95,7 @@ class TestPayloadsCarryFormerlyHardcodedConstants:
)
def test_pareto_alternative_payload_carries_cost_unit(self):
- from picarones.core.narrative.detectors import detect_pareto_alternative
+ from picarones.measurements.narrative.detectors import detect_pareto_alternative
facts = detect_pareto_alternative(_full_data())
assert facts, "fixture devrait déclencher au moins un pareto_alternative"
@@ -103,7 +103,7 @@ class TestPayloadsCarryFormerlyHardcodedConstants:
assert f.payload.get("cost_unit_pages") == 1000
def test_cost_outlier_payload_carries_cost_unit(self):
- from picarones.core.narrative.detectors import detect_cost_outlier
+ from picarones.measurements.narrative.detectors import detect_cost_outlier
facts = detect_cost_outlier(_full_data())
assert facts, "fixture devrait déclencher au moins un cost_outlier"
@@ -161,7 +161,7 @@ class TestEndToEndWithEmptyWhitelist:
@pytest.mark.parametrize("lang", ["fr", "en"])
def test_every_number_traceable_with_empty_whitelist(self, lang):
- from picarones.core.narrative import extract_numbers
+ from picarones.measurements.narrative import extract_numbers
from tests.test_sprint19_narrative_engine import _numbers_in_payload
diff --git a/tests/test_sprint26_jobs_persistence.py b/tests/test_sprint26_jobs_persistence.py
index f7534055ad3187eede3d52946d69163e8a47e022..26cf1ad8b60f2668fa9fa1854e81e88dc3cc9ae7 100644
--- a/tests/test_sprint26_jobs_persistence.py
+++ b/tests/test_sprint26_jobs_persistence.py
@@ -27,7 +27,7 @@ import time
import pytest
from fastapi.testclient import TestClient
-from picarones.core.jobs import JobStore, get_default_store, reset_default_store
+from picarones.web.jobs import JobStore, get_default_store, reset_default_store
# ---------------------------------------------------------------------------
diff --git a/tests/test_sprint27_reproducibility_snapshots.py b/tests/test_sprint27_reproducibility_snapshots.py
index 853274f560798cf6bd88a7e0615c066d9e59f547..2018c1dedcbf96e41782b8285761666e47a3cf49 100644
--- a/tests/test_sprint27_reproducibility_snapshots.py
+++ b/tests/test_sprint27_reproducibility_snapshots.py
@@ -101,7 +101,7 @@ class TestGlossarySnapshot:
class TestNormalizationSnapshot:
def test_builtin_profile_serializes(self):
- from picarones.core.normalization import get_builtin_profile
+ from picarones.measurements.normalization import get_builtin_profile
from picarones.report.snapshot import normalization_snapshot
p = get_builtin_profile("medieval_french")
s = normalization_snapshot(p)
@@ -117,7 +117,7 @@ class TestNormalizationSnapshot:
assert s["available"] is False
def test_exclude_chars_sorted(self):
- from picarones.core.normalization import get_builtin_profile
+ from picarones.measurements.normalization import get_builtin_profile
from picarones.report.snapshot import normalization_snapshot
p = get_builtin_profile("sans_ponctuation")
s = normalization_snapshot(p)
@@ -171,7 +171,7 @@ class TestSnapshotAll:
assert s["schema_version"] == 1
def test_deterministic_for_same_inputs(self):
- from picarones.core.normalization import get_builtin_profile
+ from picarones.measurements.normalization import get_builtin_profile
from picarones.report.snapshot import snapshot_all
profile = get_builtin_profile("nfc")
@@ -192,7 +192,7 @@ class TestSnapshotAll:
def generated_report_html(tmp_path_factory) -> str:
"""Génère un rapport démo et retourne son contenu HTML."""
from picarones import fixtures
- from picarones.core.normalization import get_builtin_profile
+ from picarones.measurements.normalization import get_builtin_profile
from picarones.report.generator import ReportGenerator
b = fixtures.generate_sample_benchmark(n_docs=6)
diff --git a/tests/test_sprint28_ux_save_compare.py b/tests/test_sprint28_ux_save_compare.py
index 33ffce92ea984280d98b617d45abde49e786f788..9a089fe0bfd1c9642879001e82d86d4c8b0375f7 100644
--- a/tests/test_sprint28_ux_save_compare.py
+++ b/tests/test_sprint28_ux_save_compare.py
@@ -261,7 +261,7 @@ class TestSynthesisPreviewEndpoint:
def job_with_results(self, monkeypatch, tmp_path):
"""Crée un job 'complete' + JSON résultat sur disque."""
from picarones import fixtures
- from picarones.core.jobs import get_default_store, reset_default_store
+ from picarones.web.jobs import get_default_store, reset_default_store
from picarones.web import app as web_app
# Isolate store
monkeypatch.setenv("PICARONES_JOBS_DB", str(tmp_path / "jobs.db"))
@@ -295,7 +295,7 @@ class TestSynthesisPreviewEndpoint:
assert r.status_code == 404
def test_409_when_job_not_complete(self, monkeypatch, tmp_path):
- from picarones.core.jobs import get_default_store, reset_default_store
+ from picarones.web.jobs import get_default_store, reset_default_store
from picarones.web import app as web_app
monkeypatch.setenv("PICARONES_JOBS_DB", str(tmp_path / "jobs.db"))
reset_default_store()
diff --git a/tests/test_sprint29_detector_registry.py b/tests/test_sprint29_detector_registry.py
index 5b13846103250aa617268215efd457b2d6926695..81b081cd019e69e386e180fc301886e1262a16e5 100644
--- a/tests/test_sprint29_detector_registry.py
+++ b/tests/test_sprint29_detector_registry.py
@@ -28,13 +28,13 @@ from __future__ import annotations
import pytest
-from picarones.core.narrative import build_synthesis
-from picarones.core.narrative.facts import (
+from picarones.measurements.narrative import build_synthesis
+from picarones.core.facts import (
Fact,
FactImportance,
FactType,
)
-from picarones.core.narrative.registry import (
+from picarones.measurements.narrative.registry import (
clear_registry,
default_type_order,
detector_for,
@@ -68,7 +68,7 @@ class TestRegistryPopulatedAtImport:
def test_priorities_match_historical_order(self):
"""Les priorités définies au Sprint 29 doivent reproduire l'ordre
canonique pré-Sprint 29 pour ne pas casser la lecture du rapport."""
- from picarones.core.narrative.arbiter import _FALLBACK_TYPE_ORDER
+ from picarones.measurements.narrative.arbiter import _FALLBACK_TYPE_ORDER
live = default_type_order()
# Ils doivent contenir les mêmes types dans le même ordre.
assert live == _FALLBACK_TYPE_ORDER
@@ -225,7 +225,7 @@ class TestEmptyRegistryFallback:
et ne pas planter."""
def test_select_facts_works_on_empty_registry(self):
- from picarones.core.narrative.arbiter import select_facts
+ from picarones.measurements.narrative.arbiter import select_facts
# Sauvegarder l'état complet pour le restaurer
backup = list(iter_detectors())
try:
@@ -256,7 +256,7 @@ class TestEmptyRegistryFallback:
class TestLegacyAliasStillWorks:
def test_detectors_by_type_matches_registry(self):
- from picarones.core.narrative.detectors import DETECTORS_BY_TYPE
+ from picarones.measurements.narrative.detectors import DETECTORS_BY_TYPE
registry_types = {e.fact_type for e in iter_detectors()}
legacy_types = set(DETECTORS_BY_TYPE)
# Les deux ensembles peuvent diverger si DETECTORS_BY_TYPE est
diff --git a/tests/test_sprint34_metric_registry.py b/tests/test_sprint34_metric_registry.py
index 42f5a8a024a6fb2775689a6edcfd54a53fd40360..58bfe104430cf8078cca8553d1b5089c7fa416cd 100644
--- a/tests/test_sprint34_metric_registry.py
+++ b/tests/test_sprint34_metric_registry.py
@@ -34,7 +34,7 @@ from picarones.core.modules import ArtifactType
# tests s'exécutent avec ce registre peuplé ; on n'utilise pas
# ``_reset_registry_for_tests`` parce qu'on veut justement tester l'état
# par défaut visible par le runner en production.
-import picarones.core.builtin_metrics # noqa: F401
+import picarones.measurements.builtin_metrics # noqa: F401
# ──────────────────────────────────────────────────────────────────────────
@@ -171,7 +171,7 @@ class TestParityWithLegacy:
],
)
def test_cer_matches_compute_metrics(self, ref: str, hyp: str) -> None:
- from picarones.core.metrics import compute_metrics
+ from picarones.measurements.metrics import compute_metrics
legacy = compute_metrics(ref, hyp)
registered = compute_at_junction(
diff --git a/tests/test_sprint35_inter_engine.py b/tests/test_sprint35_inter_engine.py
index 8e0e9346c498a288e6e3b4ab6ab4bfe14279506b..1d10aec2bb0d9caf4eac5fb7d6ba6b04216c63df 100644
--- a/tests/test_sprint35_inter_engine.py
+++ b/tests/test_sprint35_inter_engine.py
@@ -1,6 +1,6 @@
"""Tests Sprint 35 — métriques inter-moteurs (Étape 2 du plan).
-Couvre les deux familles de mesures du module ``picarones.core.inter_engine`` :
+Couvre les deux familles de mesures du module ``picarones.measurements.inter_engine`` :
1. **Divergence taxonomique** : KL et JS-divergence sur les
distributions de classes d'erreur, plus la matrice triangulaire
@@ -23,7 +23,7 @@ import math
import pytest
-from picarones.core.inter_engine import (
+from picarones.measurements.inter_engine import (
complementarity_gap,
jensen_shannon_divergence,
kl_divergence,
@@ -187,7 +187,7 @@ class TestOracleTokenRecall:
}
assert oracle_token_recall(ref, hyps) == pytest.approx(1.0)
# Et chacun seul ne fait que la moitié
- from picarones.core.inter_engine import complementarity_gap
+ from picarones.measurements.inter_engine import complementarity_gap
gap = complementarity_gap(ref, hyps)
assert gap["best_single_recall"] == pytest.approx(0.5)
assert gap["oracle_recall"] == pytest.approx(1.0)
diff --git a/tests/test_sprint36_ensemble_narrative.py b/tests/test_sprint36_ensemble_narrative.py
index 8afa26df845a27f8d2243d125dd71d080224927d..60e2a08beb42ecfee14eaca77c7f88e7c24a640a 100644
--- a/tests/test_sprint36_ensemble_narrative.py
+++ b/tests/test_sprint36_ensemble_narrative.py
@@ -22,10 +22,10 @@ import re
import pytest
-from picarones.core.inter_engine import compute_inter_engine_analysis
-from picarones.core.narrative.detectors import detect_ensemble_opportunity
-from picarones.core.narrative.facts import FactImportance, FactType
-from picarones.core.narrative.renderer import extract_numbers, render_fact
+from picarones.measurements.inter_engine import compute_inter_engine_analysis
+from picarones.measurements.narrative.detectors import detect_ensemble_opportunity
+from picarones.core.facts import FactImportance, FactType
+from picarones.measurements.narrative.renderer import extract_numbers, render_fact
# ──────────────────────────────────────────────────────────────────────────
@@ -233,7 +233,7 @@ class TestEnsembleOpportunityDetector:
class TestSynthesisIntegration:
def test_detector_registered_by_default(self) -> None:
- from picarones.core.narrative.registry import iter_detectors
+ from picarones.measurements.narrative.registry import iter_detectors
types = {entry.fact_type for entry in iter_detectors()}
assert FactType.ENSEMBLE_OPPORTUNITY in types
@@ -241,7 +241,7 @@ class TestSynthesisIntegration:
def test_synthesis_includes_ensemble_phrase(self) -> None:
"""Le détecteur s'active dans le pipeline complet et la phrase
rendue contient bien les chiffres clés."""
- from picarones.core.narrative import build_synthesis
+ from picarones.measurements.narrative import build_synthesis
# benchmark_data minimal qui n'active QUE notre détecteur (pas
# de ranking, pas de stats — pour isoler).
@@ -251,7 +251,7 @@ class TestSynthesisIntegration:
assert any("voting" in s.lower() or "tess" in s for s in sentences)
def test_synthesis_en_locale(self) -> None:
- from picarones.core.narrative import build_synthesis
+ from picarones.measurements.narrative import build_synthesis
data = _build_data(relative_gap=0.83)
out = build_synthesis(data, lang="en", max_facts=5)
@@ -301,7 +301,7 @@ class TestTraceability:
def test_no_extraneous_numbers_in_template(self) -> None:
"""Le template lui-même ne contient pas de nombres en dur."""
- from picarones.core.narrative.renderer import _load_templates
+ from picarones.measurements.narrative.renderer import _load_templates
tpl = _load_templates("fr").get("ensemble_opportunity", "")
assert tpl
diff --git a/tests/test_sprint38_ner_metrics.py b/tests/test_sprint38_ner_metrics.py
index 30aae2bc1a80c414f66e1121ed7c205055558061..841bec73673b39b837f6cf8676d65f0454af341e 100644
--- a/tests/test_sprint38_ner_metrics.py
+++ b/tests/test_sprint38_ner_metrics.py
@@ -1,6 +1,6 @@
"""Tests Sprint 38 — métriques NER (couche de calcul pure).
-Le module ``picarones.core.ner`` expose :
+Le module ``picarones.measurements.ner`` expose :
- la dataclass ``Entity`` ;
- ``compute_ner_metrics(ref, hyp, iou_threshold=0.5)`` qui aligne deux
@@ -33,7 +33,7 @@ import pytest
from picarones.core.metric_registry import compute_at_junction, select_metrics
from picarones.core.modules import ArtifactType
-from picarones.core.ner import Entity, compute_ner_metrics, ner_f1
+from picarones.measurements.ner import Entity, compute_ner_metrics, ner_f1
# ──────────────────────────────────────────────────────────────────────────
@@ -255,7 +255,7 @@ class TestEntityValidation:
class TestRegistryIntegration:
def test_ner_f1_registered_for_entities_pair(self) -> None:
# Force l'enregistrement
- import picarones.core.ner # noqa: F401
+ import picarones.measurements.ner # noqa: F401
selected = select_metrics(
(ArtifactType.ENTITIES, ArtifactType.ENTITIES),
@@ -264,7 +264,7 @@ class TestRegistryIntegration:
assert "ner_f1" in names
def test_compute_at_junction_uses_ner_f1(self) -> None:
- import picarones.core.ner # noqa: F401
+ import picarones.measurements.ner # noqa: F401
ref = [{"label": "PER", "start": 0, "end": 5, "text": "Marie"}]
hyp = [{"label": "PER", "start": 0, "end": 5, "text": "Marie"}]
diff --git a/tests/test_sprint39_calibration.py b/tests/test_sprint39_calibration.py
index bc814da3e5b22184e06701ee9bff253fc1b9684c..6eb3fcde92e9d50caaf81e4d061ba4cc8a9959a0 100644
--- a/tests/test_sprint39_calibration.py
+++ b/tests/test_sprint39_calibration.py
@@ -1,6 +1,6 @@
"""Tests Sprint 39 — métriques de calibration (ECE, MCE, reliability).
-Le module ``picarones.core.calibration`` expose :
+Le module ``picarones.measurements.calibration`` expose :
- ``CalibrationBin`` : un bin du reliability diagram
- ``reliability_diagram(confidences, is_correct, n_bins=10)``
@@ -34,7 +34,7 @@ from __future__ import annotations
import pytest
-from picarones.core.calibration import (
+from picarones.measurements.calibration import (
CalibrationBin,
compute_calibration_metrics,
expected_calibration_error,
diff --git a/tests/test_sprint40_ner_runner.py b/tests/test_sprint40_ner_runner.py
index 22ff9c5227b94f44b41229801e0793fa0b6333e7..6c9e2d1f0d8509490b099ce27015c9c865c7a653 100644
--- a/tests/test_sprint40_ner_runner.py
+++ b/tests/test_sprint40_ner_runner.py
@@ -27,14 +27,14 @@ from pathlib import Path
import pytest
from picarones.core.corpus import Corpus, Document, EntitiesGT, GTLevel, TextGT
-from picarones.core.ner_backends import (
+from picarones.measurements.ner_backends import (
SPACY_PROFILES,
SpacyEntityExtractor,
get_extractor,
is_spacy_available,
)
from picarones.core.results import DocumentResult, EngineReport
-from picarones.core.runner import _aggregate_ner, _attach_ner_metrics
+from picarones.measurements.runner import _aggregate_ner, _attach_ner_metrics
# ──────────────────────────────────────────────────────────────────────────
@@ -49,7 +49,7 @@ class TestSpacyExtractor:
"""Sans spaCy installé, l'extracteur retourne [] avec un warning
explicite et ne lève pas."""
ext = SpacyEntityExtractor("fr_core_news_sm")
- with caplog.at_level("WARNING", logger="picarones.core.ner_backends"):
+ with caplog.at_level("WARNING", logger="picarones.measurements.ner_backends"):
result = ext("Marie de Bourgogne en 1477.")
# Sans spaCy, on a toujours [] et un warning
if not is_spacy_available():
@@ -97,7 +97,7 @@ def _make_document_result(
hypothesis: str = "Marie de Bourgogne en 1477.",
ner_metrics: dict | None = None,
) -> DocumentResult:
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
return DocumentResult(
doc_id=doc_id,
@@ -291,7 +291,7 @@ class TestRobustness:
dr1 = _make_document_result(
doc_id="doc1", hypothesis="Marie de Bourgogne en 1477.",
)
- with caplog.at_level("WARNING", logger="picarones.core.runner"):
+ with caplog.at_level("WARNING", logger="picarones.measurements.runner"):
_attach_ner_metrics(corpus, [dr1], _broken_extractor)
# Pas de propagation, ner_metrics reste None
diff --git a/tests/test_sprint42_calibration_runner.py b/tests/test_sprint42_calibration_runner.py
index 8aab14bdbb12409641780c4021cee43d8419bec3..beff814b238eddb560b533989e8e9f00e9fc0089 100644
--- a/tests/test_sprint42_calibration_runner.py
+++ b/tests/test_sprint42_calibration_runner.py
@@ -29,7 +29,7 @@ from __future__ import annotations
import pytest
-from picarones.core.runner import (
+from picarones.measurements.runner import (
_aggregate_calibration,
_calibration_from_engine_result,
)
@@ -59,7 +59,7 @@ class TestEngineResultExtension:
def _make_dr(calibration_metrics: dict | None = None) -> DocumentResult:
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
return DocumentResult(
doc_id="d1", image_path="/tmp/x.png",
@@ -243,7 +243,7 @@ class TestBackwardCompat:
def test_engine_result_default_no_calibration(self) -> None:
# Un EngineResult sans token_confidences → calibration_metrics
# ne doit pas être calculée.
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
ocr = EngineResult(
engine_name="e",
image_path="/tmp/x.png",
@@ -260,7 +260,7 @@ class TestBackwardCompat:
assert dr.calibration_metrics is None
def test_engine_result_with_confs_triggers_calibration(self) -> None:
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
ocr = EngineResult(
engine_name="e",
image_path="/tmp/x.png",
diff --git a/tests/test_sprint44_median_default.py b/tests/test_sprint44_median_default.py
index 1b9f174db358f3116f9c8c6793cd77bf5320b008..29902bc8fa2622907aa80b0632872b68f0ce4ff3 100644
--- a/tests/test_sprint44_median_default.py
+++ b/tests/test_sprint44_median_default.py
@@ -23,10 +23,10 @@ import re
import pytest
-from picarones.core.metrics import MetricsResult
-from picarones.core.narrative.detectors import detect_median_mean_gap_warning
-from picarones.core.narrative.facts import FactImportance, FactType
-from picarones.core.narrative.renderer import extract_numbers, render_fact
+from picarones.measurements.metrics import MetricsResult
+from picarones.measurements.narrative.detectors import detect_median_mean_gap_warning
+from picarones.core.facts import FactImportance, FactType
+from picarones.measurements.narrative.renderer import extract_numbers, render_fact
from picarones.core.results import BenchmarkResult, DocumentResult, EngineReport
@@ -225,7 +225,7 @@ class TestTraceability:
)
def test_template_has_no_hardcoded_numbers(self) -> None:
- from picarones.core.narrative.renderer import _load_templates
+ from picarones.measurements.narrative.renderer import _load_templates
for lang in ("fr", "en"):
tpl = _load_templates(lang).get("median_mean_gap_warning", "")
assert tpl, f"Template absent pour {lang}"
@@ -242,12 +242,12 @@ class TestTraceability:
class TestSynthesisIntegration:
def test_detector_registered_by_default(self) -> None:
- from picarones.core.narrative.registry import iter_detectors
+ from picarones.measurements.narrative.registry import iter_detectors
types = {entry.fact_type for entry in iter_detectors()}
assert FactType.MEDIAN_MEAN_GAP_WARNING in types
def test_synthesis_includes_warning_when_asymmetric(self) -> None:
- from picarones.core.narrative import build_synthesis
+ from picarones.measurements.narrative import build_synthesis
data = {"ranking": [{
"engine": "tess", "median_cer": 0.03, "mean_cer": 0.07,
"documents": 100,
diff --git a/tests/test_sprint45_stratification.py b/tests/test_sprint45_stratification.py
index 7f64c7b7e1f8ddaacdba7f914f122d67060e74a0..84b58928f42e39a31680c890b08ffaff43b07929 100644
--- a/tests/test_sprint45_stratification.py
+++ b/tests/test_sprint45_stratification.py
@@ -26,7 +26,7 @@ from __future__ import annotations
import pytest
-from picarones.core.metrics import MetricsResult
+from picarones.measurements.metrics import MetricsResult
from picarones.core.results import BenchmarkResult, DocumentResult, EngineReport
diff --git a/tests/test_sprint46_stratification_html.py b/tests/test_sprint46_stratification_html.py
index a3922f4b9aab51cb2b378ebfee4361d23f37c496..9605de042879486c486e82a6645c606a78b6950a 100644
--- a/tests/test_sprint46_stratification_html.py
+++ b/tests/test_sprint46_stratification_html.py
@@ -26,10 +26,10 @@ from pathlib import Path
import pytest
-from picarones.core.metrics import MetricsResult
-from picarones.core.narrative.detectors import detect_stratification_recommended
-from picarones.core.narrative.facts import FactImportance, FactType
-from picarones.core.narrative.renderer import extract_numbers, render_fact
+from picarones.measurements.metrics import MetricsResult
+from picarones.measurements.narrative.detectors import detect_stratification_recommended
+from picarones.core.facts import FactImportance, FactType
+from picarones.measurements.narrative.renderer import extract_numbers, render_fact
from picarones.core.results import DocumentResult
from picarones.report.generator import ReportGenerator
from picarones.report.stratification_render import build_stratified_ranking_html
@@ -260,7 +260,7 @@ class TestTraceability:
)
def test_template_has_no_hardcoded_numbers(self) -> None:
- from picarones.core.narrative.renderer import _load_templates
+ from picarones.measurements.narrative.renderer import _load_templates
for lang in ("fr", "en"):
tpl = _load_templates(lang).get("stratification_recommended", "")
assert tpl, f"Template absent pour {lang}"
diff --git a/tests/test_sprint47_tesseract_confidences.py b/tests/test_sprint47_tesseract_confidences.py
index 8e0d470d2bb9682ce136274e508aa9807b7efe8d..00304ccc158b82f55e4294762a3a0968ea19c524 100644
--- a/tests/test_sprint47_tesseract_confidences.py
+++ b/tests/test_sprint47_tesseract_confidences.py
@@ -249,7 +249,7 @@ class TestEndToEndWithRunner:
def test_runner_picks_up_confidences_and_computes_calibration(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path,
) -> None:
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
from picarones.engines.base import EngineResult
# Simulation : on appelle directement _compute_document_result
@@ -289,5 +289,5 @@ class TestPytesseractAbsent:
monkeypatch.setattr(tesseract_module, "_PYTESSERACT_AVAILABLE", False)
engine = TesseractEngine()
- result = engine._extract_token_confidences(tmp_path / "p.png")
+ result = engine.run(tmp_path / "p.png").token_confidences
assert result is None
diff --git a/tests/test_sprint48_pero_confidences.py b/tests/test_sprint48_pero_confidences.py
index ed4157036ba2361969281399056b702bdfd18934..e96acef6db8ee520dd123306874060443c683250 100644
--- a/tests/test_sprint48_pero_confidences.py
+++ b/tests/test_sprint48_pero_confidences.py
@@ -67,7 +67,7 @@ class TestExtractFromLayout:
_mock_line("Bonjour le monde", 0.92),
]),
])
- out = engine._extract_token_confidences_from_layout(layout)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(layout))
assert out is not None
assert out == [
{"token": "Bonjour", "confidence": 0.92},
@@ -83,7 +83,7 @@ class TestExtractFromLayout:
_mock_line("Deuxième ligne", 0.80),
]),
])
- out = engine._extract_token_confidences_from_layout(layout)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(layout))
assert out is not None
# Chaque mot porte la confidence de SA ligne
assert {"token": "Première", "confidence": 0.95} in out
@@ -98,7 +98,7 @@ class TestExtractFromLayout:
_mock_line("ok", 0.95), # ok
]),
])
- out = engine._extract_token_confidences_from_layout(layout)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(layout))
assert out == [{"token": "ok", "confidence": 0.95}]
def test_skips_none_confidence(self) -> None:
@@ -109,7 +109,7 @@ class TestExtractFromLayout:
_mock_line("sans_conf", None),
]),
])
- out = engine._extract_token_confidences_from_layout(layout)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(layout))
assert out == [{"token": "avec_conf", "confidence": 0.85}]
def test_skips_negative_confidence(self) -> None:
@@ -120,7 +120,7 @@ class TestExtractFromLayout:
_mock_line("dropped", -0.1),
]),
])
- out = engine._extract_token_confidences_from_layout(layout)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(layout))
assert out == [{"token": "ok", "confidence": 0.9}]
@@ -135,7 +135,7 @@ class TestExposeFlag:
layout = _mock_layout([
_mock_region([_mock_line("hello", 0.9)]),
])
- assert engine._extract_token_confidences_from_layout(layout) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(layout)) is None
# ──────────────────────────────────────────────────────────────────────────
@@ -146,12 +146,12 @@ class TestExposeFlag:
class TestDegenerateLayouts:
def test_none_layout(self) -> None:
engine = PeroOCREngine()
- assert engine._extract_token_confidences_from_layout(None) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(None)) is None
def test_empty_regions(self) -> None:
engine = PeroOCREngine()
layout = _mock_layout([])
- assert engine._extract_token_confidences_from_layout(layout) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(layout)) is None
def test_only_lines_without_conf_returns_none(self) -> None:
engine = PeroOCREngine()
@@ -161,7 +161,7 @@ class TestDegenerateLayouts:
_mock_line("ok2", None),
]),
])
- assert engine._extract_token_confidences_from_layout(layout) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(layout)) is None
# ──────────────────────────────────────────────────────────────────────────
@@ -242,7 +242,7 @@ class TestRunPipeline:
class TestEndToEndWithRunner:
def test_runner_picks_up_confidences(self) -> None:
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
from picarones.engines.base import EngineResult
ocr = EngineResult(
diff --git a/tests/test_sprint49_mistral_confidences.py b/tests/test_sprint49_mistral_confidences.py
index 325ab80a28915d3784132f826ffacf014d2196d8..c657b1ac7e6140d899238db85eb64a93cab997e6 100644
--- a/tests/test_sprint49_mistral_confidences.py
+++ b/tests/test_sprint49_mistral_confidences.py
@@ -42,7 +42,7 @@ class TestExtractFromResponse:
],
}],
}
- out = engine._extract_token_confidences_from_response(response)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(response))
assert out == [
{"token": "Bonjour", "confidence": 0.95},
{"token": "monde", "confidence": 0.90},
@@ -58,7 +58,7 @@ class TestExtractFromResponse:
],
}],
}
- out = engine._extract_token_confidences_from_response(response)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(response))
assert out is not None
# 3 tokens (2 mots + 1 mot), avec leurs confidences respectives
assert {"token": "première", "confidence": 0.88} in out
@@ -74,7 +74,7 @@ class TestExtractFromResponse:
],
}],
}
- out = engine._extract_token_confidences_from_response(response)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(response))
assert out == [
{"token": "bloc1", "confidence": 0.82},
{"token": "mot2", "confidence": 0.82},
@@ -90,7 +90,7 @@ class TestExtractFromResponse:
],
}],
}
- out = engine._extract_token_confidences_from_response(response)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(response))
assert out == [{"token": "ok", "confidence": 0.9}]
def test_skips_none_confidence(self) -> None:
@@ -104,7 +104,7 @@ class TestExtractFromResponse:
],
}],
}
- out = engine._extract_token_confidences_from_response(response)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(response))
assert out == [{"token": "avec_conf", "confidence": 0.85}]
def test_skips_negative_confidence(self) -> None:
@@ -117,7 +117,7 @@ class TestExtractFromResponse:
],
}],
}
- out = engine._extract_token_confidences_from_response(response)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(response))
assert out == [{"token": "ok", "confidence": 0.9}]
def test_combines_words_and_lines(self) -> None:
@@ -128,7 +128,7 @@ class TestExtractFromResponse:
"lines": [{"text": "ligne mots", "confidence": 0.7}],
}],
}
- out = engine._extract_token_confidences_from_response(response)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(response))
assert out is not None
assert len(out) == 3 # 1 word explicit + 2 mots de la ligne
@@ -141,17 +141,17 @@ class TestExtractFromResponse:
class TestDegenerateResponses:
def test_none_response(self) -> None:
engine = MistralOCREngine()
- assert engine._extract_token_confidences_from_response(None) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(None)) is None
def test_empty_dict(self) -> None:
engine = MistralOCREngine()
- assert engine._extract_token_confidences_from_response({}) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences({})) is None
def test_no_pages(self) -> None:
engine = MistralOCREngine()
- assert engine._extract_token_confidences_from_response(
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(
{"pages": []},
- ) is None
+ )) is None
def test_pages_without_confidences(self) -> None:
engine = MistralOCREngine()
@@ -160,12 +160,12 @@ class TestDegenerateResponses:
{"markdown": "Texte sans annotation de confidence"},
],
}
- assert engine._extract_token_confidences_from_response(response) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(response)) is None
def test_non_dict_input(self) -> None:
engine = MistralOCREngine()
- assert engine._extract_token_confidences_from_response("not a dict") is None
- assert engine._extract_token_confidences_from_response([1, 2, 3]) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences("not a dict")) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences([1, 2, 3])) is None
# ──────────────────────────────────────────────────────────────────────────
@@ -181,7 +181,7 @@ class TestExposeFlag:
"words": [{"text": "ok", "confidence": 0.9}],
}],
}
- assert engine._extract_token_confidences_from_response(response) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(response)) is None
# ──────────────────────────────────────────────────────────────────────────
@@ -207,7 +207,7 @@ def _mock_run_with_response(
return text, raw_response
monkeypatch.setattr(
- MistralOCREngine, "_run_ocr_with_response", _fake,
+ MistralOCREngine, "_run_with_native", _fake,
)
return engine
@@ -274,7 +274,7 @@ class TestRunOverride:
class TestEndToEndWithRunner:
def test_runner_picks_up_mistral_confidences(self) -> None:
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
from picarones.engines.base import EngineResult
ocr = EngineResult(
diff --git a/tests/test_sprint4_normalization_iiif.py b/tests/test_sprint4_normalization_iiif.py
index 4d8001e9ee3d5b5684cdb9807852a73a0a4772a9..016cbc9a9cda3f7c3e4a93d0a138b8d02d07ee51 100644
--- a/tests/test_sprint4_normalization_iiif.py
+++ b/tests/test_sprint4_normalization_iiif.py
@@ -4,14 +4,14 @@ from __future__ import annotations
import pytest
-from picarones.core.normalization import (
+from picarones.measurements.normalization import (
NormalizationProfile,
DEFAULT_DIPLOMATIC_PROFILE,
_apply_diplomatic_table,
get_builtin_profile,
)
-from picarones.core.metrics import compute_metrics, aggregate_metrics, MetricsResult
-from picarones.importers.iiif import (
+from picarones.measurements.metrics import compute_metrics, aggregate_metrics, MetricsResult
+from picarones.extras.importers.iiif import (
IIIFManifestParser,
parse_page_selector,
_extract_label,
@@ -205,7 +205,7 @@ class TestDiplomaticCER:
assert "diplomatic_profile_name" in d
def test_cer_diplomatic_with_custom_profile(self):
- from picarones.core.normalization import NormalizationProfile
+ from picarones.measurements.normalization import NormalizationProfile
profile = NormalizationProfile(
name="test_profile",
diplomatic_table={"ſ": "s"}
diff --git a/tests/test_sprint50_google_vision_confidences.py b/tests/test_sprint50_google_vision_confidences.py
index cbefd7b3fbc0818ce20c50e7d256e141cfc0960c..79742af78f64a87da712524b956962ed285a5a64 100644
--- a/tests/test_sprint50_google_vision_confidences.py
+++ b/tests/test_sprint50_google_vision_confidences.py
@@ -65,7 +65,7 @@ class TestExtractFromFullText:
def test_reconstructs_word_from_symbols(self) -> None:
engine = GoogleVisionEngine()
full = _full_text([_word("Bonjour", 0.95)])
- assert engine._extract_token_confidences_from_full_text(full) == [
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(full)) == [
{"token": "Bonjour", "confidence": 0.95},
]
@@ -75,7 +75,7 @@ class TestExtractFromFullText:
_word("Bonjour", 0.95),
_word("monde", 0.88),
])
- out = engine._extract_token_confidences_from_full_text(full)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(full))
assert out == [
{"token": "Bonjour", "confidence": 0.95},
{"token": "monde", "confidence": 0.88},
@@ -88,7 +88,7 @@ class TestExtractFromFullText:
{"symbols": [{"text": "nope"}]}, # pas de confidence
{"confidence": None, "symbols": [{"text": "nope"}]}, # None
])
- out = engine._extract_token_confidences_from_full_text(full)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(full))
assert out == [{"token": "ok", "confidence": 0.95}]
def test_skips_negative_confidence(self) -> None:
@@ -97,7 +97,7 @@ class TestExtractFromFullText:
_word("ok", 0.9),
_word("dropped", -0.1),
])
- out = engine._extract_token_confidences_from_full_text(full)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(full))
assert out == [{"token": "ok", "confidence": 0.9}]
def test_skips_empty_text(self) -> None:
@@ -106,7 +106,7 @@ class TestExtractFromFullText:
_word("", 0.95),
_word("ok", 0.9),
])
- out = engine._extract_token_confidences_from_full_text(full)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(full))
assert out == [{"token": "ok", "confidence": 0.9}]
def test_traverses_multiple_pages_and_blocks(self) -> None:
@@ -122,7 +122,7 @@ class TestExtractFromFullText:
]},
],
}
- out = engine._extract_token_confidences_from_full_text(full)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(full))
assert out is not None
tokens = [tc["token"] for tc in out]
assert tokens == ["alpha", "beta", "gamma"]
@@ -137,7 +137,7 @@ class TestExposeFlag:
def test_disabled_returns_none(self) -> None:
engine = GoogleVisionEngine(config={"expose_confidences": False})
full = _full_text([_word("ok", 0.95)])
- assert engine._extract_token_confidences_from_full_text(full) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(full)) is None
# ──────────────────────────────────────────────────────────────────────────
@@ -148,23 +148,23 @@ class TestExposeFlag:
class TestDegenerateInputs:
def test_none(self) -> None:
engine = GoogleVisionEngine()
- assert engine._extract_token_confidences_from_full_text(None) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(None)) is None
def test_empty_dict(self) -> None:
engine = GoogleVisionEngine()
- assert engine._extract_token_confidences_from_full_text({}) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences({})) is None
def test_no_pages(self) -> None:
engine = GoogleVisionEngine()
- assert engine._extract_token_confidences_from_full_text(
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(
{"pages": []},
- ) is None
+ )) is None
def test_pages_without_blocks(self) -> None:
engine = GoogleVisionEngine()
- assert engine._extract_token_confidences_from_full_text(
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(
{"pages": [{"text": "raw text only"}]},
- ) is None
+ )) is None
# ──────────────────────────────────────────────────────────────────────────
@@ -223,7 +223,7 @@ def _patch_run_with_full(
return text, full
monkeypatch.setattr(
- GoogleVisionEngine, "_run_ocr_with_full_annotation", _fake,
+ GoogleVisionEngine, "_run_with_native", _fake,
)
return engine
@@ -320,7 +320,7 @@ class TestRESTPath:
assert "pages" in full
# L'extraction passe ensuite normalement
- out = engine._extract_token_confidences_from_full_text(full)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(full))
assert out == [{"token": "Bonjour", "confidence": 0.97}]
@@ -331,7 +331,7 @@ class TestRESTPath:
class TestEndToEndWithRunner:
def test_runner_picks_up_google_vision_confidences(self) -> None:
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
from picarones.engines.base import EngineResult
ocr = EngineResult(
diff --git a/tests/test_sprint51_azure_confidences.py b/tests/test_sprint51_azure_confidences.py
index 357a190f12725bcf5643d8932b552245f4280295..d09f45c7bfdb87465d02806c0702c89bdacda32f 100644
--- a/tests/test_sprint51_azure_confidences.py
+++ b/tests/test_sprint51_azure_confidences.py
@@ -52,7 +52,7 @@ class TestExtractFromResult:
_word("Bonjour", 0.97),
_word("monde", 0.93),
])
- out = engine._extract_token_confidences_from_result(result)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(result))
assert out == [
{"token": "Bonjour", "confidence": 0.97},
{"token": "monde", "confidence": 0.93},
@@ -65,7 +65,7 @@ class TestExtractFromResult:
{"content": "no_conf"}, # pas de confidence
_word("none_conf", None),
])
- out = engine._extract_token_confidences_from_result(result)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(result))
assert out == [{"token": "ok", "confidence": 0.95}]
def test_skips_negative_confidence(self) -> None:
@@ -74,7 +74,7 @@ class TestExtractFromResult:
_word("ok", 0.9),
_word("dropped", -0.1),
])
- out = engine._extract_token_confidences_from_result(result)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(result))
assert out == [{"token": "ok", "confidence": 0.9}]
def test_skips_empty_content(self) -> None:
@@ -83,7 +83,7 @@ class TestExtractFromResult:
_word("", 0.95),
_word("ok", 0.9),
])
- out = engine._extract_token_confidences_from_result(result)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(result))
assert out == [{"token": "ok", "confidence": 0.9}]
def test_traverses_multiple_pages(self) -> None:
@@ -94,7 +94,7 @@ class TestExtractFromResult:
{"words": [_word("gamma", 0.8)]},
],
}
- out = engine._extract_token_confidences_from_result(result)
+ out = engine._normalize_token_confidences(engine._extract_raw_confidences(result))
assert [tc["token"] for tc in (out or [])] == ["alpha", "beta", "gamma"]
@@ -106,8 +106,8 @@ class TestExtractFromResult:
class TestExposeFlag:
def test_disabled_returns_none(self) -> None:
engine = AzureDocIntelEngine(config={"expose_confidences": False})
- assert engine._extract_token_confidences_from_result(
- _result([_word("ok", 0.9)]),
+ assert engine._normalize_token_confidences(
+ engine._extract_raw_confidences(_result([_word("ok", 0.9)])),
) is None
@@ -119,23 +119,23 @@ class TestExposeFlag:
class TestDegenerateInputs:
def test_none(self) -> None:
engine = AzureDocIntelEngine()
- assert engine._extract_token_confidences_from_result(None) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(None)) is None
def test_empty_dict(self) -> None:
engine = AzureDocIntelEngine()
- assert engine._extract_token_confidences_from_result({}) is None
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences({})) is None
def test_no_pages(self) -> None:
engine = AzureDocIntelEngine()
- assert engine._extract_token_confidences_from_result(
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(
{"pages": []},
- ) is None
+ )) is None
def test_pages_without_words(self) -> None:
engine = AzureDocIntelEngine()
- assert engine._extract_token_confidences_from_result(
+ assert engine._normalize_token_confidences(engine._extract_raw_confidences(
{"pages": [{"lines": [{"content": "no words"}]}]},
- ) is None
+ )) is None
# ──────────────────────────────────────────────────────────────────────────
@@ -194,7 +194,7 @@ def _patch_run_with_result(
return text, analyze_result
monkeypatch.setattr(
- AzureDocIntelEngine, "_run_ocr_with_result", _fake,
+ AzureDocIntelEngine, "_run_with_native", _fake,
)
return engine
@@ -252,7 +252,7 @@ class TestRunOverride:
class TestEndToEndWithRunner:
def test_runner_picks_up_azure_confidences(self) -> None:
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
from picarones.engines.base import EngineResult
ocr = EngineResult(
diff --git a/tests/test_sprint52_readability.py b/tests/test_sprint52_readability.py
index 27dafdb9a3a6ffdae33375c5f7190b2c2a647431..effba6db03029bec9f87e37739f6fe511f093d51 100644
--- a/tests/test_sprint52_readability.py
+++ b/tests/test_sprint52_readability.py
@@ -30,7 +30,7 @@ import pytest
from picarones.core.metric_registry import select_metrics
from picarones.core.modules import ArtifactType
-from picarones.core.readability import (
+from picarones.measurements.readability import (
count_sentences,
count_syllables,
count_syllables_word,
@@ -226,7 +226,7 @@ class TestFleschDelta:
class TestRegistryIntegration:
def test_flesch_metrics_registered_for_text_text(self) -> None:
# Force l'import qui peuple le registre
- import picarones.core.readability # noqa: F401
+ import picarones.measurements.readability # noqa: F401
selected = select_metrics(
(ArtifactType.TEXT, ArtifactType.TEXT),
diff --git a/tests/test_sprint53_reading_order.py b/tests/test_sprint53_reading_order.py
index 4426a0a04170a0a96f7f1cd1ba566c0db3f467e5..72e1d18bdca80ac65c3473bef8a0cf4bfa62e672 100644
--- a/tests/test_sprint53_reading_order.py
+++ b/tests/test_sprint53_reading_order.py
@@ -28,7 +28,7 @@ import pytest
from picarones.core.metric_registry import compute_at_junction, select_metrics
from picarones.core.modules import ArtifactType
-from picarones.core.reading_order import (
+from picarones.measurements.reading_order import (
compute_reading_order_metrics,
reading_order_f1,
)
@@ -184,7 +184,7 @@ class TestDetailedCounts:
class TestRegistryIntegration:
def test_metric_registered_for_reading_order_pair(self) -> None:
# Force l'import qui peuple le registre
- import picarones.core.reading_order # noqa: F401
+ import picarones.measurements.reading_order # noqa: F401
selected = select_metrics(
(ArtifactType.READING_ORDER, ArtifactType.READING_ORDER),
diff --git a/tests/test_sprint54_layout.py b/tests/test_sprint54_layout.py
index 13b5c0bf4aceb2dc69f81b9af88a651e8ab65ef4..f7afb4f1da430f9148dedbcc6ad07716fb194c6c 100644
--- a/tests/test_sprint54_layout.py
+++ b/tests/test_sprint54_layout.py
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest
-from picarones.core.layout import (
+from picarones.measurements.layout import (
Region,
_iou_bbox,
compute_layout_metrics,
diff --git a/tests/test_sprint55_unicode_blocks.py b/tests/test_sprint55_unicode_blocks.py
index 848cd526b4ec5cf2b50d05a5e1e5777b79ae7f23..14d5b0c7a7dc0c72b19ddc8f398d4fc357d32484 100644
--- a/tests/test_sprint55_unicode_blocks.py
+++ b/tests/test_sprint55_unicode_blocks.py
@@ -25,7 +25,7 @@ import pytest
from picarones.core.metric_registry import compute_at_junction, select_metrics
from picarones.core.modules import ArtifactType
-from picarones.core.unicode_blocks import (
+from picarones.measurements.unicode_blocks import (
compute_unicode_block_accuracy,
get_block,
unicode_block_global_accuracy,
@@ -187,7 +187,7 @@ class TestShortcut:
class TestRegistryIntegration:
def test_metric_registered_for_text_text(self) -> None:
# Force l'import qui peuple le registre
- import picarones.core.unicode_blocks # noqa: F401
+ import picarones.measurements.unicode_blocks # noqa: F401
selected = select_metrics(
(ArtifactType.TEXT, ArtifactType.TEXT),
diff --git a/tests/test_sprint56_abbreviations.py b/tests/test_sprint56_abbreviations.py
index 6b4993c684a5d5c9ab8fececb4b4f80d6d3a32ae..10e14cfe602cdc0826d72243ae5543afeea279a0 100644
--- a/tests/test_sprint56_abbreviations.py
+++ b/tests/test_sprint56_abbreviations.py
@@ -27,7 +27,7 @@ from __future__ import annotations
import pytest
-from picarones.core.abbreviations import (
+from picarones.measurements.abbreviations import (
ABBREVIATION_EXPANSIONS,
abbreviation_expansion_score,
abbreviation_strict_score,
@@ -205,7 +205,7 @@ class TestShortcuts:
class TestRegistryIntegration:
def test_metrics_registered_for_text_text(self) -> None:
# Force l'import qui peuple le registre
- import picarones.core.abbreviations # noqa: F401
+ import picarones.measurements.abbreviations # noqa: F401
selected = select_metrics(
(ArtifactType.TEXT, ArtifactType.TEXT),
diff --git a/tests/test_sprint57_mufi.py b/tests/test_sprint57_mufi.py
index becae6f72894e0db2288d7b2874fd64d2346f4be..c5c5aa63de92b8c00ba4cb8ad6a7ba7a257d6ff2 100644
--- a/tests/test_sprint57_mufi.py
+++ b/tests/test_sprint57_mufi.py
@@ -33,7 +33,7 @@ import pytest
from picarones.core.metric_registry import compute_at_junction, select_metrics
from picarones.core.modules import ArtifactType
-from picarones.core.mufi import (
+from picarones.measurements.mufi import (
compute_mufi_coverage,
is_mufi_char,
mufi_coverage,
@@ -203,7 +203,7 @@ class TestShortcut:
class TestRegistryIntegration:
def test_metric_registered_for_text_text(self) -> None:
# Force l'import qui peuple le registre
- import picarones.core.mufi # noqa: F401
+ import picarones.measurements.mufi # noqa: F401
selected = select_metrics(
(ArtifactType.TEXT, ArtifactType.TEXT),
diff --git a/tests/test_sprint58_early_modern.py b/tests/test_sprint58_early_modern.py
index 96d5701a8aa6c0fbc3c4b0c788acf7d319b728b6..c806e32bcb338583ec2bc3ef3df6ea55ef59b762 100644
--- a/tests/test_sprint58_early_modern.py
+++ b/tests/test_sprint58_early_modern.py
@@ -27,7 +27,7 @@ from __future__ import annotations
import pytest
-from picarones.core.early_modern_typography import (
+from picarones.measurements.early_modern_typography import (
AMPERSAND,
DOTLESS_I,
LIGATURES,
@@ -271,7 +271,7 @@ class TestShortcut:
class TestRegistryIntegration:
def test_metric_registered(self) -> None:
# Force l'import qui peuple le registre
- import picarones.core.early_modern_typography # noqa: F401
+ import picarones.measurements.early_modern_typography # noqa: F401
selected = select_metrics(
(ArtifactType.TEXT, ArtifactType.TEXT),
diff --git a/tests/test_sprint59_modern_archives.py b/tests/test_sprint59_modern_archives.py
index 45c0efca71be559510e5f0ffdb850c87bca3cf0f..492956fa7547568be1988c38f2bf28d92b1a1d97 100644
--- a/tests/test_sprint59_modern_archives.py
+++ b/tests/test_sprint59_modern_archives.py
@@ -36,7 +36,7 @@ from __future__ import annotations
import pytest
from picarones.core.metric_registry import compute_at_junction, select_metrics
-from picarones.core.modern_archives import (
+from picarones.measurements.modern_archives import (
ADDRESS,
ADMINISTRATIVE,
BIBLIOGRAPHIC,
@@ -497,7 +497,7 @@ class TestShortcuts:
class TestRegistryIntegration:
def test_strict_metric_registered(self) -> None:
- import picarones.core.modern_archives # noqa: F401
+ import picarones.measurements.modern_archives # noqa: F401
selected = select_metrics(
(ArtifactType.TEXT, ArtifactType.TEXT),
diff --git a/tests/test_sprint5_advanced_metrics.py b/tests/test_sprint5_advanced_metrics.py
index a50814d43587ed0fb8c65c4e6c7ce19f8ae12daa..4c1b7b2d4fa910567eaa0cc4c81ac5e900e483ba 100644
--- a/tests/test_sprint5_advanced_metrics.py
+++ b/tests/test_sprint5_advanced_metrics.py
@@ -17,7 +17,7 @@ import pytest
# Tests ConfusionMatrix
# ===========================================================================
-from picarones.core.confusion import (
+from picarones.measurements.confusion import (
EMPTY_CHAR,
build_confusion_matrix,
aggregate_confusion_matrices,
@@ -146,7 +146,7 @@ class TestTopConfusedChars:
# Tests LigatureScore
# ===========================================================================
-from picarones.core.char_scores import (
+from picarones.measurements.char_scores import (
LIGATURE_TABLE,
LigatureScore,
DiacriticScore,
@@ -288,7 +288,7 @@ class TestAggregateDiacriticScores:
# Tests TaxonomyResult
# ===========================================================================
-from picarones.core.taxonomy import (
+from picarones.measurements.taxonomy import (
TaxonomyResult,
ERROR_CLASSES,
classify_errors,
@@ -395,7 +395,7 @@ class TestAggregateTaxonomy:
# Tests StructureResult
# ===========================================================================
-from picarones.core.structure import (
+from picarones.measurements.structure import (
StructureResult,
analyze_structure,
aggregate_structure,
@@ -504,7 +504,7 @@ class TestAggregateStructure:
# Tests ImageQualityResult
# ===========================================================================
-from picarones.core.image_quality import (
+from picarones.measurements.image_quality import (
ImageQualityResult,
generate_mock_quality_scores,
aggregate_image_quality,
diff --git a/tests/test_sprint60_roman_numerals.py b/tests/test_sprint60_roman_numerals.py
index 487b1db34df4d85bf282096d4f643f4694f642be..cc5b91aaafbcbbe72e7e15f193d11b0fa4445717 100644
--- a/tests/test_sprint60_roman_numerals.py
+++ b/tests/test_sprint60_roman_numerals.py
@@ -23,7 +23,7 @@ import pytest
from picarones.core.metric_registry import compute_at_junction, select_metrics
from picarones.core.modules import ArtifactType
-from picarones.core.roman_numerals import (
+from picarones.measurements.roman_numerals import (
ALL_STATUSES,
STATUS_CASE_CHANGED,
STATUS_CONVERTED_TO_ARABIC,
@@ -385,7 +385,7 @@ class TestShortcuts:
class TestRegistryIntegration:
def test_metrics_registered(self) -> None:
- import picarones.core.roman_numerals # noqa: F401
+ import picarones.measurements.roman_numerals # noqa: F401
selected = select_metrics(
(ArtifactType.TEXT, ArtifactType.TEXT),
diff --git a/tests/test_sprint61_philological_runner.py b/tests/test_sprint61_philological_runner.py
index 9828f04f99193c7c1f78c13e499fb3eb516f72d5..73e9f794de06cc58bfc0dd1c3f489615d2148fde 100644
--- a/tests/test_sprint61_philological_runner.py
+++ b/tests/test_sprint61_philological_runner.py
@@ -24,12 +24,12 @@ Couvre :
from __future__ import annotations
-from picarones.core.philological_runner import (
+from picarones.measurements.philological_runner import (
aggregate_philological_metrics,
compute_philological_metrics,
)
from picarones.core.results import DocumentResult, EngineReport
-from picarones.core.metrics import MetricsResult
+from picarones.measurements.metrics import MetricsResult
def _make_doc(
@@ -255,7 +255,7 @@ class TestRunnerIntegration:
``philological_metrics`` quand la GT a du signal."""
def test_runner_attaches_philological(self, tmp_path) -> None:
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
from picarones.engines.base import EngineResult
# Créer une image fictive (le module image_quality échouera
@@ -280,7 +280,7 @@ class TestRunnerIntegration:
assert "roman_numerals" in dr.philological_metrics
def test_runner_omits_philological_on_plain_text(self, tmp_path) -> None:
- from picarones.core.runner import _compute_document_result
+ from picarones.measurements.runner import _compute_document_result
from picarones.engines.base import EngineResult
img = tmp_path / "doc.png"
diff --git a/tests/test_sprint63_pipeline_runner.py b/tests/test_sprint63_pipeline_runner.py
index 167869b87dca88a92b6db87aedd2378d508046a8..3eba51d4ae8986474b67390a7a45ed27caaa18f9 100644
--- a/tests/test_sprint63_pipeline_runner.py
+++ b/tests/test_sprint63_pipeline_runner.py
@@ -28,7 +28,7 @@ from typing import Any
from picarones.core.corpus import Document, GTLevel, TextGT
from picarones.core.modules import ArtifactType, BaseModule
-from picarones.core.pipeline_runner import (
+from picarones.core.pipeline import (
PipelineResult,
PipelineRunner,
PipelineSpec,
diff --git a/tests/test_sprint64_pipeline_benchmark.py b/tests/test_sprint64_pipeline_benchmark.py
index 05dba05412e672032b1cf50717db43ef293f4fe3..03cebd0b50f9c5136a603659b14c7f658b063eef 100644
--- a/tests/test_sprint64_pipeline_benchmark.py
+++ b/tests/test_sprint64_pipeline_benchmark.py
@@ -31,13 +31,13 @@ from typing import Any
from picarones.core.corpus import Corpus, Document, GTLevel, TextGT
from picarones.core.modules import ArtifactType, BaseModule
-from picarones.core.pipeline_benchmark import (
+from picarones.measurements.pipeline_benchmark import (
PipelineBenchmarkResult,
StepAggregate,
default_initial_inputs,
run_pipeline_benchmark,
)
-from picarones.core.pipeline_runner import PipelineSpec, PipelineStep
+from picarones.core.pipeline import PipelineSpec, PipelineStep
# ──────────────────────────────────────────────────────────────────────────
diff --git a/tests/test_sprint65_pipeline_comparison.py b/tests/test_sprint65_pipeline_comparison.py
index 546cf56171251a055e768b8fd44f742b33ed0d03..e39ee8c09f0bc5a12c6a91bdf60fcc93f50434a9 100644
--- a/tests/test_sprint65_pipeline_comparison.py
+++ b/tests/test_sprint65_pipeline_comparison.py
@@ -33,11 +33,11 @@ import pytest
from picarones.core.corpus import Corpus, Document, GTLevel, TextGT
from picarones.core.modules import ArtifactType, BaseModule
-from picarones.core.pipeline_comparison import (
+from picarones.measurements.pipeline_comparison import (
PipelineComparisonResult,
compare_pipelines,
)
-from picarones.core.pipeline_runner import PipelineSpec, PipelineStep
+from picarones.core.pipeline import PipelineSpec, PipelineStep
# ──────────────────────────────────────────────────────────────────────────
diff --git a/tests/test_sprint66_dag_branching.py b/tests/test_sprint66_dag_branching.py
index e2f45c8e6aa9cefb9cd65c959eddbbca0b63e383..4108ce33beadc9134ee85dacbd2243c955ef206a 100644
--- a/tests/test_sprint66_dag_branching.py
+++ b/tests/test_sprint66_dag_branching.py
@@ -32,7 +32,7 @@ from typing import Any
from picarones.core.corpus import Document, GTLevel, TextGT
from picarones.core.modules import ArtifactType, BaseModule
-from picarones.core.pipeline_runner import (
+from picarones.core.pipeline import (
PipelineRunner,
PipelineSpec,
PipelineStep,
diff --git a/tests/test_sprint67_pipeline_html.py b/tests/test_sprint67_pipeline_html.py
index 5cc29073795525c94658bd0a6522796b74e14ab2..717346860ae6438c60957933a3d6c2acb8bf73ac 100644
--- a/tests/test_sprint67_pipeline_html.py
+++ b/tests/test_sprint67_pipeline_html.py
@@ -21,7 +21,7 @@ from __future__ import annotations
import json
from pathlib import Path
-from picarones.core.pipeline_benchmark import (
+from picarones.measurements.pipeline_benchmark import (
PipelineBenchmarkResult,
StepAggregate,
)
diff --git a/tests/test_sprint68_pipeline_comparison_html.py b/tests/test_sprint68_pipeline_comparison_html.py
index 8921a394fc89d7c9b675f6b218f8a96d327ffde3..3ae7a7d6151f61e604db8e869b86d9d80c56d01a 100644
--- a/tests/test_sprint68_pipeline_comparison_html.py
+++ b/tests/test_sprint68_pipeline_comparison_html.py
@@ -32,11 +32,11 @@ import json
from pathlib import Path
from picarones.core.modules import ArtifactType
-from picarones.core.pipeline_benchmark import (
+from picarones.measurements.pipeline_benchmark import (
PipelineBenchmarkResult,
StepAggregate,
)
-from picarones.core.pipeline_comparison import PipelineComparisonResult
+from picarones.measurements.pipeline_comparison import PipelineComparisonResult
from picarones.report.pipeline_render import (
RankingSpec,
build_pipeline_comparison_report_html,
diff --git a/tests/test_sprint69_user_doc.py b/tests/test_sprint69_user_doc.py
index 3466c73604aba062745065c3d02caa3b98f4875e..519b9c757c0576b90e98b09b06d882bfc94eaa6a 100644
--- a/tests/test_sprint69_user_doc.py
+++ b/tests/test_sprint69_user_doc.py
@@ -151,7 +151,7 @@ class TestCodeSnippets:
# Les imports doivent pointer vers les vrais modules
# picarones.core.* et picarones.report.*
assert "from picarones.core.modules import" in doc
- assert "from picarones.core.pipeline_runner import" in doc
- assert "from picarones.core.pipeline_benchmark import" in doc
- assert "from picarones.core.pipeline_comparison import" in doc
+ assert "from picarones.core.pipeline import" in doc
+ assert "from picarones.measurements.pipeline_benchmark import" in doc
+ assert "from picarones.measurements.pipeline_comparison import" in doc
assert "from picarones.report.pipeline_render import" in doc
diff --git a/tests/test_sprint6_web_interface.py b/tests/test_sprint6_web_interface.py
index 78b1f89ac02597d1dcbced43d0da4ea63e529ad6..2ed63c64819d56ab0691504a60dca7a707e1840d 100644
--- a/tests/test_sprint6_web_interface.py
+++ b/tests/test_sprint6_web_interface.py
@@ -57,13 +57,13 @@ def client():
@pytest.fixture
def htr_catalogue():
- from picarones.importers.htr_united import HTRUnitedCatalogue
+ from picarones.extras.importers.htr_united import HTRUnitedCatalogue
return HTRUnitedCatalogue.from_demo()
@pytest.fixture
def hf_importer():
- from picarones.importers.huggingface import HuggingFaceImporter
+ from picarones.extras.importers.huggingface import HuggingFaceImporter
return HuggingFaceImporter()
@@ -74,7 +74,7 @@ def hf_importer():
class TestHTRUnitedEntry:
def test_from_dict_basic(self):
- from picarones.importers.htr_united import HTRUnitedEntry
+ from picarones.extras.importers.htr_united import HTRUnitedEntry
d = {
"id": "test-corpus", "title": "Test Corpus", "url": "https://github.com/test/corpus",
"language": ["French"], "script": ["Gothic"], "century": [14, 15],
@@ -88,7 +88,7 @@ class TestHTRUnitedEntry:
assert e.lines == 5000
def test_as_dict_roundtrip(self):
- from picarones.importers.htr_united import HTRUnitedEntry
+ from picarones.extras.importers.htr_united import HTRUnitedEntry
d = {
"id": "rtrip", "title": "Round Trip", "url": "https://github.com/a/b",
"language": ["Latin"], "script": ["Caroline"], "century": [9],
@@ -102,19 +102,19 @@ class TestHTRUnitedEntry:
assert out["format"] == "PAGE"
def test_century_str_roman(self):
- from picarones.importers.htr_united import HTRUnitedEntry
+ from picarones.extras.importers.htr_united import HTRUnitedEntry
e = HTRUnitedEntry(id="x", title="x", url="x", century=[12, 14])
cs = e.century_str
assert "XIIe" in cs
assert "XIVe" in cs
def test_century_str_single(self):
- from picarones.importers.htr_united import HTRUnitedEntry
+ from picarones.extras.importers.htr_united import HTRUnitedEntry
e = HTRUnitedEntry(id="x", title="x", url="x", century=[19])
assert "XIXe" in e.century_str
def test_default_fields(self):
- from picarones.importers.htr_united import HTRUnitedEntry
+ from picarones.extras.importers.htr_united import HTRUnitedEntry
e = HTRUnitedEntry(id="minimal", title="Min", url="http://x")
assert e.language == []
assert e.lines == 0
@@ -122,14 +122,14 @@ class TestHTRUnitedEntry:
assert e.tags == []
def test_from_dict_missing_fields(self):
- from picarones.importers.htr_united import HTRUnitedEntry
+ from picarones.extras.importers.htr_united import HTRUnitedEntry
e = HTRUnitedEntry.from_dict({"id": "sparse", "title": "Sparse"})
assert e.id == "sparse"
assert e.institution == ""
assert e.lines == 0
def test_as_dict_has_all_keys(self):
- from picarones.importers.htr_united import HTRUnitedEntry
+ from picarones.extras.importers.htr_united import HTRUnitedEntry
e = HTRUnitedEntry(id="k", title="K", url="http://k")
d = e.as_dict()
for key in ["id", "title", "url", "language", "script", "century",
@@ -137,7 +137,7 @@ class TestHTRUnitedEntry:
assert key in d, f"Missing key: {key}"
def test_url_preserved(self):
- from picarones.importers.htr_united import HTRUnitedEntry
+ from picarones.extras.importers.htr_united import HTRUnitedEntry
url = "https://github.com/HTR-United/cremma-medieval"
e = HTRUnitedEntry(id="c", title="CREMMA", url=url)
assert e.url == url
@@ -243,14 +243,14 @@ class TestHTRUnitedSearch:
class TestHTRUnitedImport:
def test_import_creates_meta_file(self, tmp_path, htr_catalogue):
- from picarones.importers.htr_united import import_htr_united_corpus
+ from picarones.extras.importers.htr_united import import_htr_united_corpus
entry = htr_catalogue.entries[0]
result = import_htr_united_corpus(entry, tmp_path, max_samples=5)
meta_file = Path(result["metadata_file"])
assert meta_file.exists()
def test_import_meta_content(self, tmp_path, htr_catalogue):
- from picarones.importers.htr_united import import_htr_united_corpus
+ from picarones.extras.importers.htr_united import import_htr_united_corpus
entry = htr_catalogue.entries[0]
result = import_htr_united_corpus(entry, tmp_path, max_samples=5)
meta = json.loads(Path(result["metadata_file"]).read_text())
@@ -258,14 +258,14 @@ class TestHTRUnitedImport:
assert meta["entry_id"] == entry.id
def test_import_returns_dict_keys(self, tmp_path, htr_catalogue):
- from picarones.importers.htr_united import import_htr_united_corpus
+ from picarones.extras.importers.htr_united import import_htr_united_corpus
entry = htr_catalogue.entries[0]
result = import_htr_united_corpus(entry, tmp_path, max_samples=5)
for k in ["entry_id", "title", "output_dir", "files_imported", "metadata_file"]:
assert k in result, f"Missing key: {k}"
def test_import_creates_output_dir(self, tmp_path, htr_catalogue):
- from picarones.importers.htr_united import import_htr_united_corpus
+ from picarones.extras.importers.htr_united import import_htr_united_corpus
entry = htr_catalogue.entries[0]
new_dir = tmp_path / "new_subdir" / "corpus"
import_htr_united_corpus(entry, new_dir, max_samples=5)
@@ -279,7 +279,7 @@ class TestHTRUnitedImport:
class TestHuggingFaceDataset:
def test_from_dict_basic(self):
- from picarones.importers.huggingface import HuggingFaceDataset
+ from picarones.extras.importers.huggingface import HuggingFaceDataset
d = {
"dataset_id": "test/dataset", "title": "Test Dataset",
"description": "A test dataset.", "language": ["French"],
@@ -292,7 +292,7 @@ class TestHuggingFaceDataset:
assert ds.downloads == 500
def test_as_dict_roundtrip(self):
- from picarones.importers.huggingface import HuggingFaceDataset
+ from picarones.extras.importers.huggingface import HuggingFaceDataset
ds = HuggingFaceDataset(
dataset_id="a/b", title="AB", description="desc",
language=["Latin"], tags=["htr"],
@@ -302,12 +302,12 @@ class TestHuggingFaceDataset:
assert d["language"] == ["Latin"]
def test_hf_url(self):
- from picarones.importers.huggingface import HuggingFaceDataset
+ from picarones.extras.importers.huggingface import HuggingFaceDataset
ds = HuggingFaceDataset(dataset_id="CATMuS/medieval", title="CATMuS")
assert ds.hf_url == "https://huggingface.co/datasets/CATMuS/medieval"
def test_as_dict_has_all_keys(self):
- from picarones.importers.huggingface import HuggingFaceDataset
+ from picarones.extras.importers.huggingface import HuggingFaceDataset
ds = HuggingFaceDataset(dataset_id="x/y", title="XY")
d = ds.as_dict()
for k in ["dataset_id", "title", "description", "language", "tags",
@@ -315,17 +315,17 @@ class TestHuggingFaceDataset:
assert k in d, f"Missing: {k}"
def test_default_source(self):
- from picarones.importers.huggingface import HuggingFaceDataset
+ from picarones.extras.importers.huggingface import HuggingFaceDataset
ds = HuggingFaceDataset(dataset_id="x/y", title="XY")
assert ds.source == "reference"
def test_from_dict_uses_id_as_fallback_title(self):
- from picarones.importers.huggingface import HuggingFaceDataset
+ from picarones.extras.importers.huggingface import HuggingFaceDataset
ds = HuggingFaceDataset.from_dict({"dataset_id": "owner/repo"})
assert ds.title == "owner/repo"
def test_replace_source_helper(self):
- from picarones.importers.huggingface import HuggingFaceDataset
+ from picarones.extras.importers.huggingface import HuggingFaceDataset
ds = HuggingFaceDataset(dataset_id="x/y", title="XY", source="reference")
ds2 = ds._replace_source("api")
assert ds2.source == "api"
@@ -392,23 +392,23 @@ class TestHuggingFaceImporter:
class TestHuggingFaceReferenceData:
def test_reference_datasets_loaded(self):
- from picarones.importers.huggingface import _REFERENCE_DATASETS
+ from picarones.extras.importers.huggingface import _REFERENCE_DATASETS
assert len(_REFERENCE_DATASETS) >= 5
def test_catmus_present(self):
- from picarones.importers.huggingface import _REFERENCE_DATASETS
+ from picarones.extras.importers.huggingface import _REFERENCE_DATASETS
ids = [d["dataset_id"] for d in _REFERENCE_DATASETS]
assert any("CATMuS" in did or "catmus" in did.lower() for did in ids)
def test_all_have_required_fields(self):
- from picarones.importers.huggingface import _REFERENCE_DATASETS
+ from picarones.extras.importers.huggingface import _REFERENCE_DATASETS
for d in _REFERENCE_DATASETS:
assert "dataset_id" in d
assert "title" in d
assert "language" in d
def test_all_are_image_to_text(self):
- from picarones.importers.huggingface import _REFERENCE_DATASETS
+ from picarones.extras.importers.huggingface import _REFERENCE_DATASETS
for d in _REFERENCE_DATASETS:
assert d.get("task", "image-to-text") == "image-to-text"
@@ -900,14 +900,14 @@ class TestRunnerProgressCallback:
def test_callback_signature_accepted(self):
"""run_benchmark accepte un paramètre progress_callback."""
import inspect
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
sig = inspect.signature(run_benchmark)
assert "progress_callback" in sig.parameters
def test_callback_is_optional(self):
"""progress_callback est optionnel (valeur par défaut None)."""
import inspect
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
sig = inspect.signature(run_benchmark)
param = sig.parameters["progress_callback"]
assert param.default is None
@@ -915,7 +915,7 @@ class TestRunnerProgressCallback:
def test_callback_called_with_mock_engine(self, tmp_corpus):
"""Le callback est appelé pour chaque document."""
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine
class MockEngine(BaseOCREngine):
@@ -936,7 +936,7 @@ class TestRunnerProgressCallback:
def test_callback_receives_engine_name(self, tmp_corpus):
"""Le callback reçoit le nom du moteur."""
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine
class MockEngine(BaseOCREngine):
@@ -957,7 +957,7 @@ class TestRunnerProgressCallback:
def test_callback_exception_does_not_crash(self, tmp_corpus):
"""Une exception dans le callback ne plante pas le benchmark."""
from picarones.core.corpus import load_corpus_from_directory
- from picarones.core.runner import run_benchmark
+ from picarones.measurements.runner import run_benchmark
from picarones.engines.base import BaseOCREngine
class MockEngine(BaseOCREngine):
diff --git a/tests/test_sprint70_pipeline_cli.py b/tests/test_sprint70_pipeline_cli.py
index 05c9d707f6620aecd7262e3a7f91478492275c80..f1e04eb147cdd4ca00cf469a78a55516748512f0 100644
--- a/tests/test_sprint70_pipeline_cli.py
+++ b/tests/test_sprint70_pipeline_cli.py
@@ -27,7 +27,7 @@ import pytest
from click.testing import CliRunner
from picarones.core.modules import ArtifactType, BaseModule
-from picarones.core.pipeline_spec_loader import (
+from picarones.measurements.pipeline_spec_loader import (
PipelineSpecLoadError,
_resolve_class,
load_comparison_specs_from_dict,
diff --git a/tests/test_sprint71_rare_tokens.py b/tests/test_sprint71_rare_tokens.py
index 0d15d74e011448ffbc1e8c23c96f6a412ddfd6b5..44b4a89f0c60ba523dc19c591cf14737ee2b80b9 100644
--- a/tests/test_sprint71_rare_tokens.py
+++ b/tests/test_sprint71_rare_tokens.py
@@ -24,7 +24,7 @@ from __future__ import annotations
import pytest
-from picarones.core.rare_tokens import (
+from picarones.measurements.rare_tokens import (
compute_rare_token_recall,
extract_rare_tokens,
frequency_distribution,
diff --git a/tests/test_sprint72_worst_lines.py b/tests/test_sprint72_worst_lines.py
index a823bc807a943b67dc9f64e65fa45e169716bed4..377352fad9671bad96e05da76dd5b8f2069e737d 100644
--- a/tests/test_sprint72_worst_lines.py
+++ b/tests/test_sprint72_worst_lines.py
@@ -27,7 +27,7 @@ from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
-from picarones.core.worst_lines import WorstLineEntry, extract_worst_lines
+from picarones.measurements.worst_lines import WorstLineEntry, extract_worst_lines
from picarones.report.worst_lines_render import build_worst_lines_table_html
diff --git a/tests/test_sprint73_baseline_comparison.py b/tests/test_sprint73_baseline_comparison.py
index dc9487e569a1f2ea8e0ee76f330f57d4d40b3868..1eabf03aee40101cece79b058abd916119081e23 100644
--- a/tests/test_sprint73_baseline_comparison.py
+++ b/tests/test_sprint73_baseline_comparison.py
@@ -31,13 +31,13 @@ from typing import Any, Optional
import pytest
-from picarones.core.baseline_comparison import (
+from picarones.measurements.baseline_comparison import (
compute_corpus_difficulty_percentile,
compute_engine_baseline,
)
-from picarones.core.narrative.detectors import detect_engine_off_baseline
-from picarones.core.narrative.facts import FactImportance, FactType
-from picarones.core.narrative.renderer import render_fact
+from picarones.measurements.narrative.detectors import detect_engine_off_baseline
+from picarones.core.facts import FactImportance, FactType
+from picarones.measurements.narrative.renderer import render_fact
# ──────────────────────────────────────────────────────────────────────────
diff --git a/tests/test_sprint75_taxonomy_cooccurrence.py b/tests/test_sprint75_taxonomy_cooccurrence.py
index 5f45422c7154895c90dd0bdfbcd9b70ee7617e1a..9c7b457f542dd893a7c1e49325611e074e7b6427 100644
--- a/tests/test_sprint75_taxonomy_cooccurrence.py
+++ b/tests/test_sprint75_taxonomy_cooccurrence.py
@@ -26,7 +26,7 @@ from pathlib import Path
import pytest
-from picarones.core.taxonomy_cooccurrence import (
+from picarones.measurements.taxonomy_cooccurrence import (
compute_taxonomy_cooccurrence,
)
from picarones.report.taxonomy_cooccurrence_render import (
diff --git a/tests/test_sprint76_taxonomy_intra_doc.py b/tests/test_sprint76_taxonomy_intra_doc.py
index d7b9c82bb3ef7f7111bc2ef81a35e93ff48ac42a..0c957d51373abc1f2975f157d7f60f8bdd312a3d 100644
--- a/tests/test_sprint76_taxonomy_intra_doc.py
+++ b/tests/test_sprint76_taxonomy_intra_doc.py
@@ -26,7 +26,7 @@ from pathlib import Path
import pytest
-from picarones.core.taxonomy_intra_doc import (
+from picarones.measurements.taxonomy_intra_doc import (
compute_taxonomy_position_heatmap,
)
from picarones.report.taxonomy_intra_doc_render import (
diff --git a/tests/test_sprint77_taxonomy_comparison.py b/tests/test_sprint77_taxonomy_comparison.py
index 9e8525adde5f818fb842b02ce1c956f382000c4a..b793b69a979313f60a8bd3f891f05377d1874e9d 100644
--- a/tests/test_sprint77_taxonomy_comparison.py
+++ b/tests/test_sprint77_taxonomy_comparison.py
@@ -23,7 +23,7 @@ from __future__ import annotations
import json
from pathlib import Path
-from picarones.core.taxonomy_comparison import (
+from picarones.measurements.taxonomy_comparison import (
RECOVERABILITY,
compare_taxonomies,
)
@@ -91,7 +91,7 @@ class TestCompare:
def test_recoverability_constant_complete(self) -> None:
# Sanité : RECOVERABILITY couvre toutes les classes du module
- from picarones.core.taxonomy import ERROR_CLASSES
+ from picarones.measurements.taxonomy import ERROR_CLASSES
for cls in ERROR_CLASSES:
assert cls in RECOVERABILITY
diff --git a/tests/test_sprint78_equivalence_profile.py b/tests/test_sprint78_equivalence_profile.py
index 30456577fa56b1277b0eb394c0d5debaac3151e4..2d5f1491896c4e7220c296d90278f1c35039b35b 100644
--- a/tests/test_sprint78_equivalence_profile.py
+++ b/tests/test_sprint78_equivalence_profile.py
@@ -23,7 +23,7 @@ Couvre :
from __future__ import annotations
-from picarones.core.equivalence_profile import (
+from picarones.measurements.equivalence_profile import (
BUILTIN_EQUIVALENCES,
EquivalenceRule,
apply_selected_equivalences,
diff --git a/tests/test_sprint79_cost_projection.py b/tests/test_sprint79_cost_projection.py
index 0fd6b5319b819bccbaead7821be7ca18369ef918..e9a47c16ec3967ef998bf4670aafa00100e45ca4 100644
--- a/tests/test_sprint79_cost_projection.py
+++ b/tests/test_sprint79_cost_projection.py
@@ -23,7 +23,7 @@ from __future__ import annotations
import pytest
-from picarones.core.cost_projection import (
+from picarones.measurements.cost_projection import (
ProjectedCost,
cost_gap_table,
project_all_engines,
@@ -31,7 +31,7 @@ from picarones.core.cost_projection import (
project_cost_total,
project_engine,
)
-from picarones.core.pricing import EngineCost
+from picarones.measurements.pricing import EngineCost
def _ec(name: str, cost_1k: float | None, co2_1k: float | None = None,
diff --git a/tests/test_sprint7_advanced_report.py b/tests/test_sprint7_advanced_report.py
index 522faed50528b4625d0f36effe4daf78aac810ee..cb2c1c284a71eb0af1ede1a4d88ee936e659f637 100644
--- a/tests/test_sprint7_advanced_report.py
+++ b/tests/test_sprint7_advanced_report.py
@@ -53,41 +53,41 @@ def html_s7(sample_benchmark_s7):
class TestBootstrapCI:
def test_returns_tuple_of_two(self):
- from picarones.core.statistics import bootstrap_ci
+ from picarones.measurements.statistics import bootstrap_ci
result = bootstrap_ci([0.1, 0.2, 0.3])
assert isinstance(result, tuple) and len(result) == 2
def test_lower_le_upper(self):
- from picarones.core.statistics import bootstrap_ci
+ from picarones.measurements.statistics import bootstrap_ci
lo, hi = bootstrap_ci([0.1, 0.2, 0.3, 0.4, 0.5])
assert lo <= hi
def test_ci_contains_mean(self):
- from picarones.core.statistics import bootstrap_ci
+ from picarones.measurements.statistics import bootstrap_ci
values = [0.1, 0.15, 0.2, 0.12, 0.18, 0.13, 0.17]
lo, hi = bootstrap_ci(values)
mean = sum(values) / len(values)
assert lo <= mean <= hi
def test_empty_returns_zeros(self):
- from picarones.core.statistics import bootstrap_ci
+ from picarones.measurements.statistics import bootstrap_ci
lo, hi = bootstrap_ci([])
assert lo == 0.0 and hi == 0.0
def test_single_value(self):
- from picarones.core.statistics import bootstrap_ci
+ from picarones.measurements.statistics import bootstrap_ci
lo, hi = bootstrap_ci([0.25])
assert lo <= 0.25 <= hi
def test_reproducible_with_seed(self):
- from picarones.core.statistics import bootstrap_ci
+ from picarones.measurements.statistics import bootstrap_ci
vals = [0.1, 0.2, 0.3, 0.15, 0.25]
r1 = bootstrap_ci(vals, seed=1)
r2 = bootstrap_ci(vals, seed=1)
assert r1 == r2
def test_wider_with_more_variance(self):
- from picarones.core.statistics import bootstrap_ci
+ from picarones.measurements.statistics import bootstrap_ci
narrow = [0.10, 0.11, 0.10, 0.11, 0.10]
wide = [0.01, 0.50, 0.02, 0.49, 0.01]
lo_n, hi_n = bootstrap_ci(narrow, n_iter=500)
@@ -101,7 +101,7 @@ class TestBootstrapCI:
class TestWilcoxonTest:
def test_returns_dict_with_keys(self):
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
r = wilcoxon_test([0.1]*5, [0.1]*5)
assert "statistic" in r
assert "p_value" in r
@@ -109,13 +109,13 @@ class TestWilcoxonTest:
assert "interpretation" in r
def test_identical_series_not_significant(self):
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
vals = [0.1, 0.2, 0.3, 0.15, 0.05]
r = wilcoxon_test(vals, vals)
assert not r["significant"]
def test_clearly_different_series_significant(self):
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
a = [0.01]*12
b = [0.80]*12
r = wilcoxon_test(a, b)
@@ -123,37 +123,37 @@ class TestWilcoxonTest:
assert r["p_value"] < 0.05
def test_p_value_in_range(self):
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
a = [0.1, 0.15, 0.2, 0.08]
b = [0.2, 0.25, 0.3, 0.18]
r = wilcoxon_test(a, b)
assert 0.0 <= r["p_value"] <= 1.0
def test_interpretation_is_string(self):
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
r = wilcoxon_test([0.1, 0.2], [0.1, 0.2])
assert isinstance(r["interpretation"], str) and len(r["interpretation"]) > 10
def test_n_pairs_correct(self):
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
r = wilcoxon_test([0.1, 0.2, 0.3], [0.1, 0.2, 0.3])
# tous les diffs = 0, filtrés en mode wilcox
assert r["n_pairs"] == 0
def test_mismatched_lengths_raises(self):
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
with pytest.raises(ValueError):
wilcoxon_test([0.1, 0.2], [0.1])
def test_w_plus_w_minus_present(self):
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
a = [0.1, 0.2, 0.3, 0.15, 0.25, 0.18, 0.12, 0.22, 0.08, 0.27]
b = [0.2, 0.3, 0.4, 0.25, 0.35, 0.28, 0.22, 0.32, 0.18, 0.37]
r = wilcoxon_test(a, b)
assert "W_plus" in r and "W_minus" in r
def test_significant_larger_sample(self):
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
import random
rng = random.Random(0)
a = [rng.uniform(0.0, 0.05) for _ in range(15)]
@@ -162,7 +162,7 @@ class TestWilcoxonTest:
assert r["significant"]
def test_symmetry(self):
- from picarones.core.statistics import wilcoxon_test
+ from picarones.measurements.statistics import wilcoxon_test
a = [0.1, 0.2, 0.3, 0.15, 0.25, 0.18, 0.22, 0.08, 0.27, 0.14]
b = [0.2, 0.3, 0.4, 0.25, 0.35, 0.28, 0.32, 0.18, 0.37, 0.24]
r_ab = wilcoxon_test(a, b)
@@ -177,35 +177,35 @@ class TestWilcoxonTest:
class TestPairwiseStats:
def test_returns_list(self):
- from picarones.core.statistics import compute_pairwise_stats
+ from picarones.measurements.statistics import compute_pairwise_stats
r = compute_pairwise_stats({"A": [0.1, 0.2], "B": [0.3, 0.4]})
assert isinstance(r, list)
def test_correct_pair_count_2_engines(self):
- from picarones.core.statistics import compute_pairwise_stats
+ from picarones.measurements.statistics import compute_pairwise_stats
r = compute_pairwise_stats({"A": [0.1]*5, "B": [0.2]*5})
assert len(r) == 1
def test_correct_pair_count_3_engines(self):
- from picarones.core.statistics import compute_pairwise_stats
+ from picarones.measurements.statistics import compute_pairwise_stats
r = compute_pairwise_stats({
"A": [0.1]*5, "B": [0.2]*5, "C": [0.3]*5
})
assert len(r) == 3
def test_pair_has_engine_names(self):
- from picarones.core.statistics import compute_pairwise_stats
+ from picarones.measurements.statistics import compute_pairwise_stats
r = compute_pairwise_stats({"A": [0.1]*5, "B": [0.2]*5})
assert r[0]["engine_a"] in ["A", "B"]
assert r[0]["engine_b"] in ["A", "B"]
def test_pair_has_p_value(self):
- from picarones.core.statistics import compute_pairwise_stats
+ from picarones.measurements.statistics import compute_pairwise_stats
r = compute_pairwise_stats({"A": [0.1]*5, "B": [0.2]*5})
assert "p_value" in r[0]
def test_single_engine_returns_empty(self):
- from picarones.core.statistics import compute_pairwise_stats
+ from picarones.measurements.statistics import compute_pairwise_stats
r = compute_pairwise_stats({"A": [0.1]*5})
assert r == []
@@ -216,33 +216,33 @@ class TestPairwiseStats:
class TestReliabilityCurve:
def test_returns_list(self):
- from picarones.core.statistics import compute_reliability_curve
+ from picarones.measurements.statistics import compute_reliability_curve
r = compute_reliability_curve([0.1, 0.2, 0.3])
assert isinstance(r, list)
def test_correct_number_of_steps(self):
- from picarones.core.statistics import compute_reliability_curve
+ from picarones.measurements.statistics import compute_reliability_curve
r = compute_reliability_curve([0.1]*10, steps=5)
assert len(r) == 5
def test_pct_docs_increases(self):
- from picarones.core.statistics import compute_reliability_curve
+ from picarones.measurements.statistics import compute_reliability_curve
r = compute_reliability_curve([0.1, 0.2, 0.3, 0.4, 0.5], steps=5)
pcts = [p["pct_docs"] for p in r]
assert pcts == sorted(pcts)
def test_mean_cer_increases(self):
- from picarones.core.statistics import compute_reliability_curve
+ from picarones.measurements.statistics import compute_reliability_curve
r = compute_reliability_curve([0.05, 0.10, 0.20, 0.30, 0.50], steps=5)
cers = [p["mean_cer"] for p in r]
assert cers[0] <= cers[-1]
def test_empty_returns_empty(self):
- from picarones.core.statistics import compute_reliability_curve
+ from picarones.measurements.statistics import compute_reliability_curve
assert compute_reliability_curve([]) == []
def test_last_point_includes_all(self):
- from picarones.core.statistics import compute_reliability_curve
+ from picarones.measurements.statistics import compute_reliability_curve
vals = [0.1, 0.2, 0.3]
r = compute_reliability_curve(vals, steps=4)
last = r[-1]
@@ -250,7 +250,7 @@ class TestReliabilityCurve:
assert last["mean_cer"] == pytest.approx(expected, rel=1e-4)
def test_each_point_has_required_keys(self):
- from picarones.core.statistics import compute_reliability_curve
+ from picarones.measurements.statistics import compute_reliability_curve
r = compute_reliability_curve([0.1, 0.2, 0.3], steps=3)
for p in r:
assert "pct_docs" in p and "mean_cer" in p
@@ -262,47 +262,47 @@ class TestReliabilityCurve:
class TestVennData:
def test_venn2_type(self):
- from picarones.core.statistics import compute_venn_data
+ from picarones.measurements.statistics import compute_venn_data
r = compute_venn_data({"A": {"e1","e2"}, "B": {"e2","e3"}})
assert r["type"] == "venn2"
def test_venn3_type(self):
- from picarones.core.statistics import compute_venn_data
+ from picarones.measurements.statistics import compute_venn_data
r = compute_venn_data({"A": {"e1"}, "B": {"e2"}, "C": {"e3"}})
assert r["type"] == "venn3"
def test_venn2_counts_correct(self):
- from picarones.core.statistics import compute_venn_data
+ from picarones.measurements.statistics import compute_venn_data
r = compute_venn_data({"A": {"e1","e2","e3"}, "B": {"e2","e3","e4"}})
assert r["only_a"] == 1
assert r["only_b"] == 1
assert r["both"] == 2
def test_venn2_disjoint(self):
- from picarones.core.statistics import compute_venn_data
+ from picarones.measurements.statistics import compute_venn_data
r = compute_venn_data({"A": {"e1"}, "B": {"e2"}})
assert r["both"] == 0
assert r["only_a"] == 1
assert r["only_b"] == 1
def test_venn2_subset(self):
- from picarones.core.statistics import compute_venn_data
+ from picarones.measurements.statistics import compute_venn_data
r = compute_venn_data({"A": {"e1","e2"}, "B": {"e1","e2","e3"}})
assert r["only_a"] == 0
def test_venn3_abc_count(self):
- from picarones.core.statistics import compute_venn_data
+ from picarones.measurements.statistics import compute_venn_data
shared = {"e1","e2"}
r = compute_venn_data({"A": shared, "B": shared, "C": shared})
assert r["abc"] == 2
def test_empty_returns_empty(self):
- from picarones.core.statistics import compute_venn_data
+ from picarones.measurements.statistics import compute_venn_data
r = compute_venn_data({})
assert r == {}
def test_labels_present(self):
- from picarones.core.statistics import compute_venn_data
+ from picarones.measurements.statistics import compute_venn_data
r = compute_venn_data({"moteur_a": {"e1"}, "moteur_b": {"e2"}})
assert r["label_a"] == "moteur_a"
assert r["label_b"] == "moteur_b"
@@ -324,17 +324,17 @@ class TestErrorClustering:
]
def test_returns_list(self):
- from picarones.core.statistics import cluster_errors
+ from picarones.measurements.statistics import cluster_errors
result = cluster_errors(self._sample_data())
assert isinstance(result, list)
def test_max_clusters_respected(self):
- from picarones.core.statistics import cluster_errors
+ from picarones.measurements.statistics import cluster_errors
result = cluster_errors(self._sample_data(), max_clusters=3)
assert len(result) <= 3
def test_cluster_has_required_keys(self):
- from picarones.core.statistics import cluster_errors
+ from picarones.measurements.statistics import cluster_errors
result = cluster_errors(self._sample_data())
if result:
c = result[0]
@@ -344,7 +344,7 @@ class TestErrorClustering:
assert hasattr(c, "examples")
def test_as_dict_method(self):
- from picarones.core.statistics import cluster_errors
+ from picarones.measurements.statistics import cluster_errors
result = cluster_errors(self._sample_data())
if result:
d = result[0].as_dict()
@@ -354,24 +354,24 @@ class TestErrorClustering:
assert "examples" in d
def test_sorted_by_count_descending(self):
- from picarones.core.statistics import cluster_errors
+ from picarones.measurements.statistics import cluster_errors
result = cluster_errors(self._sample_data())
if len(result) >= 2:
assert result[0].count >= result[1].count
def test_examples_capped_at_5(self):
- from picarones.core.statistics import cluster_errors
+ from picarones.measurements.statistics import cluster_errors
result = cluster_errors(self._sample_data())
for c in result:
assert len(c.as_dict()["examples"]) <= 5
def test_empty_data_returns_empty(self):
- from picarones.core.statistics import cluster_errors
+ from picarones.measurements.statistics import cluster_errors
result = cluster_errors([])
assert result == []
def test_cluster_id_unique(self):
- from picarones.core.statistics import cluster_errors
+ from picarones.measurements.statistics import cluster_errors
result = cluster_errors(self._sample_data())
ids = [c.cluster_id for c in result]
assert len(ids) == len(set(ids))
@@ -392,12 +392,12 @@ class TestCorrelationMatrix:
]
def test_returns_dict_with_labels_and_matrix(self):
- from picarones.core.statistics import compute_correlation_matrix
+ from picarones.measurements.statistics import compute_correlation_matrix
r = compute_correlation_matrix(self._sample_metrics())
assert "labels" in r and "matrix" in r
def test_matrix_is_square(self):
- from picarones.core.statistics import compute_correlation_matrix
+ from picarones.measurements.statistics import compute_correlation_matrix
r = compute_correlation_matrix(self._sample_metrics())
n = len(r["labels"])
assert len(r["matrix"]) == n
@@ -405,13 +405,13 @@ class TestCorrelationMatrix:
assert len(row) == n
def test_diagonal_is_one(self):
- from picarones.core.statistics import compute_correlation_matrix
+ from picarones.measurements.statistics import compute_correlation_matrix
r = compute_correlation_matrix(self._sample_metrics())
for i in range(len(r["labels"])):
assert r["matrix"][i][i] == pytest.approx(1.0)
def test_cer_quality_negatively_correlated(self):
- from picarones.core.statistics import compute_correlation_matrix
+ from picarones.measurements.statistics import compute_correlation_matrix
r = compute_correlation_matrix(self._sample_metrics())
labels = r["labels"]
if "cer" in labels and "quality_score" in labels:
@@ -420,7 +420,7 @@ class TestCorrelationMatrix:
assert r["matrix"][i][j] < 0 # plus la qualité est bonne, plus le CER est bas
def test_symmetric_matrix(self):
- from picarones.core.statistics import compute_correlation_matrix
+ from picarones.measurements.statistics import compute_correlation_matrix
r = compute_correlation_matrix(self._sample_metrics())
n = len(r["labels"])
for i in range(n):
@@ -428,18 +428,18 @@ class TestCorrelationMatrix:
assert r["matrix"][i][j] == pytest.approx(r["matrix"][j][i], abs=1e-6)
def test_empty_returns_empty(self):
- from picarones.core.statistics import compute_correlation_matrix
+ from picarones.measurements.statistics import compute_correlation_matrix
r = compute_correlation_matrix([])
assert r == {"labels": [], "matrix": []}
def test_custom_metric_keys(self):
- from picarones.core.statistics import compute_correlation_matrix
+ from picarones.measurements.statistics import compute_correlation_matrix
data = [{"a": 1.0, "b": 2.0, "c": 3.0}] * 5
r = compute_correlation_matrix(data, metric_keys=["a", "b"])
assert r["labels"] == ["a", "b"]
def test_values_in_range(self):
- from picarones.core.statistics import compute_correlation_matrix
+ from picarones.measurements.statistics import compute_correlation_matrix
r = compute_correlation_matrix(self._sample_metrics())
for row in r["matrix"]:
for v in row:
@@ -452,60 +452,60 @@ class TestCorrelationMatrix:
class TestDifficultyScore:
def test_returns_difficulty_score(self):
- from picarones.core.difficulty import compute_difficulty_score
+ from picarones.measurements.difficulty import compute_difficulty_score
ds = compute_difficulty_score("doc1", "maiſtre Froiſſart", [0.1, 0.2, 0.3])
- from picarones.core.difficulty import DifficultyScore
+ from picarones.measurements.difficulty import DifficultyScore
assert isinstance(ds, DifficultyScore)
def test_score_in_range(self):
- from picarones.core.difficulty import compute_difficulty_score
+ from picarones.measurements.difficulty import compute_difficulty_score
ds = compute_difficulty_score("doc1", "hello world", [0.1, 0.2])
assert 0.0 <= ds.score <= 1.0
def test_more_variance_higher_score(self):
- from picarones.core.difficulty import compute_difficulty_score
+ from picarones.measurements.difficulty import compute_difficulty_score
low_var = compute_difficulty_score("doc1", "hello", [0.1, 0.1, 0.1])
high_var = compute_difficulty_score("doc1", "hello", [0.0, 0.5, 1.0])
assert high_var.score > low_var.score
def test_bad_quality_image_harder(self):
- from picarones.core.difficulty import compute_difficulty_score
+ from picarones.measurements.difficulty import compute_difficulty_score
good_img = compute_difficulty_score("doc1", "hello", [0.1], image_quality_score=0.9)
bad_img = compute_difficulty_score("doc1", "hello", [0.1], image_quality_score=0.1)
assert bad_img.score > good_img.score
def test_special_chars_increase_difficulty(self):
- from picarones.core.difficulty import compute_difficulty_score
+ from picarones.measurements.difficulty import compute_difficulty_score
plain = compute_difficulty_score("doc1", "hello world plain text", [0.1])
heritage = compute_difficulty_score("doc1", "maiſtre Froiſſart ꝑ &", [0.1])
assert heritage.score > plain.score
def test_components_present(self):
- from picarones.core.difficulty import compute_difficulty_score
+ from picarones.measurements.difficulty import compute_difficulty_score
ds = compute_difficulty_score("doc1", "text", [0.1, 0.2])
assert hasattr(ds, "variance_component")
assert hasattr(ds, "quality_component")
assert hasattr(ds, "density_component")
def test_as_dict_has_doc_id(self):
- from picarones.core.difficulty import compute_difficulty_score
+ from picarones.measurements.difficulty import compute_difficulty_score
ds = compute_difficulty_score("folio_001", "text", [0.1])
d = ds.as_dict()
assert d["doc_id"] == "folio_001"
def test_as_dict_rounded(self):
- from picarones.core.difficulty import compute_difficulty_score
+ from picarones.measurements.difficulty import compute_difficulty_score
ds = compute_difficulty_score("doc1", "text", [0.1])
d = ds.as_dict()
assert isinstance(d["score"], float)
def test_no_engines_gives_low_variance(self):
- from picarones.core.difficulty import compute_difficulty_score
+ from picarones.measurements.difficulty import compute_difficulty_score
ds = compute_difficulty_score("doc1", "text", [])
assert ds.cer_variance == 0.0
def test_difficulty_label(self):
- from picarones.core.difficulty import difficulty_label
+ from picarones.measurements.difficulty import difficulty_label
assert difficulty_label(0.1) == "Facile"
assert difficulty_label(0.35) == "Modéré"
assert difficulty_label(0.6) == "Difficile"
@@ -518,7 +518,7 @@ class TestDifficultyScore:
class TestAllDifficulties:
def test_returns_dict(self):
- from picarones.core.difficulty import compute_all_difficulties
+ from picarones.measurements.difficulty import compute_all_difficulties
r = compute_all_difficulties(
["doc1", "doc2"],
{"doc1": "hello", "doc2": "world"},
@@ -527,7 +527,7 @@ class TestAllDifficulties:
assert isinstance(r, dict)
def test_all_docs_present(self):
- from picarones.core.difficulty import compute_all_difficulties
+ from picarones.measurements.difficulty import compute_all_difficulties
r = compute_all_difficulties(
["d1", "d2", "d3"],
{"d1": "a", "d2": "b", "d3": "c"},
@@ -536,7 +536,7 @@ class TestAllDifficulties:
assert set(r.keys()) == {"d1", "d2", "d3"}
def test_scores_in_range(self):
- from picarones.core.difficulty import compute_all_difficulties
+ from picarones.measurements.difficulty import compute_all_difficulties
r = compute_all_difficulties(
["d1", "d2"],
{"d1": "maiſtre Jean", "d2": "simple text"},
@@ -546,7 +546,7 @@ class TestAllDifficulties:
assert 0.0 <= ds.score <= 1.0
def test_with_image_quality(self):
- from picarones.core.difficulty import compute_all_difficulties
+ from picarones.measurements.difficulty import compute_all_difficulties
r = compute_all_difficulties(
["d1"],
{"d1": "text"},
@@ -558,12 +558,12 @@ class TestAllDifficulties:
assert r["d1"].quality_component > 0.5
def test_empty_corpus(self):
- from picarones.core.difficulty import compute_all_difficulties
+ from picarones.measurements.difficulty import compute_all_difficulties
r = compute_all_difficulties([], {}, {})
assert r == {}
def test_missing_gt_handled(self):
- from picarones.core.difficulty import compute_all_difficulties
+ from picarones.measurements.difficulty import compute_all_difficulties
r = compute_all_difficulties(
["d1"],
{}, # GT manquante
diff --git a/tests/test_sprint80_lexical_modernization.py b/tests/test_sprint80_lexical_modernization.py
index 15e32d7e68e76f45cdb7d237867c0f34d92c644d..89d18aab72cdad1cd9cb0a48668ca15d703844d5 100644
--- a/tests/test_sprint80_lexical_modernization.py
+++ b/tests/test_sprint80_lexical_modernization.py
@@ -27,7 +27,7 @@ from __future__ import annotations
import json
from pathlib import Path
-from picarones.core.lexical_modernization import (
+from picarones.measurements.lexical_modernization import (
aggregate_lexical_modernization,
compute_lexical_modernization,
top_modernized_tokens,
diff --git a/tests/test_sprint81_robustness_projection.py b/tests/test_sprint81_robustness_projection.py
index 1c40c7435dcb03a962946e19404729e3d9c34707..c6da5e8919e6f921fd047b044b6b402836f8ef6c 100644
--- a/tests/test_sprint81_robustness_projection.py
+++ b/tests/test_sprint81_robustness_projection.py
@@ -24,7 +24,7 @@ from __future__ import annotations
import pytest
-from picarones.core.robustness_projection import (
+from picarones.measurements.robustness_projection import (
_extract_quality_value,
_interpolate_cer,
aggregate_projection_per_engine,
diff --git a/tests/test_sprint82_levers.py b/tests/test_sprint82_levers.py
index c0a25fdf6adfffd69fd3ee171fc10b52cf5aa79c..bc240335c0ca52f7a981edf70191c685789e8a72 100644
--- a/tests/test_sprint82_levers.py
+++ b/tests/test_sprint82_levers.py
@@ -19,7 +19,7 @@ import json
import re
from pathlib import Path
-from picarones.core.levers import (
+from picarones.measurements.levers import (
Lever,
LeverImportance,
LeverType,
diff --git a/tests/test_sprint83_reliability.py b/tests/test_sprint83_reliability.py
index 847b2ebb10655170d9974dc58deacb2f519ab09a..937c1b72d5be302377dd57e889fb2c81de5a125d 100644
--- a/tests/test_sprint83_reliability.py
+++ b/tests/test_sprint83_reliability.py
@@ -29,7 +29,7 @@ from __future__ import annotations
import pytest
-from picarones.core.reliability import (
+from picarones.measurements.reliability import (
_aligned_char_pairs,
cohen_kappa,
compute_iaa,
diff --git a/tests/test_sprint84_searchability.py b/tests/test_sprint84_searchability.py
index 4f574fdcc7aaafce451d5bba2ae815b7f1bc8bb7..bf3486a83bba4a525290a245c4f5fa4d522b2725 100644
--- a/tests/test_sprint84_searchability.py
+++ b/tests/test_sprint84_searchability.py
@@ -23,7 +23,7 @@ from __future__ import annotations
import pytest
-from picarones.core.searchability import (
+from picarones.measurements.searchability import (
compute_searchability,
levenshtein_distance,
searchability_recall_metric,
diff --git a/tests/test_sprint85_numerical_sequences.py b/tests/test_sprint85_numerical_sequences.py
index ebda43afbc1a2ca641a7233463044bd6dd7d6505..709d1864f1e55f80520f37f54bca29b087dd48e1 100644
--- a/tests/test_sprint85_numerical_sequences.py
+++ b/tests/test_sprint85_numerical_sequences.py
@@ -16,7 +16,7 @@ Couvre :
from __future__ import annotations
-from picarones.core.numerical_sequences import (
+from picarones.measurements.numerical_sequences import (
CATEGORIES,
_detect_currencies,
_detect_foliations,
diff --git a/tests/test_sprint86_aii5_html.py b/tests/test_sprint86_aii5_html.py
index 0ecc4899a3107e567a434ccb3e5994a53a9d1687..f62cd8c5739ec61e0d8b81d1db5bdc2c387d637e 100644
--- a/tests/test_sprint86_aii5_html.py
+++ b/tests/test_sprint86_aii5_html.py
@@ -18,11 +18,11 @@ from __future__ import annotations
import json
from pathlib import Path
-from picarones.core.numerical_sequences_runner import (
+from picarones.measurements.numerical_sequences_runner import (
aggregate_numerical_sequence_metrics,
compute_numerical_sequence_metrics_adaptive,
)
-from picarones.core.metrics import MetricsResult
+from picarones.measurements.metrics import MetricsResult
from picarones.core.results import DocumentResult, EngineReport
@@ -32,7 +32,7 @@ def _stub_metrics() -> MetricsResult:
wer=0.0, wer_normalized=0.0, mer=0.0, wil=0.0,
reference_length=0, hypothesis_length=0,
)
-from picarones.core.searchability_runner import (
+from picarones.measurements.searchability_runner import (
aggregate_searchability_metrics,
compute_searchability_metrics,
)
diff --git a/tests/test_sprint87_readability_html.py b/tests/test_sprint87_readability_html.py
index 9b53e7e4bdb8b5ffe7888f96e33cf7adbc165282..2b37cff13ad2926e674f1280dc382fd6cb00b61e 100644
--- a/tests/test_sprint87_readability_html.py
+++ b/tests/test_sprint87_readability_html.py
@@ -16,8 +16,8 @@ from __future__ import annotations
import json
from pathlib import Path
-from picarones.core.metrics import MetricsResult
-from picarones.core.readability_runner import (
+from picarones.measurements.metrics import MetricsResult
+from picarones.measurements.readability_runner import (
aggregate_readability_metrics,
compute_readability_metrics,
)
diff --git a/tests/test_sprint88_robustness_projection_html.py b/tests/test_sprint88_robustness_projection_html.py
index 8267f4fccff46fecb994e2787a4d6d01d6be5335..8dd9d461ac6d477390f45712c19dc4e0a440ecad 100644
--- a/tests/test_sprint88_robustness_projection_html.py
+++ b/tests/test_sprint88_robustness_projection_html.py
@@ -20,7 +20,7 @@ from __future__ import annotations
import json
from pathlib import Path
-from picarones.core.robustness_projection import (
+from picarones.measurements.robustness_projection import (
aggregate_projection_per_engine,
project_robustness_on_corpus,
)
diff --git a/tests/test_sprint89_specialization.py b/tests/test_sprint89_specialization.py
index 78f826c2105bf5fe0390f13529346b55ac91d8fc..586fae5a0b7db494e59092375c6e00a5455792c4 100644
--- a/tests/test_sprint89_specialization.py
+++ b/tests/test_sprint89_specialization.py
@@ -15,7 +15,7 @@ from __future__ import annotations
import json
from pathlib import Path
-from picarones.core.specialization import (
+from picarones.measurements.specialization import (
DEFAULT_THRESHOLDS,
classify_specialization,
compute_specialization_matrix,
diff --git a/tests/test_sprint8_escriptorium_gallica.py b/tests/test_sprint8_escriptorium_gallica.py
index c3629f67274193689be3211a1fb5632151b30388..2b34364e956e0ce16810059ca131fdf3fe41e7b7 100644
--- a/tests/test_sprint8_escriptorium_gallica.py
+++ b/tests/test_sprint8_escriptorium_gallica.py
@@ -32,54 +32,54 @@ if TYPE_CHECKING:
class TestEScriptoriumClient:
def test_import_module(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
assert EScriptoriumClient is not None
def test_init_attributes(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://escriptorium.example.org", token="tok123", timeout=60)
assert client.base_url == "https://escriptorium.example.org"
assert client.token == "tok123"
assert client.timeout == 60
def test_base_url_trailing_slash_stripped(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://escriptorium.example.org/", token="tok")
assert not client.base_url.endswith("/")
def test_headers_contain_token(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="mytoken")
headers = client._headers()
assert "Token mytoken" in headers.get("Authorization", "")
def test_headers_contain_accept_json(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="tok")
headers = client._headers()
assert "application/json" in headers.get("Accept", "")
def test_test_connection_success(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="tok")
with patch.object(client, "_get", return_value={"results": [], "count": 0}):
assert client.test_connection() is True
def test_test_connection_failure(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="bad")
with patch.object(client, "_get", side_effect=RuntimeError("403")):
assert client.test_connection() is False
def test_list_projects_empty(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="tok")
with patch.object(client, "_paginate", return_value=[]):
projects = client.list_projects()
assert projects == []
def test_list_projects_parses_items(self):
- from picarones.importers.escriptorium import EScriptoriumClient, EScriptoriumProject
+ from picarones.extras.importers.escriptorium import EScriptoriumClient, EScriptoriumProject
client = EScriptoriumClient("https://example.org", token="tok")
mock_data = [
{"pk": 1, "name": "Projet Test", "slug": "projet-test",
@@ -94,7 +94,7 @@ class TestEScriptoriumClient:
assert projects[0].document_count == 5
def test_list_documents_with_project_filter(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="tok")
with patch.object(client, "_paginate", return_value=[]) as mock_pag:
client.list_documents(project_pk=42)
@@ -102,7 +102,7 @@ class TestEScriptoriumClient:
assert call_kwargs[0][1]["project"] == 42
def test_list_parts_returns_list(self):
- from picarones.importers.escriptorium import EScriptoriumClient, EScriptoriumPart
+ from picarones.extras.importers.escriptorium import EScriptoriumClient, EScriptoriumPart
client = EScriptoriumClient("https://example.org", token="tok")
mock_data = [
{"pk": 10, "title": "f. 1r", "image": "https://example.org/img/1.jpg", "order": 0},
@@ -115,7 +115,7 @@ class TestEScriptoriumClient:
assert parts[0].pk == 10
def test_escriptorium_project_as_dict(self):
- from picarones.importers.escriptorium import EScriptoriumProject
+ from picarones.extras.importers.escriptorium import EScriptoriumProject
p = EScriptoriumProject(pk=1, name="Test", slug="test", owner="user", document_count=3)
d = p.as_dict()
assert d["pk"] == 1
@@ -130,25 +130,25 @@ class TestEScriptoriumClient:
class TestEScriptoriumConnect:
def test_connect_success(self):
- from picarones.importers.escriptorium import connect_escriptorium, EScriptoriumClient
+ from picarones.extras.importers.escriptorium import connect_escriptorium, EScriptoriumClient
with patch.object(EScriptoriumClient, "test_connection", return_value=True):
client = connect_escriptorium("https://example.org", token="tok")
assert isinstance(client, EScriptoriumClient)
def test_connect_failure_raises(self):
- from picarones.importers.escriptorium import connect_escriptorium, EScriptoriumClient
+ from picarones.extras.importers.escriptorium import connect_escriptorium, EScriptoriumClient
with patch.object(EScriptoriumClient, "test_connection", return_value=False):
with pytest.raises(RuntimeError, match="Impossible de se connecter"):
connect_escriptorium("https://example.org", token="bad")
def test_connect_returns_client_with_correct_url(self):
- from picarones.importers.escriptorium import connect_escriptorium, EScriptoriumClient
+ from picarones.extras.importers.escriptorium import connect_escriptorium, EScriptoriumClient
with patch.object(EScriptoriumClient, "test_connection", return_value=True):
client = connect_escriptorium("https://myinstance.org", token="tok")
assert "myinstance.org" in client.base_url
def test_connect_timeout_passed(self):
- from picarones.importers.escriptorium import connect_escriptorium, EScriptoriumClient
+ from picarones.extras.importers.escriptorium import connect_escriptorium, EScriptoriumClient
with patch.object(EScriptoriumClient, "test_connection", return_value=True):
client = connect_escriptorium("https://example.org", token="tok", timeout=120)
assert client.timeout == 120
@@ -162,7 +162,7 @@ class TestEScriptoriumExport:
def _make_benchmark(self, engine_name: str = "tesseract") -> "BenchmarkResult":
from picarones.core.results import BenchmarkResult, EngineReport, DocumentResult
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
metrics = MetricsResult(cer=0.05, wer=0.10, cer_nfc=0.05,
cer_caseless=0.04, cer_diplomatic=0.04,
wer_normalized=0.09, mer=0.09, wil=0.05,
@@ -189,14 +189,14 @@ class TestEScriptoriumExport:
)
def test_export_unknown_engine_raises(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="tok")
bm = self._make_benchmark("tesseract")
with pytest.raises(ValueError, match="unknown_engine"):
client.export_benchmark_as_layer(bm, doc_pk=1, engine_name="unknown_engine")
def test_export_returns_count(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="tok")
bm = self._make_benchmark("tesseract")
with patch.object(client, "_post", return_value={}):
@@ -206,7 +206,7 @@ class TestEScriptoriumExport:
assert count == 1
def test_export_layer_name_default(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="tok")
bm = self._make_benchmark("tesseract")
calls = []
@@ -215,7 +215,7 @@ class TestEScriptoriumExport:
assert calls[0]["name"] == "picarones_tesseract"
def test_export_custom_layer_name(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="tok")
bm = self._make_benchmark("tesseract")
calls = []
@@ -226,9 +226,9 @@ class TestEScriptoriumExport:
assert calls[0]["name"] == "my_layer"
def test_export_skips_error_docs(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
from picarones.core.results import BenchmarkResult, EngineReport, DocumentResult
- from picarones.core.metrics import MetricsResult
+ from picarones.measurements.metrics import MetricsResult
metrics = MetricsResult(cer=0.1, wer=0.2, cer_nfc=0.1, cer_caseless=0.1,
cer_diplomatic=0.1, wer_normalized=0.2, mer=0.2, wil=0.1,
reference_length=50, hypothesis_length=50)
@@ -244,7 +244,7 @@ class TestEScriptoriumExport:
assert count == 1 # seul le doc sans erreur est exporté
def test_export_with_part_mapping(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="tok")
bm = self._make_benchmark("tesseract")
calls = []
@@ -256,7 +256,7 @@ class TestEScriptoriumExport:
assert "999" in calls[0]
def test_export_post_error_is_logged_not_raised(self):
- from picarones.importers.escriptorium import EScriptoriumClient
+ from picarones.extras.importers.escriptorium import EScriptoriumClient
client = EScriptoriumClient("https://example.org", token="tok")
bm = self._make_benchmark("tesseract")
with patch.object(client, "_post", side_effect=RuntimeError("500")):
@@ -264,7 +264,7 @@ class TestEScriptoriumExport:
assert count == 0
def test_document_result_as_dict_used(self):
- from picarones.importers.escriptorium import EScriptoriumDocument
+ from picarones.extras.importers.escriptorium import EScriptoriumDocument
d = EScriptoriumDocument(pk=42, name="Doc", project="1", part_count=10,
transcription_layers=["manual", "auto"])
d_dict = d.as_dict()
@@ -279,22 +279,22 @@ class TestEScriptoriumExport:
class TestGallicaRecord:
def test_import_module(self):
- from picarones.importers.gallica import GallicaRecord
+ from picarones.extras.importers.gallica import GallicaRecord
assert GallicaRecord is not None
def test_ark_property(self):
- from picarones.importers.gallica import GallicaRecord
+ from picarones.extras.importers.gallica import GallicaRecord
r = GallicaRecord(ark="12148/btv1b8453561w", title="Test")
assert "12148/btv1b8453561w" in r.url
def test_manifest_url(self):
- from picarones.importers.gallica import GallicaRecord
+ from picarones.extras.importers.gallica import GallicaRecord
r = GallicaRecord(ark="12148/btv1b8453561w", title="Test")
assert "manifest.json" in r.manifest_url
assert "12148/btv1b8453561w" in r.manifest_url
def test_as_dict_keys(self):
- from picarones.importers.gallica import GallicaRecord
+ from picarones.extras.importers.gallica import GallicaRecord
r = GallicaRecord(ark="12148/btv1b8453561w", title="Froissart", creator="Froissart")
d = r.as_dict()
assert "ark" in d
@@ -303,12 +303,12 @@ class TestGallicaRecord:
assert "url" in d
def test_has_ocr_default_false(self):
- from picarones.importers.gallica import GallicaRecord
+ from picarones.extras.importers.gallica import GallicaRecord
r = GallicaRecord(ark="12148/xxx", title="Test")
assert r.has_ocr is False
def test_has_ocr_true(self):
- from picarones.importers.gallica import GallicaRecord
+ from picarones.extras.importers.gallica import GallicaRecord
r = GallicaRecord(ark="12148/xxx", title="Test", has_ocr=True)
assert r.has_ocr is True
@@ -320,31 +320,31 @@ class TestGallicaRecord:
class TestGallicaClient:
def test_import_module(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
assert GallicaClient is not None
def test_init_defaults(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient()
assert client.timeout == 30
assert client.delay >= 0
def test_search_returns_list(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient(delay_between_requests=0)
with patch.object(client, "_fetch_url", side_effect=RuntimeError("network")):
results = client.search(title="Froissart", max_results=5)
assert isinstance(results, list)
def test_search_empty_on_network_error(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient(delay_between_requests=0)
with patch.object(client, "_fetch_url", side_effect=RuntimeError("timeout")):
results = client.search(title="test")
assert results == []
def test_get_ocr_text_returns_string(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient(delay_between_requests=0)
with patch.object(client, "_fetch_url", return_value=b"Froissart transcription"):
text = client.get_ocr_text("12148/btv1b8453561w", page=1)
@@ -352,7 +352,7 @@ class TestGallicaClient:
assert "Froissart" in text
def test_get_ocr_text_empty_on_html_response(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient(delay_between_requests=0)
html = b"
Page non disponible"
with patch.object(client, "_fetch_url", return_value=html):
@@ -360,14 +360,14 @@ class TestGallicaClient:
assert text == ""
def test_get_ocr_text_empty_on_error(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient(delay_between_requests=0)
with patch.object(client, "_fetch_url", side_effect=RuntimeError("404")):
text = client.get_ocr_text("12148/xxx", page=99)
assert text == ""
def test_get_metadata_returns_dict(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient(delay_between_requests=0)
xml_bytes = b"""
@@ -401,13 +401,13 @@ class TestGallicaClient:
assert records == []
def test_parse_sru_invalid_xml_returns_empty(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient(delay_between_requests=0)
records = client._parse_sru_response(b"not xml at all !!!", max_results=10)
assert records == []
def test_client_has_delay_attribute(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient(delay_between_requests=0.1)
assert client.delay == 0.1
@@ -419,46 +419,46 @@ class TestGallicaClient:
class TestGallicaSearchQuery:
def test_build_query_title(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient()
query = client._build_sru_query(title="Froissart")
assert "Froissart" in query
assert "dc.title" in query
def test_build_query_author(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient()
query = client._build_sru_query(author="Froissart")
assert "dc.creator" in query
def test_build_query_date_range(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient()
query = client._build_sru_query(date_from=1380, date_to=1420)
assert "1380" in query
assert "1420" in query
def test_build_query_date_from_only(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient()
query = client._build_sru_query(date_from=1400)
assert "1400" in query
assert ">=" in query
def test_build_query_ark(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient()
query = client._build_sru_query(ark="12148/btv1b8453561w")
assert "12148/btv1b8453561w" in query
def test_build_query_empty_returns_default(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient()
query = client._build_sru_query()
assert len(query) > 0
def test_build_query_combined(self):
- from picarones.importers.gallica import GallicaClient
+ from picarones.extras.importers.gallica import GallicaClient
client = GallicaClient()
query = client._build_sru_query(title="Froissart", author="Jean", date_from=1380)
assert "Froissart" in query
@@ -466,7 +466,7 @@ class TestGallicaSearchQuery:
assert "1380" in query
def test_search_gallica_function(self):
- from picarones.importers.gallica import search_gallica, GallicaClient
+ from picarones.extras.importers.gallica import search_gallica, GallicaClient
with patch.object(GallicaClient, "search", return_value=[]):
results = search_gallica(title="test")
assert isinstance(results, list)
@@ -479,18 +479,18 @@ class TestGallicaSearchQuery:
class TestGallicaOCR:
def test_ocr_url_format(self):
- from picarones.importers import gallica as g
+ from picarones.extras.importers import gallica as g
url = g._OCR_BRUT_TPL.format(ark="12148/btv1b8453561w", page=3)
assert "12148/btv1b8453561w" in url
assert "f3" in url
assert "texteBrut" in url
def test_import_gallica_document_function_exists(self):
- from picarones.importers.gallica import import_gallica_document
+ from picarones.extras.importers.gallica import import_gallica_document
assert callable(import_gallica_document)
def test_gallica_base_url(self):
- from picarones.importers import gallica as g
+ from picarones.extras.importers import gallica as g
assert "gallica.bnf.fr" in g._GALLICA_BASE
def test_ark_normalization_in_import(self):
@@ -502,13 +502,13 @@ class TestGallicaOCR:
assert m.group(1) == "12148/btv1b8453561w"
def test_iiif_manifest_url_pattern(self):
- from picarones.importers import gallica as g
+ from picarones.extras.importers import gallica as g
url = g._IIIF_MANIFEST_TPL.format(ark="12148/btv1b8453561w")
assert "manifest.json" in url
assert "12148/btv1b8453561w" in url
def test_gallica_record_url_structure(self):
- from picarones.importers.gallica import GallicaRecord
+ from picarones.extras.importers.gallica import GallicaRecord
r = GallicaRecord(ark="12148/btv1b8453561w", title="Test")
assert r.url.startswith("https://gallica.bnf.fr")
assert "12148/btv1b8453561w" in r.url
@@ -521,19 +521,19 @@ class TestGallicaOCR:
class TestImportersInit:
def test_escriptorium_client_exported(self):
- from picarones.importers import EScriptoriumClient
+ from picarones.extras.importers import EScriptoriumClient
assert EScriptoriumClient is not None
def test_gallica_client_exported(self):
- from picarones.importers import GallicaClient
+ from picarones.extras.importers import GallicaClient
assert GallicaClient is not None
def test_search_gallica_exported(self):
- from picarones.importers import search_gallica
+ from picarones.extras.importers import search_gallica
assert callable(search_gallica)
def test_connect_escriptorium_exported(self):
- from picarones.importers import connect_escriptorium
+ from picarones.extras.importers import connect_escriptorium
assert callable(connect_escriptorium)
diff --git a/tests/test_sprint8_longitudinal_robustness.py b/tests/test_sprint8_longitudinal_robustness.py
index 076dbe0606e2c861278395737666105fc363950d..6357095deb94370f59947cb4ea2302aaa6ed7c0f 100644
--- a/tests/test_sprint8_longitudinal_robustness.py
+++ b/tests/test_sprint8_longitudinal_robustness.py
@@ -29,11 +29,11 @@ class TestBenchmarkHistory:
@pytest.fixture
def db(self):
- from picarones.core.history import BenchmarkHistory
+ from picarones.measurements.history import BenchmarkHistory
return BenchmarkHistory(":memory:")
def test_import_module(self):
- from picarones.core.history import BenchmarkHistory
+ from picarones.measurements.history import BenchmarkHistory
assert BenchmarkHistory is not None
def test_init_in_memory(self, db):
@@ -142,11 +142,11 @@ class TestBenchmarkHistory:
class TestHistoryEntry:
def test_import(self):
- from picarones.core.history import HistoryEntry
+ from picarones.measurements.history import HistoryEntry
assert HistoryEntry is not None
def test_cer_percent(self):
- from picarones.core.history import HistoryEntry
+ from picarones.measurements.history import HistoryEntry
entry = HistoryEntry(
run_id="r1", timestamp="2025-01-01T00:00:00+00:00",
corpus_name="C", engine_name="tesseract",
@@ -155,12 +155,12 @@ class TestHistoryEntry:
assert abs(entry.cer_percent - 12.0) < 0.01
def test_cer_percent_none(self):
- from picarones.core.history import HistoryEntry
+ from picarones.measurements.history import HistoryEntry
entry = HistoryEntry("r", "2025", "C", "e", None, None, 0)
assert entry.cer_percent is None
def test_as_dict_keys(self):
- from picarones.core.history import HistoryEntry
+ from picarones.measurements.history import HistoryEntry
entry = HistoryEntry("r1", "2025-01-01", "C", "tesseract", 0.10, 0.18, 5)
d = entry.as_dict()
assert "run_id" in d
@@ -168,14 +168,14 @@ class TestHistoryEntry:
assert "engine_name" in d
def test_as_dict_metadata(self):
- from picarones.core.history import HistoryEntry
+ from picarones.measurements.history import HistoryEntry
entry = HistoryEntry("r1", "2025-01-01", "C", "tesseract", 0.10, 0.18, 5,
metadata={"key": "value"})
d = entry.as_dict()
assert d["metadata"] == {"key": "value"}
def test_query_result_is_history_entry(self):
- from picarones.core.history import BenchmarkHistory, HistoryEntry
+ from picarones.measurements.history import BenchmarkHistory, HistoryEntry
db = BenchmarkHistory(":memory:")
db.record_single("r1", "C", "tesseract", 0.10, 0.18, 5)
entries = db.query()
@@ -190,7 +190,7 @@ class TestRegressionResult:
@pytest.fixture
def db_with_runs(self):
- from picarones.core.history import BenchmarkHistory
+ from picarones.measurements.history import BenchmarkHistory
db = BenchmarkHistory(":memory:")
db.record_single("r1", "C", "tesseract", 0.12, 0.20, 10, timestamp="2025-01-01T00:00:00+00:00")
db.record_single("r2", "C", "tesseract", 0.15, 0.25, 10, timestamp="2025-06-01T00:00:00+00:00")
@@ -212,7 +212,7 @@ class TestRegressionResult:
assert result.current_cer is not None
def test_detect_no_regression(self):
- from picarones.core.history import BenchmarkHistory
+ from picarones.measurements.history import BenchmarkHistory
db = BenchmarkHistory(":memory:")
# CER diminue = amélioration = pas de régression
db.record_single("r1", "C", "tesseract", 0.15, 0.25, 5, timestamp="2025-01-01T00:00:00+00:00")
@@ -222,14 +222,14 @@ class TestRegressionResult:
assert result.is_regression is False
def test_detect_regression_none_if_single_run(self):
- from picarones.core.history import BenchmarkHistory
+ from picarones.measurements.history import BenchmarkHistory
db = BenchmarkHistory(":memory:")
db.record_single("r1", "C", "tesseract", 0.12, 0.20, 5)
result = db.detect_regression("tesseract")
assert result is None
def test_detect_all_regressions(self):
- from picarones.core.history import BenchmarkHistory
+ from picarones.measurements.history import BenchmarkHistory
db = BenchmarkHistory(":memory:")
db.record_single("r1", "C", "tesseract", 0.10, 0.18, 5, timestamp="2025-01-01T00:00:00+00:00")
db.record_single("r2", "C", "tesseract", 0.20, 0.35, 5, timestamp="2025-06-01T00:00:00+00:00")
@@ -244,7 +244,7 @@ class TestRegressionResult:
assert "engine_name" in d
def test_regression_threshold_respected(self):
- from picarones.core.history import BenchmarkHistory
+ from picarones.measurements.history import BenchmarkHistory
db = BenchmarkHistory(":memory:")
db.record_single("r1", "C", "tesseract", 0.100, 0.18, 5, timestamp="2025-01-01T00:00:00+00:00")
db.record_single("r2", "C", "tesseract", 0.105, 0.19, 5, timestamp="2025-06-01T00:00:00+00:00")
@@ -264,27 +264,27 @@ class TestRegressionResult:
class TestGenerateDemoHistory:
def test_generate_fills_db(self):
- from picarones.core.history import BenchmarkHistory, generate_demo_history
+ from picarones.measurements.history import BenchmarkHistory, generate_demo_history
db = BenchmarkHistory(":memory:")
generate_demo_history(db, n_runs=5)
assert db.count() > 0
def test_generate_creates_multiple_engines(self):
- from picarones.core.history import BenchmarkHistory, generate_demo_history
+ from picarones.measurements.history import BenchmarkHistory, generate_demo_history
db = BenchmarkHistory(":memory:")
generate_demo_history(db, n_runs=4)
engines = db.list_engines()
assert len(engines) >= 2
def test_generate_n_runs(self):
- from picarones.core.history import BenchmarkHistory, generate_demo_history
+ from picarones.measurements.history import BenchmarkHistory, generate_demo_history
db = BenchmarkHistory(":memory:")
generate_demo_history(db, n_runs=8)
# 8 runs × 3 moteurs = 24 entrées
assert db.count() == 8 * 3
def test_cer_values_in_range(self):
- from picarones.core.history import BenchmarkHistory, generate_demo_history
+ from picarones.measurements.history import BenchmarkHistory, generate_demo_history
db = BenchmarkHistory(":memory:")
generate_demo_history(db, n_runs=5)
entries = db.query()
@@ -294,7 +294,7 @@ class TestGenerateDemoHistory:
def test_regression_detectable_in_demo(self):
"""La démo inclut une régression simulée au run 5 (tesseract)."""
- from picarones.core.history import BenchmarkHistory, generate_demo_history
+ from picarones.measurements.history import BenchmarkHistory, generate_demo_history
db = BenchmarkHistory(":memory:")
generate_demo_history(db, n_runs=8, seed=42)
# Vérifier que l'historique a été créé
@@ -311,33 +311,33 @@ class TestGenerateDemoHistory:
class TestDegradationLevels:
def test_import_constants(self):
- from picarones.core.robustness import DEGRADATION_LEVELS, ALL_DEGRADATION_TYPES
+ from picarones.measurements.robustness import DEGRADATION_LEVELS, ALL_DEGRADATION_TYPES
assert len(DEGRADATION_LEVELS) > 0
assert len(ALL_DEGRADATION_TYPES) > 0
def test_all_types_in_levels(self):
- from picarones.core.robustness import DEGRADATION_LEVELS, ALL_DEGRADATION_TYPES
+ from picarones.measurements.robustness import DEGRADATION_LEVELS, ALL_DEGRADATION_TYPES
for t in ALL_DEGRADATION_TYPES:
assert t in DEGRADATION_LEVELS
def test_noise_levels(self):
- from picarones.core.robustness import DEGRADATION_LEVELS
+ from picarones.measurements.robustness import DEGRADATION_LEVELS
levels = DEGRADATION_LEVELS["noise"]
assert len(levels) >= 2
assert 0 in levels # niveau original
def test_blur_levels(self):
- from picarones.core.robustness import DEGRADATION_LEVELS
+ from picarones.measurements.robustness import DEGRADATION_LEVELS
levels = DEGRADATION_LEVELS["blur"]
assert 0 in levels
def test_resolution_levels_include_1(self):
- from picarones.core.robustness import DEGRADATION_LEVELS
+ from picarones.measurements.robustness import DEGRADATION_LEVELS
levels = DEGRADATION_LEVELS["resolution"]
assert 1.0 in levels # résolution originale
def test_labels_match_levels(self):
- from picarones.core.robustness import DEGRADATION_LEVELS, DEGRADATION_LABELS
+ from picarones.measurements.robustness import DEGRADATION_LEVELS, DEGRADATION_LABELS
for dtype in DEGRADATION_LEVELS:
if dtype in DEGRADATION_LABELS:
assert len(DEGRADATION_LABELS[dtype]) == len(DEGRADATION_LEVELS[dtype])
@@ -355,60 +355,60 @@ class TestDegradationFunctions:
return _make_placeholder_png(40, 30)
def test_degrade_image_bytes_imports(self):
- from picarones.core.robustness import degrade_image_bytes
+ from picarones.measurements.robustness import degrade_image_bytes
assert callable(degrade_image_bytes)
def test_degrade_noise_returns_bytes(self):
- from picarones.core.robustness import degrade_image_bytes
+ from picarones.measurements.robustness import degrade_image_bytes
png = self._make_png()
result = degrade_image_bytes(png, "noise", 0)
assert isinstance(result, bytes)
assert len(result) > 0
def test_degrade_blur_returns_bytes(self):
- from picarones.core.robustness import degrade_image_bytes
+ from picarones.measurements.robustness import degrade_image_bytes
png = self._make_png()
result = degrade_image_bytes(png, "blur", 0)
assert isinstance(result, bytes)
def test_degrade_rotation_returns_bytes(self):
- from picarones.core.robustness import degrade_image_bytes
+ from picarones.measurements.robustness import degrade_image_bytes
png = self._make_png()
result = degrade_image_bytes(png, "rotation", 0)
assert isinstance(result, bytes)
def test_degrade_resolution_returns_bytes(self):
- from picarones.core.robustness import degrade_image_bytes
+ from picarones.measurements.robustness import degrade_image_bytes
png = self._make_png()
result = degrade_image_bytes(png, "resolution", 1.0)
assert isinstance(result, bytes)
def test_degrade_binarization_returns_bytes(self):
- from picarones.core.robustness import degrade_image_bytes
+ from picarones.measurements.robustness import degrade_image_bytes
png = self._make_png()
result = degrade_image_bytes(png, "binarization", 0)
assert isinstance(result, bytes)
def test_degrade_noise_level_5(self):
- from picarones.core.robustness import degrade_image_bytes
+ from picarones.measurements.robustness import degrade_image_bytes
png = self._make_png()
result = degrade_image_bytes(png, "noise", 5)
assert isinstance(result, bytes)
def test_degrade_blur_level_2(self):
- from picarones.core.robustness import degrade_image_bytes
+ from picarones.measurements.robustness import degrade_image_bytes
png = self._make_png()
result = degrade_image_bytes(png, "blur", 2)
assert isinstance(result, bytes)
def test_degrade_resolution_half(self):
- from picarones.core.robustness import degrade_image_bytes
+ from picarones.measurements.robustness import degrade_image_bytes
png = self._make_png()
result = degrade_image_bytes(png, "resolution", 0.5)
assert isinstance(result, bytes)
def test_degrade_rotation_10_degrees(self):
- from picarones.core.robustness import degrade_image_bytes
+ from picarones.measurements.robustness import degrade_image_bytes
png = self._make_png()
result = degrade_image_bytes(png, "rotation", 10)
assert isinstance(result, bytes)
@@ -421,11 +421,11 @@ class TestDegradationFunctions:
class TestDegradationCurve:
def test_import(self):
- from picarones.core.robustness import DegradationCurve
+ from picarones.measurements.robustness import DegradationCurve
assert DegradationCurve is not None
def test_as_dict_keys(self):
- from picarones.core.robustness import DegradationCurve
+ from picarones.measurements.robustness import DegradationCurve
curve = DegradationCurve(
engine_name="tesseract",
degradation_type="noise",
@@ -440,7 +440,7 @@ class TestDegradationCurve:
assert "cer_values" in d
def test_critical_threshold(self):
- from picarones.core.robustness import DegradationCurve
+ from picarones.measurements.robustness import DegradationCurve
curve = DegradationCurve(
engine_name="tesseract",
degradation_type="noise",
@@ -453,7 +453,7 @@ class TestDegradationCurve:
assert curve.critical_threshold_level == 15
def test_none_cer_allowed(self):
- from picarones.core.robustness import DegradationCurve
+ from picarones.measurements.robustness import DegradationCurve
curve = DegradationCurve(
engine_name="e",
degradation_type="blur",
@@ -464,17 +464,17 @@ class TestDegradationCurve:
assert curve.cer_values[0] is None
def test_default_cer_threshold(self):
- from picarones.core.robustness import DegradationCurve
+ from picarones.measurements.robustness import DegradationCurve
curve = DegradationCurve("e", "noise", [0], ["o"], [0.1])
assert curve.cer_threshold == 0.20
def test_engine_name_preserved(self):
- from picarones.core.robustness import DegradationCurve
+ from picarones.measurements.robustness import DegradationCurve
curve = DegradationCurve("pero_ocr", "blur", [0, 1], ["o", "r=1"], [0.05, 0.08])
assert curve.engine_name == "pero_ocr"
def test_as_dict_roundtrip(self):
- from picarones.core.robustness import DegradationCurve
+ from picarones.measurements.robustness import DegradationCurve
curve = DegradationCurve(
engine_name="tesseract",
degradation_type="rotation",
@@ -495,11 +495,11 @@ class TestDegradationCurve:
class TestRobustnessReport:
def test_import(self):
- from picarones.core.robustness import RobustnessReport
+ from picarones.measurements.robustness import RobustnessReport
assert RobustnessReport is not None
def test_get_curves_for_engine(self):
- from picarones.core.robustness import RobustnessReport, DegradationCurve
+ from picarones.measurements.robustness import RobustnessReport, DegradationCurve
c1 = DegradationCurve("tesseract", "noise", [0, 5], ["o", "σ=5"], [0.10, 0.15])
c2 = DegradationCurve("pero_ocr", "noise", [0, 5], ["o", "σ=5"], [0.07, 0.10])
report = RobustnessReport(["tesseract", "pero_ocr"], "C", ["noise"], [c1, c2])
@@ -508,7 +508,7 @@ class TestRobustnessReport:
assert tess_curves[0].engine_name == "tesseract"
def test_get_curves_for_type(self):
- from picarones.core.robustness import RobustnessReport, DegradationCurve
+ from picarones.measurements.robustness import RobustnessReport, DegradationCurve
c1 = DegradationCurve("tesseract", "noise", [0, 5], ["o", "σ=5"], [0.10, 0.15])
c2 = DegradationCurve("tesseract", "blur", [0, 2], ["o", "r=2"], [0.10, 0.14])
report = RobustnessReport(["tesseract"], "C", ["noise", "blur"], [c1, c2])
@@ -517,7 +517,7 @@ class TestRobustnessReport:
assert noise_curves[0].degradation_type == "noise"
def test_as_dict_keys(self):
- from picarones.core.robustness import RobustnessReport
+ from picarones.measurements.robustness import RobustnessReport
report = RobustnessReport(["tesseract"], "C", ["noise"], [])
d = report.as_dict()
assert "engine_names" in d
@@ -525,7 +525,7 @@ class TestRobustnessReport:
assert "summary" in d
def test_as_dict_json_serializable(self):
- from picarones.core.robustness import RobustnessReport, DegradationCurve
+ from picarones.measurements.robustness import RobustnessReport, DegradationCurve
c = DegradationCurve("e", "noise", [0, 5], ["o", "n5"], [0.1, 0.2])
report = RobustnessReport(["e"], "C", ["noise"], [c])
d = report.as_dict()
@@ -534,18 +534,18 @@ class TestRobustnessReport:
assert len(json_str) > 0
def test_summary_populated(self):
- from picarones.core.robustness import generate_demo_robustness_report
+ from picarones.measurements.robustness import generate_demo_robustness_report
report = generate_demo_robustness_report(engine_names=["tesseract"], seed=1)
assert isinstance(report.summary, dict)
assert len(report.summary) > 0
def test_corpus_name_preserved(self):
- from picarones.core.robustness import RobustnessReport
+ from picarones.measurements.robustness import RobustnessReport
report = RobustnessReport(["e"], "Mon Corpus", ["noise"], [])
assert report.corpus_name == "Mon Corpus"
def test_engine_names_list(self):
- from picarones.core.robustness import RobustnessReport
+ from picarones.measurements.robustness import RobustnessReport
report = RobustnessReport(["tesseract", "pero_ocr"], "C", [], [])
assert "tesseract" in report.engine_names
assert "pero_ocr" in report.engine_names
@@ -558,17 +558,17 @@ class TestRobustnessReport:
class TestRobustnessAnalyzer:
def test_import(self):
- from picarones.core.robustness import RobustnessAnalyzer
+ from picarones.measurements.robustness import RobustnessAnalyzer
assert RobustnessAnalyzer is not None
def test_init_single_engine(self):
- from picarones.core.robustness import RobustnessAnalyzer
+ from picarones.measurements.robustness import RobustnessAnalyzer
mock_engine = type("E", (), {"name": "tesseract"})()
analyzer = RobustnessAnalyzer(mock_engine)
assert len(analyzer.engines) == 1
def test_init_list_engines(self):
- from picarones.core.robustness import RobustnessAnalyzer
+ from picarones.measurements.robustness import RobustnessAnalyzer
engines = [
type("E", (), {"name": "tesseract"})(),
type("E", (), {"name": "pero_ocr"})(),
@@ -577,33 +577,33 @@ class TestRobustnessAnalyzer:
assert len(analyzer.engines) == 2
def test_default_degradation_types(self):
- from picarones.core.robustness import RobustnessAnalyzer, ALL_DEGRADATION_TYPES
+ from picarones.measurements.robustness import RobustnessAnalyzer, ALL_DEGRADATION_TYPES
e = type("E", (), {"name": "e"})()
analyzer = RobustnessAnalyzer(e)
assert set(analyzer.degradation_types) == set(ALL_DEGRADATION_TYPES)
def test_custom_degradation_types(self):
- from picarones.core.robustness import RobustnessAnalyzer
+ from picarones.measurements.robustness import RobustnessAnalyzer
e = type("E", (), {"name": "e"})()
analyzer = RobustnessAnalyzer(e, degradation_types=["noise", "blur"])
assert analyzer.degradation_types == ["noise", "blur"]
def test_find_critical_level_found(self):
- from picarones.core.robustness import RobustnessAnalyzer
+ from picarones.measurements.robustness import RobustnessAnalyzer
levels = [0, 5, 15, 30]
cer_values = [0.10, 0.15, 0.22, 0.35]
critical = RobustnessAnalyzer._find_critical_level(levels, cer_values, 0.20)
assert critical == 15
def test_find_critical_level_none(self):
- from picarones.core.robustness import RobustnessAnalyzer
+ from picarones.measurements.robustness import RobustnessAnalyzer
levels = [0, 5, 15]
cer_values = [0.05, 0.10, 0.15]
critical = RobustnessAnalyzer._find_critical_level(levels, cer_values, 0.20)
assert critical is None
def test_build_summary(self):
- from picarones.core.robustness import RobustnessAnalyzer, DegradationCurve
+ from picarones.measurements.robustness import RobustnessAnalyzer, DegradationCurve
curves = [
DegradationCurve("tesseract", "noise", [0, 5], ["o", "n5"], [0.10, 0.20]),
DegradationCurve("pero_ocr", "noise", [0, 5], ["o", "n5"], [0.07, 0.12]),
@@ -620,33 +620,33 @@ class TestRobustnessAnalyzer:
class TestGenerateDemoRobustness:
def test_import(self):
- from picarones.core.robustness import generate_demo_robustness_report
+ from picarones.measurements.robustness import generate_demo_robustness_report
assert callable(generate_demo_robustness_report)
def test_returns_report(self):
- from picarones.core.robustness import generate_demo_robustness_report, RobustnessReport
+ from picarones.measurements.robustness import generate_demo_robustness_report, RobustnessReport
report = generate_demo_robustness_report()
assert isinstance(report, RobustnessReport)
def test_default_engines(self):
- from picarones.core.robustness import generate_demo_robustness_report
+ from picarones.measurements.robustness import generate_demo_robustness_report
report = generate_demo_robustness_report()
assert "tesseract" in report.engine_names
assert "pero_ocr" in report.engine_names
def test_custom_engines(self):
- from picarones.core.robustness import generate_demo_robustness_report
+ from picarones.measurements.robustness import generate_demo_robustness_report
report = generate_demo_robustness_report(engine_names=["moteur_custom"])
assert "moteur_custom" in report.engine_names
def test_all_degradation_types_present(self):
- from picarones.core.robustness import generate_demo_robustness_report, ALL_DEGRADATION_TYPES
+ from picarones.measurements.robustness import generate_demo_robustness_report, ALL_DEGRADATION_TYPES
report = generate_demo_robustness_report()
types_in_report = {c.degradation_type for c in report.curves}
assert types_in_report == set(ALL_DEGRADATION_TYPES)
def test_cer_values_in_range(self):
- from picarones.core.robustness import generate_demo_robustness_report
+ from picarones.measurements.robustness import generate_demo_robustness_report
report = generate_demo_robustness_report(seed=99)
for curve in report.curves:
for cer in curve.cer_values:
@@ -655,7 +655,7 @@ class TestGenerateDemoRobustness:
def test_cer_increases_with_degradation(self):
"""Pour la plupart des types, le CER doit augmenter avec le niveau de dégradation."""
- from picarones.core.robustness import generate_demo_robustness_report
+ from picarones.measurements.robustness import generate_demo_robustness_report
report = generate_demo_robustness_report(seed=42)
for curve in report.curves:
valid = [c for c in curve.cer_values if c is not None]
@@ -667,18 +667,18 @@ class TestGenerateDemoRobustness:
)
def test_reproducible_with_seed(self):
- from picarones.core.robustness import generate_demo_robustness_report
+ from picarones.measurements.robustness import generate_demo_robustness_report
r1 = generate_demo_robustness_report(seed=7)
r2 = generate_demo_robustness_report(seed=7)
assert r1.curves[0].cer_values == r2.curves[0].cer_values
def test_summary_contains_most_robust(self):
- from picarones.core.robustness import generate_demo_robustness_report
+ from picarones.measurements.robustness import generate_demo_robustness_report
report = generate_demo_robustness_report()
assert any("most_robust" in k for k in report.summary)
def test_json_serializable(self):
- from picarones.core.robustness import generate_demo_robustness_report
+ from picarones.measurements.robustness import generate_demo_robustness_report
report = generate_demo_robustness_report()
d = report.as_dict()
json_str = json.dumps(d, ensure_ascii=False)
diff --git a/tests/test_sprint90_engine_unstable.py b/tests/test_sprint90_engine_unstable.py
index 885c58db44a3cadd0d23376de2ce6b50a8a9032e..6a22c3f3c9ac90f0b59ea3e6601f01bbe5b77b5b 100644
--- a/tests/test_sprint90_engine_unstable.py
+++ b/tests/test_sprint90_engine_unstable.py
@@ -21,9 +21,9 @@ import json
import re
from pathlib import Path
-from picarones.core.narrative import build_synthesis
-from picarones.core.narrative.detectors import detect_engine_unstable
-from picarones.core.narrative.facts import FactImportance, FactType
+from picarones.measurements.narrative import build_synthesis
+from picarones.measurements.narrative.detectors import detect_engine_unstable
+from picarones.core.facts import FactImportance, FactType
from picarones.report.multirun_stability_render import (
build_multirun_stability_html,
)
@@ -47,7 +47,7 @@ class TestFactType:
assert FactType.ENGINE_UNSTABLE.value == "engine_unstable"
def test_in_arbiter_fallback_order(self) -> None:
- from picarones.core.narrative.arbiter import _FALLBACK_TYPE_ORDER
+ from picarones.measurements.narrative.arbiter import _FALLBACK_TYPE_ORDER
assert FactType.ENGINE_UNSTABLE in _FALLBACK_TYPE_ORDER
diff --git a/tests/test_sprint91_throughput.py b/tests/test_sprint91_throughput.py
index 542d78863d61cf819724c308259c67ce9d40c92d..7eead5f344f574e47470bad09dded200dc855ece 100644
--- a/tests/test_sprint91_throughput.py
+++ b/tests/test_sprint91_throughput.py
@@ -20,11 +20,11 @@ from pathlib import Path
import pytest
-from picarones.core.marginal_cost import (
+from picarones.measurements.marginal_cost import (
compute_marginal_cost,
compute_marginal_cost_matrix,
)
-from picarones.core.throughput import (
+from picarones.measurements.throughput import (
aggregate_effective_throughput,
compute_effective_throughput,
)
diff --git a/tests/test_sprint92_longitudinal.py b/tests/test_sprint92_longitudinal.py
index 997c720f3ae83c40af805634e21652138337a058..89b0d5811a927fd0ceed09827e9e18d4607f352a 100644
--- a/tests/test_sprint92_longitudinal.py
+++ b/tests/test_sprint92_longitudinal.py
@@ -24,15 +24,15 @@ from pathlib import Path
import pytest
-from picarones.core.longitudinal import (
+from picarones.measurements.longitudinal import (
compute_corpus_longitudinal,
compute_engine_longitudinal,
compute_linear_trend,
detect_change_point,
)
-from picarones.core.narrative import build_synthesis
-from picarones.core.narrative.detectors import detect_regression_in_history
-from picarones.core.narrative.facts import FactImportance, FactType
+from picarones.measurements.narrative import build_synthesis
+from picarones.measurements.narrative.detectors import detect_regression_in_history
+from picarones.core.facts import FactImportance, FactType
from picarones.report.longitudinal_render import build_longitudinal_html
diff --git a/tests/test_sprint93_image_predictive.py b/tests/test_sprint93_image_predictive.py
index 3cdb7e9889bbb0de18939f00d2b92dc8c1c3fffe..74e636f9e6d8bd023340be5a61510487c21df52d 100644
--- a/tests/test_sprint93_image_predictive.py
+++ b/tests/test_sprint93_image_predictive.py
@@ -29,7 +29,7 @@ from pathlib import Path
import pytest
-from picarones.core.image_predictive import (
+from picarones.measurements.image_predictive import (
DEFAULT_COMPLEXITY_WEIGHTS,
aggregate_corpus_predictive,
compute_corpus_homogeneity,
diff --git a/tests/test_sprint94_error_absorption.py b/tests/test_sprint94_error_absorption.py
index c6176e7d50ce5775334ff774d64223d7319ab91c..270f6acec881ae94ff2d710bcf24fcf4a99db5bc 100644
--- a/tests/test_sprint94_error_absorption.py
+++ b/tests/test_sprint94_error_absorption.py
@@ -25,7 +25,7 @@ from pathlib import Path
import pytest
-from picarones.core.error_absorption import (
+from picarones.measurements.error_absorption import (
aggregate_error_absorption,
compute_error_absorption,
)
diff --git a/tests/test_sprint96_incremental_comparison.py b/tests/test_sprint96_incremental_comparison.py
index 1ed3f02ed94abe0e20bb99cd91c55400a1ad3aae..3e3625a8cdf3c540912dd0caf66c61ddc0dde5d3 100644
--- a/tests/test_sprint96_incremental_comparison.py
+++ b/tests/test_sprint96_incremental_comparison.py
@@ -27,7 +27,7 @@ from pathlib import Path
import pytest
-from picarones.core.incremental_comparison import (
+from picarones.measurements.incremental_comparison import (
PipelineRun,
compare_isolated_effect,
)
diff --git a/tests/test_sprint97_module_policy.py b/tests/test_sprint97_module_policy.py
index 968e8f454dd53f47c816dd99121b52185b7a9751..c34c724417e49a48211650043e84f8dd7407309a 100644
--- a/tests/test_sprint97_module_policy.py
+++ b/tests/test_sprint97_module_policy.py
@@ -28,7 +28,7 @@ from __future__ import annotations
import json
from pathlib import Path
-from picarones.core.module_policy import (
+from picarones.measurements.module_policy import (
AuditCheck,
AuditResult,
ModuleManifest,