Spaces:
Running
feat(sprint-H.5): cleanup baselines + tests obsolètes
Browse filesSprint H.5 du plan v2.0 — nettoyage post-migration des
artefacts qui ne servent plus à rien maintenant que les
paquets legacy top-level ont tous été supprimés (H.1) et
que ``adapters/legacy_modules/`` a disparu (H.2.a).
Suppressions
------------
- ``tests/architecture/test_legacy_canonical_parity.py`` : la
table ``LEGACY_PARITY`` était devenue vide au fil des Lots
A-G (chaque entrée retirée en même temps que son shim), et
``LEGACY_PACKAGES = ("llm",)`` pointait vers un dossier
inexistant depuis H.1. Le test passait trivialement (3
passed, 1 skipped) — pure dead code. Le seul invariant
qui reste à garder est l'absence d'import legacy depuis le
rewrite, déjà couvert par
``test_no_legacy_imports_in_rewrite.py`` (lui-même avec
``LEGACY_PACKAGES = ()``).
Documentation
-------------
- ``CLAUDE.md`` : retire la référence au test supprimé,
pointe vers ``test_no_legacy_imports_in_rewrite`` comme
garde-fou survivant.
- ``docs/reference/api-stable.md`` : warning bandeau mis à
jour — liste explicite des paquets legacy supprimés au
lieu de pointer vers la table de parité disparue.
- ``docs/migration/SESSION_HANDOVER.md`` :
- Section 0 (principe directeur) : pointe vers
``test_no_legacy_imports_in_rewrite`` ;
- Section 1 (sources de vérité) : remplace l'entrée
``test_legacy_canonical_parity`` par ce dernier test ;
- Section 3.F : note explicative sur la suppression du
test au sprint H.5 ;
- Section 4.C : table de parité « sans objet à v2.0 » ;
- Section 5 : remplace la sub-phase 7.B.2 obsolète par la
feuille de route restante (H.2.b-d, H.4, H.6).
Doc paths cleanup (-11 broken paths : 172 → 161)
-------------------------------------------------
Corrige les références cassées dans les docs encore actives :
- ``docs/how-to/cli-workflows.md`` (-3) : ``picarones/cli/*``
→ ``picarones/interfaces/cli/_legacy/*`` ;
``picarones/extras/importers/_http.py`` →
``picarones/adapters/corpus/_http.py``.
- ``docs/explanation/narrative-engine.en.md`` (-3) :
``picarones/measurements/narrative/`` →
``picarones/reports/narrative/`` (sed global, 4
occurrences).
- ``docs/explanation/narrative-engine.md`` (-1) :
``picarones/fixtures.py`` → ``picarones/evaluation/synthetic.py``.
- ``docs/reference/normalization-profiles.md`` (-1) :
``picarones/measurements/builtin_hooks.py`` →
``picarones/evaluation/metrics/builtin_hooks.py``.
- ``docs/developer/doc-consistency.md`` (-1) : moteurs OCR,
CLI, web → leurs nouveaux chemins post-rewrite.
- ``docs/migration/SESSION_HANDOVER.md`` (-2) : ré-écriture
de la section 5 (qui référençait
``picarones/evaluation/pipeline.py`` supprimé en D.6.b)
+ reformulation de la mention historique de
``picarones/measurements/__init__.py``.
``BROKEN_PATHS_BASELINE`` : 172 → 161.
Reste 161 broken paths, tous dans :
- ``CHANGELOG.md`` (114) : journal historique versionné,
intouchable.
- ``docs/audits/*.md`` (34) : audits historiques, intouchables.
- ``docs/migration/{pipeline-convergence-plan,
legacy-retirement-plan, executor-equivalence,
sprint-D-audit}.md`` (9) : plans/audits décrivant des
chemins legacy à des fins de comparaison historique.
- ``docs/roadmap/evolution-2026.md`` (4) : plan stratégique
historique décrivant la création initiale des modules.
Tests
-----
- ``pytest tests/`` : 4611 passed, 9 skipped, 24 deselected,
0 failed.
- ``ruff check picarones/ tests/`` : All checks passed.
Reste à faire pour atteindre v2.0
---------------------------------
- H.2.b-d : refonte ``BaseOCREngine.run()`` →
``BaseOCRAdapter.execute()`` + suppression
``adapters/legacy_engines/`` + ``adapters/legacy_pipelines/``
+ ``OCRLLMPipeline``.
- H.4 : refonte des routes/commandes dans
``interfaces/{cli,web}/_legacy/`` pour consommer le
rewrite pur.
- H.6 : bump version + tag v2.0.0 + section CHANGELOG.
https://claude.ai/code/session_01NxyVKqg2SowXLZdM4H1ZDE
- CLAUDE.md +4 -5
- docs/developer/doc-consistency.md +4 -4
- docs/explanation/narrative-engine.en.md +4 -4
- docs/explanation/narrative-engine.md +1 -1
- docs/how-to/cli-workflows.md +3 -3
- docs/migration/SESSION_HANDOVER.md +61 -141
- docs/reference/api-stable.md +10 -8
- docs/reference/normalization-profiles.md +1 -1
- tests/architecture/test_doc_paths.py +11 -1
- tests/architecture/test_legacy_canonical_parity.py +0 -391
|
@@ -221,11 +221,10 @@ Pour le travail courant, ce qui compte :
|
|
| 221 |
changes acceptés.
|
| 222 |
- **Principe directeur** : suppression agressive. Pas de shim
|
| 223 |
qui survit à son usage. Dès qu'un caller migre vers le
|
| 224 |
-
canonique, son shim est supprimé.
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
fonctionnalité n'est silencieusement perdue.
|
| 229 |
- **Plan maître** : [`docs/migration/legacy-retirement-plan.md`](docs/migration/legacy-retirement-plan.md)
|
| 230 |
— cartographie complète des Phases 0-11 avec statut.
|
| 231 |
- **Sub-plan convergence pipeline** : [`docs/migration/pipeline-convergence-plan.md`](docs/migration/pipeline-convergence-plan.md)
|
|
|
|
| 221 |
changes acceptés.
|
| 222 |
- **Principe directeur** : suppression agressive. Pas de shim
|
| 223 |
qui survit à son usage. Dès qu'un caller migre vers le
|
| 224 |
+
canonique, son shim est supprimé. Le test
|
| 225 |
+
[`tests/architecture/test_no_legacy_imports_in_rewrite.py`](tests/architecture/test_no_legacy_imports_in_rewrite.py)
|
| 226 |
+
garde l'invariant inverse : les paquets rewrite ne doivent
|
| 227 |
+
jamais réintroduire un import vers du legacy disparu.
|
|
|
|
| 228 |
- **Plan maître** : [`docs/migration/legacy-retirement-plan.md`](docs/migration/legacy-retirement-plan.md)
|
| 229 |
— cartographie complète des Phases 0-11 avec statut.
|
| 230 |
- **Sub-plan convergence pipeline** : [`docs/migration/pipeline-convergence-plan.md`](docs/migration/pipeline-convergence-plan.md)
|
|
@@ -30,11 +30,11 @@ Vérifie que :
|
|
| 30 |
|
| 31 |
| Item | Source de vérité | Sens du contrat |
|
| 32 |
|---|---|---|
|
| 33 |
-
| Moteurs OCR | `picarones/
|
| 34 |
-
| Commandes CLI | `picarones/cli/*.py` (Click) | Toute commande listée dans le README doit apparaître dans `picarones --help` |
|
| 35 |
-
| Endpoints API | `picarones/web/app.py` (`app.openapi()`) | Tout endpoint listé doit exister dans la spec OpenAPI |
|
| 36 |
| Compteur de tests | `pytest --collect-only` | Toute mention « N tests » ou « N passed » doit être à 5 % près du baseline |
|
| 37 |
-
| Variables `AWS_*` | `picarones/
|
| 38 |
|
| 39 |
**Direction unidirectionnelle** : on vérifie que ce qui est *annoncé*
|
| 40 |
existe — pas que tout ce qui existe est annoncé. La direction réciproque
|
|
|
|
| 30 |
|
| 31 |
| Item | Source de vérité | Sens du contrat |
|
| 32 |
|---|---|---|
|
| 33 |
+
| Moteurs OCR | `picarones/adapters/legacy_engines/*.py` | Tout moteur listé dans le tableau « Supported Engines » du README doit avoir un adapter |
|
| 34 |
+
| Commandes CLI | `picarones/interfaces/cli/_legacy/*.py` (Click) | Toute commande listée dans le README doit apparaître dans `picarones --help` |
|
| 35 |
+
| Endpoints API | `picarones/interfaces/web/_legacy/app.py` (`app.openapi()`) | Tout endpoint listé doit exister dans la spec OpenAPI |
|
| 36 |
| Compteur de tests | `pytest --collect-only` | Toute mention « N tests » ou « N passed » doit être à 5 % près du baseline |
|
| 37 |
+
| Variables `AWS_*` | `picarones/adapters/legacy_engines/aws*.py` | Si documentées, un adapter doit exister |
|
| 38 |
|
| 39 |
**Direction unidirectionnelle** : on vérifie que ce qui est *annoncé*
|
| 40 |
existe — pas que tout ce qui existe est annoncé. La direction réciproque
|
|
@@ -24,7 +24,7 @@ class FactType(str, Enum):
|
|
| 24 |
|
| 25 |
### 2. Add the FR + EN templates
|
| 26 |
|
| 27 |
-
`picarones/
|
| 28 |
|
| 29 |
```yaml
|
| 30 |
your_new_fact: >-
|
|
@@ -37,7 +37,7 @@ Same in `en.yaml` with the English version.
|
|
| 37 |
### 3. Implement the detector
|
| 38 |
|
| 39 |
In an existing detector module (e.g.
|
| 40 |
-
`picarones/
|
| 41 |
quality-related facts) or a new one if a new family is justified:
|
| 42 |
|
| 43 |
```python
|
|
@@ -66,7 +66,7 @@ input — the anti-hallucination test would catch it.
|
|
| 66 |
|
| 67 |
### 4. Register the detector in the package `__init__`
|
| 68 |
|
| 69 |
-
`picarones/
|
| 70 |
|
| 71 |
```python
|
| 72 |
from picarones.measurements.narrative.detectors.quality import (
|
|
@@ -79,7 +79,7 @@ And add it to `__all__`.
|
|
| 79 |
|
| 80 |
### 5. Update the arbiter ordering
|
| 81 |
|
| 82 |
-
`picarones/
|
| 83 |
type to `_FALLBACK_TYPE_ORDER` at the right position.
|
| 84 |
|
| 85 |
### 6. Write tests
|
|
|
|
| 24 |
|
| 25 |
### 2. Add the FR + EN templates
|
| 26 |
|
| 27 |
+
`picarones/reports/narrative/templates/fr.yaml`:
|
| 28 |
|
| 29 |
```yaml
|
| 30 |
your_new_fact: >-
|
|
|
|
| 37 |
### 3. Implement the detector
|
| 38 |
|
| 39 |
In an existing detector module (e.g.
|
| 40 |
+
`picarones/reports/narrative/detectors/quality.py` for
|
| 41 |
quality-related facts) or a new one if a new family is justified:
|
| 42 |
|
| 43 |
```python
|
|
|
|
| 66 |
|
| 67 |
### 4. Register the detector in the package `__init__`
|
| 68 |
|
| 69 |
+
`picarones/reports/narrative/detectors/__init__.py`:
|
| 70 |
|
| 71 |
```python
|
| 72 |
from picarones.measurements.narrative.detectors.quality import (
|
|
|
|
| 79 |
|
| 80 |
### 5. Update the arbiter ordering
|
| 81 |
|
| 82 |
+
`picarones/reports/narrative/arbiter.py` — append your new
|
| 83 |
type to `_FALLBACK_TYPE_ORDER` at the right position.
|
| 84 |
|
| 85 |
### 6. Write tests
|
|
@@ -209,7 +209,7 @@ picarones demo --output /tmp/demo.html --docs 8
|
|
| 209 |
|
| 210 |
Si la synthèse ne contient pas votre fait, vérifiez :
|
| 211 |
1. Que votre détecteur retourne bien quelque chose sur les données de
|
| 212 |
-
démo (`grep -A 20 "def generate_sample_benchmark" picarones/
|
| 213 |
2. Que l'importance est suffisante (> `MEDIUM`) pour passer le filtre
|
| 214 |
par défaut de l'arbitre.
|
| 215 |
3. Que votre type n'est pas en collision avec un autre déjà retenu pour
|
|
|
|
| 209 |
|
| 210 |
Si la synthèse ne contient pas votre fait, vérifiez :
|
| 211 |
1. Que votre détecteur retourne bien quelque chose sur les données de
|
| 212 |
+
démo (`grep -A 20 "def generate_sample_benchmark" picarones/evaluation/synthetic.py`).
|
| 213 |
2. Que l'importance est suffisante (> `MEDIUM`) pour passer le filtre
|
| 214 |
par défaut de l'arbitre.
|
| 215 |
3. Que votre type n'est pas en collision avec un autre déjà retenu pour
|
|
@@ -106,7 +106,7 @@ picarones import iiif \
|
|
| 106 |
Télécharge un manifeste IIIF v2/v3 (BnF Gallica, Bodleian, Vatican…) et
|
| 107 |
crée un corpus local avec `.gt.txt` extraits de l'OCR ALTO si présent.
|
| 108 |
Depuis le chantier 4, IIIF et Gallica utilisent les mêmes helpers HTTP
|
| 109 |
-
factorisés ([`picarones/
|
| 110 |
avec garde-fou `file://`/`ftp://`/`javascript://`.
|
| 111 |
|
| 112 |
## Outils utilitaires
|
|
@@ -183,9 +183,9 @@ pour découvrir la sortie sans corpus réel.
|
|
| 183 |
|
| 184 |
## Code source
|
| 185 |
|
| 186 |
-
- [`picarones/cli/__init__.py`](../picarones/cli/__init__.py) — groupe
|
| 187 |
Click + helpers + commandes simples.
|
| 188 |
-
- [`picarones/cli/_workflows.py`](../picarones/cli/_workflows.py) —
|
| 189 |
run, diagnose, economics, edition, compare + helper `_run_workflow`.
|
| 190 |
- Voir aussi [`docs/reference/normalization-profiles.md`](profiles.md) et
|
| 191 |
[`docs/reference/views.md`](views.md).
|
|
|
|
| 106 |
Télécharge un manifeste IIIF v2/v3 (BnF Gallica, Bodleian, Vatican…) et
|
| 107 |
crée un corpus local avec `.gt.txt` extraits de l'OCR ALTO si présent.
|
| 108 |
Depuis le chantier 4, IIIF et Gallica utilisent les mêmes helpers HTTP
|
| 109 |
+
factorisés ([`picarones/adapters/corpus/_http.py`](../picarones/adapters/corpus/_http.py))
|
| 110 |
avec garde-fou `file://`/`ftp://`/`javascript://`.
|
| 111 |
|
| 112 |
## Outils utilitaires
|
|
|
|
| 183 |
|
| 184 |
## Code source
|
| 185 |
|
| 186 |
+
- [`picarones/interfaces/cli/_legacy/__init__.py`](../picarones/interfaces/cli/_legacy/__init__.py) — groupe
|
| 187 |
Click + helpers + commandes simples.
|
| 188 |
+
- [`picarones/interfaces/cli/_legacy/_workflows.py`](../picarones/interfaces/cli/_legacy/_workflows.py) —
|
| 189 |
run, diagnose, economics, edition, compare + helper `_run_workflow`.
|
| 190 |
- Voir aussi [`docs/reference/normalization-profiles.md`](profiles.md) et
|
| 191 |
[`docs/reference/views.md`](views.md).
|
|
@@ -18,15 +18,13 @@
|
|
| 18 |
acceptés.
|
| 19 |
- Dès qu'un caller migre vers le canonique, son shim est
|
| 20 |
**supprimé** (pas conservé pour un usage hypothétique).
|
| 21 |
-
-
|
| 22 |
-
``tests/architecture/
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
fonctionnalité legacy n'est silencieusement perdue** au cours
|
| 29 |
-
de la migration. C'est le journal de bord vivant.
|
| 30 |
|
| 31 |
---
|
| 32 |
|
|
@@ -40,10 +38,10 @@ de la migration. C'est le journal de bord vivant.
|
|
| 40 |
sous-plan détaillé de la convergence ``BaseModule`` /
|
| 41 |
``PipelineRunner`` → ``StepExecutor`` / ``PipelineExecutor``
|
| 42 |
(Sub-phases 7.A-7.D).
|
| 43 |
-
3. **[`../../tests/architecture/
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
4. **[`../../CLAUDE.md`](../../CLAUDE.md)** — règles d'architecture
|
| 48 |
à respecter, statut de la migration, et liens vers le reste.
|
| 49 |
5. **`git log --oneline -10`** — les 10 derniers commits
|
|
@@ -160,26 +158,18 @@ référence cassée. Soit :
|
|
| 160 |
Quand le fichier sera créé en réalité, abaisser
|
| 161 |
``BROKEN_PATHS_BASELINE``.
|
| 162 |
|
| 163 |
-
### 3.F Test parité legacy ↔ canonique
|
| 164 |
-
|
| 165 |
-
``tests/architecture/test_legacy_canonical_parity.py`` maintient
|
| 166 |
-
une table 3-états (``LEGACY_PARITY``) :
|
| 167 |
-
|
| 168 |
-
- ``canonical: <module.symbol>`` — équivalent canonique existe.
|
| 169 |
-
Le test vérifie présence + signatures compatibles.
|
| 170 |
-
- ``dropped: <raison>`` — feature volontairement abandonnée
|
| 171 |
-
avec justification écrite.
|
| 172 |
-
- ``unmigrated: <cible prévue>`` — migration prévue ; cible
|
| 173 |
-
peut ne pas encore exister.
|
| 174 |
-
|
| 175 |
-
À chaque migration d'un symbole, **mettre à jour la table**.
|
| 176 |
-
Les symboles non trackés sont comptés via
|
| 177 |
-
``BOOTSTRAP_BASELINE`` (à diminuer à chaque session).
|
| 178 |
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
### 3.G README généré
|
| 185 |
|
|
@@ -243,17 +233,9 @@ sprint par sprint en migrant chaque caller.
|
|
| 243 |
|
| 244 |
### 4.C Symboles legacy non tracés dans la table de parité
|
| 245 |
|
| 246 |
-
**
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
Répartition :
|
| 250 |
-
|
| 251 |
-
- ``measurements/`` : 104
|
| 252 |
-
- ``pipelines/`` : 6
|
| 253 |
-
|
| 254 |
-
Le test ``test_no_untracked_legacy_symbol_above_baseline``
|
| 255 |
-
autorise temporairement 110 (``BOOTSTRAP_BASELINE = 110``).
|
| 256 |
-
À diminuer à chaque session.
|
| 257 |
|
| 258 |
### 4.D Plan de bataille pour les imports tests
|
| 259 |
|
|
@@ -300,10 +282,11 @@ L'ordre recommandé, par lots de symboles cohérents :
|
|
| 300 |
structure, taxonomy, taxonomy_comparison,
|
| 301 |
taxonomy_cooccurrence, taxonomy_intra_doc, throughput,
|
| 302 |
worst_lines}`` → ``evaluation.metrics.{...}``.
|
| 303 |
-
- ``
|
| 304 |
refléter la nouvelle composition (modules legacy
|
| 305 |
restants + `import picarones.evaluation.metrics`
|
| 306 |
-
unique pour déclencher les décorateurs).
|
|
|
|
| 307 |
- ``test_no_flat_files_in_measurements::WHITELIST_FLAT_FILES_S3``
|
| 308 |
réduit de 60 → 25 entrées.
|
| 309 |
- ``test_module_coverage::TEST_ONLY_BASELINE`` réduit
|
|
@@ -389,101 +372,38 @@ commit (principe « no shim survives its caller »).
|
|
| 389 |
|
| 390 |
---
|
| 391 |
|
| 392 |
-
## 5.
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
- Invoque ``PipelineExecutor.run(canonical_spec,
|
| 425 |
-
document_ref, canonical_inputs, context)``.
|
| 426 |
-
- Reconvertit le ``PipelineResult`` canonique en
|
| 427 |
-
``PipelineResult`` legacy.
|
| 428 |
-
- Calcule ``junction_metrics`` en post-étape (parcourt
|
| 429 |
-
les ``StepResult.produced_artifacts``, lit le payload
|
| 430 |
-
du registre, appelle ``compute_at_junction`` contre la
|
| 431 |
-
GT du document si ``GTLevel`` correspond).
|
| 432 |
-
|
| 433 |
-
4. **Tester** : tous les tests existants doivent toujours
|
| 434 |
-
passer (les 7 fichiers axe B + ``test_sprint63_pipeline_runner``,
|
| 435 |
-
etc.). C'est l'invariant de la sub-phase 7.B.2.
|
| 436 |
-
|
| 437 |
-
5. **Lint** : ``ruff check picarones/ tests/``.
|
| 438 |
-
|
| 439 |
-
6. **Commit + push** avec message décrivant ce qui a été
|
| 440 |
-
fait + pointer vers la sub-phase 7.B.3 comme prochaine
|
| 441 |
-
étape.
|
| 442 |
-
|
| 443 |
-
### Alternative pragmatique
|
| 444 |
-
|
| 445 |
-
Si le refactor 7.B.2 est trop gros pour une session,
|
| 446 |
-
**commencer par le Lot A de la section 4.D** (migrer les ~30
|
| 447 |
-
imports tests qui consomment ``core.modules`` et
|
| 448 |
-
``core.facts`` vers leur canonique ``domain/``). Cela vide
|
| 449 |
-
une portion de la table de parité et permet de **supprimer les
|
| 450 |
-
shims** ``core.modules.py`` et ``core.facts.py`` en bloc —
|
| 451 |
-
résultat tangible et bien aligné avec le principe
|
| 452 |
-
« suppression agressive ».
|
| 453 |
-
|
| 454 |
-
Pareil pour Lots B-F : chaque lot est indépendant, fait
|
| 455 |
-
progresser la migration, et démontre concrètement la
|
| 456 |
-
suppression du legacy.
|
| 457 |
-
|
| 458 |
-
### Pièges anticipés pour 7.B.2
|
| 459 |
-
|
| 460 |
-
- **Sémantique différente des inputs entre legacy et canonique** :
|
| 461 |
-
le legacy passe ``Document.image_path`` comme un string
|
| 462 |
-
pur dans ``initial_inputs[ArtifactType.IMAGE]`` ; le canonique
|
| 463 |
-
attend un ``Artifact(uri=...)``. ``wrap_initial_inputs``
|
| 464 |
-
fait la conversion mais il faut s'assurer que les modules
|
| 465 |
-
consomment bien le ``uri`` côté `_BaseModuleAdapter`.
|
| 466 |
-
|
| 467 |
-
- **``junction_metrics`` calcul** : le legacy
|
| 468 |
-
``PipelineRunner.run`` calcule ``junction_metrics`` à
|
| 469 |
-
chaque step (cf. ligne 519-540 actuellement). Le canonique
|
| 470 |
-
``PipelineExecutor`` ne le fait pas. Il faut donc faire
|
| 471 |
-
ce calcul **après** l'exécution canonique, en parcourant
|
| 472 |
-
les artefacts produits et en lisant les payloads via le
|
| 473 |
-
registre.
|
| 474 |
-
|
| 475 |
-
- **``output_types`` partial** : si un module produit un
|
| 476 |
-
output type non déclaré, le legacy le tolère (on remplit
|
| 477 |
-
``StepResult.output_types`` avec ce qui est effectivement
|
| 478 |
-
produit, pas ce qui est déclaré). Le canonique
|
| 479 |
-
``PipelineExecutor`` rejette en ``error="missing_output: ..."``.
|
| 480 |
-
Vérifier la sémantique attendue par les tests.
|
| 481 |
-
|
| 482 |
-
- **Spec conversion** : ``PipelineStep`` legacy a
|
| 483 |
-
``inputs_from: dict[ArtifactType, str]`` (mapping
|
| 484 |
-
type→step_name). ``PipelineStep`` canonique a
|
| 485 |
-
``inputs_from: tuple[InputBinding, ...]``. Conversion
|
| 486 |
-
attentive nécessaire.
|
| 487 |
|
| 488 |
---
|
| 489 |
|
|
@@ -500,7 +420,7 @@ section 2.
|
|
| 500 |
Ou pour aller direct à l'action :
|
| 501 |
|
| 502 |
```
|
| 503 |
-
|
| 504 |
```
|
| 505 |
|
| 506 |
(Claude Code va automatiquement lire CLAUDE.md à l'init, qui
|
|
|
|
| 18 |
acceptés.
|
| 19 |
- Dès qu'un caller migre vers le canonique, son shim est
|
| 20 |
**supprimé** (pas conservé pour un usage hypothétique).
|
| 21 |
+
- L'invariant inverse est gardé par
|
| 22 |
+
``tests/architecture/test_no_legacy_imports_in_rewrite.py`` :
|
| 23 |
+
les paquets rewrite (`domain → formats → evaluation →
|
| 24 |
+
pipeline → adapters → app → reports → interfaces`) ne
|
| 25 |
+
doivent jamais importer depuis un paquet legacy. Pour le
|
| 26 |
+
retrait final v2.0, ``LEGACY_PACKAGES = ()`` (vide) — tous
|
| 27 |
+
les paquets top-level legacy ont été supprimés.
|
|
|
|
|
|
|
| 28 |
|
| 29 |
---
|
| 30 |
|
|
|
|
| 38 |
sous-plan détaillé de la convergence ``BaseModule`` /
|
| 39 |
``PipelineRunner`` → ``StepExecutor`` / ``PipelineExecutor``
|
| 40 |
(Sub-phases 7.A-7.D).
|
| 41 |
+
3. **[`../../tests/architecture/test_no_legacy_imports_in_rewrite.py`](../../tests/architecture/test_no_legacy_imports_in_rewrite.py)** —
|
| 42 |
+
garde-fou architectural : aucun module rewrite n'importe
|
| 43 |
+
depuis un paquet legacy (``LEGACY_PACKAGES`` désormais
|
| 44 |
+
vide à v2.0).
|
| 45 |
4. **[`../../CLAUDE.md`](../../CLAUDE.md)** — règles d'architecture
|
| 46 |
à respecter, statut de la migration, et liens vers le reste.
|
| 47 |
5. **`git log --oneline -10`** — les 10 derniers commits
|
|
|
|
| 158 |
Quand le fichier sera créé en réalité, abaisser
|
| 159 |
``BROKEN_PATHS_BASELINE``.
|
| 160 |
|
| 161 |
+
### 3.F Test parité legacy ↔ canonique (retiré au sprint H.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
+
``tests/architecture/test_legacy_canonical_parity.py`` a été
|
| 164 |
+
supprimé au sprint H.5 (mai 2026) : la table 3-états était
|
| 165 |
+
vidée au fil des Lots A-G (chaque entrée retirée en même temps
|
| 166 |
+
que le shim concerné), et tous les paquets legacy top-level
|
| 167 |
+
qu'elle scannait (``llm/``, ``measurements/``, ``engines/``,
|
| 168 |
+
``modules/``, ``report/``, ``core/``, ``cli/``, ``web/``,
|
| 169 |
+
``extras/``, ``pipelines/``) ont été supprimés ou relocalisés
|
| 170 |
+
sous ``adapters/legacy_*`` et ``interfaces/*/_legacy/``. Le
|
| 171 |
+
seul invariant qui reste à garder est l'absence d'import legacy
|
| 172 |
+
depuis le rewrite (``test_no_legacy_imports_in_rewrite``).
|
| 173 |
|
| 174 |
### 3.G README généré
|
| 175 |
|
|
|
|
| 233 |
|
| 234 |
### 4.C Symboles legacy non tracés dans la table de parité
|
| 235 |
|
| 236 |
+
**Sans objet à v2.0** : la table de parité a été retirée au
|
| 237 |
+
sprint H.5, en même temps que la suppression des derniers
|
| 238 |
+
paquets legacy top-level (cf. 3.F).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
### 4.D Plan de bataille pour les imports tests
|
| 241 |
|
|
|
|
| 282 |
structure, taxonomy, taxonomy_comparison,
|
| 283 |
taxonomy_cooccurrence, taxonomy_intra_doc, throughput,
|
| 284 |
worst_lines}`` → ``evaluation.metrics.{...}``.
|
| 285 |
+
- L'ancien ``measurements/__init__.py`` réécrit pour
|
| 286 |
refléter la nouvelle composition (modules legacy
|
| 287 |
restants + `import picarones.evaluation.metrics`
|
| 288 |
+
unique pour déclencher les décorateurs). Tout le
|
| 289 |
+
sous-package a été supprimé au sprint E.6.
|
| 290 |
- ``test_no_flat_files_in_measurements::WHITELIST_FLAT_FILES_S3``
|
| 291 |
réduit de 60 → 25 entrées.
|
| 292 |
- ``test_module_coverage::TEST_ONLY_BASELINE`` réduit
|
|
|
|
| 372 |
|
| 373 |
---
|
| 374 |
|
| 375 |
+
## 5. Prochaines sub-phases à exécuter (post H.5)
|
| 376 |
+
|
| 377 |
+
Les sprints A-G + H.1-H.3 + H.5 ont été achevés. Restent
|
| 378 |
+
les chantiers suivants pour atteindre v2.0 :
|
| 379 |
+
|
| 380 |
+
### H.2.b-d — refonte API ``BaseOCREngine`` → ``BaseOCRAdapter``
|
| 381 |
+
|
| 382 |
+
- ``adapters/legacy_engines/`` (Tesseract, Pero, Mistral OCR,
|
| 383 |
+
Google Vision, Azure DI) doit être promu en
|
| 384 |
+
``adapters/ocr/`` aux contrats ``StepExecutor``
|
| 385 |
+
(``execute(inputs, params, context)`` au lieu de
|
| 386 |
+
``run(image_path)``).
|
| 387 |
+
- Suppression de ``OCRLLMPipeline`` + ``adapters/legacy_pipelines/``
|
| 388 |
+
une fois les callers migrés vers la construction directe
|
| 389 |
+
d'une ``PipelineSpec`` via
|
| 390 |
+
``picarones.pipeline.make_ocr_llm_pipeline_spec``.
|
| 391 |
+
- Suppression de ``BaseOCREngine``, ``BaseModule``,
|
| 392 |
+
``adapters/legacy_engines/``, ``adapters/legacy_pipelines/``.
|
| 393 |
+
|
| 394 |
+
### H.4 — refonte interfaces
|
| 395 |
+
|
| 396 |
+
- ``interfaces/cli/_legacy/`` : refondre les commandes Click
|
| 397 |
+
pour consommer directement ``BenchmarkService`` /
|
| 398 |
+
``RunOrchestrator`` au lieu du runner legacy.
|
| 399 |
+
- ``interfaces/web/_legacy/`` : refondre les routes FastAPI
|
| 400 |
+
pour consommer le rewrite pur (sans ``OCRLLMPipeline``).
|
| 401 |
+
|
| 402 |
+
### H.6 — release v2.0
|
| 403 |
+
|
| 404 |
+
- Bump version dans ``pyproject.toml`` + ``picarones/_version.py``.
|
| 405 |
+
- Section CHANGELOG « 2.0.0 — Legacy retirement complete ».
|
| 406 |
+
- Tag ``v2.0.0``.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
|
| 408 |
---
|
| 409 |
|
|
|
|
| 420 |
Ou pour aller direct à l'action :
|
| 421 |
|
| 422 |
```
|
| 423 |
+
Attaque H.2.b — refonte BaseOCREngine → BaseOCRAdapter.
|
| 424 |
```
|
| 425 |
|
| 426 |
(Claude Code va automatiquement lire CLAUDE.md à l'init, qui
|
|
@@ -8,14 +8,16 @@
|
|
| 8 |
> [`docs/explanation/architecture.md`](../explanation/architecture.md)).
|
| 9 |
>
|
| 10 |
> **Pendant la migration** (jusqu'à la version 2.0), l'API
|
| 11 |
-
> publique est en cours de refonte.
|
| 12 |
-
> (`picarones.core.
|
| 13 |
-
>
|
| 14 |
-
>
|
| 15 |
-
>
|
| 16 |
-
>
|
| 17 |
-
>
|
| 18 |
-
>
|
|
|
|
|
|
|
| 19 |
|
| 20 |
## Définition
|
| 21 |
|
|
|
|
| 8 |
> [`docs/explanation/architecture.md`](../explanation/architecture.md)).
|
| 9 |
>
|
| 10 |
> **Pendant la migration** (jusqu'à la version 2.0), l'API
|
| 11 |
+
> publique est en cours de refonte. Les chemins legacy
|
| 12 |
+
> top-level (`picarones.core.*`, `picarones.measurements.*`,
|
| 13 |
+
> `picarones.engines.*`, `picarones.modules.*`,
|
| 14 |
+
> `picarones.report.*`, `picarones.llm.*`,
|
| 15 |
+
> `picarones.cli.*`, `picarones.extras.*`,
|
| 16 |
+
> `picarones.fixtures`) ont **tous été supprimés**. Les
|
| 17 |
+
> nouveaux imports doivent utiliser les chemins canoniques
|
| 18 |
+
> (`picarones.domain.*`, `picarones.evaluation.*`,
|
| 19 |
+
> `picarones.adapters.*`, `picarones.reports.*`,
|
| 20 |
+
> `picarones.interfaces.*`).
|
| 21 |
|
| 22 |
## Définition
|
| 23 |
|
|
@@ -150,7 +150,7 @@ def my_hook(*, ground_truth, hypothesis, image_path, corpus_lang, ocr_result):
|
|
| 150 |
|
| 151 |
- [`picarones/evaluation/metric_hooks.py`](../picarones/evaluation/metric_hooks.py)
|
| 152 |
— registre, profils, `run_document_hooks()`, `run_corpus_aggregators()`.
|
| 153 |
-
- [`picarones/
|
| 154 |
— les 12 hooks doc + 12 agrégateurs natifs Picarones.
|
| 155 |
- [`tests/test_metric_hooks.py`](../tests/test_metric_hooks.py)
|
| 156 |
— tests unitaires + rétrocompat profil `standard`.
|
|
|
|
| 150 |
|
| 151 |
- [`picarones/evaluation/metric_hooks.py`](../picarones/evaluation/metric_hooks.py)
|
| 152 |
— registre, profils, `run_document_hooks()`, `run_corpus_aggregators()`.
|
| 153 |
+
- [`picarones/evaluation/metrics/builtin_hooks.py`](../picarones/evaluation/metrics/builtin_hooks.py)
|
| 154 |
— les 12 hooks doc + 12 agrégateurs natifs Picarones.
|
| 155 |
- [`tests/test_metric_hooks.py`](../tests/test_metric_hooks.py)
|
| 156 |
— tests unitaires + rétrocompat profil `standard`.
|
|
@@ -116,7 +116,17 @@ REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
| 116 |
# ``picarones.pipeline.legacy_*``. Les docs concernées
|
| 117 |
# (CHANGELOG.md, audits, sub-plans) gardent volontairement les
|
| 118 |
# anciens chemins pour la traçabilité historique.
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
#: Patrons de fichiers de documentation à scanner.
|
| 122 |
DOC_GLOBS: tuple[str, ...] = (
|
|
|
|
| 116 |
# ``picarones.pipeline.legacy_*``. Les docs concernées
|
| 117 |
# (CHANGELOG.md, audits, sub-plans) gardent volontairement les
|
| 118 |
# anciens chemins pour la traçabilité historique.
|
| 119 |
+
# Sprint H.5 : -11 broken paths — fix des refs actives dans
|
| 120 |
+
# docs/how-to/cli-workflows.md (cli/ → interfaces/cli/_legacy/,
|
| 121 |
+
# extras/importers/_http.py → adapters/corpus/_http.py),
|
| 122 |
+
# docs/explanation/narrative-engine{.,en}.md (measurements/narrative/
|
| 123 |
+
# → reports/narrative/, fixtures.py → evaluation/synthetic.py),
|
| 124 |
+
# docs/reference/normalization-profiles.md (measurements/builtin_hooks
|
| 125 |
+
# → evaluation/metrics/builtin_hooks), docs/developer/doc-consistency.md
|
| 126 |
+
# (engines/, cli/, web/ → leurs nouveaux chemins),
|
| 127 |
+
# docs/migration/SESSION_HANDOVER.md (refonte section 5 pour pointer
|
| 128 |
+
# vers H.2.b-d/H.4/H.6 au lieu de l'ex sub-phase 7.B.2 obsolète).
|
| 129 |
+
BROKEN_PATHS_BASELINE = 161
|
| 130 |
|
| 131 |
#: Patrons de fichiers de documentation à scanner.
|
| 132 |
DOC_GLOBS: tuple[str, ...] = (
|
|
@@ -1,391 +0,0 @@
|
|
| 1 |
-
"""Test architectural — parité legacy ↔ canonique.
|
| 2 |
-
|
| 3 |
-
Pourquoi ce test
|
| 4 |
-
----------------
|
| 5 |
-
Le retrait du legacy se fait par phases (cf. :doc:`docs/migration/legacy-retirement-plan.md`).
|
| 6 |
-
À chaque phase, des symboles publics legacy migrent vers leur
|
| 7 |
-
équivalent canonique. Sans garde-fou, deux risques :
|
| 8 |
-
|
| 9 |
-
1. **Suppression silencieuse d'une feature** : un symbole legacy
|
| 10 |
-
disparaît sans équivalent canonique → la feature est perdue.
|
| 11 |
-
2. **Drift de l'API** : un nouveau symbole legacy est ajouté
|
| 12 |
-
sans équivalent canonique → la dette de migration grossit.
|
| 13 |
-
|
| 14 |
-
Ce test maintient un **journal de bord vivant** :
|
| 15 |
-
:data:`LEGACY_PARITY` est une table 3-états qui pour chaque
|
| 16 |
-
symbole legacy connu déclare :
|
| 17 |
-
|
| 18 |
-
- ``canonical: <module.symbol>`` — symbole équivalent dans
|
| 19 |
-
l'arbre canonique.
|
| 20 |
-
- ``dropped: <raison>`` — feature volontairement abandonnée.
|
| 21 |
-
- ``unmigrated: <cible prévue>`` — migration prévue, à venir.
|
| 22 |
-
|
| 23 |
-
Limites du test
|
| 24 |
-
---------------
|
| 25 |
-
**Ce test ne vérifie que la présence de symbole, pas le
|
| 26 |
-
comportement.** Deux symboles peuvent porter le même nom et
|
| 27 |
-
avoir des sémantiques différentes (cf. ``ArtifactType`` 6 vs 10
|
| 28 |
-
valeurs). Les différences comportementales sont signalées par
|
| 29 |
-
le champ optionnel ``behavior_diff`` qui sert de mémoire à
|
| 30 |
-
l'équipe.
|
| 31 |
-
|
| 32 |
-
Pour la vérification comportementale réelle :
|
| 33 |
-
|
| 34 |
-
- Les tests unitaires métier couvrent les usages individuels.
|
| 35 |
-
- Le test d'intégration ``tests/integration/test_sprint_a14_s12_executor_equivalence.py``
|
| 36 |
-
compare des comportements bout-en-bout.
|
| 37 |
-
- La régression bit-for-bit sur les rapports HTML (cible Phase
|
| 38 |
-
11 du retrait du legacy) couvrira la sortie utilisateur finale.
|
| 39 |
-
|
| 40 |
-
Maintenance
|
| 41 |
-
-----------
|
| 42 |
-
- À chaque migration d'un symbole, ajouter ou mettre à jour son
|
| 43 |
-
entrée dans :data:`LEGACY_PARITY`.
|
| 44 |
-
- Si un nouveau symbole legacy est introduit (rare mais possible
|
| 45 |
-
pour patcher un bug bloquant), l'inscrire avec
|
| 46 |
-
``"unmigrated": "<cible>"``.
|
| 47 |
-
- Si un symbole est supprimé du legacy, retirer son entrée.
|
| 48 |
-
- ``BOOTSTRAP_BASELINE`` autorise temporairement N symboles non
|
| 49 |
-
trackés ; à diminuer à chaque session de migration.
|
| 50 |
-
"""
|
| 51 |
-
|
| 52 |
-
from __future__ import annotations
|
| 53 |
-
|
| 54 |
-
import ast
|
| 55 |
-
import importlib
|
| 56 |
-
import inspect
|
| 57 |
-
import warnings
|
| 58 |
-
from pathlib import Path
|
| 59 |
-
from typing import Any
|
| 60 |
-
|
| 61 |
-
import pytest
|
| 62 |
-
|
| 63 |
-
REPO_ROOT = Path(__file__).resolve().parents[2]
|
| 64 |
-
|
| 65 |
-
#: Paquets legacy (cf. ``test_no_legacy_imports_in_rewrite``).
|
| 66 |
-
LEGACY_PACKAGES: tuple[str, ...] = (
|
| 67 |
-
"llm",
|
| 68 |
-
)
|
| 69 |
-
|
| 70 |
-
#: Combien de symboles legacy peuvent être absents de
|
| 71 |
-
#: :data:`LEGACY_PARITY` sans faire échouer le test. À diminuer
|
| 72 |
-
#: à chaque session de migration : on cible 0 quand le retrait
|
| 73 |
-
#: est complet.
|
| 74 |
-
BOOTSTRAP_BASELINE = 0
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
# ──────────────────────────────────────────────────────────────────
|
| 78 |
-
# Table de parité legacy ↔ canonique
|
| 79 |
-
# ──────────────────────────────────────────────────────────────────
|
| 80 |
-
|
| 81 |
-
#: Entrée de :data:`LEGACY_PARITY`. Exactement une des trois
|
| 82 |
-
#: clés (``canonical``, ``dropped``, ``unmigrated``) est
|
| 83 |
-
#: renseignée par entrée.
|
| 84 |
-
ParityEntry = dict[str, Any]
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
#: Mapping ``"<legacy_module>.<symbol>" → entry``. Les entrées
|
| 88 |
-
#: sont ajoutées au fil des phases de migration ; la table est le
|
| 89 |
-
#: journal de bord vivant du retrait du legacy.
|
| 90 |
-
#:
|
| 91 |
-
#: État initial : seed des migrations déjà effectuées (Phases
|
| 92 |
-
#: 1, 4-bis, 4-ter, 4-quater, 7.A). À étendre à chaque sprint.
|
| 93 |
-
LEGACY_PARITY: dict[str, ParityEntry] = {
|
| 94 |
-
# ──────────────────────────────────────────────────────────
|
| 95 |
-
# Phase 1 — diff_utils, xml_utils, facts
|
| 96 |
-
# ──────────────────────────────────────────────────────────
|
| 97 |
-
# Lot G (mai 2026) : ``picarones.core`` entièrement supprimé.
|
| 98 |
-
# Les helpers ``compute_word_diff``, ``compute_char_diff``,
|
| 99 |
-
# ``diff_stats`` vivent désormais uniquement dans
|
| 100 |
-
# ``picarones.evaluation._diff_utils`` ; ``safe_parse_xml``
|
| 101 |
-
# uniquement dans ``picarones.formats._xml_utils``.
|
| 102 |
-
# ``core.facts`` et ``core.modules`` avaient déjà été
|
| 103 |
-
# supprimés en Lot A. Les entrées correspondantes ont été
|
| 104 |
-
# retirées de cette table pour garder l'alignement avec
|
| 105 |
-
# l'arbre legacy réellement présent sur disque.
|
| 106 |
-
# ──────────────────────────────────────────────────────────
|
| 107 |
-
# Phase 4-ter — metric_registry, metric_hooks, metrics
|
| 108 |
-
# ─────────────────────────────────────────────���────────────
|
| 109 |
-
# ``core.metric_registry``, ``core.metric_hooks`` et
|
| 110 |
-
# ``core.metrics`` ont été supprimés (Lot B de la migration
|
| 111 |
-
# core → evaluation). Les symboles publics
|
| 112 |
-
# (MetricSpec, register_metric, compute_at_junction, …,
|
| 113 |
-
# PROFILE_*, KNOWN_PROFILES, MetricsResult, aggregate_metrics)
|
| 114 |
-
# sont exposés depuis
|
| 115 |
-
# ``picarones.evaluation.{metric_registry, metric_hooks,
|
| 116 |
-
# metric_result}``. Comme pour le Lot A, les entrées sont
|
| 117 |
-
# retirées en même temps que les shims pour garder la table
|
| 118 |
-
# alignée avec l'arbre legacy réellement présent sur disque.
|
| 119 |
-
# ──────────────────────────────────────────────────────────
|
| 120 |
-
# Phase 4-ter résiduel + 4-quater + 5.C.batch7 — results,
|
| 121 |
-
# corpus, pipeline (Lot C)
|
| 122 |
-
# ──────────────────────────────────────────────────────────
|
| 123 |
-
# ``core.results``, ``core.corpus`` et ``core.pipeline`` ont
|
| 124 |
-
# été supprimés (Lot C de la migration core → evaluation).
|
| 125 |
-
# Les symboles publics (BenchmarkResult, EngineReport,
|
| 126 |
-
# DocumentResult, Document, Corpus, GTLevel, TextGT, AltoGT,
|
| 127 |
-
# PageGT, EntitiesGT, ReadingOrderGT, load_corpus_from_directory,
|
| 128 |
-
# PipelineRunner, PipelineSpec, PipelineStep, PipelineResult,
|
| 129 |
-
# StepResult) sont exposés depuis
|
| 130 |
-
# ``picarones.evaluation.{benchmark_result, corpus, pipeline}``.
|
| 131 |
-
# Comme pour les Lots A et B, les entrées sont retirées en
|
| 132 |
-
# même temps que les shims pour garder la table alignée avec
|
| 133 |
-
# l'arbre legacy réellement présent sur disque.
|
| 134 |
-
# Note 7.B-7.D : le ``PipelineRunner`` canonique reste
|
| 135 |
-
# transitoire et délègue progressivement à
|
| 136 |
-
# ``PipelineExecutor`` (cf. pipeline-convergence-plan.md).
|
| 137 |
-
# ──────────────────────────────────────────────────────────
|
| 138 |
-
# Phase 7.A + Lot E — engines, modules
|
| 139 |
-
# ──────────────────────────────────────────────────────────
|
| 140 |
-
# ``picarones/engines/`` et ``picarones/modules/`` ont été
|
| 141 |
-
# supprimés (Lot E de la migration legacy → adapters/legacy_*).
|
| 142 |
-
# Les classes (BaseOCREngine, EngineResult, TesseractEngine,
|
| 143 |
-
# PeroOCREngine, MistralOCREngine, GoogleVisionEngine,
|
| 144 |
-
# AzureDocIntelEngine, engine_from_name, TextToAltoMonoRegion)
|
| 145 |
-
# sont exposées depuis
|
| 146 |
-
# ``picarones.adapters.legacy_engines.{...}`` et
|
| 147 |
-
# ``picarones.adapters.legacy_modules.alto_text_to_mono_region``.
|
| 148 |
-
# Comme pour les Lots précédents, les entrées ont été retirées
|
| 149 |
-
# pour garder la table alignée avec l'arbre legacy réellement
|
| 150 |
-
# présent sur disque.
|
| 151 |
-
# Note 7.D : ces adapters legacy seront eux-mêmes refondus en
|
| 152 |
-
# ``BaseOCRAdapter (StepExecutor)`` lors de la convergence
|
| 153 |
-
# pipeline (cf. pipeline-convergence-plan.md).
|
| 154 |
-
}
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
# ──────────────────────────────────────────────────────────────────
|
| 158 |
-
# Helpers
|
| 159 |
-
# ──────────────────────────────────────────────────────────────────
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
def _resolve(dotted: str) -> Any | None:
|
| 163 |
-
"""Résout ``"package.module.symbol"`` en l'objet effectif.
|
| 164 |
-
|
| 165 |
-
Retourne ``None`` si la résolution échoue (module introuvable
|
| 166 |
-
ou symbole absent). Émet un warning silencieux : on
|
| 167 |
-
ignore les ``DeprecationWarning`` des shims.
|
| 168 |
-
"""
|
| 169 |
-
parts = dotted.rsplit(".", 1)
|
| 170 |
-
if len(parts) != 2:
|
| 171 |
-
return None
|
| 172 |
-
module_path, symbol = parts
|
| 173 |
-
try:
|
| 174 |
-
with warnings.catch_warnings():
|
| 175 |
-
warnings.simplefilter("ignore", DeprecationWarning)
|
| 176 |
-
module = importlib.import_module(module_path)
|
| 177 |
-
except ImportError:
|
| 178 |
-
return None
|
| 179 |
-
return getattr(module, symbol, None)
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
def _signatures_compatible(legacy_obj: Any, canonical_obj: Any) -> bool:
|
| 183 |
-
"""Vérifie que deux callables ont des signatures compatibles.
|
| 184 |
-
|
| 185 |
-
Compatible = même nombre de paramètres positionnels. Pour
|
| 186 |
-
les classes, on inspecte ``__init__``. Si l'un des deux
|
| 187 |
-
n'est pas inspectable (e.g. C-builtin), on retourne ``True``
|
| 188 |
-
(on ne peut pas comparer).
|
| 189 |
-
"""
|
| 190 |
-
try:
|
| 191 |
-
sig_legacy = inspect.signature(legacy_obj)
|
| 192 |
-
sig_canonical = inspect.signature(canonical_obj)
|
| 193 |
-
except (TypeError, ValueError):
|
| 194 |
-
# Pas un callable inspectable : on tolère
|
| 195 |
-
return True
|
| 196 |
-
pos_legacy = [
|
| 197 |
-
p for p in sig_legacy.parameters.values()
|
| 198 |
-
if p.kind in (
|
| 199 |
-
inspect.Parameter.POSITIONAL_ONLY,
|
| 200 |
-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
| 201 |
-
)
|
| 202 |
-
]
|
| 203 |
-
pos_canonical = [
|
| 204 |
-
p for p in sig_canonical.parameters.values()
|
| 205 |
-
if p.kind in (
|
| 206 |
-
inspect.Parameter.POSITIONAL_ONLY,
|
| 207 |
-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
| 208 |
-
)
|
| 209 |
-
]
|
| 210 |
-
return len(pos_legacy) == len(pos_canonical)
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
def _scan_legacy_public_symbols() -> set[str]:
|
| 214 |
-
"""Liste tous les symboles publics top-level dans les
|
| 215 |
-
paquets legacy.
|
| 216 |
-
|
| 217 |
-
Scanne via AST (statique, plus stable que ``import + dir``).
|
| 218 |
-
Retourne les ``"<full_module_path>.<symbol_name>"``.
|
| 219 |
-
"""
|
| 220 |
-
out: set[str] = set()
|
| 221 |
-
for pkg in LEGACY_PACKAGES:
|
| 222 |
-
root = REPO_ROOT / "picarones" / pkg
|
| 223 |
-
if not root.is_dir():
|
| 224 |
-
continue
|
| 225 |
-
for path in root.rglob("*.py"):
|
| 226 |
-
if "__pycache__" in path.parts:
|
| 227 |
-
continue
|
| 228 |
-
if path.name == "__init__.py":
|
| 229 |
-
continue # __init__ : on ne tracke que les modules nommés
|
| 230 |
-
try:
|
| 231 |
-
tree = ast.parse(path.read_text(encoding="utf-8"))
|
| 232 |
-
except (OSError, SyntaxError):
|
| 233 |
-
continue
|
| 234 |
-
module_name = ".".join(
|
| 235 |
-
["picarones", *path.relative_to(REPO_ROOT / "picarones").with_suffix("").parts]
|
| 236 |
-
)
|
| 237 |
-
for node in tree.body:
|
| 238 |
-
names: list[str] = []
|
| 239 |
-
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
| 240 |
-
names.append(node.name)
|
| 241 |
-
elif isinstance(node, ast.Assign):
|
| 242 |
-
for target in node.targets:
|
| 243 |
-
if isinstance(target, ast.Name):
|
| 244 |
-
names.append(target.id)
|
| 245 |
-
for sym in names:
|
| 246 |
-
if sym.startswith("_"):
|
| 247 |
-
continue
|
| 248 |
-
out.add(f"{module_name}.{sym}")
|
| 249 |
-
return out
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
# ──────────────────────────────────────────────────────────────────
|
| 253 |
-
# Tests
|
| 254 |
-
# ──────────────────────────────────────────────────────────────────
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
@pytest.mark.parametrize(
|
| 258 |
-
"legacy_path,entry",
|
| 259 |
-
sorted(LEGACY_PARITY.items()),
|
| 260 |
-
ids=lambda v: v if isinstance(v, str) else "",
|
| 261 |
-
)
|
| 262 |
-
def test_each_entry_is_resolvable(legacy_path: str, entry: ParityEntry) -> None:
|
| 263 |
-
"""Chaque entrée doit avoir exactement un état renseigné.
|
| 264 |
-
|
| 265 |
-
Validation par entrée :
|
| 266 |
-
|
| 267 |
-
- ``canonical`` : les deux symboles existent + signatures
|
| 268 |
-
compatibles (warning si non).
|
| 269 |
-
- ``dropped`` : justification non vide.
|
| 270 |
-
- ``unmigrated``: cible non vide (le canonique peut ne pas
|
| 271 |
-
encore exister, c'est tout l'intérêt de cet état).
|
| 272 |
-
"""
|
| 273 |
-
states = {"canonical", "dropped", "unmigrated"}
|
| 274 |
-
present = states & entry.keys()
|
| 275 |
-
assert len(present) == 1, (
|
| 276 |
-
f"{legacy_path} doit avoir exactement un de {states}, "
|
| 277 |
-
f"trouvé : {sorted(present)}"
|
| 278 |
-
)
|
| 279 |
-
state = next(iter(present))
|
| 280 |
-
if state == "canonical":
|
| 281 |
-
canonical_path = entry["canonical"]
|
| 282 |
-
legacy_obj = _resolve(legacy_path)
|
| 283 |
-
canonical_obj = _resolve(canonical_path)
|
| 284 |
-
assert legacy_obj is not None, (
|
| 285 |
-
f"Symbole legacy ``{legacy_path}`` introuvable. "
|
| 286 |
-
"Si le symbole a été supprimé, retire son entrée de "
|
| 287 |
-
"LEGACY_PARITY."
|
| 288 |
-
)
|
| 289 |
-
assert canonical_obj is not None, (
|
| 290 |
-
f"Symbole canonique ``{canonical_path}`` introuvable. "
|
| 291 |
-
"Vérifie le chemin canonique ou que le module existe."
|
| 292 |
-
)
|
| 293 |
-
assert _signatures_compatible(legacy_obj, canonical_obj), (
|
| 294 |
-
f"Signatures incompatibles entre ``{legacy_path}`` et "
|
| 295 |
-
f"``{canonical_path}``. Mets à jour ``behavior_diff`` "
|
| 296 |
-
"pour documenter le changement, ou aligne les signatures."
|
| 297 |
-
)
|
| 298 |
-
elif state == "dropped":
|
| 299 |
-
reason = entry["dropped"]
|
| 300 |
-
assert isinstance(reason, str) and reason.strip(), (
|
| 301 |
-
f"{legacy_path} marqué dropped sans justification. "
|
| 302 |
-
"Ajoute la raison du drop pour traçabilité."
|
| 303 |
-
)
|
| 304 |
-
elif state == "unmigrated":
|
| 305 |
-
target = entry["unmigrated"]
|
| 306 |
-
assert isinstance(target, str) and target.strip(), (
|
| 307 |
-
f"{legacy_path} marqué unmigrated sans cible. "
|
| 308 |
-
"Ajoute le chemin canonique prévu."
|
| 309 |
-
)
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
def test_no_untracked_legacy_symbol_above_baseline() -> None:
|
| 313 |
-
"""Tout symbole public legacy doit être tracé dans :data:`LEGACY_PARITY`.
|
| 314 |
-
|
| 315 |
-
Mode bootstrap : :data:`BOOTSTRAP_BASELINE` autorise N
|
| 316 |
-
symboles non trackés. À diminuer à chaque sprint de
|
| 317 |
-
migration ; cible 0 quand la migration est complète.
|
| 318 |
-
"""
|
| 319 |
-
public_symbols = _scan_legacy_public_symbols()
|
| 320 |
-
untracked = public_symbols - LEGACY_PARITY.keys()
|
| 321 |
-
if len(untracked) > BOOTSTRAP_BASELINE:
|
| 322 |
-
sample = "\n".join(f" {s}" for s in sorted(untracked)[:30])
|
| 323 |
-
more = (
|
| 324 |
-
f"\n ... ({len(untracked) - 30} de plus)"
|
| 325 |
-
if len(untracked) > 30
|
| 326 |
-
else ""
|
| 327 |
-
)
|
| 328 |
-
raise AssertionError(
|
| 329 |
-
f"\n{len(untracked)} symbole(s) legacy non tracé(s) "
|
| 330 |
-
f"dans LEGACY_PARITY (baseline {BOOTSTRAP_BASELINE}).\n\n"
|
| 331 |
-
f"{sample}{more}\n\n"
|
| 332 |
-
"Ajoute chaque symbole à LEGACY_PARITY avec son état :\n"
|
| 333 |
-
" - ``canonical: <module.symbol>`` si déjà migré\n"
|
| 334 |
-
" - ``dropped: <raison>`` si abandonné\n"
|
| 335 |
-
" - ``unmigrated: <cible prévue>`` si encore à venir\n\n"
|
| 336 |
-
"Ou abaisse BOOTSTRAP_BASELINE si on est sous le seuil "
|
| 337 |
-
"(faute de quoi le test ne progresse plus)."
|
| 338 |
-
)
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
def test_baseline_should_tighten_when_progress() -> None:
|
| 342 |
-
"""Si on est sous le baseline, abaisser BOOTSTRAP_BASELINE.
|
| 343 |
-
|
| 344 |
-
Ce test est l'inverse du précédent : il rappelle que le
|
| 345 |
-
baseline doit suivre la progression. Pareil pattern que
|
| 346 |
-
``test_doc_paths::test_baseline_must_be_tightened_when_progress_made``.
|
| 347 |
-
"""
|
| 348 |
-
public_symbols = _scan_legacy_public_symbols()
|
| 349 |
-
untracked = public_symbols - LEGACY_PARITY.keys()
|
| 350 |
-
assert len(untracked) >= BOOTSTRAP_BASELINE, (
|
| 351 |
-
f"\nExcellent : {len(untracked)} symboles non tracés vs "
|
| 352 |
-
f"baseline {BOOTSTRAP_BASELINE}.\n"
|
| 353 |
-
"Mets à jour BOOTSTRAP_BASELINE dans "
|
| 354 |
-
"tests/architecture/test_legacy_canonical_parity.py."
|
| 355 |
-
)
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
def test_canonical_paths_dont_themselves_use_legacy() -> None:
|
| 359 |
-
"""Les cibles canoniques ne doivent pas pointer vers du legacy.
|
| 360 |
-
|
| 361 |
-
Cas pathologique : on déclare ``canonical:
|
| 362 |
-
picarones.evaluation.X`` mais ``picarones.evaluation.X``
|
| 363 |
-
importe en interne depuis ``picarones.core.Y``. Ce serait un
|
| 364 |
-
bug d'aiguillage.
|
| 365 |
-
|
| 366 |
-
Ce test ne couvre pas le cas (couverture par
|
| 367 |
-
``test_no_legacy_imports_in_rewrite``) ; ici on se contente
|
| 368 |
-
de vérifier que les cibles canoniques sont dans des paquets
|
| 369 |
-
rewrite reconnus.
|
| 370 |
-
"""
|
| 371 |
-
REWRITE_PREFIXES = (
|
| 372 |
-
"picarones.domain.",
|
| 373 |
-
"picarones.formats.",
|
| 374 |
-
"picarones.evaluation.",
|
| 375 |
-
"picarones.pipeline.",
|
| 376 |
-
"picarones.adapters.",
|
| 377 |
-
"picarones.app.",
|
| 378 |
-
"picarones.reports.",
|
| 379 |
-
"picarones.interfaces.",
|
| 380 |
-
)
|
| 381 |
-
misrouted: list[tuple[str, str]] = []
|
| 382 |
-
for legacy, entry in LEGACY_PARITY.items():
|
| 383 |
-
canonical = entry.get("canonical")
|
| 384 |
-
if not canonical:
|
| 385 |
-
continue
|
| 386 |
-
if not canonical.startswith(REWRITE_PREFIXES):
|
| 387 |
-
misrouted.append((legacy, canonical))
|
| 388 |
-
assert not misrouted, (
|
| 389 |
-
"Cibles canoniques en dehors des paquets rewrite :\n"
|
| 390 |
-
+ "\n".join(f" {legacy} → {canonical}" for legacy, canonical in misrouted)
|
| 391 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|