Claude commited on
Commit
5c1dfb1
·
unverified ·
1 Parent(s): e7041cb

docs(migration): plan de retrait complet du legacy vers la 2.0

Browse files

Document préparatoire à la décision : pas de cohabitation legacy +
rewrite à long terme, la 2.0 est livrée sans aucune ligne legacy.
Critère absolu : zéro bricolage, zéro semi-rendu, zéro régression
de comportement éditorial.

Plan en 12 phases (77-110 jours-personnes, 3,5-5 mois) :

- Phase 0 : foundation (harness régression + tracker + tolérances)
- Phase 1 : core/results, facts, pipeline, modules
- Phase 2 : statistics (Wilcoxon, Friedman/Nemenyi, bootstrap, …)
- Phase 3 : narrative engine (18 détecteurs + arbiter + 36 templates)
- Phase 4 : 35 mesures legacy (par cluster thématique)
- Phase 5 : 22 renderers HTML + glossaire + i18n
- Phase 6 : pipelines OCRLLMPipeline → PipelineSpec composable
- Phase 7 : modules officiels (TextToAltoMonoRegion)
- Phase 8 : importers IIIF/Gallica/eScriptorium
- Phase 9 : 9 routers web legacy → interfaces/web/
- Phase 10 : 13 commandes CLI manquantes
- Phase 11 : retrait final + bump 2.0

Le document est déclaré 'vivant' et mis à jour à chaque phase.
Définition de 'done' universelle, stratégie de régression
bit-for-bit, anti-bricolage explicite (pas de double API, pas de
shim sans date de retrait, pas de TODO sans issue, pas de
copié-collé, pas de god-module).

NOTE : ce commit livre uniquement le PLAN. L'exécution attend la
validation utilisateur avant de démarrer la Phase 0.

