Claude commited on
Commit
c6da3d3
·
unverified ·
1 Parent(s): 9011070

feat(sprint-H.5): cleanup baselines + tests obsolètes

Browse files

Sprint 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 CHANGED
@@ -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é. Tout symbole legacy public
225
- doit être tracé dans
226
- [`tests/architecture/test_legacy_canonical_parity.py`](tests/architecture/test_legacy_canonical_parity.py)
227
- c'est le journal de bord vivant qui garantit qu'aucune
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)
docs/developer/doc-consistency.md CHANGED
@@ -30,11 +30,11 @@ Vérifie que :
30
 
31
  | Item | Source de vérité | Sens du contrat |
32
  |---|---|---|
33
- | Moteurs OCR | `picarones/engines/*.py` | Tout moteur listé dans le tableau « Supported Engines » du README doit avoir un adapter |
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/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
 
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
docs/explanation/narrative-engine.en.md CHANGED
@@ -24,7 +24,7 @@ class FactType(str, Enum):
24
 
25
  ### 2. Add the FR + EN templates
26
 
27
- `picarones/measurements/narrative/templates/fr.yaml`:
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/measurements/narrative/detectors/quality.py` for
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/measurements/narrative/detectors/__init__.py`:
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/measurements/narrative/arbiter.py` — append your new
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
docs/explanation/narrative-engine.md CHANGED
@@ -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/fixtures.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
 
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
docs/how-to/cli-workflows.md CHANGED
@@ -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/extras/importers/_http.py`](../picarones/extras/importers/_http.py))
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).
docs/migration/SESSION_HANDOVER.md CHANGED
@@ -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
- - Tout symbole legacy public doit être tracé dans
22
- ``tests/architecture/test_legacy_canonical_parity.py`` :
23
- `canonical: ...` (équivalent canonique existe), `dropped: ...`
24
- (volontairement abandonné, justifié), ou `unmigrated: ...`
25
- (cible prévue, en cours).
26
-
27
- Le test ``test_legacy_canonical_parity`` garantit qu'**aucune
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/test_legacy_canonical_parity.py`](../../tests/architecture/test_legacy_canonical_parity.py)** —
44
- journal vivant de la migration : table 3-états des symboles
45
- legacy avec leur équivalent canonique. À mettre à jour à
46
- chaque migration.
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
- Limites du test : il ne vérifie que la **présence** et les
180
- **signatures**, pas le comportement réel. Les différences
181
- sémantiques sont signalées via le champ ``behavior_diff``
182
- optionnel.
 
 
 
 
 
 
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
- **110 symboles** publics dans les paquets legacy ne sont pas
247
- encore dans
248
- ``tests/architecture/test_legacy_canonical_parity.py::LEGACY_PARITY``.
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
- - ``picarones/measurements/__init__.py`` réécrit pour
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. Prochaine sub-phase à exécuter
393
-
394
- **Sub-phase 7.B.2** refactoriser le corps de
395
- ``PipelineRunner.run`` dans
396
- ``picarones/evaluation/pipeline.py`` (lignes 384-590) pour
397
- qu'il délègue au canonique ``PipelineExecutor`` via le
398
- wrapper ``_BaseModuleAdapter`` créé en 7.B.1.
399
-
400
- ### Plan d'exécution
401
-
402
- 1. **Lire** ``picarones/evaluation/pipeline.py:PipelineRunner.run``
403
- en entier pour comprendre la logique actuelle (résolution
404
- d'inputs versionnés, exécution chronométrée, capture
405
- d'erreur, évaluation auto vs GT, conversion outputs).
406
-
407
- 2. **(Phase 7.D — module supprimé)** : ``_BaseModuleAdapter`` et
408
- ``_PayloadRegistry`` vivaient dans ``_legacy_module_adapter.py``,
409
- retiré avec le runner en 7.D.
410
-
411
- 3. **Écrire** un nouveau corps de ``PipelineRunner.run`` qui :
412
- - Crée un ``_PayloadRegistry`` par appel.
413
- - Wrappe les ``initial_inputs`` legacy via
414
- ``wrap_initial_inputs(...)``.
415
- - Convertit la ``PipelineSpec`` legacy en ``PipelineSpec``
416
- canonique (``picarones.domain.pipeline_spec.PipelineSpec``).
417
- Chaque ``PipelineStep.module: BaseModule`` devient un
418
- ``adapter_name: str``, et l'adapter est
419
- ``_BaseModuleAdapter(module, registry)``.
420
- - Construit un ``adapter_resolver`` qui retourne le
421
- wrapper de chaque module.
422
- - Construit un ``RunContext``.
423
- - Convertit le ``Document`` legacy en ``DocumentRef``.
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
- Continue la sub-phase 7.B.2.
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
docs/reference/api-stable.md CHANGED
@@ -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. Tous les chemins legacy
12
- > (`picarones.core.X`, `picarones.measurements.X`, etc.) sont
13
- > des shims `DeprecationWarning` qui ré-exportent depuis le
14
- > canonique. Les nouveaux imports doivent utiliser les chemins
15
- > canoniques (`picarones.domain.*`, `picarones.evaluation.*`).
16
- >
17
- > Le tableau de parité legacy canonique vit dans
18
- > [`tests/architecture/test_legacy_canonical_parity.py`](../../tests/architecture/test_legacy_canonical_parity.py).
 
 
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
 
docs/reference/normalization-profiles.md CHANGED
@@ -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/measurements/builtin_hooks.py`](../picarones/measurements/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`.
 
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`.
tests/architecture/test_doc_paths.py CHANGED
@@ -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
- BROKEN_PATHS_BASELINE = 172
 
 
 
 
 
 
 
 
 
 
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, ...] = (
tests/architecture/test_legacy_canonical_parity.py DELETED
@@ -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
- )