docs/migration/legacy-retirement-plan.md ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Plan de retrait complet du legacy — vers la 2.0
2
+
3
+ > **Décision stratégique** : pas de cohabitation legacy + rewrite à
4
+ > long terme. La 2.0 est livrée **sans aucune ligne legacy**.
5
+ > L'arborescence cible (domain → formats → evaluation → pipeline →
6
+ > adapters → app → reports_v2 → interfaces) est unique.
7
+ >
8
+ > **Critère absolu** : zéro bricolage, zéro semi-rendu, zéro
9
+ > régression de comportement éditorial. Une institution comme la
10
+ > BnF ne tolère pas un *partial rewrite*.
11
+ >
12
+ > **Pas de contrainte de date** : on livre quand tout est propre.
13
+ >
14
+ > **Document vivant** : ce plan est mis à jour à chaque phase
15
+ > achevée. Toute exception ou découverte doit être inscrite ici.
16
+
17
+ ## Définition de « done » universelle
18
+
19
+ Chaque phase est terminée quand **tous** les critères suivants sont
20
+ remplis :
21
+
22
+ 1. **Code** : les modules legacy de la phase ont été soit migrés,
23
+ soit déclarés sans équivalent et supprimés (avec justification).
24
+ 2. **Tests** : tous les tests qui pointaient vers le legacy sont
25
+ migrés vers le rewrite ; les nouveaux tests couvrent le rewrite
26
+ à un niveau ≥ celui du legacy.
27
+ 3. **Régression** : le harness `tests/regression/legacy_vs_rewrite/`
28
+ prouve que le rewrite produit les mêmes résultats que le legacy
29
+ sur les corpus de référence (tolérance ε explicite par métrique).
30
+ 4. **Doc** : la doc utilisateur, opérationnelle et architecturale
31
+ ne mentionne plus le legacy de la phase ; les chemins cassés
32
+ `tests/architecture/test_doc_paths.py` baseline diminue.
33
+ 5. **Lint** : `ruff check picarones/ tests/` clean.
34
+ 6. **Suite complète** : `pytest tests/` 100 % vert sur 3 OS × 3
35
+ versions Python (3.11, 3.12, 3.13).
36
+ 7. **Coverage** : ≥ 85 %, pas de dégradation > 0,5 pt vs. la phase
37
+ précédente.
38
+
39
+ ## Phases
40
+
41
+ ### Phase 0 — Foundation (en cours)
42
+
43
+ **Objectif** : poser les garde-fous qui rendent les 11 phases
44
+ suivantes **vérifiables** sans introduire de régression invisible.
45
+
46
+ **Livrables** :
47
+
48
+ - [x] `docs/migration/legacy-retirement-plan.md` (ce document) —
49
+ inventaire complet, phases, acceptance criteria.
50
+ - [ ] `tests/regression/legacy_vs_rewrite/` — harness qui exécute
51
+ legacy + rewrite sur 3 corpus de référence et compare bit-for-bit
52
+ (avec ε explicite par métrique).
53
+ - [ ] `docs/migration/regression-tolerances.md` — table des
54
+ tolérances acceptables par métrique (ex : CER ε = 0, narrative
55
+ templates ε = 0 mais ordre des facts non-significatif, etc.).
56
+ - [ ] Test architectural `test_no_legacy_imports_in_rewrite.py` qui
57
+ garantit qu'un module rewrite ne réintroduit jamais d'import
58
+ legacy.
59
+
60
+ **Critère de fin** : harness vert sur 3 corpus de référence pour
61
+ les fonctionnalités déjà migrées (5 OCR, 4 LLM, 4 VLM, vues
62
+ canoniques). Toute migration future doit ajouter son corpus de
63
+ régression.
64
+
65
+ ### Phase 1 — Foundation conceptuelle (`core/`, `domain/`)
66
+
67
+ **Modules à migrer** :
68
+
69
+ | Legacy | Cible rewrite | Note |
70
+ |--------|---------------|------|
71
+ | `core/results.py` (677 LOC, `BenchmarkResult`/`EngineReport`/`DocumentResult` + 30 champs agrégés) | `domain/run_result.py` + champs agrégés en `Artifact` typés | Le plus critique |
72
+ | `core/facts.py` | déjà dans `domain/facts.py` | Vérifier parité |
73
+ | `core/pipeline.py` (legacy `PipelineSpec` + `BaseModule`) | `domain/pipeline_spec.py` + `domain/module_protocol.py` | Migration des callers |
74
+ | `core/modules.py` (`BaseModule`, `ArtifactType` 6 valeurs) | `domain/artifacts.py` (déjà 10 valeurs) + `domain/module_protocol.py` | Le superset |
75
+ | `core/metric_registry.py` + `metric_hooks.py` | `evaluation/registry/` (déjà migré) | Vérifier callers |
76
+ | `core/corpus.py` (`Document`, `Corpus`, `GTLevel`) | `domain/corpus.py` + `domain/documents.py` (déjà migré) | Modèle différent — convertisseur |
77
+ | `core/diff_utils.py` | `evaluation/utils/diff.py` (à créer) | Pure utility |
78
+ | `core/xml_utils.py` | `formats/xml_utils.py` (à créer) | Pure utility |
79
+ | `core/metrics.py` | `evaluation/metrics/_base.py` (à créer) | API helpers |
80
+
81
+ **Effort** : 5-8 jours-personnes.
82
+
83
+ **Acceptance** : aucun fichier `picarones.{adapters,evaluation,pipeline,
84
+ app,reports_v2,interfaces}` n'importe plus `picarones.core`. Le
85
+ package `core/` peut être vidé en gardant uniquement des shims
86
+ `DeprecationWarning` (ou directement supprimé si aucun caller
87
+ externe ne le lit).
88
+
89
+ ### Phase 2 — Statistics (`measurements/statistics/`)
90
+
91
+ **Modules** : `wilcoxon.py`, `friedman_nemenyi.py`, `bootstrap.py`,
92
+ `pareto.py`, `clustering.py`, `correlation.py`, `distributions.py`,
93
+ `cdd_render.py`.
94
+
95
+ **Cible** : nouveau sous-package `picarones/evaluation/statistics/`.
96
+
97
+ **Effort** : 5-7 jours. Code mathématique pur, pas de couplage
98
+ applicatif.
99
+
100
+ **Acceptance** : régression bit-for-bit sur les outputs des 8 tests
101
+ statistiques + le rendu CDD SVG.
102
+
103
+ ### Phase 3 — Narrative engine (`measurements/narrative/`)
104
+
105
+ **Modules** : `arbiter.py`, `renderer.py`, `registry.py` + 18
106
+ détecteurs en 6 familles (`ranking/`, `pareto/`, `stratum/`,
107
+ `quality/`, `history/`, `ensemble/`) + 36 templates YAML FR/EN +
108
+ `core/facts.py`.
109
+
110
+ **Cible** : `picarones/reports_v2/narrative/` (le narratif est de
111
+ la **présentation**, pas du domaine — il vit du côté rapport, pas
112
+ de l'évaluation).
113
+
114
+ **Effort** : 8-12 jours. Le bloc le plus délicat — 18 détecteurs
115
+ chacun avec garde-fous anti-hallucination, arbitre de cohérence
116
+ inter-détecteurs, renderer Jinja par templates YAML.
117
+
118
+ **Acceptance** : régression bit-for-bit sur la synthèse rendue
119
+ pour chaque cas-test legacy `test_sprint{16,19,29,36,39,42,44,46,
120
+ 51,56,73,81,90,92}_*.py` (les sprints qui ont introduit chaque
121
+ détecteur).
122
+
123
+ ### Phase 4 — 35 mesures legacy (`measurements/*.py`)
124
+
125
+ **Modules** (ordre par cluster thématique, chaque cluster = un PR/sprint) :
126
+
127
+ #### 4.A — Philologique (8 modules) — 5 jours
128
+
129
+ `mufi.py`, `abbreviations.py`, `early_modern_typography.py`,
130
+ `modern_archives.py`, `roman_numerals.py`, `unicode_blocks.py`,
131
+ `equivalence_profile.py`, `philological_hooks.py`.
132
+
133
+ #### 4.B — NER + readability (6 modules) — 4 jours
134
+
135
+ `ner.py`, `ner_backends.py`, `readability.py`, `readability_hooks.py`,
136
+ `searchability.py`, `searchability_hooks.py`.
137
+
138
+ #### 4.C — Structurel (5 modules) — 3 jours
139
+
140
+ `reading_order.py`, `structure.py`, `alto_metrics.py`, `line_metrics.py`
141
+ (legacy), `numerical_sequences.py` + `numerical_sequences_hooks.py`.
142
+
143
+ #### 4.D — Robustness, reliability, history (4 modules) — 4 jours
144
+
145
+ `robustness.py`, `reliability.py`, `history.py`, `specialization.py`.
146
+
147
+ #### 4.E — Pipeline metrics (3 modules) — 3 jours
148
+
149
+ `pipeline_benchmark.py`, `pipeline_comparison.py`,
150
+ `pipeline_spec_loader.py` — opèrent sur le `PipelineSpec` legacy.
151
+ À fondre dans la couche `app/services/` du rewrite.
152
+
153
+ #### 4.F — Utility / hooks (9 modules) — 4 jours
154
+
155
+ `builtin_hooks.py`, `builtin_metrics.py`, `metrics.py`, `char_scores.py`,
156
+ `cost_projection.py`, `difficulty.py`, `taxonomy.py`,
157
+ `taxonomy_intra_doc.py`, `runner/*` (sous-package).
158
+
159
+ **Cible** : chaque mesure migre vers `evaluation/metrics/<thème>/`
160
+ ou un sous-package adapté. Enregistrement via la `MetricRegistry`
161
+ typée (signature `(input_type, output_type) → score`).
162
+
163
+ **Effort total Phase 4** : 23-28 jours.
164
+
165
+ **Acceptance** : régression bit-for-bit sur les ~5000 tests existants
166
+ qui pointent vers ces métriques.
167
+
168
+ ### Phase 5 — Reports HTML (`report/`)
169
+
170
+ **Modules** :
171
+
172
+ - 22 renderers thématiques : `baseline_render.py`, `calibration_render.py`,
173
+ `difficulty_render.py`, `error_absorption_render.py`,
174
+ `image_predictive_render.py`, `incremental_comparison_render.py`,
175
+ `inter_engine_render.py`, `levers_render.py`,
176
+ `lexical_modernization_render.py`, `longitudinal_render.py`,
177
+ `marginal_cost_render.py`, `module_audit_render.py`,
178
+ `multirun_stability_render.py`, `ner_render.py`,
179
+ `numerical_sequences_render.py`, `philological_render.py`,
180
+ `pipeline_dag_render.py`, `pipeline_render.py`,
181
+ `rare_token_recall_render.py`, `readability_render.py`,
182
+ `robustness_projection_render.py`, `searchability_render.py`,
183
+ `specialization_render.py`, `stratification_render.py`,
184
+ `taxonomy_comparison_render.py`, `taxonomy_cooccurrence_render.py`,
185
+ `taxonomy_intra_doc_render.py`, `throughput_render.py`,
186
+ `worst_lines_render.py`.
187
+ - 5 vues : `views/{advanced_taxonomy,diagnostics,economics,
188
+ pipeline,robustness}.py`.
189
+ - `generator.py` (orchestrateur), `comparison.py`, `snapshot.py`,
190
+ `assets.py`, `colors.py`, `render_helpers.py`, `report_data/`.
191
+ - `templates/` (~10 fichiers Jinja2), `glossary/` (2 YAML, 25
192
+ entrées), `i18n/`, `vendor/`.
193
+
194
+ **Cible** : `picarones/reports_v2/html/views/<theme>.py` + helpers
195
+ partagés dans `reports_v2/html/_helpers/`. Glossaire dans
196
+ `reports_v2/html/glossary/`. Templates Jinja2 dans
197
+ `reports_v2/html/templates/`.
198
+
199
+ **Effort** : 12-18 jours.
200
+
201
+ **Acceptance** : régression bit-for-bit sur le HTML produit pour
202
+ les 3 corpus de référence. Aucun renderer legacy laissé.
203
+
204
+ ### Phase 6 — Pipelines OCR+LLM (`pipelines/`)
205
+
206
+ **Modules** : `pipelines/base.OCRLLMPipeline` (3 modes), `pipelines/
207
+ over_normalization.detect_over_normalization`.
208
+
209
+ **Cible** :
210
+
211
+ - Les 3 modes deviennent des `PipelineSpec` YAML composés (OCR
212
+ adapter → LLM adapter avec `inputs_from`).
213
+ - `over_normalization` devient une métrique enregistrée dans
214
+ `evaluation/metrics/over_normalization.py`.
215
+
216
+ **Effort** : 3-5 jours.
217
+
218
+ **Acceptance** : les 3 callers internes (`web/benchmark_utils.py`,
219
+ `measurements/runner/document.py`, `fixtures.py`) consomment des
220
+ `PipelineSpec` YAML rewrite.
221
+
222
+ ### Phase 7 — Modules officiels (`modules/`)
223
+
224
+ **Module** : `modules/alto_text_to_mono_region.TextToAltoMonoRegion`
225
+ (310 LOC) — baseline TEXT → ALTO.
226
+
227
+ **Cible** : `picarones/formats/alto/baseline_reconstruction.py` ou
228
+ `picarones/evaluation/projectors/text_to_alto.py` (selon où la
229
+ sémantique colle le mieux).
230
+
231
+ **Effort** : 1 jour.
232
+
233
+ ### Phase 8 — Importers (`extras/importers/`)
234
+
235
+ **Modules** : `iiif.py`, `gallica.py`, `escriptorium.py`, `_http.py`,
236
+ `_fallback_log.py`.
237
+
238
+ **Cible** : `picarones/adapters/corpus/{iiif,gallica,escriptorium}.py`
239
+ + helpers partagés dans `adapters/corpus/_http.py`.
240
+
241
+ **Effort** : 3-5 jours.
242
+
243
+ ### Phase 9 — Web UI riche (`web/`)
244
+
245
+ **Modules** : 9 routers (`config`, `engines`, `history`, `home`,
246
+ `importers`, `normalization`, `reports`, `synthesis`, `system`) +
247
+ utilitaires (`benchmark_utils.py`, `engine_utils.py`,
248
+ `corpus_utils.py`, `config_utils.py`, `state.py`, `security.py`,
249
+ `models.py`, `jobs.py`, `maintenance.py`, `app.py`) + templates
250
+ Jinja2.
251
+
252
+ **Cible** : `picarones/interfaces/web/routers/<router>.py` + utils
253
+ partagés dans `interfaces/web/_utils/` + templates dans
254
+ `interfaces/web/templates/`.
255
+
256
+ **Effort** : 8-12 jours.
257
+
258
+ **Acceptance** : régression sur tous les `tests/web/test_sprint*.py`
259
+ existants. L'UI riche (sélecteur moteurs dynamique, gallery,
260
+ stratification, narrative inline, browse corpus) doit produire les
261
+ mêmes pages HTML.
262
+
263
+ ### Phase 10 — CLI complète (`cli/`)
264
+
265
+ **Commandes** : 13 commandes legacy non couvertes (`metrics`,
266
+ `engines`, `info`, `demo`, `diagnose`, `economics`, `edition`,
267
+ `compare`, `import` group, `serve`, `history`, `robustness`,
268
+ `pipeline` group avec sous-commandes `run` et `compare`).
269
+
270
+ **Cible** : `picarones/interfaces/cli/<command>.py`. L'entry point
271
+ `console_scripts` du `pyproject.toml` doit pointer sur
272
+ `picarones.interfaces.cli:cli` (à la place de `picarones.cli:cli`).
273
+
274
+ **Effort** : 4-6 jours.
275
+
276
+ ### Phase 11 — Retrait final + release 2.0
277
+
278
+ - Suppression des 10 packages legacy.
279
+ - Suppression des shims `DeprecationWarning` introduits aux phases
280
+ précédentes.
281
+ - Mise à jour du `pyproject.toml` (`console_scripts`,
282
+ `[project.urls]`).
283
+ - Rédaction du CHANGELOG 2.0 final avec liste exhaustive des
284
+ breaking changes (les utilisateurs externes ont eu
285
+ `DeprecationWarning` à chaque phase).
286
+ - Génération SBOM + signature SLSA Level 3 (cf.
287
+ `docs/operations/supply-chain.md`).
288
+ - Bump `_version.py` et tag `v2.0.0`.
289
+
290
+ **Effort** : 3-5 jours.
291
+
292
+ ## Estimation totale
293
+
294
+ | Phase | Effort min | Effort max |
295
+ |-------|------------|------------|
296
+ | 0 | 2 j | 3 j |
297
+ | 1 | 5 j | 8 j |
298
+ | 2 | 5 j | 7 j |
299
+ | 3 | 8 j | 12 j |
300
+ | 4 | 23 j | 28 j |
301
+ | 5 | 12 j | 18 j |
302
+ | 6 | 3 j | 5 j |
303
+ | 7 | 1 j | 1 j |
304
+ | 8 | 3 j | 5 j |
305
+ | 9 | 8 j | 12 j |
306
+ | 10 | 4 j | 6 j |
307
+ | 11 | 3 j | 5 j |
308
+ | **Total** | **77 j** | **110 j** |
309
+
310
+ Soit **3,5 à 5 mois** d'effort focalisé en mode développeur unique.
311
+ Aucune contrainte de date — on livre quand c'est propre.
312
+
313
+ ## Stratégie de régression — invariant non négociable
314
+
315
+ À chaque phase :
316
+
317
+ 1. **Avant** : exécuter le harness legacy sur 3 corpus de référence
318
+ (small / medium / large) → capture des outputs en JSON / HTML
319
+ bit-for-bit.
320
+ 2. **Pendant** : réécrire la fonctionnalité dans le rewrite.
321
+ 3. **Après** : exécuter le harness rewrite et **diff** vs. snapshot
322
+ legacy.
323
+ 4. **Tolérance** : explicite par métrique dans
324
+ `docs/migration/regression-tolerances.md`. Tout écart non
325
+ tolerance = régression à corriger avant merge.
326
+
327
+ Cela évite le piège classique du rewrite : *« ça compile, ça tourne,
328
+ mais le CER a glissé de 0,002 par doc »*.
329
+
330
+ ## Anti-bricolage — règles
331
+
332
+ 1. **Pas de double API** : pendant la migration d'un module, on ne
333
+ garde **pas** le legacy en parallèle dans le code de production.
334
+ Soit on importe l'ancien, soit le nouveau. Le harness de
335
+ régression suffit pour valider.
336
+ 2. **Pas de shim sans date de retrait** : tout `DeprecationWarning`
337
+ introduit doit être inscrit dans le CHANGELOG avec date de
338
+ retrait (la 2.0).
339
+ 3. **Pas de TODO dans le code mergé** : un TODO = une issue ouverte
340
+ référencée par numéro.
341
+ 4. **Pas de copié-collé** : si une logique apparaît dans deux
342
+ modules, extraire en helper partagé dès la deuxième occurrence.
343
+ 5. **Pas de god-module** : `tests/architecture/test_file_budgets.py`
344
+ reste l'autorité.
345
+
346
+ ## Statut
347
+
348
+ | Phase | Statut |
349
+ |-------|--------|
350
+ | 0 | 🟡 En cours |
351
+ | 1-11 | ⚪ À démarrer |
352
+
353
+ **Dernière mise à jour** : 2026-05.