Spaces:
Sleeping
Sleeping
Merge pull request #57 from maribakulj/claude/repo-analysis-cukvm
Browse filesMigration legacy → rewrite : 165+ shims supprimés (Lots A à J + fix templates)
This view is limited to 50 files because it contains too many changes. See raw diff
- .github/workflows/ci.yml +44 -11
- .gitignore +11 -1
- .well-known/security.txt +18 -0
- CHANGELOG.md +25 -25
- CLAUDE.md +0 -0
- GOVERNANCE.md +1 -1
- NOTICE +28 -0
- README.md +28 -29
- SECURITY.en.md +109 -0
- SPECS.md +99 -63
- docs/api/adapters.md +82 -0
- docs/api/app.md +39 -0
- docs/api/domain.md +30 -0
- docs/api/evaluation.md +47 -0
- docs/api/index.md +51 -0
- docs/api/pipeline.md +25 -0
- docs/architecture.md +0 -179
- docs/developer/extending-i18n.md +1 -1
- docs/developer/index.en.md +1 -1
- docs/developer/index.md +36 -20
- docs/developer/module-policy.md +4 -3
- docs/explanation/architecture.md +190 -0
- docs/{developer → explanation}/narrative-engine.en.md +2 -2
- docs/{developer → explanation}/narrative-engine.md +0 -0
- docs/{cli-workflows.md → how-to/cli-workflows.md} +2 -2
- INSTALL.md → docs/how-to/install.md +8 -19
- docs/index.md +160 -0
- docs/legal/THIRD_PARTY_LICENSES.md +155 -0
- docs/legal/dpa-template.md +218 -0
- docs/migration/SESSION_HANDOVER.md +508 -0
- docs/migration/legacy-retirement-plan.md +1239 -0
- docs/migration/pipeline-convergence-plan.md +410 -0
- docs/migration/regression-tolerances.md +178 -0
- docs/operations/deployment-institutional.md +1 -1
- docs/operations/observability.md +208 -0
- docs/operations/runbook.md +374 -0
- docs/operations/supply-chain.md +125 -0
- docs/{views → reference}/alto-view.md +0 -0
- docs/{api-stable.md → reference/api-stable.md} +45 -31
- docs/{views → reference}/comparing-views.md +0 -0
- docs/{profiles.md → reference/normalization-profiles.md} +5 -5
- docs/{reproducibility-snapshots.md → reference/reproducibility-snapshots.md} +0 -0
- docs/{views → reference}/text-view.md +0 -0
- docs/{views.md → reference/views.md} +9 -9
- BACKLOG_POST_LIVRAISON.md → docs/roadmap/backlog.md +0 -0
- docs/roadmap/rewrite-2026.md +14 -13
- docs/security/threat-model.md +148 -0
- docs/{user → tutorials}/reading-a-report.en.md +2 -2
- docs/{user → tutorials}/reading-a-report.md +2 -2
- docs/{user → tutorials}/writing-a-pipeline-module.md +9 -8
.github/workflows/ci.yml
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
# - Linux, macOS, Windows
|
| 6 |
# - Couverture exigée >= 85 % (--cov-fail-under, plancher 2 pts sous baseline 87 %)
|
| 7 |
# - Timeout pytest 5 min par test individuel (pytest-timeout, mode thread)
|
| 8 |
-
# - Type-check mypy (strict sur picarones/
|
| 9 |
# - Scanners sécurité : bandit (statique) + pip-audit (CVE deps) + trivy (image)
|
| 10 |
# - Build de la distribution Python
|
| 11 |
# - Vérification de l'exécutable demo
|
|
@@ -92,22 +92,55 @@ jobs:
|
|
| 92 |
# ── Tests ───────────────────────────────────────────────────
|
| 93 |
# Sprint A1 : --cov-fail-under=85 (baseline mesuré 87 %, marge 2 pts).
|
| 94 |
# pytest-timeout est configuré dans pyproject.toml [tool.pytest.ini_options].
|
| 95 |
-
#
|
| 96 |
-
#
|
| 97 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
- name: Run tests
|
| 99 |
# Sur Python 3.13, on continue malgré une erreur pour ne pas bloquer
|
| 100 |
# le merge pendant la fenêtre informationnelle de 6 mois (m-8).
|
| 101 |
continue-on-error: ${{ matrix.python-version == '3.13' }}
|
| 102 |
-
timeout-minutes:
|
| 103 |
shell: bash
|
| 104 |
run: |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
env:
|
| 109 |
PYTHONIOENCODING: utf-8
|
| 110 |
PYTHONUTF8: "1"
|
|
|
|
| 111 |
|
| 112 |
# ── Couverture ──────────────────────────────────────────────
|
| 113 |
# Conditions :
|
|
@@ -245,7 +278,7 @@ jobs:
|
|
| 245 |
# Job 5 : Type-checking — Sprint A1 (item M-4)
|
| 246 |
#
|
| 247 |
# mypy est configuré dans pyproject.toml [tool.mypy] :
|
| 248 |
-
# - strict sur picarones.
|
| 249 |
# - lax ailleurs (follow_imports=silent)
|
| 250 |
# Deux checks pré-existants désactivés (disallow_any_generics et
|
| 251 |
# warn_return_any), à ré-activer en Sprint A11 après fix des
|
|
@@ -270,8 +303,8 @@ jobs:
|
|
| 270 |
python -m pip install --upgrade pip setuptools wheel
|
| 271 |
pip install -e ".[dev,web,stats]"
|
| 272 |
|
| 273 |
-
- name: Run mypy on picarones/
|
| 274 |
-
run: python -m mypy picarones/
|
| 275 |
|
| 276 |
# ──────────────────────────────────────────────────────────────────
|
| 277 |
# Job 6 : Sécurité — Sprint A1 (item B-7)
|
|
|
|
| 5 |
# - Linux, macOS, Windows
|
| 6 |
# - Couverture exigée >= 85 % (--cov-fail-under, plancher 2 pts sous baseline 87 %)
|
| 7 |
# - Timeout pytest 5 min par test individuel (pytest-timeout, mode thread)
|
| 8 |
+
# - Type-check mypy (strict sur picarones/domain/, lax ailleurs — durci en A11)
|
| 9 |
# - Scanners sécurité : bandit (statique) + pip-audit (CVE deps) + trivy (image)
|
| 10 |
# - Build de la distribution Python
|
| 11 |
# - Vérification de l'exécutable demo
|
|
|
|
| 92 |
# ── Tests ───────────────────────────────────────────────────
|
| 93 |
# Sprint A1 : --cov-fail-under=85 (baseline mesuré 87 %, marge 2 pts).
|
| 94 |
# pytest-timeout est configuré dans pyproject.toml [tool.pytest.ini_options].
|
| 95 |
+
#
|
| 96 |
+
# Garde-fous anti-hang :
|
| 97 |
+
#
|
| 98 |
+
# 1. ``timeout-minutes: 12`` au niveau step : cap dur GitHub si
|
| 99 |
+
# tout le reste échoue.
|
| 100 |
+
# 2. ``timeout`` GNU autour de pytest : SIGTERM à 9 minutes,
|
| 101 |
+
# SIGKILL 30s après si Python n'a pas obéi. Couvre
|
| 102 |
+
# spécifiquement le cas d'un hang de SHUTDOWN de
|
| 103 |
+
# l'interpréteur Python 3.12+ (threads non-daemon, connexions
|
| 104 |
+
# sqlite non fermées, ResourceWarnings — observé sur ubuntu
|
| 105 |
+
# 3.12 où pytest finit en 3:21 et l'interpréteur reste 12 min
|
| 106 |
+
# avant de rendre la main).
|
| 107 |
+
# 3. ``-X faulthandler`` : si le hang revient, on aura les stack
|
| 108 |
+
# traces de tous les threads dans le log avant le SIGKILL.
|
| 109 |
+
# 4. ``PYTHONFAULTHANDLER=1`` redondance ceinture-bretelles.
|
| 110 |
+
#
|
| 111 |
+
# Le code de retour 124 (SIGTERM par GNU timeout) ou 137 (SIGKILL)
|
| 112 |
+
# est traité comme un échec normal — on perd l'info pytest mais
|
| 113 |
+
# on préserve la latence de la CI.
|
| 114 |
- name: Run tests
|
| 115 |
# Sur Python 3.13, on continue malgré une erreur pour ne pas bloquer
|
| 116 |
# le merge pendant la fenêtre informationnelle de 6 mois (m-8).
|
| 117 |
continue-on-error: ${{ matrix.python-version == '3.13' }}
|
| 118 |
+
timeout-minutes: 12
|
| 119 |
shell: bash
|
| 120 |
run: |
|
| 121 |
+
# ``timeout`` n'est pas standard sur macOS (BSD vs GNU) — on
|
| 122 |
+
# détecte et on adapte. Sur Windows, le shell bash de
|
| 123 |
+
# Git-Bash n'a pas timeout : on retombe sur python direct.
|
| 124 |
+
if command -v timeout >/dev/null 2>&1; then
|
| 125 |
+
timeout --signal=SIGTERM --kill-after=30 540 \
|
| 126 |
+
python -X faulthandler -m pytest tests/ -q --tb=short --no-header \
|
| 127 |
+
--cov=picarones --cov-report=xml --cov-report=term-missing \
|
| 128 |
+
--cov-fail-under=85
|
| 129 |
+
elif command -v gtimeout >/dev/null 2>&1; then
|
| 130 |
+
# macOS Homebrew coreutils.
|
| 131 |
+
gtimeout --signal=SIGTERM --kill-after=30 540 \
|
| 132 |
+
python -X faulthandler -m pytest tests/ -q --tb=short --no-header \
|
| 133 |
+
--cov=picarones --cov-report=xml --cov-report=term-missing \
|
| 134 |
+
--cov-fail-under=85
|
| 135 |
+
else
|
| 136 |
+
python -X faulthandler -m pytest tests/ -q --tb=short --no-header \
|
| 137 |
+
--cov=picarones --cov-report=xml --cov-report=term-missing \
|
| 138 |
+
--cov-fail-under=85
|
| 139 |
+
fi
|
| 140 |
env:
|
| 141 |
PYTHONIOENCODING: utf-8
|
| 142 |
PYTHONUTF8: "1"
|
| 143 |
+
PYTHONFAULTHANDLER: "1"
|
| 144 |
|
| 145 |
# ── Couverture ──────────────────────────────────────────────
|
| 146 |
# Conditions :
|
|
|
|
| 278 |
# Job 5 : Type-checking — Sprint A1 (item M-4)
|
| 279 |
#
|
| 280 |
# mypy est configuré dans pyproject.toml [tool.mypy] :
|
| 281 |
+
# - strict sur picarones.domain.* (couche 1 du rewrite, ex-picarones.core)
|
| 282 |
# - lax ailleurs (follow_imports=silent)
|
| 283 |
# Deux checks pré-existants désactivés (disallow_any_generics et
|
| 284 |
# warn_return_any), à ré-activer en Sprint A11 après fix des
|
|
|
|
| 303 |
python -m pip install --upgrade pip setuptools wheel
|
| 304 |
pip install -e ".[dev,web,stats]"
|
| 305 |
|
| 306 |
+
- name: Run mypy on picarones/domain (strict)
|
| 307 |
+
run: python -m mypy picarones/domain/
|
| 308 |
|
| 309 |
# ──────────────────────────────────────────────────────────────────
|
| 310 |
# Job 6 : Sécurité — Sprint A1 (item B-7)
|
.gitignore
CHANGED
|
@@ -28,9 +28,19 @@ jobs.db-shm
|
|
| 28 |
jobs.db-wal
|
| 29 |
|
| 30 |
# Exceptions : fichiers HTML sources du package (templates Jinja2, pas rapports)
|
| 31 |
-
!picarones/report/templates/*.html
|
| 32 |
!picarones/web/templates/*.html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
# Sprint A14-S3 — sous-package du code (homonyme de corpus/ data ignoré ligne 21)
|
| 34 |
!picarones/adapters/corpus/
|
| 35 |
!picarones/adapters/corpus/**
|
|
|
|
|
|
|
|
|
|
| 36 |
_version.py
|
|
|
|
| 28 |
jobs.db-wal
|
| 29 |
|
| 30 |
# Exceptions : fichiers HTML sources du package (templates Jinja2, pas rapports)
|
|
|
|
| 31 |
!picarones/web/templates/*.html
|
| 32 |
+
# Lot G fix (mai 2026) — Phase 5.E avait migré les templates de
|
| 33 |
+
# picarones/report/templates/ vers picarones/reports_v2/html/templates/
|
| 34 |
+
# mais oublié l'exception .gitignore correspondante : les 10 .html
|
| 35 |
+
# avaient donc été silencieusement ignorés par git lors du commit
|
| 36 |
+
# cc53ead, faisant échouer ~91 tests (TemplateNotFound _header.html
|
| 37 |
+
# etc.). Cette nouvelle exception remplace l'ancienne (plus en
|
| 38 |
+
# vigueur depuis la suppression de picarones/report/ au Lot F).
|
| 39 |
+
!picarones/reports_v2/html/templates/*.html
|
| 40 |
# Sprint A14-S3 — sous-package du code (homonyme de corpus/ data ignoré ligne 21)
|
| 41 |
!picarones/adapters/corpus/
|
| 42 |
!picarones/adapters/corpus/**
|
| 43 |
+
# Phase 4-quater (cleanup) : ré-ignorer __pycache__/ dans ce sous-package
|
| 44 |
+
# (la négation ci-dessus est trop large et casse la règle ligne 1).
|
| 45 |
+
picarones/adapters/corpus/**/__pycache__/
|
| 46 |
_version.py
|
.well-known/security.txt
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Picarones — security.txt (RFC 9116)
|
| 2 |
+
#
|
| 3 |
+
# This file is meant to be served at:
|
| 4 |
+
# https://<deployment-host>/.well-known/security.txt
|
| 5 |
+
#
|
| 6 |
+
# Institutional deployments (BnF, LoC, BL) MUST update the values
|
| 7 |
+
# below before going live — the canonical contact for the upstream
|
| 8 |
+
# project is the GitHub Security Advisories endpoint, but each
|
| 9 |
+
# deployment SHOULD designate its own security contact.
|
| 10 |
+
|
| 11 |
+
Contact: https://github.com/maribakulj/Picarones/security/advisories/new
|
| 12 |
+
Expires: 2027-05-31T23:59:59.000Z
|
| 13 |
+
Encryption: https://github.com/maribakulj/Picarones/security/advisories/new
|
| 14 |
+
Acknowledgments: https://github.com/maribakulj/Picarones/security/advisories
|
| 15 |
+
Preferred-Languages: fr, en
|
| 16 |
+
Canonical: https://github.com/maribakulj/Picarones/blob/main/.well-known/security.txt
|
| 17 |
+
Policy: https://github.com/maribakulj/Picarones/blob/main/SECURITY.md
|
| 18 |
+
Hiring: https://github.com/maribakulj/Picarones/issues
|
CHANGELOG.md
CHANGED
|
@@ -7,9 +7,15 @@ La numérotation de version suit [Semantic Versioning](https://semver.org/lang/f
|
|
| 7 |
|
| 8 |
---
|
| 9 |
|
| 10 |
-
## [Unreleased] —
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
Le ``FilesystemArtifactStore`` produisait des filenames de la forme
|
| 15 |
``<step_hash>:<output_type>.json`` (séparateur ``:``). ``:`` est un
|
|
@@ -29,7 +35,7 @@ les ``ArtifactType`` et tous les caractères Windows réservés.
|
|
| 29 |
acceptable. Aucun impact sur les artefacts persistés (l'index
|
| 30 |
``index.jsonl`` est régénéré automatiquement).
|
| 31 |
|
| 32 |
-
### CI : exclusion des tests live + timeout codecov
|
| 33 |
|
| 34 |
Voir commit `ce30e80` :
|
| 35 |
|
|
@@ -40,11 +46,9 @@ Voir commit `ce30e80` :
|
|
| 40 |
``timeout-minutes: 5`` sur ``Upload coverage to Codecov`` ;
|
| 41 |
``fail_ci_if_error: false`` sur codecov.
|
| 42 |
|
| 43 |
-
--
|
| 44 |
-
|
| 45 |
-
## [Unreleased] — audit institutionnel S58-S59 (post-S57) — 2026-05
|
| 46 |
|
| 47 |
-
### ⚠️ BREAKING CHANGES (déprécations en cours, suppression en 2.0)
|
| 48 |
|
| 49 |
Trois symboles supprimés au S57 sont **restaurés en S59** comme alias
|
| 50 |
dépréciés avec `DeprecationWarning` à l'accès. Ils seront supprimés
|
|
@@ -142,17 +146,15 @@ réseau (TimeoutError, ConnectionError, URLError).
|
|
| 142 |
- `tests/architecture/test_manifest_reproducibility.py` : 4 tests.
|
| 143 |
- `tests/interfaces/web/test_rate_limit_xff.py` : 7 tests.
|
| 144 |
|
| 145 |
-
--
|
| 146 |
-
|
| 147 |
-
## [Unreleased] — rewrite A14 (S27-S46) + audit remediation (S47-S57) — 2026-05
|
| 148 |
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
|
| 155 |
-
### Phase rewrite (S27-S46) — partial rewrite
|
| 156 |
|
| 157 |
20 sprints sur la directive *« rewrite tout, le plus solide, sans dette
|
| 158 |
technique »*. Stratégie : **rewrite parallèle**, pas full rewrite — le
|
|
@@ -177,7 +179,7 @@ benchmark, jobs), JobStore SQLite, UI Jinja2 + i18n FR/EN.
|
|
| 177 |
SearchView). Vues thématiques legacy (Pareto, narrative, glossary,
|
| 178 |
case-studies) à porter une à une post-livraison.
|
| 179 |
|
| 180 |
-
### Phase remédiation (S47-S57) — 30 dettes adressées en 6 vagues
|
| 181 |
|
| 182 |
| Vague | Sprint | Issues | Thème |
|
| 183 |
|-------|--------|--------|-------|
|
|
@@ -191,7 +193,7 @@ case-studies) à porter une à une post-livraison.
|
|
| 191 |
|
| 192 |
**Tous les 30 issues sont adressés au S57**.
|
| 193 |
|
| 194 |
-
### S57 — détail des rectifications
|
| 195 |
|
| 196 |
- **#15 Lazy imports SDK tiers** : confirmé intentionnel — `mistralai`,
|
| 197 |
`anthropic`, `openai`, `ollama` sont importés à l'intérieur des
|
|
@@ -254,11 +256,9 @@ case-studies) à porter une à une post-livraison.
|
|
| 254 |
qualité d'image, présence de notes marginales). Un seuil à 10
|
| 255 |
points faisait échouer la CI sur du bruit légitime.
|
| 256 |
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
## [Unreleased] — fix CI perf_regression — 2026-05
|
| 260 |
|
| 261 |
-
### ⚠️ BREAKING CHANGE — sémantique `--fail-if-cer-above`
|
| 262 |
|
| 263 |
L'option `picarones run --fail-if-cer-above` interprétait sa valeur
|
| 264 |
comme un **pourcentage** (ex : `15.0` = 15 %). Désormais elle attend
|
|
@@ -1889,7 +1889,7 @@ sur des monolithes globaux.
|
|
| 1889 |
ingénieur qui veut **brancher son propre module** dans Picarones
|
| 1890 |
(correcteur LLM, reconstructeur ALTO, classifieur d'entités,
|
| 1891 |
re-segmenteur…) trouve maintenant un guide complet bout-en-bout.
|
| 1892 |
-
- Nouveau document `docs/
|
| 1893 |
- **TL;DR** avec un exemple `MyCorrector` minimal en 25 lignes.
|
| 1894 |
- Section **« Le contrat ``BaseModule`` »** : tableau des
|
| 1895 |
champs obligatoires (``input_types``, ``output_types``,
|
|
@@ -3437,7 +3437,7 @@ sur des monolithes globaux.
|
|
| 3437 |
`CONFIDENCE_WARNING`, `cost_unit_pages=1000` propagé dans
|
| 3438 |
`PARETO_ALTERNATIVE`/`COST_OUTLIER`, paramètre `select_facts(..., type_order=...)`,
|
| 3439 |
test stabilité bootstrap (±0,5 pp inter-seeds), test E2E synthèse EN.
|
| 3440 |
-
Doc « Politique éditoriale » dans `docs/
|
| 3441 |
- **Sprint 24** — durcissement sécurité institutionnelle : mode public
|
| 3442 |
(`PICARONES_PUBLIC_MODE=1`), `PICARONES_BROWSE_ROOTS`, validation Pillow
|
| 3443 |
sur upload (CVE-2023-50447), rate limit + sémaphore concurrence,
|
|
@@ -3520,7 +3520,7 @@ sur des monolithes globaux.
|
|
| 3520 |
vue opt-in « score composite personnel » avec curseurs à 0 par défaut
|
| 3521 |
et formule visible. État persisté en URL. +21 tests.
|
| 3522 |
- **Sprint 22** — études de cas (`docs/case-studies/`),
|
| 3523 |
-
`docs/
|
| 3524 |
`docs/developer/`. Garde-fou « pas de fausses études prétendant
|
| 3525 |
être réelles ». +18 tests.
|
| 3526 |
|
|
|
|
| 7 |
|
| 8 |
---
|
| 9 |
|
| 10 |
+
## [Unreleased] — towards 1.3.0 (release institutionnelle BnF) — 2026-05
|
| 11 |
|
| 12 |
+
> Section unique conforme à Keep-a-Changelog. Les chantiers actifs
|
| 13 |
+
> sont regroupés ci-dessous par thème ; chaque thème reflète un audit
|
| 14 |
+
> ou un fix livré sur la branche ``claude/repo-analysis-cukvm``.
|
| 15 |
+
|
| 16 |
+
### Fix CI : Windows + cap timeout (S59)
|
| 17 |
+
|
| 18 |
+
#### Bug Windows : `:` dans les clés du store
|
| 19 |
|
| 20 |
Le ``FilesystemArtifactStore`` produisait des filenames de la forme
|
| 21 |
``<step_hash>:<output_type>.json`` (séparateur ``:``). ``:`` est un
|
|
|
|
| 35 |
acceptable. Aucun impact sur les artefacts persistés (l'index
|
| 36 |
``index.jsonl`` est régénéré automatiquement).
|
| 37 |
|
| 38 |
+
#### CI : exclusion des tests live + timeout codecov
|
| 39 |
|
| 40 |
Voir commit `ce30e80` :
|
| 41 |
|
|
|
|
| 46 |
``timeout-minutes: 5`` sur ``Upload coverage to Codecov`` ;
|
| 47 |
``fail_ci_if_error: false`` sur codecov.
|
| 48 |
|
| 49 |
+
### Audit institutionnel S58-S59 (post-S57)
|
|
|
|
|
|
|
| 50 |
|
| 51 |
+
#### ⚠️ BREAKING CHANGES (déprécations en cours, suppression en 2.0)
|
| 52 |
|
| 53 |
Trois symboles supprimés au S57 sont **restaurés en S59** comme alias
|
| 54 |
dépréciés avec `DeprecationWarning` à l'accès. Ils seront supprimés
|
|
|
|
| 146 |
- `tests/architecture/test_manifest_reproducibility.py` : 4 tests.
|
| 147 |
- `tests/interfaces/web/test_rate_limit_xff.py` : 7 tests.
|
| 148 |
|
| 149 |
+
### Rewrite A14 (S27-S46) + audit remediation (S47-S57)
|
|
|
|
|
|
|
| 150 |
|
| 151 |
+
Cette section couvre la phase **rewrite ciblé** (S27-S46) puis les
|
| 152 |
+
**6 vagues de remédiation** des dettes identifiées en audit
|
| 153 |
+
*institutional readiness 2026-05* (S47-S57). Détail complet dans
|
| 154 |
+
`docs/migration/rewrite-status-s46.md` et
|
| 155 |
+
`docs/audits/remediation-plan-2026-05.md`.
|
| 156 |
|
| 157 |
+
#### Phase rewrite (S27-S46) — partial rewrite
|
| 158 |
|
| 159 |
20 sprints sur la directive *« rewrite tout, le plus solide, sans dette
|
| 160 |
technique »*. Stratégie : **rewrite parallèle**, pas full rewrite — le
|
|
|
|
| 179 |
SearchView). Vues thématiques legacy (Pareto, narrative, glossary,
|
| 180 |
case-studies) à porter une à une post-livraison.
|
| 181 |
|
| 182 |
+
#### Phase remédiation (S47-S57) — 30 dettes adressées en 6 vagues
|
| 183 |
|
| 184 |
| Vague | Sprint | Issues | Thème |
|
| 185 |
|-------|--------|--------|-------|
|
|
|
|
| 193 |
|
| 194 |
**Tous les 30 issues sont adressés au S57**.
|
| 195 |
|
| 196 |
+
#### S57 — détail des rectifications
|
| 197 |
|
| 198 |
- **#15 Lazy imports SDK tiers** : confirmé intentionnel — `mistralai`,
|
| 199 |
`anthropic`, `openai`, `ollama` sont importés à l'intérieur des
|
|
|
|
| 256 |
qualité d'image, présence de notes marginales). Un seuil à 10
|
| 257 |
points faisait échouer la CI sur du bruit légitime.
|
| 258 |
|
| 259 |
+
### Fix CI perf_regression
|
|
|
|
|
|
|
| 260 |
|
| 261 |
+
#### ⚠️ BREAKING CHANGE — sémantique `--fail-if-cer-above`
|
| 262 |
|
| 263 |
L'option `picarones run --fail-if-cer-above` interprétait sa valeur
|
| 264 |
comme un **pourcentage** (ex : `15.0` = 15 %). Désormais elle attend
|
|
|
|
| 1889 |
ingénieur qui veut **brancher son propre module** dans Picarones
|
| 1890 |
(correcteur LLM, reconstructeur ALTO, classifieur d'entités,
|
| 1891 |
re-segmenteur…) trouve maintenant un guide complet bout-en-bout.
|
| 1892 |
+
- Nouveau document `docs/tutorials/writing-a-pipeline-module.md` :
|
| 1893 |
- **TL;DR** avec un exemple `MyCorrector` minimal en 25 lignes.
|
| 1894 |
- Section **« Le contrat ``BaseModule`` »** : tableau des
|
| 1895 |
champs obligatoires (``input_types``, ``output_types``,
|
|
|
|
| 3437 |
`CONFIDENCE_WARNING`, `cost_unit_pages=1000` propagé dans
|
| 3438 |
`PARETO_ALTERNATIVE`/`COST_OUTLIER`, paramètre `select_facts(..., type_order=...)`,
|
| 3439 |
test stabilité bootstrap (±0,5 pp inter-seeds), test E2E synthèse EN.
|
| 3440 |
+
Doc « Politique éditoriale » dans `docs/explanation/narrative-engine.md`.
|
| 3441 |
- **Sprint 24** — durcissement sécurité institutionnelle : mode public
|
| 3442 |
(`PICARONES_PUBLIC_MODE=1`), `PICARONES_BROWSE_ROOTS`, validation Pillow
|
| 3443 |
sur upload (CVE-2023-50447), rate limit + sémaphore concurrence,
|
|
|
|
| 3520 |
vue opt-in « score composite personnel » avec curseurs à 0 par défaut
|
| 3521 |
et formule visible. État persisté en URL. +21 tests.
|
| 3522 |
- **Sprint 22** — études de cas (`docs/case-studies/`),
|
| 3523 |
+
`docs/tutorials/reading-a-report.md`, trois guides développeur dans
|
| 3524 |
`docs/developer/`. Garde-fou « pas de fausses études prétendant
|
| 3525 |
être réelles ». +18 tests.
|
| 3526 |
|
CLAUDE.md
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
GOVERNANCE.md
CHANGED
|
@@ -97,7 +97,7 @@ prestation (cf. modalités à définir au cas par cas).
|
|
| 97 |
## Politique de breaking changes
|
| 98 |
|
| 99 |
L'API publique de Picarones est définie par
|
| 100 |
-
[`docs/api-stable.md`](docs/api-stable.md). Elle inclut :
|
| 101 |
|
| 102 |
- les symboles ré-exportés depuis `picarones/__init__.py` ;
|
| 103 |
- les commandes CLI `picarones X` documentées dans le README ;
|
|
|
|
| 97 |
## Politique de breaking changes
|
| 98 |
|
| 99 |
L'API publique de Picarones est définie par
|
| 100 |
+
[`docs/reference/api-stable.md`](docs/reference/api-stable.md). Elle inclut :
|
| 101 |
|
| 102 |
- les symboles ré-exportés depuis `picarones/__init__.py` ;
|
| 103 |
- les commandes CLI `picarones X` documentées dans le README ;
|
NOTICE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Picarones
|
| 2 |
+
Copyright 2025-2026 the Picarones contributors
|
| 3 |
+
|
| 4 |
+
Licensed under the Apache License, Version 2.0 (the "License"); you
|
| 5 |
+
may not use this software except in compliance with the License.
|
| 6 |
+
You may obtain a copy of the License at
|
| 7 |
+
|
| 8 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
|
| 10 |
+
Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 13 |
+
implied. See the License for the specific language governing
|
| 14 |
+
permissions and limitations under the License.
|
| 15 |
+
|
| 16 |
+
────────────────────────────────────────────────────────────────────
|
| 17 |
+
Third-party software
|
| 18 |
+
────────────────────────────────────────────────────────────────────
|
| 19 |
+
|
| 20 |
+
This product includes software developed by third parties. The
|
| 21 |
+
authoritative list of third-party dependencies, their licenses and
|
| 22 |
+
their copyright notices is maintained in:
|
| 23 |
+
|
| 24 |
+
docs/legal/THIRD_PARTY_LICENSES.md
|
| 25 |
+
|
| 26 |
+
That file is regenerated by ``scripts/gen_third_party_licenses.py``
|
| 27 |
+
on every release. In case of discrepancy, the file in the
|
| 28 |
+
``docs/legal/`` directory at the time of release prevails.
|
README.md
CHANGED
|
@@ -102,7 +102,7 @@ Three families of metrics calibrated for historical documents:
|
|
| 102 |
trend with change-point detection; controlled per-slot ANOVA-like
|
| 103 |
comparison.
|
| 104 |
|
| 105 |
-
For the full list with definitions, see [`docs/views.md`](docs/views.md)
|
| 106 |
and the contextual glossary embedded in every report (25 bilingual
|
| 107 |
entries).
|
| 108 |
|
|
@@ -189,7 +189,7 @@ picarones serve --port 8080
|
|
| 189 |
```
|
| 190 |
|
| 191 |
For Docker, institutional deployment, or HuggingFace Spaces, see
|
| 192 |
-
[`
|
| 193 |
[`docs/operations/deployment-institutional.md`](docs/operations/deployment-institutional.md).
|
| 194 |
|
| 195 |
---
|
|
@@ -210,12 +210,12 @@ For Docker, institutional deployment, or HuggingFace Spaces, see
|
|
| 210 |
|
| 211 |
LLM/VLM adapters (used through pipelines, not as standalone OCR
|
| 212 |
engines): GPT-4o, Claude, Mistral Large, Ollama (local). See
|
| 213 |
-
[`docs/cli-workflows.md`](docs/cli-workflows.md).
|
| 214 |
|
| 215 |
The `Engine` table is regenerated automatically by
|
| 216 |
`scripts/gen_readme_tables.py` — adding a new adapter under
|
| 217 |
-
`picarones/
|
| 218 |
-
fail.
|
| 219 |
|
| 220 |
---
|
| 221 |
|
|
@@ -244,7 +244,7 @@ fail.
|
|
| 244 |
<!-- /generated:cli -->
|
| 245 |
|
| 246 |
Each command supports `--help` for full options. See
|
| 247 |
-
[`docs/cli-workflows.md`](docs/cli-workflows.md) for end-to-end
|
| 248 |
examples.
|
| 249 |
|
| 250 |
---
|
|
@@ -299,7 +299,7 @@ client generation.
|
|
| 299 |
|
| 300 |
Picarones ships **11 built-in normalization profiles** for historical
|
| 301 |
text comparison (defined in
|
| 302 |
-
[`picarones/
|
| 303 |
exposed via `/api/normalization/profiles`):
|
| 304 |
|
| 305 |
`nfc`, `caseless`, `minimal`, `medieval_french`,
|
|
@@ -309,7 +309,7 @@ exposed via `/api/normalization/profiles`):
|
|
| 309 |
|
| 310 |
Custom profiles can be loaded from YAML files with user-defined
|
| 311 |
diplomatic tables and `exclude_chars` sets. See
|
| 312 |
-
[`docs/profiles.md`](docs/profiles.md).
|
| 313 |
|
| 314 |
A traceability table mapping each profile to its source standard
|
| 315 |
(MUFI v4.0, TEI P5, DEAF) will ship in Sprint A12 (B-6).
|
|
@@ -320,24 +320,23 @@ A traceability table mapping each profile to its source standard
|
|
| 320 |
|
| 321 |
```
|
| 322 |
picarones/
|
| 323 |
-
├──
|
| 324 |
-
├──
|
| 325 |
-
├──
|
| 326 |
-
├──
|
| 327 |
-
├──
|
| 328 |
-
├──
|
| 329 |
-
├──
|
| 330 |
-
|
| 331 |
-
├── cli/ Cercle 3 — Click CLI (15 commands)
|
| 332 |
-
├── web/ Cercle 3 — FastAPI app + 11 routers
|
| 333 |
-
├── prompts/ 8 versioned prompt templates
|
| 334 |
-
└── data/ Indicative tables (pricing.yaml)
|
| 335 |
```
|
| 336 |
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
|
|
|
|
|
|
|
|
|
| 341 |
|
| 342 |
---
|
| 343 |
|
|
@@ -396,7 +395,7 @@ ruff check picarones/ tests/
|
|
| 396 |
python -m mypy picarones/core/
|
| 397 |
```
|
| 398 |
|
| 399 |
-
**Test suite**: ~
|
| 400 |
floor at 85% (currently ~87%). The `network` marker excludes tests
|
| 401 |
requiring live HTTP. A handful of tests depend on optional engines
|
| 402 |
(`pero-ocr`, `pytesseract`) and are skipped/fail gracefully when
|
|
@@ -456,11 +455,11 @@ experimental demonstrator and the CLI as the supported interface.
|
|
| 456 |
|
| 457 |
| Audience | Entry point |
|
| 458 |
|----------|-------------|
|
| 459 |
-
| **End user** | [`docs/
|
| 460 |
| **Developer** | [`docs/developer/index.md`](docs/developer/index.md) ([EN](docs/developer/index.en.md)) |
|
| 461 |
| **Operations / DSI** | [`docs/operations/deployment-institutional.md`](docs/operations/deployment-institutional.md), [`docs/operations/data-retention-rgpd.md`](docs/operations/data-retention-rgpd.md), [`docs/operations/release-process.md`](docs/operations/release-process.md) |
|
| 462 |
-
| **Architect** | [`docs/architecture.md`](docs/architecture.md), [`docs/api-stable.md`](docs/api-stable.md) |
|
| 463 |
-
| **Researcher** | [`docs/case-studies/`](docs/case-studies/), [`docs/reproducibility-snapshots.md`](docs/reproducibility-snapshots.md) |
|
| 464 |
| **Contributor** | [`CONTRIBUTING.md`](CONTRIBUTING.md), [`GOVERNANCE.md`](GOVERNANCE.md), [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) |
|
| 465 |
| **Security** | [`SECURITY.md`](SECURITY.md) |
|
| 466 |
| **Accessibility** | [`ACCESSIBILITY.md`](ACCESSIBILITY.md) |
|
|
@@ -477,7 +476,7 @@ shipped (see [`BACKLOG_POST_LIVRAISON.md`](BACKLOG_POST_LIVRAISON.md)).
|
|
| 477 |
Cite the GitHub repository with the commit SHA used in your benchmark.
|
| 478 |
Every Picarones report embeds the commit hash and a snapshot of the
|
| 479 |
parameters used (cf.
|
| 480 |
-
[`docs/reproducibility-snapshots.md`](docs/reproducibility-snapshots.md))
|
| 481 |
so the cited commit is sufficient to attribute the result.
|
| 482 |
|
| 483 |
---
|
|
|
|
| 102 |
trend with change-point detection; controlled per-slot ANOVA-like
|
| 103 |
comparison.
|
| 104 |
|
| 105 |
+
For the full list with definitions, see [`docs/reference/views.md`](docs/reference/views.md)
|
| 106 |
and the contextual glossary embedded in every report (25 bilingual
|
| 107 |
entries).
|
| 108 |
|
|
|
|
| 189 |
```
|
| 190 |
|
| 191 |
For Docker, institutional deployment, or HuggingFace Spaces, see
|
| 192 |
+
[`docs/how-to/install.md`](docs/how-to/install.md) and
|
| 193 |
[`docs/operations/deployment-institutional.md`](docs/operations/deployment-institutional.md).
|
| 194 |
|
| 195 |
---
|
|
|
|
| 210 |
|
| 211 |
LLM/VLM adapters (used through pipelines, not as standalone OCR
|
| 212 |
engines): GPT-4o, Claude, Mistral Large, Ollama (local). See
|
| 213 |
+
[`docs/how-to/cli-workflows.md`](docs/how-to/cli-workflows.md).
|
| 214 |
|
| 215 |
The `Engine` table is regenerated automatically by
|
| 216 |
`scripts/gen_readme_tables.py` — adding a new adapter under
|
| 217 |
+
`picarones/adapters/legacy_engines/` makes the next CI run update
|
| 218 |
+
this table or fail.
|
| 219 |
|
| 220 |
---
|
| 221 |
|
|
|
|
| 244 |
<!-- /generated:cli -->
|
| 245 |
|
| 246 |
Each command supports `--help` for full options. See
|
| 247 |
+
[`docs/how-to/cli-workflows.md`](docs/how-to/cli-workflows.md) for end-to-end
|
| 248 |
examples.
|
| 249 |
|
| 250 |
---
|
|
|
|
| 299 |
|
| 300 |
Picarones ships **11 built-in normalization profiles** for historical
|
| 301 |
text comparison (defined in
|
| 302 |
+
[`picarones/formats/text/normalization.py`](picarones/formats/text/normalization.py),
|
| 303 |
exposed via `/api/normalization/profiles`):
|
| 304 |
|
| 305 |
`nfc`, `caseless`, `minimal`, `medieval_french`,
|
|
|
|
| 309 |
|
| 310 |
Custom profiles can be loaded from YAML files with user-defined
|
| 311 |
diplomatic tables and `exclude_chars` sets. See
|
| 312 |
+
[`docs/reference/normalization-profiles.md`](docs/reference/normalization-profiles.md).
|
| 313 |
|
| 314 |
A traceability table mapping each profile to its source standard
|
| 315 |
(MUFI v4.0, TEI P5, DEAF) will ship in Sprint A12 (B-6).
|
|
|
|
| 320 |
|
| 321 |
```
|
| 322 |
picarones/
|
| 323 |
+
├── domain/ Layer 1 — pure types (Pydantic, stdlib only)
|
| 324 |
+
├── formats/ Layer 2 — parsing/serialization (ALTO, PAGE XML)
|
| 325 |
+
├── evaluation/ Layer 3 — metrics & analyses
|
| 326 |
+
├── pipeline/ Layer 4 — canonical pipeline executor
|
| 327 |
+
├── adapters/ Layer 5 — external libs (OCR, LLM, VLM, corpus)
|
| 328 |
+
├── app/ Layer 6 — application services
|
| 329 |
+
├── reports_v2/ Layer 7 — HTML / JSON / CSV report renderers
|
| 330 |
+
└── interfaces/ Layer 8 — CLI Click, Web FastAPI
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
```
|
| 332 |
|
| 333 |
+
Legacy paths (`core/, measurements/, engines/, llm/, pipelines/,
|
| 334 |
+
report/, modules/`) still present as shims, in active retirement
|
| 335 |
+
(see `docs/migration/`). Strict 8-layer architecture: imports flow
|
| 336 |
+
outer → inner. Enforced by
|
| 337 |
+
`tests/architecture/test_layer_dependencies.py`. See
|
| 338 |
+
[`docs/explanation/architecture.md`](docs/explanation/architecture.md)
|
| 339 |
+
for the full manifesto.
|
| 340 |
|
| 341 |
---
|
| 342 |
|
|
|
|
| 395 |
python -m mypy picarones/core/
|
| 396 |
```
|
| 397 |
|
| 398 |
+
**Test suite**: ~5000 tests, ~3 min on a modern laptop. Coverage
|
| 399 |
floor at 85% (currently ~87%). The `network` marker excludes tests
|
| 400 |
requiring live HTTP. A handful of tests depend on optional engines
|
| 401 |
(`pero-ocr`, `pytesseract`) and are skipped/fail gracefully when
|
|
|
|
| 455 |
|
| 456 |
| Audience | Entry point |
|
| 457 |
|----------|-------------|
|
| 458 |
+
| **End user** | [`docs/tutorials/reading-a-report.md`](docs/tutorials/reading-a-report.md) ([EN](docs/tutorials/reading-a-report.en.md)) |
|
| 459 |
| **Developer** | [`docs/developer/index.md`](docs/developer/index.md) ([EN](docs/developer/index.en.md)) |
|
| 460 |
| **Operations / DSI** | [`docs/operations/deployment-institutional.md`](docs/operations/deployment-institutional.md), [`docs/operations/data-retention-rgpd.md`](docs/operations/data-retention-rgpd.md), [`docs/operations/release-process.md`](docs/operations/release-process.md) |
|
| 461 |
+
| **Architect** | [`docs/explanation/architecture.md`](docs/explanation/architecture.md), [`docs/reference/api-stable.md`](docs/reference/api-stable.md) |
|
| 462 |
+
| **Researcher** | [`docs/case-studies/`](docs/case-studies/), [`docs/reference/reproducibility-snapshots.md`](docs/reference/reproducibility-snapshots.md) |
|
| 463 |
| **Contributor** | [`CONTRIBUTING.md`](CONTRIBUTING.md), [`GOVERNANCE.md`](GOVERNANCE.md), [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) |
|
| 464 |
| **Security** | [`SECURITY.md`](SECURITY.md) |
|
| 465 |
| **Accessibility** | [`ACCESSIBILITY.md`](ACCESSIBILITY.md) |
|
|
|
|
| 476 |
Cite the GitHub repository with the commit SHA used in your benchmark.
|
| 477 |
Every Picarones report embeds the commit hash and a snapshot of the
|
| 478 |
parameters used (cf.
|
| 479 |
+
[`docs/reference/reproducibility-snapshots.md`](docs/reference/reproducibility-snapshots.md))
|
| 480 |
so the cited commit is sufficient to attribute the result.
|
| 481 |
|
| 482 |
---
|
SECURITY.en.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- translation: machine + human review pending -->
|
| 2 |
+
|
| 3 |
+
# SECURITY — Picarones (English)
|
| 4 |
+
|
| 5 |
+
> French version: [`SECURITY.md`](SECURITY.md) (canonical).
|
| 6 |
+
> Detailed threat model: [`docs/security/threat-model.md`](docs/security/threat-model.md).
|
| 7 |
+
>
|
| 8 |
+
> This is a summary translation focused on what an English-speaking
|
| 9 |
+
> auditor needs. The canonical FR version remains authoritative
|
| 10 |
+
> for institutional sign-off. Full alignment scheduled for a
|
| 11 |
+
> dedicated human-review sprint.
|
| 12 |
+
|
| 13 |
+
## Reporting a vulnerability
|
| 14 |
+
|
| 15 |
+
If you discover a security vulnerability in Picarones, please **do
|
| 16 |
+
not file a public GitHub issue**. Instead, use one of the following
|
| 17 |
+
private channels:
|
| 18 |
+
|
| 19 |
+
- **GitHub Security Advisories** (preferred):
|
| 20 |
+
https://github.com/maribakulj/Picarones/security/advisories/new
|
| 21 |
+
- **`/.well-known/security.txt`** on any institutional deployment
|
| 22 |
+
(RFC 9116) — the contact address is documented there.
|
| 23 |
+
|
| 24 |
+
We acknowledge reports within **72 hours** and aim to ship a fix
|
| 25 |
+
within **30 days** for HIGH severity issues, **90 days** for MEDIUM.
|
| 26 |
+
A coordinated disclosure agreement is offered for non-trivial issues.
|
| 27 |
+
|
| 28 |
+
## Supported versions
|
| 29 |
+
|
| 30 |
+
| Version | Status | Security fixes |
|
| 31 |
+
|---------|--------|----------------|
|
| 32 |
+
| 1.x (current) | Active | Yes |
|
| 33 |
+
| 0.x | End of life | No — please upgrade |
|
| 34 |
+
| Pre-release branches | Best effort | On request |
|
| 35 |
+
|
| 36 |
+
## Deployment contexts
|
| 37 |
+
|
| 38 |
+
Picarones is designed for three deployment contexts:
|
| 39 |
+
|
| 40 |
+
1. **Developer machine** (Codespaces, laptop) — local access only,
|
| 41 |
+
relaxed defaults to keep iteration fast.
|
| 42 |
+
2. **Institutional server** (intranet, scientific cluster) —
|
| 43 |
+
authenticated internal access, with cost guards (rate limit, body
|
| 44 |
+
size limit, max concurrent jobs).
|
| 45 |
+
3. **Public space** (HuggingFace Space, online demo) — anyone can
|
| 46 |
+
reach the API; cloud API keys (OpenAI, Anthropic, Mistral, Azure…)
|
| 47 |
+
must NOT be exposed to financial DoS.
|
| 48 |
+
|
| 49 |
+
## Security controls — quick reference
|
| 50 |
+
|
| 51 |
+
| Variable | Default | Effect |
|
| 52 |
+
|----------|---------|--------|
|
| 53 |
+
| `PICARONES_PUBLIC_MODE` | off | If `1`/`true`, refuses cloud OCR/LLM with shared keys and enables rate limit |
|
| 54 |
+
| `PICARONES_MAX_UPLOAD_MB` | `100` | Max upload size in MiB |
|
| 55 |
+
| `PICARONES_MAX_CONCURRENT_JOBS` | `2` | Max parallel benchmark jobs (in-process semaphore) |
|
| 56 |
+
| `PICARONES_RATE_LIMIT_PER_HOUR` | `5` (public mode) | Max jobs per IP per hour, `0` disables |
|
| 57 |
+
| `PICARONES_CSP` | hardened policy | Override Content-Security-Policy |
|
| 58 |
+
| `PICARONES_CSRF_REQUIRED` | off | If `1`/`true`, enables CSRF protection (double-submit cookie + HMAC) |
|
| 59 |
+
| `PICARONES_CSRF_SECRET` | auto | HMAC secret for CSRF tokens; **must be set in production** |
|
| 60 |
+
|
| 61 |
+
## In-process middlewares
|
| 62 |
+
|
| 63 |
+
The `picarones.interfaces.web.security` module provides four
|
| 64 |
+
middlewares that institutional operators wire via `create_app(...)`:
|
| 65 |
+
|
| 66 |
+
- `SecurityHeadersMiddleware` — adds CSP, X-Frame-Options,
|
| 67 |
+
X-Content-Type-Options, Referrer-Policy, Permissions-Policy to
|
| 68 |
+
every response.
|
| 69 |
+
- `BodySizeLimitMiddleware` — rejects requests where
|
| 70 |
+
`Content-Length` exceeds a threshold. **Known limitation**: does
|
| 71 |
+
not catch `Transfer-Encoding: chunked`; nginx
|
| 72 |
+
`client_max_body_size` is recommended in front.
|
| 73 |
+
- `RateLimitMiddleware` — sliding window, in-memory,
|
| 74 |
+
`trust_proxy_count: int` for safe `X-Forwarded-For` parsing,
|
| 75 |
+
LRU eviction on `max_clients=10000` to bound memory.
|
| 76 |
+
- `AuthenticationMiddleware` — opt-in wrapper around an
|
| 77 |
+
`AuthenticationBackend` Protocol; the institution plugs in its
|
| 78 |
+
SSO/LDAP/JWT scheme.
|
| 79 |
+
|
| 80 |
+
## Audit trail
|
| 81 |
+
|
| 82 |
+
Sensitive job mutations (`POST /api/jobs`, `DELETE /api/jobs/{id}`)
|
| 83 |
+
emit a structured `[audit]` log line at INFO level with the source
|
| 84 |
+
IP, ready to be ingested by the institution's SIEM.
|
| 85 |
+
|
| 86 |
+
## Reproducibility and integrity
|
| 87 |
+
|
| 88 |
+
`RunManifest` is byte-deterministic (`model_dump_json` with ordered
|
| 89 |
+
fields). The SHA-256 hash of a manifest can be cited in a scientific
|
| 90 |
+
publication to anchor the run. Cryptographic signing of manifests
|
| 91 |
+
(Sigstore) is on the roadmap.
|
| 92 |
+
|
| 93 |
+
## Cloud API key management
|
| 94 |
+
|
| 95 |
+
Cloud keys (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `MISTRAL_API_KEY`,
|
| 96 |
+
`GOOGLE_APPLICATION_CREDENTIALS`, `AZURE_DOC_INTEL_*`) are read from
|
| 97 |
+
environment variables only. Adapters never log keys. For
|
| 98 |
+
institutional deployments, source the env from a secrets vault
|
| 99 |
+
(HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, etc.) at
|
| 100 |
+
process startup.
|
| 101 |
+
|
| 102 |
+
See also [`docs/operations/runbook.md`](docs/operations/runbook.md)
|
| 103 |
+
for incident response and [`docs/legal/dpa-template.md`](docs/legal/dpa-template.md)
|
| 104 |
+
for the data processing agreement template covering cloud
|
| 105 |
+
sub-processors.
|
| 106 |
+
|
| 107 |
+
## Last revised
|
| 108 |
+
|
| 109 |
+
2026-05. This document is reviewed at every major release.
|
SPECS.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
| 23 |
## Table des matières
|
| 24 |
|
| 25 |
1. [Vision et positionnement](#1-vision-et-positionnement)
|
| 26 |
-
2. [Architecture en
|
| 27 |
3. [Module 1 — Corpus et imports](#3-module-1--corpus-et-imports)
|
| 28 |
4. [Module 2 — Adaptateurs OCR / HTR](#4-module-2--adaptateurs-ocr--htr)
|
| 29 |
5. [Module 3 — Pipelines OCR+LLM et pipelines composables](#5-module-3--pipelines-ocrllm-et-pipelines-composables)
|
|
@@ -125,72 +125,108 @@ plusieurs briques nouvelles dans l'écosystème OCR/HTR open-source :
|
|
| 125 |
|
| 126 |
---
|
| 127 |
|
| 128 |
-
## 2. Architecture en
|
| 129 |
|
| 130 |
```
|
| 131 |
-
|
| 132 |
-
│
|
| 133 |
-
▼
|
| 134 |
-
Cercle 2 (measurements, engines, llm, pipelines, modules)
|
| 135 |
-
│
|
| 136 |
-
▼
|
| 137 |
-
Cercle 1 (core)
|
| 138 |
```
|
| 139 |
|
| 140 |
**Règle de dépendance** : les imports vont uniquement de
|
| 141 |
-
l'extérieur vers l'intérieur
|
| 142 |
-
|
| 143 |
-
`tests/
|
| 144 |
l'AST de chaque fichier et bloque toute violation au merge.
|
| 145 |
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
---
|
| 196 |
|
|
@@ -263,7 +299,7 @@ Endpoint `POST /api/corpus/upload`. Validation Pillow
|
|
| 263 |
### 4.1 Architecture des adaptateurs
|
| 264 |
|
| 265 |
Chaque moteur OCR est une classe Python qui hérite de
|
| 266 |
-
`BaseOCREngine` (`picarones/
|
| 267 |
de `BaseModule` (Sprint 33). Une instance déclare son
|
| 268 |
`execution_mode` (`"io"` ou `"cpu"`) que le runner utilise pour
|
| 269 |
choisir entre `ThreadPoolExecutor` (cloud APIs) et
|
|
@@ -431,7 +467,7 @@ canonique (champ `reference`).
|
|
| 431 |
|
| 432 |
### 6.2 Profils de normalisation
|
| 433 |
|
| 434 |
-
11 profils livrés (`picarones/
|
| 435 |
exposés via `/api/normalization/profiles`) : `nfc`, `caseless`,
|
| 436 |
`minimal`, `medieval_french`, `early_modern_french`,
|
| 437 |
`medieval_latin`, `medieval_english`, `early_modern_english`,
|
|
@@ -649,7 +685,7 @@ qui contient :
|
|
| 649 |
git, paquets installés (top 200).
|
| 650 |
|
| 651 |
Procédure complète de re-jeu d'un benchmark à 5 ans d'écart :
|
| 652 |
-
[`docs/reproducibility-snapshots.md`](docs/reproducibility-snapshots.md)
|
| 653 |
(Sprint A8 / M-12).
|
| 654 |
|
| 655 |
### 9.2 Reproductibilité des builds
|
|
|
|
| 23 |
## Table des matières
|
| 24 |
|
| 25 |
1. [Vision et positionnement](#1-vision-et-positionnement)
|
| 26 |
+
2. [Architecture en 8 couches concentriques](#2-architecture-en-8-couches-concentriques)
|
| 27 |
3. [Module 1 — Corpus et imports](#3-module-1--corpus-et-imports)
|
| 28 |
4. [Module 2 — Adaptateurs OCR / HTR](#4-module-2--adaptateurs-ocr--htr)
|
| 29 |
5. [Module 3 — Pipelines OCR+LLM et pipelines composables](#5-module-3--pipelines-ocrllm-et-pipelines-composables)
|
|
|
|
| 125 |
|
| 126 |
---
|
| 127 |
|
| 128 |
+
## 2. Architecture en 8 couches concentriques
|
| 129 |
|
| 130 |
```
|
| 131 |
+
domain → formats → evaluation → pipeline → adapters → app → reports_v2 → interfaces
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
```
|
| 133 |
|
| 134 |
**Règle de dépendance** : les imports vont uniquement de
|
| 135 |
+
l'extérieur vers l'intérieur (de gauche à droite dans le
|
| 136 |
+
diagramme). La règle est appliquée par
|
| 137 |
+
`tests/architecture/test_layer_dependencies.py` qui parse
|
| 138 |
l'AST de chaque fichier et bloque toute violation au merge.
|
| 139 |
|
| 140 |
+
> **Note sur le legacy** : le projet est en cours de retrait
|
| 141 |
+
> du legacy. Une arborescence historique
|
| 142 |
+
> (``picarones/{core,measurements,engines,llm,pipelines,
|
| 143 |
+
> report,modules}``) cohabite encore et est en train de
|
| 144 |
+
> disparaître phase par phase. Cf.
|
| 145 |
+
> [`docs/migration/legacy-retirement-plan.md`](docs/migration/legacy-retirement-plan.md)
|
| 146 |
+
> pour le statut et le calendrier. Tout nouveau code va
|
| 147 |
+
> dans l'arborescence canonique ; les chemins legacy
|
| 148 |
+
> existants sont des shims minimaux destinés à être
|
| 149 |
+
> supprimés.
|
| 150 |
+
|
| 151 |
+
### 2.1 `picarones/domain/` — types purs
|
| 152 |
+
|
| 153 |
+
Cercle le plus interne. Stdlib + Pydantic uniquement, aucune
|
| 154 |
+
I/O, aucun framework, aucun module legacy.
|
| 155 |
+
|
| 156 |
+
| Module | Contenu |
|
| 157 |
+
|---|---|
|
| 158 |
+
| `artifacts.py` | `Artifact`, `ArtifactType` (10 types : IMAGE, RAW_TEXT, CORRECTED_TEXT, ALTO_XML, PAGE_XML, CANONICAL_DOCUMENT, ENTITIES, READING_ORDER, ALIGNMENT, CONFIDENCES) |
|
| 159 |
+
| `corpus.py` | `CorpusSpec` |
|
| 160 |
+
| `documents.py` | `DocumentRef` |
|
| 161 |
+
| `evaluation_spec.py` | `MetricSpec`, `EvaluationView`, `EvaluationSpec` |
|
| 162 |
+
| `pipeline_spec.py` | `PipelineSpec`, `PipelineStep`, `INITIAL_STEP_ID` |
|
| 163 |
+
| `projection_spec.py` | `ProjectionSpec` |
|
| 164 |
+
| `provenance.py` | `ProvenanceRecord` |
|
| 165 |
+
| `run_manifest.py` | `RunManifest` |
|
| 166 |
+
| `module_protocol.py` | `BaseModule` (ABC, voie de retrait au profit de `StepExecutor`) |
|
| 167 |
+
| `facts.py` | `Fact`, `FactType`, `FactImportance`, `DetectorRegistry` |
|
| 168 |
+
| `errors.py` | Hiérarchie d'exceptions (`PicaronesError`, `AdapterStepError`, …) |
|
| 169 |
+
|
| 170 |
+
### 2.2 `picarones/formats/` — parsing / sérialisation
|
| 171 |
+
|
| 172 |
+
ALTO 4, PAGE XML, JSON, XML utilitaires. Stdlib + lxml +
|
| 173 |
+
defusedxml. Pas de logique métier.
|
| 174 |
+
|
| 175 |
+
### 2.3 `picarones/evaluation/` — métriques et calcul
|
| 176 |
+
|
| 177 |
+
Cœur de la valeur ajoutée. Stdlib + numpy + scipy + jiwer +
|
| 178 |
+
spacy + rapidfuzz.
|
| 179 |
+
|
| 180 |
+
| Sous-paquet | Contenu |
|
| 181 |
+
|---|---|
|
| 182 |
+
| `metrics/` | ~30 métriques (CER, WER, MUFI, philological, NER, calibration, taxonomy, …) |
|
| 183 |
+
| `statistics/` | Wilcoxon, Friedman/Nemenyi, bootstrap, Pareto, clustering, CDD |
|
| 184 |
+
| `views/`, `projectors/` | EvaluationView (Sprint S13+), projecteurs `AltoToText`, `PageToText`, `CanonicalToText` |
|
| 185 |
+
| `corpus.py` | `Document`, `Corpus`, `GTLevel`, payloads (legacy en cours de retrait) |
|
| 186 |
+
| `metric_registry.py`, `metric_hooks.py`, `metric_result.py` | Registres typés + hooks + dataclasses résultats |
|
| 187 |
+
| `pipeline.py`, `pipeline_benchmark.py`, `pipeline_comparison.py` | `PipelineRunner` legacy + orchestration corpus-wide (en cours de convergence vers `pipeline.executor`) |
|
| 188 |
+
| `benchmark_result.py` | `BenchmarkResult`, `EngineReport`, `DocumentResult`, sérialisation JSON |
|
| 189 |
+
| `engines/` | OCR engines legacy (`BaseOCREngine`-based) — temporairement avant suppression complète |
|
| 190 |
+
| `_diff_utils.py` | `compute_word_diff`, `compute_char_diff`, `diff_stats` |
|
| 191 |
+
|
| 192 |
+
### 2.4 `picarones/pipeline/` — orchestration canonique
|
| 193 |
+
|
| 194 |
+
`PipelineExecutor` instance-based, `StepExecutor` Protocol,
|
| 195 |
+
`ExecutionPlan` immuable. Cible canonique pour le bench
|
| 196 |
+
d'axe B (pipelines composées).
|
| 197 |
+
|
| 198 |
+
### 2.5 `picarones/adapters/` — adapters externes
|
| 199 |
+
|
| 200 |
+
Adapters OCR / LLM / VLM consommant des libs externes
|
| 201 |
+
(pytesseract, mistralai, openai, anthropic, google.cloud,
|
| 202 |
+
azure.*, pero_ocr, ollama). Implémentent `StepExecutor`.
|
| 203 |
+
|
| 204 |
+
| Sous-paquet | Contenu |
|
| 205 |
+
|---|---|
|
| 206 |
+
| `ocr/` | `TesseractAdapter`, `PeroOCRAdapter`, `MistralOCRAdapter`, `GoogleVisionAdapter`, `AzureDocIntelAdapter`, `PrecomputedAdapter` |
|
| 207 |
+
| `llm/` | `BaseLLMAdapter` + Mistral / OpenAI / Anthropic / Ollama |
|
| 208 |
+
| `vlm/` | Adapters VLM (zero-shot OCR via vision-language models) |
|
| 209 |
+
| `corpus/` | Loaders externes : IIIF, Gallica, HTR-United, HuggingFace |
|
| 210 |
+
| `storage/` | `ArtifactStore`, `JobStore` (S29 + S47) |
|
| 211 |
+
| `legacy_engines/`, `legacy_modules/` | Engines + modules legacy `BaseModule`-based (en cours de retrait, cf. Phase 7.A) |
|
| 212 |
+
|
| 213 |
+
### 2.6 `picarones/app/` — services applicatifs
|
| 214 |
+
|
| 215 |
+
`BenchmarkService`, `CorpusRunner`, `RunOrchestrator`.
|
| 216 |
+
Orchestrent les pipelines canoniques sur corpus.
|
| 217 |
+
|
| 218 |
+
### 2.7 `picarones/reports_v2/` — rendu HTML / JSON / CSV
|
| 219 |
+
|
| 220 |
+
Rapport final consommant un `BenchmarkResult` ou `RunResult`.
|
| 221 |
+
22 renderers thématiques + 5 vues (advanced_taxonomy,
|
| 222 |
+
diagnostics, economics, pipeline, robustness) +
|
| 223 |
+
`ReportGenerator` orchestrateur + templates Jinja2 +
|
| 224 |
+
glossaire bilingue (25 entrées) + i18n FR/EN.
|
| 225 |
+
|
| 226 |
+
### 2.8 `picarones/interfaces/` — entrées utilisateur
|
| 227 |
+
|
| 228 |
+
CLI Click, Web FastAPI, IIIF/Gallica/eScriptorium importers
|
| 229 |
+
exposés en interface.
|
| 230 |
|
| 231 |
---
|
| 232 |
|
|
|
|
| 299 |
### 4.1 Architecture des adaptateurs
|
| 300 |
|
| 301 |
Chaque moteur OCR est une classe Python qui hérite de
|
| 302 |
+
`BaseOCREngine` (`picarones/adapters/legacy_engines/base.py`), elle-même héritière
|
| 303 |
de `BaseModule` (Sprint 33). Une instance déclare son
|
| 304 |
`execution_mode` (`"io"` ou `"cpu"`) que le runner utilise pour
|
| 305 |
choisir entre `ThreadPoolExecutor` (cloud APIs) et
|
|
|
|
| 467 |
|
| 468 |
### 6.2 Profils de normalisation
|
| 469 |
|
| 470 |
+
11 profils livrés (`picarones/formats/text/normalization.py`,
|
| 471 |
exposés via `/api/normalization/profiles`) : `nfc`, `caseless`,
|
| 472 |
`minimal`, `medieval_french`, `early_modern_french`,
|
| 473 |
`medieval_latin`, `medieval_english`, `early_modern_english`,
|
|
|
|
| 685 |
git, paquets installés (top 200).
|
| 686 |
|
| 687 |
Procédure complète de re-jeu d'un benchmark à 5 ans d'écart :
|
| 688 |
+
[`docs/reference/reproducibility-snapshots.md`](docs/reference/reproducibility-snapshots.md)
|
| 689 |
(Sprint A8 / M-12).
|
| 690 |
|
| 691 |
### 9.2 Reproductibilité des builds
|
docs/api/adapters.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# `picarones.adapters` — implémentations concrètes
|
| 2 |
+
|
| 3 |
+
## OCR
|
| 4 |
+
|
| 5 |
+
::: picarones.adapters.ocr.base
|
| 6 |
+
options:
|
| 7 |
+
show_root_heading: true
|
| 8 |
+
|
| 9 |
+
::: picarones.adapters.ocr.tesseract
|
| 10 |
+
options:
|
| 11 |
+
show_root_heading: true
|
| 12 |
+
members: ["TesseractAdapter"]
|
| 13 |
+
|
| 14 |
+
::: picarones.adapters.ocr.pero_ocr
|
| 15 |
+
options:
|
| 16 |
+
show_root_heading: true
|
| 17 |
+
members: ["PeroOCRAdapter"]
|
| 18 |
+
|
| 19 |
+
::: picarones.adapters.ocr.mistral_ocr
|
| 20 |
+
options:
|
| 21 |
+
show_root_heading: true
|
| 22 |
+
members: ["MistralOCRAdapter"]
|
| 23 |
+
|
| 24 |
+
::: picarones.adapters.ocr.google_vision
|
| 25 |
+
options:
|
| 26 |
+
show_root_heading: true
|
| 27 |
+
members: ["GoogleVisionAdapter"]
|
| 28 |
+
|
| 29 |
+
::: picarones.adapters.ocr.azure_doc_intel
|
| 30 |
+
options:
|
| 31 |
+
show_root_heading: true
|
| 32 |
+
members: ["AzureDocIntelAdapter"]
|
| 33 |
+
|
| 34 |
+
## LLM
|
| 35 |
+
|
| 36 |
+
::: picarones.adapters.llm.base
|
| 37 |
+
options:
|
| 38 |
+
show_root_heading: true
|
| 39 |
+
members: ["BaseLLMAdapter", "LLMAdapterError", "LLMResult", "normalize_llm_content"]
|
| 40 |
+
|
| 41 |
+
::: picarones.adapters.llm.anthropic_adapter
|
| 42 |
+
options:
|
| 43 |
+
show_root_heading: true
|
| 44 |
+
|
| 45 |
+
::: picarones.adapters.llm.openai_adapter
|
| 46 |
+
options:
|
| 47 |
+
show_root_heading: true
|
| 48 |
+
|
| 49 |
+
::: picarones.adapters.llm.mistral_adapter
|
| 50 |
+
options:
|
| 51 |
+
show_root_heading: true
|
| 52 |
+
|
| 53 |
+
::: picarones.adapters.llm.ollama_adapter
|
| 54 |
+
options:
|
| 55 |
+
show_root_heading: true
|
| 56 |
+
|
| 57 |
+
## VLM
|
| 58 |
+
|
| 59 |
+
::: picarones.adapters.vlm.base
|
| 60 |
+
options:
|
| 61 |
+
show_root_heading: true
|
| 62 |
+
members: ["BaseVLMAdapter", "VLMAdapterError"]
|
| 63 |
+
|
| 64 |
+
## Storage
|
| 65 |
+
|
| 66 |
+
::: picarones.adapters.storage.artifact_store
|
| 67 |
+
options:
|
| 68 |
+
show_root_heading: true
|
| 69 |
+
|
| 70 |
+
::: picarones.adapters.storage.job_store
|
| 71 |
+
options:
|
| 72 |
+
show_root_heading: true
|
| 73 |
+
|
| 74 |
+
## Helpers
|
| 75 |
+
|
| 76 |
+
::: picarones.adapters.output_paths
|
| 77 |
+
options:
|
| 78 |
+
show_root_heading: true
|
| 79 |
+
|
| 80 |
+
::: picarones.adapters._retry
|
| 81 |
+
options:
|
| 82 |
+
show_root_heading: true
|
docs/api/app.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# `picarones.app` — services applicatifs
|
| 2 |
+
|
| 3 |
+
## Schémas
|
| 4 |
+
|
| 5 |
+
::: picarones.app.schemas.run_spec
|
| 6 |
+
options:
|
| 7 |
+
show_root_heading: true
|
| 8 |
+
|
| 9 |
+
## Services
|
| 10 |
+
|
| 11 |
+
::: picarones.app.services.run_orchestrator
|
| 12 |
+
options:
|
| 13 |
+
show_root_heading: true
|
| 14 |
+
|
| 15 |
+
::: picarones.app.services.benchmark_service
|
| 16 |
+
options:
|
| 17 |
+
show_root_heading: true
|
| 18 |
+
|
| 19 |
+
::: picarones.app.services.job_runner
|
| 20 |
+
options:
|
| 21 |
+
show_root_heading: true
|
| 22 |
+
|
| 23 |
+
::: picarones.app.services.dependencies
|
| 24 |
+
options:
|
| 25 |
+
show_root_heading: true
|
| 26 |
+
|
| 27 |
+
::: picarones.app.services.path_security
|
| 28 |
+
options:
|
| 29 |
+
show_root_heading: true
|
| 30 |
+
|
| 31 |
+
::: picarones.app.services.registry_service
|
| 32 |
+
options:
|
| 33 |
+
show_root_heading: true
|
| 34 |
+
|
| 35 |
+
## Résultats
|
| 36 |
+
|
| 37 |
+
::: picarones.app.results
|
| 38 |
+
options:
|
| 39 |
+
show_root_heading: true
|
docs/api/domain.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# `picarones.domain` — types purs
|
| 2 |
+
|
| 3 |
+
::: picarones.domain.artifacts
|
| 4 |
+
options:
|
| 5 |
+
show_root_heading: true
|
| 6 |
+
members_order: source
|
| 7 |
+
|
| 8 |
+
::: picarones.domain.documents
|
| 9 |
+
options:
|
| 10 |
+
show_root_heading: true
|
| 11 |
+
|
| 12 |
+
::: picarones.domain.corpus
|
| 13 |
+
options:
|
| 14 |
+
show_root_heading: true
|
| 15 |
+
|
| 16 |
+
::: picarones.domain.evaluation_spec
|
| 17 |
+
options:
|
| 18 |
+
show_root_heading: true
|
| 19 |
+
|
| 20 |
+
::: picarones.domain.pipeline_spec
|
| 21 |
+
options:
|
| 22 |
+
show_root_heading: true
|
| 23 |
+
|
| 24 |
+
::: picarones.domain.run_manifest
|
| 25 |
+
options:
|
| 26 |
+
show_root_heading: true
|
| 27 |
+
|
| 28 |
+
::: picarones.domain.errors
|
| 29 |
+
options:
|
| 30 |
+
show_root_heading: true
|
docs/api/evaluation.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# `picarones.evaluation` — métriques et vues
|
| 2 |
+
|
| 3 |
+
## Vues
|
| 4 |
+
|
| 5 |
+
::: picarones.evaluation.views.base
|
| 6 |
+
options:
|
| 7 |
+
show_root_heading: true
|
| 8 |
+
|
| 9 |
+
::: picarones.evaluation.views.executor
|
| 10 |
+
options:
|
| 11 |
+
show_root_heading: true
|
| 12 |
+
|
| 13 |
+
::: picarones.evaluation.views.text_view
|
| 14 |
+
options:
|
| 15 |
+
show_root_heading: true
|
| 16 |
+
|
| 17 |
+
::: picarones.evaluation.views.alto_view
|
| 18 |
+
options:
|
| 19 |
+
show_root_heading: true
|
| 20 |
+
|
| 21 |
+
::: picarones.evaluation.views.search_view
|
| 22 |
+
options:
|
| 23 |
+
show_root_heading: true
|
| 24 |
+
|
| 25 |
+
## Registre
|
| 26 |
+
|
| 27 |
+
::: picarones.evaluation.registry.registry
|
| 28 |
+
options:
|
| 29 |
+
show_root_heading: true
|
| 30 |
+
|
| 31 |
+
## Projecteurs
|
| 32 |
+
|
| 33 |
+
::: picarones.evaluation.projectors.base
|
| 34 |
+
options:
|
| 35 |
+
show_root_heading: true
|
| 36 |
+
|
| 37 |
+
::: picarones.evaluation.projectors.alto
|
| 38 |
+
options:
|
| 39 |
+
show_root_heading: true
|
| 40 |
+
|
| 41 |
+
::: picarones.evaluation.projectors.canonical
|
| 42 |
+
options:
|
| 43 |
+
show_root_heading: true
|
| 44 |
+
|
| 45 |
+
::: picarones.evaluation.projectors.pagexml
|
| 46 |
+
options:
|
| 47 |
+
show_root_heading: true
|
docs/api/index.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# API Reference (auto-générée)
|
| 2 |
+
|
| 3 |
+
> **Audience** : développeur tiers, contributeur, mainteneur. Cette
|
| 4 |
+
> référence est **générée automatiquement** depuis les docstrings du
|
| 5 |
+
> code par [mkdocstrings](https://mkdocstrings.github.io/), au build
|
| 6 |
+
> du site de documentation.
|
| 7 |
+
>
|
| 8 |
+
> Pour la **politique de stabilité** de l'API publique (semver,
|
| 9 |
+
> deprecation periods, symboles cibles), voir
|
| 10 |
+
> [`../reference/api-stable.md`](../reference/api-stable.md).
|
| 11 |
+
>
|
| 12 |
+
> Pour l'**architecture** et le **pourquoi** des choix de design,
|
| 13 |
+
> voir [`../explanation/architecture.md`](../explanation/architecture.md).
|
| 14 |
+
|
| 15 |
+
## Build local
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
pip install -e ".[docs]"
|
| 19 |
+
mkdocs serve # hot-reload sur http://localhost:8000
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
ou
|
| 23 |
+
|
| 24 |
+
```bash
|
| 25 |
+
mkdocs build # site statique dans site/
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
## Structure
|
| 29 |
+
|
| 30 |
+
L'API publique est groupée par cercle architectural :
|
| 31 |
+
|
| 32 |
+
| Cercle | Référence |
|
| 33 |
+
|--------|-----------|
|
| 34 |
+
| Domain (types purs) | [`domain.md`](domain.md) |
|
| 35 |
+
| Pipeline (orchestration) | [`pipeline.md`](pipeline.md) |
|
| 36 |
+
| Evaluation (métriques + vues) | [`evaluation.md`](evaluation.md) |
|
| 37 |
+
| Adapters (OCR/LLM/VLM) | [`adapters.md`](adapters.md) |
|
| 38 |
+
| App services (orchestrateur, jobs) | [`app.md`](app.md) |
|
| 39 |
+
|
| 40 |
+
## Stabilité
|
| 41 |
+
|
| 42 |
+
Tous les symboles documentés ici sont de l'**API publique** ce qui
|
| 43 |
+
signifie :
|
| 44 |
+
|
| 45 |
+
- Suivent semver — un retrait nécessite une release majeure et une
|
| 46 |
+
deprecation period d'au moins une release mineure (`DeprecationWarning`
|
| 47 |
+
émis depuis la version N, suppression en N+2 majeure).
|
| 48 |
+
- Sont vérifiés par `tests/core/test_public_api_signatures.py`.
|
| 49 |
+
|
| 50 |
+
Les symboles **privés** (préfixe `_` ou non listés dans `__all__`)
|
| 51 |
+
peuvent changer sans préavis.
|
docs/api/pipeline.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# `picarones.pipeline` — orchestration mono-document
|
| 2 |
+
|
| 3 |
+
::: picarones.pipeline.executor
|
| 4 |
+
options:
|
| 5 |
+
show_root_heading: true
|
| 6 |
+
|
| 7 |
+
::: picarones.pipeline.planner
|
| 8 |
+
options:
|
| 9 |
+
show_root_heading: true
|
| 10 |
+
|
| 11 |
+
::: picarones.pipeline.runner
|
| 12 |
+
options:
|
| 13 |
+
show_root_heading: true
|
| 14 |
+
|
| 15 |
+
::: picarones.pipeline.validation
|
| 16 |
+
options:
|
| 17 |
+
show_root_heading: true
|
| 18 |
+
|
| 19 |
+
::: picarones.pipeline.types
|
| 20 |
+
options:
|
| 21 |
+
show_root_heading: true
|
| 22 |
+
|
| 23 |
+
::: picarones.pipeline.protocols
|
| 24 |
+
options:
|
| 25 |
+
show_root_heading: true
|
docs/architecture.md
DELETED
|
@@ -1,179 +0,0 @@
|
|
| 1 |
-
# Architecture Picarones — manifeste
|
| 2 |
-
|
| 3 |
-
Picarones est un **banc d'essai** pour pipelines OCR/HTR sur documents
|
| 4 |
-
patrimoniaux. Le code est organisé en **3 cercles concentriques** avec
|
| 5 |
-
une règle de dépendance stricte : les flèches d'import vont uniquement
|
| 6 |
-
de l'extérieur vers l'intérieur.
|
| 7 |
-
|
| 8 |
-
```
|
| 9 |
-
Cercle 3 (extras, report, cli, web)
|
| 10 |
-
│
|
| 11 |
-
▼
|
| 12 |
-
Cercle 2 (measurements, engines, llm, pipelines, modules)
|
| 13 |
-
│
|
| 14 |
-
▼
|
| 15 |
-
Cercle 1 (core)
|
| 16 |
-
```
|
| 17 |
-
|
| 18 |
-
## Cercle 1 — `picarones/core/` : abstractions de domaine
|
| 19 |
-
|
| 20 |
-
Pas de logique métier, pas d'I/O. Uniquement des **contrats** que les
|
| 21 |
-
cercles supérieurs implémentent.
|
| 22 |
-
|
| 23 |
-
| Module | Contenu |
|
| 24 |
-
|---|---|
|
| 25 |
-
| `modules.py` | `BaseModule`, `ArtifactType`, `validate_inputs`/`validate_outputs` |
|
| 26 |
-
| `corpus.py` | `Document`, `Corpus`, `GTLevel`, payloads typés (`TextGT`, `AltoGT`, `PageGT`, `EntitiesGT`, `ReadingOrderGT`) |
|
| 27 |
-
| `results.py` | `DocumentResult`, `EngineReport`, `BenchmarkResult` |
|
| 28 |
-
| `metric_registry.py` | `MetricSpec`, `register_metric`, `select_metrics`, `compute_at_junction` |
|
| 29 |
-
| `metric_hooks.py` | `register_document_metric`, `register_corpus_aggregator`, profils de calcul |
|
| 30 |
-
| `pipeline.py` | `PipelineRunner`, `PipelineSpec`, `PipelineStep` (DAG de modules) |
|
| 31 |
-
| `facts.py` | `Fact`, `FactType`, `FactImportance`, `DetectorRegistry` |
|
| 32 |
-
|
| 33 |
-
**Règle** : un module du cercle 1 peut importer un autre module du
|
| 34 |
-
cercle 1. Il ne peut **rien** importer des cercles 2 ou 3.
|
| 35 |
-
|
| 36 |
-
## Cercle 2 — implémentations officielles
|
| 37 |
-
|
| 38 |
-
Les implémentations distribuées par défaut dans le package `picarones`.
|
| 39 |
-
|
| 40 |
-
### `picarones/measurements/` — métriques (~50 modules)
|
| 41 |
-
|
| 42 |
-
| Catégorie | Modules |
|
| 43 |
-
|---|---|
|
| 44 |
-
| Coeur | `metrics.py`, `statistics/` (sous-package), `runner.py`, `builtin_hooks.py`, `builtin_metrics.py`, `normalization.py` |
|
| 45 |
-
| Erreurs | `confusion.py`, `taxonomy.py`, `taxonomy_comparison.py`, `taxonomy_cooccurrence.py`, `taxonomy_intra_doc.py` |
|
| 46 |
-
| Lignes/structure | `line_metrics.py`, `structure.py`, `worst_lines.py`, `char_scores.py` |
|
| 47 |
-
| Calibration/fiabilité | `calibration.py`, `reliability.py`, `hallucination.py` |
|
| 48 |
-
| Image | `image_quality.py`, `image_predictive.py`, `difficulty.py` |
|
| 49 |
-
| Robustesse | `robustness.py`, `robustness_projection.py` |
|
| 50 |
-
| Inter-moteurs | `inter_engine.py`, `specialization.py` |
|
| 51 |
-
| Statistique avancée | `baseline_comparison.py`, `longitudinal.py`, `incremental_comparison.py` |
|
| 52 |
-
| Contenu | `searchability.py`, `numerical_sequences.py`, `rare_tokens.py`, `readability.py` |
|
| 53 |
-
| Structure ALTO | `layout.py`, `reading_order.py`, `ner.py`, `ner_backends.py`, `error_absorption.py` |
|
| 54 |
-
| Économie | `cost_projection.py`, `marginal_cost.py`, `pricing.py`, `throughput.py` |
|
| 55 |
-
| Philologie historique | `mufi.py`, `abbreviations.py`, `unicode_blocks.py`, `early_modern_typography.py`, `modern_archives.py`, `roman_numerals.py`, `lexical_modernization.py`, `philological_runner.py` |
|
| 56 |
-
| Pipelines composées | `pipeline_benchmark.py`, `pipeline_comparison.py`, `pipeline_spec_loader.py`, `alto_metrics.py` |
|
| 57 |
-
| Divers | `equivalence_profile.py`, `levers.py`, `module_policy.py`, `history.py` |
|
| 58 |
-
| Runners adaptifs | `readability_runner.py`, `searchability_runner.py`, `numerical_sequences_runner.py` |
|
| 59 |
-
| Narratif | `narrative/` (arbiter, renderer, registry, 18 détecteurs en 6 familles) |
|
| 60 |
-
|
| 61 |
-
### `picarones/engines/` — adapters OCR (5)
|
| 62 |
-
|
| 63 |
-
`tesseract.py`, `pero_ocr.py`, `mistral_ocr.py`, `google_vision.py`,
|
| 64 |
-
`azure_doc_intel.py`. Tous héritent de `picarones.core.engine.BaseOCREngine`
|
| 65 |
-
(qui vit dans `engines/base.py` pour la lisibilité).
|
| 66 |
-
|
| 67 |
-
### `picarones/llm/` — adapters LLM (4)
|
| 68 |
-
|
| 69 |
-
`mistral_adapter.py`, `openai_adapter.py`, `anthropic_adapter.py`,
|
| 70 |
-
`ollama_adapter.py`. Interface commune dans `base.py`.
|
| 71 |
-
|
| 72 |
-
### `picarones/pipelines/` — pipelines OCR+LLM intégrés
|
| 73 |
-
|
| 74 |
-
`base.py` (`OCRLLMPipeline`, qui hérite de `BaseOCREngine`),
|
| 75 |
-
`over_normalization.py`.
|
| 76 |
-
|
| 77 |
-
### `picarones/modules/` — modules `BaseModule` officiels
|
| 78 |
-
|
| 79 |
-
Démonstrateurs qui prouvent l'axe B du plan d'évolution :
|
| 80 |
-
`alto_text_to_mono_region.py`.
|
| 81 |
-
|
| 82 |
-
## Cercle 3 — extensions et présentation
|
| 83 |
-
|
| 84 |
-
### `picarones/extras/importers/` — connecteurs corpus
|
| 85 |
-
|
| 86 |
-
`iiif.py`, `gallica.py`, `htr_united.py`, `huggingface.py`,
|
| 87 |
-
`escriptorium.py`, `_http.py`. Plugins pluggable, certains expérimentaux.
|
| 88 |
-
|
| 89 |
-
### `picarones/report/` — rendu HTML
|
| 90 |
-
|
| 91 |
-
| Sous-dossier | Contenu |
|
| 92 |
-
|---|---|
|
| 93 |
-
| `generator.py` | Orchestration Jinja2 |
|
| 94 |
-
| `views/` | 5 vues thématiques (economics, advanced_taxonomy, diagnostics, pipeline, robustness) |
|
| 95 |
-
| `templates/` | Jinja2 (base, header, footer, vues, partials) |
|
| 96 |
-
| `i18n/` | FR/EN |
|
| 97 |
-
| `glossary/` | 25 entrées bilingues |
|
| 98 |
-
| `vendor/` | Chart.js |
|
| 99 |
-
| `*_render.py` | ~22 renderers (calibration, NER, Pareto, Sankey, etc.) |
|
| 100 |
-
|
| 101 |
-
Pas de sous-dossier `extras/render/` — tout le rendu est ici.
|
| 102 |
-
|
| 103 |
-
### `picarones/cli/` — Click (7 fichiers)
|
| 104 |
-
|
| 105 |
-
Point d'entrée `picarones.cli:cli` (référencé dans `pyproject.toml`).
|
| 106 |
-
15 sous-commandes : `run`, `diagnose`, `economics`, `edition`,
|
| 107 |
-
`compare`, `metrics`, `engines`, `info`, `report`, `demo`, `serve`,
|
| 108 |
-
`history`, `robustness`, `pipeline run/compare`, `import`.
|
| 109 |
-
|
| 110 |
-
### `picarones/web/` — FastAPI
|
| 111 |
-
|
| 112 |
-
Interface web (`app.py`).
|
| 113 |
-
|
| 114 |
-
## Données
|
| 115 |
-
|
| 116 |
-
| Dossier | Rôle |
|
| 117 |
-
|---|---|
|
| 118 |
-
| `picarones/prompts/` | Prompts LLM versionnés (8 fichiers, FR + EN) |
|
| 119 |
-
| `picarones/data/` | Tables indicatives (pricing, etc.) |
|
| 120 |
-
| `picarones/fixtures.py` | Corpus de démonstration |
|
| 121 |
-
|
| 122 |
-
## Règles de migration
|
| 123 |
-
|
| 124 |
-
1. **Pas de shim** : un module a un seul emplacement physique. Les
|
| 125 |
-
imports pointent directement vers la vraie source.
|
| 126 |
-
2. **Pas de double API** : une fonction a un seul nom canonique. Les
|
| 127 |
-
alias historiques sont supprimés et les tests mis à jour.
|
| 128 |
-
3. **Frontières strictes** : si un module Y du cercle N importe le
|
| 129 |
-
module X, alors le cercle de X est ≤ N. Une exception
|
| 130 |
-
pragmatique : `engines/base.py` est conceptuellement cercle 1
|
| 131 |
-
mais physiquement dans `engines/` pour rester avec ses
|
| 132 |
-
implémentations.
|
| 133 |
-
4. **Les dépendances optionnelles** (`scipy`, `spacy`, etc.) sont
|
| 134 |
-
gérées par try/except à l'import — pas par shim.
|
| 135 |
-
|
| 136 |
-
## Tests
|
| 137 |
-
|
| 138 |
-
Organisés par cercle : `tests/core/`, `tests/measurements/`,
|
| 139 |
-
`tests/engines/`, `tests/extras/`, `tests/report/`,
|
| 140 |
-
`tests/integration/` (tests E2E croisant plusieurs cercles).
|
| 141 |
-
|
| 142 |
-
Un test du cercle N **n'importe pas** les implémentations des
|
| 143 |
-
cercles > N (sauf `tests/integration/`).
|
| 144 |
-
|
| 145 |
-
## Convention de découpage des modules > 400 lignes
|
| 146 |
-
|
| 147 |
-
Quand un module Python dépasse 400 lignes ET contient plusieurs
|
| 148 |
-
responsabilités disjointes, le découper en **sous-package** plutôt
|
| 149 |
-
qu'en plusieurs modules à plat. Modèle de référence :
|
| 150 |
-
[`picarones/measurements/statistics/`](../picarones/measurements/statistics/)
|
| 151 |
-
issu du sprint « découpage de statistics.py » (mai 2026).
|
| 152 |
-
|
| 153 |
-
Convention :
|
| 154 |
-
|
| 155 |
-
1. **Renommer** `X.py` en `X/__init__.py` via `git mv` (préserve
|
| 156 |
-
l'historique du fichier original).
|
| 157 |
-
2. **Créer** dans `X/` un sous-module par famille fonctionnelle
|
| 158 |
-
(`bootstrap.py`, `wilcoxon.py`, `friedman_nemenyi.py`, etc.).
|
| 159 |
-
Chaque sous-module doit faire moins de ~400 lignes ; sinon
|
| 160 |
-
re-décomposer.
|
| 161 |
-
3. **`X/__init__.py`** ne contient QUE des ré-exports rétrocompat —
|
| 162 |
-
tous les symboles publics de l'ancien `X.py` doivent rester
|
| 163 |
-
importables via `from picarones.X import …`. Les symboles privés
|
| 164 |
-
ré-exportés doivent être ceux **réellement** consommés par les
|
| 165 |
-
tests (vérifié par grep, pas par supposition).
|
| 166 |
-
4. **`__all__`** explicite dans chaque sous-module et dans le
|
| 167 |
-
`__init__.py`.
|
| 168 |
-
5. **Tests architecture** (`tests/architecture/test_*.py`) doivent
|
| 169 |
-
continuer à passer : si nécessaire, étendre `_measurements_modules()`
|
| 170 |
-
ou `_imports_target_*` pour reconnaître les sous-packages.
|
| 171 |
-
6. **Préfixer les modules de rendu** par leur domaine
|
| 172 |
-
(`cdd_render.py` plutôt que `render_cdd.py`) pour cohérence avec
|
| 173 |
-
`picarones/report/*_render.py`.
|
| 174 |
-
|
| 175 |
-
**Quand NE PAS découper** : si les responsabilités sont fortement
|
| 176 |
-
couplées (ex: un orchestrateur qui appelle 12 sous-fonctions au
|
| 177 |
-
même endroit), le maintien dans un seul fichier > 400 lignes est
|
| 178 |
-
acceptable. Le budget par fichier (`tests/architecture/test_file_budgets.py`)
|
| 179 |
-
documente ces dérogations conscientes.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/developer/extending-i18n.md
CHANGED
|
@@ -48,7 +48,7 @@ automatiquement sur `fr` si une langue manque.
|
|
| 48 |
|
| 49 |
## Format YAML pour les templates narratifs
|
| 50 |
|
| 51 |
-
Voir `docs/
|
| 52 |
|
| 53 |
```yaml
|
| 54 |
fact_type_value: >-
|
|
|
|
| 48 |
|
| 49 |
## Format YAML pour les templates narratifs
|
| 50 |
|
| 51 |
+
Voir `docs/explanation/narrative-engine.md` pour le détail. En bref :
|
| 52 |
|
| 53 |
```yaml
|
| 54 |
fact_type_value: >-
|
docs/developer/index.en.md
CHANGED
|
@@ -13,7 +13,7 @@ module.
|
|
| 13 |
## Architecture
|
| 14 |
|
| 15 |
Picarones uses a **3-circle architecture** (manifesto in
|
| 16 |
-
[`docs/architecture.md`](../architecture.md)):
|
| 17 |
|
| 18 |
```
|
| 19 |
Circle 3 (extras, report, cli, web)
|
|
|
|
| 13 |
## Architecture
|
| 14 |
|
| 15 |
Picarones uses a **3-circle architecture** (manifesto in
|
| 16 |
+
[`docs/explanation/architecture.md`](../architecture.md)):
|
| 17 |
|
| 18 |
```
|
| 19 |
Circle 3 (extras, report, cli, web)
|
docs/developer/index.md
CHANGED
|
@@ -5,33 +5,49 @@ fondamentaux du projet.
|
|
| 5 |
|
| 6 |
## Architecture
|
| 7 |
|
| 8 |
-
Voir [CLAUDE.md](../../CLAUDE.md)
|
| 9 |
-
|
|
|
|
|
|
|
| 10 |
|
| 11 |
```
|
| 12 |
picarones/
|
| 13 |
-
├──
|
| 14 |
-
│ ├──
|
| 15 |
-
│ ├── corpus.py #
|
| 16 |
-
│ ├──
|
| 17 |
-
│ ├──
|
|
|
|
| 18 |
│ ├── facts.py # Fact, FactType, registre narratif
|
| 19 |
│ └── …
|
| 20 |
-
├──
|
| 21 |
-
|
| 22 |
-
│ ├── metrics
|
| 23 |
-
│ ├── statistics/ # Wilcoxon, Friedman,
|
| 24 |
-
│
|
| 25 |
-
│ ├──
|
| 26 |
-
│ ├──
|
| 27 |
-
│ └──
|
| 28 |
-
├──
|
| 29 |
-
├──
|
| 30 |
-
├──
|
| 31 |
-
├──
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
```
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
## Guides d'extension
|
| 36 |
|
| 37 |
- [Étendre le moteur narratif](narrative-engine.md) — ajouter un type
|
|
|
|
| 5 |
|
| 6 |
## Architecture
|
| 7 |
|
| 8 |
+
Voir [CLAUDE.md](../../CLAUDE.md) et
|
| 9 |
+
[`docs/explanation/architecture.md`](../explanation/architecture.md)
|
| 10 |
+
pour la cartographie complète. En résumé : architecture **8
|
| 11 |
+
couches concentriques** (post-rewrite, canonique) :
|
| 12 |
|
| 13 |
```
|
| 14 |
picarones/
|
| 15 |
+
├── domain/ # Layer 1 — types purs (Pydantic, stdlib only)
|
| 16 |
+
│ ├── artifacts.py # Artifact, ArtifactType (10 types)
|
| 17 |
+
│ ├── corpus.py # CorpusSpec
|
| 18 |
+
│ ├── documents.py # DocumentRef
|
| 19 |
+
│ ├── pipeline_spec.py # PipelineSpec, PipelineStep (Pydantic immutable)
|
| 20 |
+
│ ├── module_protocol.py # BaseModule (ABC, en cours de retrait au profit de StepExecutor)
|
| 21 |
│ ├── facts.py # Fact, FactType, registre narratif
|
| 22 |
│ └── …
|
| 23 |
+
├── formats/ # Layer 2 — parsing/serialization (ALTO 4, PAGE XML, JSON)
|
| 24 |
+
├── evaluation/ # Layer 3 — métriques et calcul
|
| 25 |
+
│ ├── metrics/ # ~30 métriques (CER/WER, MUFI, philological, NER, …)
|
| 26 |
+
│ ├── statistics/ # Wilcoxon, Friedman/Nemenyi, bootstrap, Pareto
|
| 27 |
+
│ ├── views/, projectors/ # EvaluationView (S13+), projecteurs Alto/Page/CanonicalToText
|
| 28 |
+
│ ├── corpus.py # Document, Corpus, GTLevel (legacy en cours de retrait)
|
| 29 |
+
│ ├── pipeline.py # PipelineRunner legacy (en cours de retrait)
|
| 30 |
+
│ └── benchmark_result.py # BenchmarkResult, EngineReport, DocumentResult
|
| 31 |
+
├── pipeline/ # Layer 4 — PipelineExecutor canonique (instance-based)
|
| 32 |
+
├── adapters/ # Layer 5 — adapters externes (libs externes autorisées)
|
| 33 |
+
│ ├── ocr/ # Tesseract, Pero, Mistral OCR, Google Vision, Azure DI
|
| 34 |
+
│ ├── llm/ # OpenAI, Anthropic, Mistral, Ollama
|
| 35 |
+
│ ├── vlm/ # Adapters VLM (zero-shot)
|
| 36 |
+
│ ├── corpus/ # IIIF, Gallica, HTR-United, HuggingFace
|
| 37 |
+
│ ├── storage/ # ArtifactStore, JobStore
|
| 38 |
+
│ └── legacy_engines/, legacy_modules/ # legacy BaseModule-based, en retrait
|
| 39 |
+
├── app/ # Layer 6 — services applicatifs (BenchmarkService, …)
|
| 40 |
+
├── reports_v2/ # Layer 7 — rendu HTML / JSON / CSV (22 renderers + 5 vues)
|
| 41 |
+
└── interfaces/ # Layer 8 — CLI Click, Web FastAPI
|
| 42 |
+
|
| 43 |
+
# Arborescence legacy en cours de retrait (cf. docs/migration/) :
|
| 44 |
+
# core/, measurements/, engines/, llm/, pipelines/, report/, modules/
|
| 45 |
```
|
| 46 |
|
| 47 |
+
Règle d'import stricte : les flèches d'import vont uniquement
|
| 48 |
+
de l'extérieur vers l'intérieur (de bas en haut dans le diagramme).
|
| 49 |
+
Vérifié par `tests/architecture/test_layer_dependencies.py`.
|
| 50 |
+
|
| 51 |
## Guides d'extension
|
| 52 |
|
| 53 |
- [Étendre le moteur narratif](narrative-engine.md) — ajouter un type
|
docs/developer/module-policy.md
CHANGED
|
@@ -14,7 +14,7 @@ qu'un module soit acceptable.
|
|
| 14 |
|
| 15 |
Pour qu'un module soit acceptable :
|
| 16 |
|
| 17 |
-
1. Il **hérite** de `picarones.
|
| 18 |
2. Il déclare ses `input_types` et `output_types` (parmi
|
| 19 |
`ArtifactType.{IMAGE, TEXT, ALTO, PAGE, ENTITIES, READING_ORDER}`).
|
| 20 |
3. Il fournit un `ModuleManifest` avec **5 champs obligatoires** :
|
|
@@ -80,11 +80,12 @@ manifest = ModuleManifest(
|
|
| 80 |
## Contrat `BaseModule`
|
| 81 |
|
| 82 |
Tout module exécutable hérite de
|
| 83 |
-
`picarones.
|
| 84 |
est :
|
| 85 |
|
| 86 |
```python
|
| 87 |
-
from picarones.
|
|
|
|
| 88 |
|
| 89 |
class MyLlmCorrecteur(BaseModule):
|
| 90 |
name = "my-llm-correcteur"
|
|
|
|
| 14 |
|
| 15 |
Pour qu'un module soit acceptable :
|
| 16 |
|
| 17 |
+
1. Il **hérite** de `picarones.domain.module_protocol.BaseModule` (Sprint 33).
|
| 18 |
2. Il déclare ses `input_types` et `output_types` (parmi
|
| 19 |
`ArtifactType.{IMAGE, TEXT, ALTO, PAGE, ENTITIES, READING_ORDER}`).
|
| 20 |
3. Il fournit un `ModuleManifest` avec **5 champs obligatoires** :
|
|
|
|
| 80 |
## Contrat `BaseModule`
|
| 81 |
|
| 82 |
Tout module exécutable hérite de
|
| 83 |
+
`picarones.domain.module_protocol.BaseModule` (Sprint 33). Le contrat minimal
|
| 84 |
est :
|
| 85 |
|
| 86 |
```python
|
| 87 |
+
from picarones.domain.artifacts import ArtifactType
|
| 88 |
+
from picarones.domain.module_protocol import BaseModule
|
| 89 |
|
| 90 |
class MyLlmCorrecteur(BaseModule):
|
| 91 |
name = "my-llm-correcteur"
|
docs/explanation/architecture.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture Picarones — manifeste
|
| 2 |
+
|
| 3 |
+
> **Audience** : développeurs et mainteneurs. Ce document explique
|
| 4 |
+
> *pourquoi* le code est organisé comme il l'est, pas seulement *où
|
| 5 |
+
> sont les fichiers*. Pour la liste exhaustive des modules, lire
|
| 6 |
+
> directement le code — il est typé et documenté.
|
| 7 |
+
|
| 8 |
+
## Deux arborescences cohabitent par design
|
| 9 |
+
|
| 10 |
+
Le projet est en transition entre une arborescence **legacy** (héritée
|
| 11 |
+
de la fondation 2025) et une arborescence **post-rewrite** (refondation
|
| 12 |
+
ciblée S27-S46, 2026). Cette cohabitation est explicite et finie dans
|
| 13 |
+
le temps :
|
| 14 |
+
|
| 15 |
+
| Arbo | Statut | Utilisation |
|
| 16 |
+
|------|--------|-------------|
|
| 17 |
+
| **Post-rewrite** | Canonique | **Tout nouveau code va ici.** |
|
| 18 |
+
| **Legacy** | Transitionnel | Reste exécutable le temps que les callers externes (HuggingFace Space, scripts BnF, notebooks de chercheurs) migrent. |
|
| 19 |
+
|
| 20 |
+
Le retrait du legacy est calendrier dans le CHANGELOG ; cf. aussi
|
| 21 |
+
`docs/migration/rewrite-status-s46.md`.
|
| 22 |
+
|
| 23 |
+
## Arbo canonique — 8 cercles concentriques
|
| 24 |
+
|
| 25 |
+
```
|
| 26 |
+
domain → formats → evaluation → pipeline → adapters → app → reports_v2 → interfaces
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
**Règle de dépendance stricte** : les flèches d'import vont uniquement
|
| 30 |
+
de l'extérieur vers l'intérieur. Vérifié par
|
| 31 |
+
`tests/architecture/test_layer_dependencies.py`. Aucun shim — un
|
| 32 |
+
module a un seul emplacement canonique.
|
| 33 |
+
|
| 34 |
+
### `picarones/domain/` — types purs
|
| 35 |
+
|
| 36 |
+
Couche 1 (la plus interne). Aucune dépendance d'exécution,
|
| 37 |
+
aucun I/O, aucun framework. Pydantic et stdlib uniquement.
|
| 38 |
+
|
| 39 |
+
| Module | Contenu |
|
| 40 |
+
|---|---|
|
| 41 |
+
| `artifacts.py` | `Artifact`, `ArtifactType` (10 types : IMAGE, RAW_TEXT, ALTO_XML, PAGE_XML, ENTITIES, READING_ORDER, ALIGNMENT, CORRECTED_TEXT, CANONICAL_DOCUMENT, CONFIDENCES) |
|
| 42 |
+
| `artifact_key.py` | `ArtifactKey` — clé canonique multi-paramètres pour la reprise par hash |
|
| 43 |
+
| `corpus.py` | `CorpusSpec`, métadonnées de corpus |
|
| 44 |
+
| `documents.py` | `DocumentRef`, `GroundTruthRef` |
|
| 45 |
+
| `evaluation_spec.py` | `MetricSpec`, `EvaluationView`, `EvaluationSpec` |
|
| 46 |
+
| `pipeline_spec.py` | `PipelineSpec`, `PipelineStep`, `INITIAL_STEP_ID` |
|
| 47 |
+
| `projection_spec.py` | `ProjectionSpec` (transformation candidate avant évaluation) |
|
| 48 |
+
| `provenance.py` | `ProvenanceRecord` |
|
| 49 |
+
| `run_manifest.py` | `RunManifest` — empreinte immuable d'un run, sérialisée en `run_manifest.json` |
|
| 50 |
+
| `errors.py` | Hiérarchie d'exceptions (`PicaronesError`, `AdapterStepError`, `ArtifactValidationError`, …) |
|
| 51 |
+
|
| 52 |
+
### `picarones/formats/` — parsers et sérialiseurs
|
| 53 |
+
|
| 54 |
+
Lecture/écriture des formats externes : ALTO XML, PAGE XML, texte
|
| 55 |
+
normalisé. Dépend du domain ; aucune logique d'évaluation.
|
| 56 |
+
|
| 57 |
+
### `picarones/evaluation/` — moteurs d'évaluation
|
| 58 |
+
|
| 59 |
+
| Sous-package | Rôle |
|
| 60 |
+
|---|---|
|
| 61 |
+
| `metrics/` | Métriques (CER/WER, philologiques, calibration, NER, layout…). Enregistrées via `@register_metric` au registre typé |
|
| 62 |
+
| `projectors/` | Projections inter-types (ALTO → texte, canonical → texte) avec `ProjectionReport` |
|
| 63 |
+
| `views/` | Vues d'évaluation : `TextView`, `AltoView`, `SearchView`. L'`EvaluationViewExecutor` aligne candidate + GT, applique normalisation + projection, calcule les métriques |
|
| 64 |
+
| `evaluation_engine.py` | Moteur central qui exécute une `EvaluationView` |
|
| 65 |
+
| `projection_engine.py` | Moteur de projection |
|
| 66 |
+
| `registry/` | `MetricRegistry` — découverte typée par signature `(input_type, output_type)` |
|
| 67 |
+
|
| 68 |
+
### `picarones/pipeline/` — DAG d'étapes
|
| 69 |
+
|
| 70 |
+
Orchestration mono-document d'une pipeline composée :
|
| 71 |
+
|
| 72 |
+
| Module | Rôle |
|
| 73 |
+
|---|---|
|
| 74 |
+
| `executor.py` | `PipelineExecutor` — exécute un `PipelineSpec` step par step, capture `StepResult`, filtre outputs sur `step.output_types` |
|
| 75 |
+
| `planner.py` | `PipelinePlanner` — résout les `inputs_from`, valide la spec, calcule les métriques aux jonctions |
|
| 76 |
+
| `validation.py` | Validation statique d'une `PipelineSpec` (types s'enchaînent, pas de cycle) |
|
| 77 |
+
| `runner.py` | `CorpusRunner` — orchestration corpus-wide avec ProcessPool/ThreadPool, backpressure, timeout, cancellation |
|
| 78 |
+
| `cache.py`, `cache_helpers.py`, `cache_protocol.py` | Reprise par hash via `ArtifactCachePort` |
|
| 79 |
+
| `yaml_io.py` | Sérialisation YAML déterministe d'une `PipelineSpec` |
|
| 80 |
+
|
| 81 |
+
### `picarones/adapters/` — implémentations concrètes
|
| 82 |
+
|
| 83 |
+
C'est ici que vivent les **dépendances externes** (pytesseract, pero,
|
| 84 |
+
mistralai, openai, anthropic, google-cloud-vision, …).
|
| 85 |
+
|
| 86 |
+
| Sous-package | Adapters |
|
| 87 |
+
|---|---|
|
| 88 |
+
| `ocr/` | TesseractAdapter, PeroOCRAdapter, MistralOCRAdapter, GoogleVisionAdapter, AzureDocIntelAdapter, PrecomputedTextAdapter |
|
| 89 |
+
| `llm/` | AnthropicLLMAdapter, OpenAILLMAdapter, MistralLLMAdapter, OllamaLLMAdapter |
|
| 90 |
+
| `vlm/` | AnthropicVLMAdapter, OpenAIVLMAdapter, MistralVLMAdapter, OllamaVLMAdapter (héritage multiple `BaseVLMAdapter + BaseLLMAdapter`, MRO guard) |
|
| 91 |
+
| `corpus/` | local folder, IIIF, Gallica, HTR-United, HuggingFace Datasets, eScriptorium |
|
| 92 |
+
| `storage/` | `InMemoryArtifactStore`, `FilesystemArtifactStore`, `JobStore` (SQLite) |
|
| 93 |
+
| `output_paths.py` | Helper partagé `resolve_output_path` (workspace-aware, read-only-mount-safe) |
|
| 94 |
+
| `_retry.py` | Helper partagé `call_with_retry` (3 retries, backoff 2/4/8s, sur 429+5xx+timeout réseau) |
|
| 95 |
+
|
| 96 |
+
**Règle** : un adapter peut importer le domain et ses libs externes.
|
| 97 |
+
Il ne doit **jamais** importer `app/` ou `interfaces/`. Il n'a aucune
|
| 98 |
+
logique d'évaluation (un OCR adapter ne calcule pas le CER — il
|
| 99 |
+
produit un artefact texte que `evaluation/` consommera).
|
| 100 |
+
|
| 101 |
+
### `picarones/app/` — services applicatifs
|
| 102 |
+
|
| 103 |
+
Orchestration entre adapters et evaluation.
|
| 104 |
+
|
| 105 |
+
| Module | Rôle |
|
| 106 |
+
|---|---|
|
| 107 |
+
| `services/run_orchestrator.py` | `RunOrchestrator.execute(RunSpec)` — point d'entrée d'un run complet |
|
| 108 |
+
| `services/benchmark_service.py` | `BenchmarkService.run` — exécute pipelines × vues × corpus, produit `RunResult` |
|
| 109 |
+
| `services/job_runner.py` | `JobRunner` — soumission asynchrone (thread daemon) avec persistance `JobStore` |
|
| 110 |
+
| `services/corpus_service.py` | Loading + sandboxing + extraction ZIP avec zip-slip protection |
|
| 111 |
+
| `services/dependencies.py` | `capture_dependencies_lock()` via `importlib.metadata` pour le `RunManifest` |
|
| 112 |
+
| `services/path_security.py` | `WorkspaceManager` — sandboxe par session |
|
| 113 |
+
| `services/registry_service.py` | Découverte des adapters et vues canoniques |
|
| 114 |
+
| `schemas/run_spec.py` | `RunSpec`, `StepSpec` — modèles YAML user-facing |
|
| 115 |
+
| `results.py` | `RunResult`, `RunDocumentResult`, `ReportRenderer` (alias type unique) |
|
| 116 |
+
|
| 117 |
+
### `picarones/reports_v2/` — rendu déterministe
|
| 118 |
+
|
| 119 |
+
| Sous-package | Rôle |
|
| 120 |
+
|---|---|
|
| 121 |
+
| `csv/render.py` | `CsvReportRenderer` — un CSV plat (`run_id, doc, pipeline, view, metric, value, status`) |
|
| 122 |
+
| `json/render.py` | `JsonReportRenderer` — manifest + documents en JSON déterministe |
|
| 123 |
+
| `html/render.py` | `HtmlReportRenderer` — rapport autonome (TextView, AltoView, SearchView) |
|
| 124 |
+
|
| 125 |
+
Le rendu est strict : pas de JS dynamique, pas d'I/O, déterministe
|
| 126 |
+
bit-for-bit à entrée constante. Permet à un relecteur 5 ans plus tard
|
| 127 |
+
de hasher un rapport et de le citer.
|
| 128 |
+
|
| 129 |
+
### `picarones/interfaces/` — points d'entrée user-facing
|
| 130 |
+
|
| 131 |
+
| Sous-package | Rôle |
|
| 132 |
+
|---|---|
|
| 133 |
+
| `cli/` | Click — `picarones-rewrite run`, `import_corpus`, `report` |
|
| 134 |
+
| `web/` | FastAPI — skeleton, routers (corpus, benchmark, jobs), middlewares de sécurité |
|
| 135 |
+
|
| 136 |
+
## Arbo legacy — `picarones/{cli,web,engines,llm,pipelines,report,measurements,extras,modules,core}/`
|
| 137 |
+
|
| 138 |
+
Reste exécutable. Ne pas y ajouter de nouveau code. Une partie est
|
| 139 |
+
re-exportée depuis l'arbo canonique via des shims dépréciés (cf.
|
| 140 |
+
`picarones/pipeline/spec.py`, alias `DEFAULT_*_PROMPT` singuliers
|
| 141 |
+
dans `BaseLLMAdapter`/`BaseVLMAdapter`) qui émettent
|
| 142 |
+
`DeprecationWarning` à l'usage. Suppression effective prévue en 2.0.
|
| 143 |
+
|
| 144 |
+
## Principes architecturaux
|
| 145 |
+
|
| 146 |
+
### Pas de shim hors deprecation period
|
| 147 |
+
|
| 148 |
+
Un module a un seul emplacement canonique. Quand un module migre,
|
| 149 |
+
on choisit explicitement entre :
|
| 150 |
+
|
| 151 |
+
- **Suppression dure** (pour la dette interne, pas de caller externe).
|
| 152 |
+
- **Shim avec `DeprecationWarning`** (pour la stabilité d'API publique).
|
| 153 |
+
Le shim a une date de retrait inscrite dans le CHANGELOG.
|
| 154 |
+
|
| 155 |
+
### Pas d'`except Exception: pass`
|
| 156 |
+
|
| 157 |
+
Toute fonctionnalité optionnelle qui échoue émet un
|
| 158 |
+
`logger.warning("[module] feature dégradée : %s", exc)` avec contexte.
|
| 159 |
+
Vérifié par `tests/architecture/test_no_side_effect_imports.py`.
|
| 160 |
+
|
| 161 |
+
### Tests architecturaux comme garde-fous
|
| 162 |
+
|
| 163 |
+
Plusieurs tests verrouillent des invariants structurels que la revue
|
| 164 |
+
de code humaine raterait :
|
| 165 |
+
|
| 166 |
+
- `test_layer_dependencies.py` — circles strictement orientés
|
| 167 |
+
- `test_file_budgets.py` — pas de god-modules
|
| 168 |
+
- `test_doc_paths.py` — chemins cités dans la doc existent
|
| 169 |
+
- `test_output_paths_uniformity.py` — tous les adapters passent par `resolve_output_path`
|
| 170 |
+
- `test_storage_keys_filesystem_safe.py` — clés du store filesystem-safe (Windows)
|
| 171 |
+
- `test_manifest_reproducibility.py` — `RunManifest` capture tout pour rejouer
|
| 172 |
+
- `test_module_coverage.py` — chaque module a un test associé
|
| 173 |
+
|
| 174 |
+
### Reproductibilité bit-for-bit
|
| 175 |
+
|
| 176 |
+
Le `RunManifest` capture systématiquement : `code_version`,
|
| 177 |
+
`pipeline_specs` complets, `adapter_kwargs`, `dependencies_lock`
|
| 178 |
+
(via `importlib.metadata`), `view_specs`, timestamps. La
|
| 179 |
+
sérialisation est déterministe (Pydantic ordered fields, JSON
|
| 180 |
+
sorted keys). Le hash du manifest peut être cité dans une
|
| 181 |
+
publication scientifique.
|
| 182 |
+
|
| 183 |
+
## Évolution
|
| 184 |
+
|
| 185 |
+
L'évolution de l'architecture est documentée :
|
| 186 |
+
|
| 187 |
+
- Plans : [`docs/roadmap/evolution-2026.md`](../roadmap/evolution-2026.md)
|
| 188 |
+
- État du rewrite : [`docs/migration/rewrite-status-s46.md`](../migration/rewrite-status-s46.md)
|
| 189 |
+
- Audits institutionnels : [`docs/audits/`](../audits/)
|
| 190 |
+
- Politique d'API publique : [`docs/reference/api-stable.md`](../reference/api-stable.md)
|
docs/{developer → explanation}/narrative-engine.en.md
RENAMED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
<!-- translation: machine + human review pending -->
|
| 2 |
-
<!-- canonical: docs/
|
| 3 |
|
| 4 |
# Extending the narrative engine
|
| 5 |
|
|
@@ -13,7 +13,7 @@ contradiction), and renders them through YAML templates with
|
|
| 13 |
|
| 14 |
## Add a new detector in 5 steps
|
| 15 |
|
| 16 |
-
### 1. Add a `FactType` in `picarones/
|
| 17 |
|
| 18 |
```python
|
| 19 |
class FactType(str, Enum):
|
|
|
|
| 1 |
<!-- translation: machine + human review pending -->
|
| 2 |
+
<!-- canonical: docs/explanation/narrative-engine.md (FR) -->
|
| 3 |
|
| 4 |
# Extending the narrative engine
|
| 5 |
|
|
|
|
| 13 |
|
| 14 |
## Add a new detector in 5 steps
|
| 15 |
|
| 16 |
+
### 1. Add a `FactType` in `picarones/domain/facts.py`
|
| 17 |
|
| 18 |
```python
|
| 19 |
class FactType(str, Enum):
|
docs/{developer → explanation}/narrative-engine.md
RENAMED
|
File without changes
|
docs/{cli-workflows.md → how-to/cli-workflows.md}
RENAMED
|
@@ -216,5 +216,5 @@ pour découvrir la sortie sans corpus réel.
|
|
| 216 |
run, diagnose, economics, edition, compare + helper `_run_workflow`.
|
| 217 |
- [`picarones/cli/_pipeline.py`](../picarones/cli/_pipeline.py) —
|
| 218 |
pipeline group.
|
| 219 |
-
- Voir aussi [`docs/profiles.md`](profiles.md) et
|
| 220 |
-
[`docs/views.md`](views.md).
|
|
|
|
| 216 |
run, diagnose, economics, edition, compare + helper `_run_workflow`.
|
| 217 |
- [`picarones/cli/_pipeline.py`](../picarones/cli/_pipeline.py) —
|
| 218 |
pipeline group.
|
| 219 |
+
- Voir aussi [`docs/reference/normalization-profiles.md`](profiles.md) et
|
| 220 |
+
[`docs/reference/views.md`](views.md).
|
INSTALL.md → docs/how-to/install.md
RENAMED
|
@@ -1,7 +1,13 @@
|
|
| 1 |
# Guide d'installation — Picarones
|
| 2 |
|
| 3 |
> Guide détaillé pour Linux, macOS et Windows.
|
| 4 |
-
> Pour une installation en 5 minutes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
---
|
| 7 |
|
|
@@ -236,19 +242,7 @@ config_path: /path/to/pero_model/config.yaml
|
|
| 236 |
EOF
|
| 237 |
```
|
| 238 |
|
| 239 |
-
### 5.3
|
| 240 |
-
|
| 241 |
-
```bash
|
| 242 |
-
pip install kraken
|
| 243 |
-
|
| 244 |
-
# Télécharger un modèle
|
| 245 |
-
kraken get 10.5281/zenodo.XXXXXXX
|
| 246 |
-
|
| 247 |
-
# Lister les modèles installés
|
| 248 |
-
kraken list
|
| 249 |
-
```
|
| 250 |
-
|
| 251 |
-
### 5.4 Ollama (LLMs locaux)
|
| 252 |
|
| 253 |
```bash
|
| 254 |
# Installer Ollama
|
|
@@ -290,11 +284,6 @@ MISTRAL_API_KEY=...
|
|
| 290 |
# Google Vision
|
| 291 |
GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
|
| 292 |
|
| 293 |
-
# AWS Textract
|
| 294 |
-
AWS_ACCESS_KEY_ID=...
|
| 295 |
-
AWS_SECRET_ACCESS_KEY=...
|
| 296 |
-
AWS_DEFAULT_REGION=eu-west-1
|
| 297 |
-
|
| 298 |
# Azure Document Intelligence
|
| 299 |
AZURE_DOC_INTEL_ENDPOINT=https://...cognitiveservices.azure.com/
|
| 300 |
AZURE_DOC_INTEL_KEY=...
|
|
|
|
| 1 |
# Guide d'installation — Picarones
|
| 2 |
|
| 3 |
> Guide détaillé pour Linux, macOS et Windows.
|
| 4 |
+
> Pour une installation en 5 minutes, voir le bloc *Setup* du
|
| 5 |
+
> [README](../../README.md).
|
| 6 |
+
>
|
| 7 |
+
> Audience : opérateur ou développeur qui installe Picarones en
|
| 8 |
+
> local ou sur un serveur. Pour un déploiement institutionnel
|
| 9 |
+
> (BnF, LoC, BL), voir aussi
|
| 10 |
+
> [`../operations/deployment-institutional.md`](../operations/deployment-institutional.md).
|
| 11 |
|
| 12 |
---
|
| 13 |
|
|
|
|
| 242 |
EOF
|
| 243 |
```
|
| 244 |
|
| 245 |
+
### 5.3 Ollama (LLMs locaux)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
|
| 247 |
```bash
|
| 248 |
# Installer Ollama
|
|
|
|
| 284 |
# Google Vision
|
| 285 |
GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
|
| 286 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
# Azure Document Intelligence
|
| 288 |
AZURE_DOC_INTEL_ENDPOINT=https://...cognitiveservices.azure.com/
|
| 289 |
AZURE_DOC_INTEL_KEY=...
|
docs/index.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Documentation Picarones — index par rôle
|
| 2 |
+
|
| 3 |
+
> **Architecture documentaire** : ce projet adopte le modèle
|
| 4 |
+
> [Diataxis](https://diataxis.fr/) — quatre quadrants :
|
| 5 |
+
> *tutorials* (apprendre), *how-to* (résoudre), *reference*
|
| 6 |
+
> (consulter), *explanation* (comprendre). Plus deux dossiers
|
| 7 |
+
> institutionnels : *governance* et *operations*.
|
| 8 |
+
>
|
| 9 |
+
> **Bilingue** : la **langue canonique est le français**. Une
|
| 10 |
+
> surface publique réduite est traduite en anglais — README,
|
| 11 |
+
> CONTRIBUTING, SECURITY, ACCESSIBILITY, deux tutoriels clés.
|
| 12 |
+
> Le reste reste FR. Politique assumée plutôt que bilingue partiel
|
| 13 |
+
> brouillé.
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## Je suis…
|
| 18 |
+
|
| 19 |
+
### …un chercheur ou archiviste qui veut benchmarker un corpus
|
| 20 |
+
|
| 21 |
+
Vous voulez exécuter Picarones sur vos documents, lire un rapport,
|
| 22 |
+
comprendre les chiffres.
|
| 23 |
+
|
| 24 |
+
1. Installer : [`how-to/install.md`](how-to/install.md)
|
| 25 |
+
2. Premier benchmark : [`tutorials/first-benchmark.md`](tutorials/first-benchmark.md)
|
| 26 |
+
3. Lire le rapport produit : [`tutorials/reading-a-report.md`](tutorials/reading-a-report.md)
|
| 27 |
+
([EN](tutorials/reading-a-report.en.md))
|
| 28 |
+
4. Cas d'école pédagogiques : [`case-studies/`](case-studies/)
|
| 29 |
+
5. Glossaire des métriques : [`reference/normalization-profiles.md`](reference/normalization-profiles.md),
|
| 30 |
+
[`reference/views.md`](reference/views.md)
|
| 31 |
+
|
| 32 |
+
### …un opérateur qui doit déployer en environnement institutionnel
|
| 33 |
+
|
| 34 |
+
Vous installez Picarones sur un NAS BnF, un cluster LoC, un serveur BL.
|
| 35 |
+
|
| 36 |
+
1. Déploiement institutionnel : [`operations/deployment-institutional.md`](operations/deployment-institutional.md)
|
| 37 |
+
2. Conformité RGPD : [`operations/data-retention-rgpd.md`](operations/data-retention-rgpd.md)
|
| 38 |
+
3. Runbook incidents : [`operations/runbook.md`](operations/runbook.md)
|
| 39 |
+
4. Observabilité (logs, métriques, alerting) : [`operations/observability.md`](operations/observability.md)
|
| 40 |
+
5. Process de release : [`operations/release-process.md`](operations/release-process.md)
|
| 41 |
+
|
| 42 |
+
### …un développeur qui veut contribuer du code
|
| 43 |
+
|
| 44 |
+
Vous ajoutez un adapter, une vue, une métrique, un détecteur narratif.
|
| 45 |
+
|
| 46 |
+
1. Vue d'ensemble du projet : [`/CONTRIBUTING.md`](../CONTRIBUTING.md)
|
| 47 |
+
([EN](../CONTRIBUTING.en.md))
|
| 48 |
+
2. Architecture en cercles : [`explanation/architecture.md`](explanation/architecture.md)
|
| 49 |
+
3. Politique modules contribués : [`developer/module-policy.md`](developer/module-policy.md)
|
| 50 |
+
4. Étendre un sous-système :
|
| 51 |
+
[glossaire](developer/extending-glossary.md) ([EN](developer/extending-glossary.en.md)) ·
|
| 52 |
+
[i18n](developer/extending-i18n.md) ([EN](developer/extending-i18n.en.md)) ·
|
| 53 |
+
[moteur narratif](developer/narrative-engine.md) ([EN](developer/narrative-engine.en.md))
|
| 54 |
+
5. Écrire un module pour le banc d'essai : [`user/writing-a-pipeline-module.md`](user/writing-a-pipeline-module.md)
|
| 55 |
+
|
| 56 |
+
### …un mainteneur ou auditeur de sécurité
|
| 57 |
+
|
| 58 |
+
Vous évaluez Picarones avant un déploiement, un audit, une revue.
|
| 59 |
+
|
| 60 |
+
1. Politique de gouvernance : [`/GOVERNANCE.md`](../GOVERNANCE.md)
|
| 61 |
+
2. Politique de sécurité : [`/SECURITY.md`](../SECURITY.md)
|
| 62 |
+
([EN](../SECURITY.en.md))
|
| 63 |
+
3. Threat model STRIDE : [`security/threat-model.md`](security/threat-model.md)
|
| 64 |
+
4. API publique stable et politique de versioning : [`reference/api-stable.md`](reference/api-stable.md)
|
| 65 |
+
5. Audits historiques : [`audits/`](audits/)
|
| 66 |
+
6. État du rewrite et migration : [`migration/rewrite-status-s46.md`](migration/rewrite-status-s46.md)
|
| 67 |
+
7. Reproductibilité bit-for-bit : [`reference/reproducibility-snapshots.md`](reference/reproducibility-snapshots.md)
|
| 68 |
+
|
| 69 |
+
### …un Délégué à la Protection des Données (DPO)
|
| 70 |
+
|
| 71 |
+
Vous évaluez les implications RGPD avant signature.
|
| 72 |
+
|
| 73 |
+
1. Politique de rétention RGPD : [`operations/data-retention-rgpd.md`](operations/data-retention-rgpd.md)
|
| 74 |
+
2. Modèle d'accord de sous-traitance (DPA) : [`legal/dpa-template.md`](legal/dpa-template.md)
|
| 75 |
+
3. Threat model : [`security/threat-model.md`](security/threat-model.md)
|
| 76 |
+
4. Liste des sous-traitants potentiels (services cloud) :
|
| 77 |
+
`pricing.yaml` + section *Adapters cloud* dans
|
| 78 |
+
[`reference/api-stable.md`](reference/api-stable.md)
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
## Index thématique
|
| 83 |
+
|
| 84 |
+
### Tutorials — j'apprends
|
| 85 |
+
|
| 86 |
+
| Document | Public | Langue |
|
| 87 |
+
|----------|--------|--------|
|
| 88 |
+
| [`tutorials/first-benchmark.md`](tutorials/first-benchmark.md) | Chercheur découvrant l'outil | FR |
|
| 89 |
+
| [`tutorials/reading-a-report.md`](tutorials/reading-a-report.md) | Chercheur lisant un rapport | FR + EN |
|
| 90 |
+
| [`tutorials/writing-a-pipeline-module.md`](tutorials/writing-a-pipeline-module.md) | Développeur tiers | FR |
|
| 91 |
+
|
| 92 |
+
### How-to — je résous un problème concret
|
| 93 |
+
|
| 94 |
+
| Document | Cible |
|
| 95 |
+
|----------|-------|
|
| 96 |
+
| [`how-to/install.md`](how-to/install.md) | Installer en local ou serveur |
|
| 97 |
+
| [`how-to/cli-workflows.md`](how-to/cli-workflows.md) | Utiliser la CLI au quotidien |
|
| 98 |
+
|
| 99 |
+
### Reference — je consulte le contrat
|
| 100 |
+
|
| 101 |
+
| Document | Sujet |
|
| 102 |
+
|----------|-------|
|
| 103 |
+
| [`reference/api-stable.md`](reference/api-stable.md) | API Python publique + politique semver |
|
| 104 |
+
| [`reference/views.md`](reference/views.md) | Vues d'évaluation (text, alto, search) |
|
| 105 |
+
| [`reference/normalization-profiles.md`](reference/normalization-profiles.md) | Profils de normalisation textuelle |
|
| 106 |
+
| [`reference/reproducibility-snapshots.md`](reference/reproducibility-snapshots.md) | Reproductibilité bit-for-bit |
|
| 107 |
+
|
| 108 |
+
### Explanation — je comprends pourquoi
|
| 109 |
+
|
| 110 |
+
| Document | Sujet |
|
| 111 |
+
|----------|-------|
|
| 112 |
+
| [`explanation/architecture.md`](explanation/architecture.md) | Architecture en cercles, principes |
|
| 113 |
+
| [`explanation/narrative-engine.md`](explanation/narrative-engine.md) | Comment le moteur narratif fonctionne |
|
| 114 |
+
|
| 115 |
+
### Operations — je déploie et j'opère
|
| 116 |
+
|
| 117 |
+
| Document | Sujet |
|
| 118 |
+
|----------|-------|
|
| 119 |
+
| [`operations/deployment-institutional.md`](operations/deployment-institutional.md) | Déploiement institutionnel |
|
| 120 |
+
| [`operations/runbook.md`](operations/runbook.md) | Réponse aux incidents |
|
| 121 |
+
| [`operations/observability.md`](operations/observability.md) | Logs, métriques, alerting |
|
| 122 |
+
| [`operations/data-retention-rgpd.md`](operations/data-retention-rgpd.md) | Conformité RGPD |
|
| 123 |
+
| [`operations/release-process.md`](operations/release-process.md) | Cycle de release |
|
| 124 |
+
|
| 125 |
+
### Governance / security / legal
|
| 126 |
+
|
| 127 |
+
| Document | Sujet |
|
| 128 |
+
|----------|-------|
|
| 129 |
+
| [`/GOVERNANCE.md`](../GOVERNANCE.md) | Gouvernance |
|
| 130 |
+
| [`/SECURITY.md`](../SECURITY.md) | Sécurité (FR + EN) |
|
| 131 |
+
| [`/CODE_OF_CONDUCT.md`](../CODE_OF_CONDUCT.md) | Code de conduite |
|
| 132 |
+
| [`/ACCESSIBILITY.md`](../ACCESSIBILITY.md) | Accessibilité |
|
| 133 |
+
| [`security/threat-model.md`](security/threat-model.md) | Threat model STRIDE |
|
| 134 |
+
| [`legal/dpa-template.md`](legal/dpa-template.md) | DPA RGPD §28 |
|
| 135 |
+
|
| 136 |
+
### Archives et historique
|
| 137 |
+
|
| 138 |
+
| Document | Sujet |
|
| 139 |
+
|----------|-------|
|
| 140 |
+
| [`/CHANGELOG.md`](../CHANGELOG.md) | Journal des versions (Keep-a-Changelog) |
|
| 141 |
+
| [`audits/`](audits/) | Audits historiques figés |
|
| 142 |
+
| [`migration/`](migration/) | Notes de migration entre versions majeures |
|
| 143 |
+
| [`roadmap/`](roadmap/) | Plans stratégiques |
|
| 144 |
+
|
| 145 |
+
---
|
| 146 |
+
|
| 147 |
+
## Conventions
|
| 148 |
+
|
| 149 |
+
- **Une seule arborescence canonique post-rewrite** :
|
| 150 |
+
`domain → formats → evaluation → pipeline → adapters → app → reports_v2 → interfaces`.
|
| 151 |
+
L'arbo legacy `picarones/{cli,web,engines,llm,pipelines,report}/`
|
| 152 |
+
reste exécutable mais n'accepte plus de nouveau code.
|
| 153 |
+
- **Tout chemin `picarones/.../X.py` cité dans la doc doit exister**.
|
| 154 |
+
Vérifié par `tests/architecture/test_doc_paths.py` (baseline 73,
|
| 155 |
+
doit décroître).
|
| 156 |
+
- **Les chiffres en prose qui dépendent de l'état du code** (compte
|
| 157 |
+
de tests, nombre d'adapters) sont régénérés par
|
| 158 |
+
`scripts/gen_readme_tables.py` — modifier le code, pas la doc.
|
| 159 |
+
- **Cohérence FR/EN** : un fichier `xxx.md` en FR + un fichier
|
| 160 |
+
`xxx.en.md` en EN miroir. Pas de fragments mêlés.
|
docs/legal/THIRD_PARTY_LICENSES.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Third-party licenses
|
| 2 |
+
|
| 3 |
+
> **Audience** : équipe juridique, DSI institutionnelle, mainteneur
|
| 4 |
+
> de release. Audit des licences des dépendances tierces utilisées
|
| 5 |
+
> par Picarones, requis par Apache 2.0 §4(d) et par les politiques
|
| 6 |
+
> d'achat institutionnelles (BnF, LoC, BL).
|
| 7 |
+
>
|
| 8 |
+
> **Régénération** : ce fichier est censé être régénéré à chaque
|
| 9 |
+
> release par `scripts/gen_third_party_licenses.py` (à venir, cf.
|
| 10 |
+
> [`docs/roadmap/backlog.md`](../roadmap/backlog.md)). Tant que le
|
| 11 |
+
> script n'existe pas, mise à jour manuelle au moment de la release.
|
| 12 |
+
>
|
| 13 |
+
> **Date du dernier rafraîchissement** : 2026-05.
|
| 14 |
+
|
| 15 |
+
## Politique générale
|
| 16 |
+
|
| 17 |
+
Picarones est distribué sous **Apache License 2.0**. Cette licence
|
| 18 |
+
est compatible avec toutes les licences listées ci-dessous (MIT, BSD,
|
| 19 |
+
PSF, Apache 2.0 elles-mêmes ; pas de dépendance GPL/LGPL/AGPL en
|
| 20 |
+
runtime).
|
| 21 |
+
|
| 22 |
+
Les dépendances optionnelles (extras `[mistral]`, `[anthropic]`,
|
| 23 |
+
`[openai]`, `[ollama]`, `[google]`, `[azure]`, `[hf]`, `[escriptorium]`,
|
| 24 |
+
`[iiif]`, `[stats]`, `[ner]`) ne sont chargées qu'à la demande de
|
| 25 |
+
l'utilisateur ; elles n'affectent pas la licence du distribué de base.
|
| 26 |
+
|
| 27 |
+
## Dépendances de runtime (cœur)
|
| 28 |
+
|
| 29 |
+
| Paquet | Licence | Copyright | Usage |
|
| 30 |
+
|--------|---------|-----------|-------|
|
| 31 |
+
| [click](https://palletsprojects.com/p/click/) | BSD-3-Clause | © Pallets | CLI |
|
| 32 |
+
| [jiwer](https://github.com/jitsi/jiwer) | Apache-2.0 | © 8x8, Inc. | CER / WER |
|
| 33 |
+
| [Pillow](https://python-pillow.org/) | HPND (MIT-style) | © Jeffrey A. Clark + Pillow contributors | Images |
|
| 34 |
+
| [PyYAML](https://pyyaml.org/) | MIT | © Kirill Simonov | YAML |
|
| 35 |
+
| [pytesseract](https://github.com/madmaze/pytesseract) | Apache-2.0 | © Matthias A. Lee | OCR Tesseract wrapper |
|
| 36 |
+
| [tqdm](https://tqdm.github.io/) | MIT + MPL-2.0 | © tqdm contributors | Barres de progression |
|
| 37 |
+
| [numpy](https://numpy.org/) | BSD-3-Clause | © NumPy developers | Calculs numériques |
|
| 38 |
+
| [jinja2](https://palletsprojects.com/p/jinja/) | BSD-3-Clause | © Pallets | Templating HTML |
|
| 39 |
+
| [defusedxml](https://github.com/tiran/defusedxml) | PSF-2.0 | © Christian Heimes | Parsing XML sécurisé |
|
| 40 |
+
| [pydantic](https://docs.pydantic.dev/) | MIT | © Samuel Colvin and contributors | Modèles immuables |
|
| 41 |
+
|
| 42 |
+
## Dépendances de runtime — extras
|
| 43 |
+
|
| 44 |
+
### `[web]`
|
| 45 |
+
|
| 46 |
+
| Paquet | Licence | Usage |
|
| 47 |
+
|--------|---------|-------|
|
| 48 |
+
| [fastapi](https://fastapi.tiangolo.com/) | MIT | API web |
|
| 49 |
+
| [uvicorn](https://www.uvicorn.org/) | BSD-3-Clause | Serveur ASGI |
|
| 50 |
+
| [python-multipart](https://github.com/Kludex/python-multipart) | Apache-2.0 | Upload form-data |
|
| 51 |
+
| [starlette](https://www.starlette.io/) | BSD-3-Clause | (transitif via FastAPI) |
|
| 52 |
+
| [httpx](https://www.python-httpx.org/) | BSD-3-Clause | Client HTTP (tests) |
|
| 53 |
+
|
| 54 |
+
### `[mistral]`
|
| 55 |
+
|
| 56 |
+
| Paquet | Licence | Usage |
|
| 57 |
+
|--------|---------|-------|
|
| 58 |
+
| [mistralai](https://github.com/mistralai/client-python) | Apache-2.0 | SDK Mistral OCR + chat/vision |
|
| 59 |
+
|
| 60 |
+
### `[anthropic]`
|
| 61 |
+
|
| 62 |
+
| Paquet | Licence | Usage |
|
| 63 |
+
|--------|---------|-------|
|
| 64 |
+
| [anthropic](https://github.com/anthropics/anthropic-sdk-python) | MIT | SDK Claude |
|
| 65 |
+
|
| 66 |
+
### `[openai]`
|
| 67 |
+
|
| 68 |
+
| Paquet | Licence | Usage |
|
| 69 |
+
|--------|---------|-------|
|
| 70 |
+
| [openai](https://github.com/openai/openai-python) | Apache-2.0 | SDK OpenAI |
|
| 71 |
+
|
| 72 |
+
### `[ollama]`
|
| 73 |
+
|
| 74 |
+
| Paquet | Licence | Usage |
|
| 75 |
+
|--------|---------|-------|
|
| 76 |
+
| [ollama](https://github.com/ollama/ollama-python) | MIT | Client Ollama local |
|
| 77 |
+
|
| 78 |
+
### `[google]`
|
| 79 |
+
|
| 80 |
+
| Paquet | Licence | Usage |
|
| 81 |
+
|--------|---------|-------|
|
| 82 |
+
| [google-cloud-vision](https://github.com/googleapis/python-vision) | Apache-2.0 | OCR Google Vision |
|
| 83 |
+
|
| 84 |
+
### `[azure]`
|
| 85 |
+
|
| 86 |
+
| Paquet | Licence | Usage |
|
| 87 |
+
|--------|---------|-------|
|
| 88 |
+
| [azure-ai-documentintelligence](https://github.com/Azure/azure-sdk-for-python) | MIT | OCR Azure DI |
|
| 89 |
+
|
| 90 |
+
### `[hf]`
|
| 91 |
+
|
| 92 |
+
| Paquet | Licence | Usage |
|
| 93 |
+
|--------|---------|-------|
|
| 94 |
+
| [datasets](https://github.com/huggingface/datasets) | Apache-2.0 | Datasets HuggingFace |
|
| 95 |
+
| [huggingface-hub](https://github.com/huggingface/huggingface_hub) | Apache-2.0 | Hub HuggingFace |
|
| 96 |
+
|
| 97 |
+
### `[ner]`
|
| 98 |
+
|
| 99 |
+
| Paquet | Licence | Usage |
|
| 100 |
+
|--------|---------|-------|
|
| 101 |
+
| [spacy](https://spacy.io/) | MIT | NER |
|
| 102 |
+
|
| 103 |
+
### `[stats]`
|
| 104 |
+
|
| 105 |
+
| Paquet | Licence | Usage |
|
| 106 |
+
|--------|---------|-------|
|
| 107 |
+
| [scipy](https://scipy.org/) | BSD-3-Clause | Tests statistiques (Friedman, Nemenyi) |
|
| 108 |
+
|
| 109 |
+
## Dépendances de développement
|
| 110 |
+
|
| 111 |
+
Les paquets utilisés uniquement en développement (tests, lint,
|
| 112 |
+
sécurité) ne sont pas redistribués avec Picarones et n'apparaissent
|
| 113 |
+
dans aucun wheel. Pour traçabilité supply-chain :
|
| 114 |
+
|
| 115 |
+
| Paquet | Licence | Usage |
|
| 116 |
+
|--------|---------|-------|
|
| 117 |
+
| pytest | MIT | Tests unitaires |
|
| 118 |
+
| pytest-cov | MIT | Couverture |
|
| 119 |
+
| pytest-timeout | MIT | Timeout par test |
|
| 120 |
+
| ruff | MIT | Lint |
|
| 121 |
+
| mypy | MIT | Type checking |
|
| 122 |
+
| bandit | Apache-2.0 | Audit sécurité statique |
|
| 123 |
+
| pip-audit | Apache-2.0 | Audit CVE des dépendances |
|
| 124 |
+
|
| 125 |
+
## Modèles tiers
|
| 126 |
+
|
| 127 |
+
Picarones n'embarque **aucun modèle tiers** dans ses wheels. Les
|
| 128 |
+
modèles sont :
|
| 129 |
+
|
| 130 |
+
- soit **téléchargés à l'usage** par l'utilisateur (Tesseract `*.traineddata`,
|
| 131 |
+
Pero OCR via Zenodo, modèles spaCy via `python -m spacy download`) ;
|
| 132 |
+
- soit **invoqués via des APIs cloud** sous le contrat du fournisseur
|
| 133 |
+
(Mistral AI, Anthropic, OpenAI, Google, Azure).
|
| 134 |
+
|
| 135 |
+
Les conditions d'utilisation de chaque modèle / API sont à la charge
|
| 136 |
+
de l'utilisateur et de l'institution déployant Picarones.
|
| 137 |
+
|
| 138 |
+
## Police d'écriture / fontes
|
| 139 |
+
|
| 140 |
+
Picarones n'embarque aucune fonte. Les rapports HTML utilisent les
|
| 141 |
+
fontes système du navigateur.
|
| 142 |
+
|
| 143 |
+
## Données
|
| 144 |
+
|
| 145 |
+
Aucun corpus, aucune image, aucune vérité terrain n'est embarquée
|
| 146 |
+
dans les wheels. Les fixtures de test (`tests/fixtures/`) sont
|
| 147 |
+
synthétiques (générées) ou citées depuis leur source originale (cf.
|
| 148 |
+
`tests/fixtures/reference_corpus/README.md`).
|
| 149 |
+
|
| 150 |
+
## Comment signaler une omission
|
| 151 |
+
|
| 152 |
+
Une dépendance manquante, une licence incorrecte, un copyright
|
| 153 |
+
mal attribué : ouvrir une issue avec le label `legal` ou écrire à
|
| 154 |
+
l'adresse de contact dans [`/SECURITY.md`](../../SECURITY.md). Une
|
| 155 |
+
correction sera publiée dans la prochaine release patch.
|
docs/legal/dpa-template.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Modèle d'Accord de Sous-Traitance (DPA)
|
| 2 |
+
|
| 3 |
+
> **Audience** : Délégué à la Protection des Données (DPO) de
|
| 4 |
+
> l'institution déployant Picarones, équipe juridique de cette même
|
| 5 |
+
> institution, mainteneur du projet.
|
| 6 |
+
>
|
| 7 |
+
> **Statut** : modèle de référence — à adapter et à signer entre
|
| 8 |
+
> l'institution (responsable de traitement) et chaque sous-traitant
|
| 9 |
+
> activé via les adapters cloud. Ce document **n'est pas un contrat
|
| 10 |
+
> en lui-même** ; il définit les clauses minimales à inclure.
|
| 11 |
+
>
|
| 12 |
+
> **Référence légale** : Article 28 du Règlement (UE) 2016/679 (RGPD),
|
| 13 |
+
> [version consolidée](https://eur-lex.europa.eu/eli/reg/2016/679/oj).
|
| 14 |
+
|
| 15 |
+
## Pourquoi un DPA ?
|
| 16 |
+
|
| 17 |
+
Lorsqu'une institution patrimoniale (BnF, LoC, BL) déploie Picarones
|
| 18 |
+
en activant des adapters cloud (Mistral OCR, OpenAI, Anthropic,
|
| 19 |
+
Google Vision, Azure Document Intelligence), elle envoie des
|
| 20 |
+
documents qui peuvent contenir des **données à caractère personnel**
|
| 21 |
+
(PII) — typiquement :
|
| 22 |
+
|
| 23 |
+
- Registres d'état civil (naissances, mariages, décès).
|
| 24 |
+
- Recensements (noms, adresses, professions).
|
| 25 |
+
- Correspondance personnelle (lettres privées, journaux).
|
| 26 |
+
- Notes manuscrites avec mentions nominatives.
|
| 27 |
+
|
| 28 |
+
L'envoi de ces données à un tiers (le fournisseur cloud) constitue
|
| 29 |
+
une **sous-traitance** au sens RGPD §28 ; un accord écrit (DPA) est
|
| 30 |
+
**obligatoire** entre l'institution (responsable de traitement) et
|
| 31 |
+
chaque sous-traitant.
|
| 32 |
+
|
| 33 |
+
## Périmètre
|
| 34 |
+
|
| 35 |
+
Ce modèle couvre la sous-traitance des opérations de transcription
|
| 36 |
+
OCR/HTR effectuées par des services cloud activés par l'institution
|
| 37 |
+
via Picarones. **Il ne couvre pas** :
|
| 38 |
+
|
| 39 |
+
- Le déploiement Picarones lui-même (l'institution est seule
|
| 40 |
+
responsable de l'instance).
|
| 41 |
+
- Les adapters locaux (Tesseract, Pero OCR, Ollama) qui n'envoient
|
| 42 |
+
rien à l'extérieur.
|
| 43 |
+
|
| 44 |
+
## Clauses minimales (RGPD §28.3)
|
| 45 |
+
|
| 46 |
+
### 1. Objet et durée du traitement
|
| 47 |
+
|
| 48 |
+
Transcription automatique de documents numérisés via OCR, HTR ou VLM
|
| 49 |
+
cloud, pour la durée du marché entre l'institution et le fournisseur.
|
| 50 |
+
|
| 51 |
+
### 2. Nature et finalité du traitement
|
| 52 |
+
|
| 53 |
+
- **Nature** : envoi d'images de documents et/ou de fragments de
|
| 54 |
+
texte ; réception de transcriptions textuelles ou de descriptions
|
| 55 |
+
structurées (ALTO, JSON canonique).
|
| 56 |
+
- **Finalité** : fournir à l'institution un benchmark comparatif de
|
| 57 |
+
pipelines OCR/HTR sur son corpus, dans le cadre d'une évaluation
|
| 58 |
+
technique préalable à un déploiement de production.
|
| 59 |
+
|
| 60 |
+
### 3. Type de données à caractère personnel
|
| 61 |
+
|
| 62 |
+
Selon le corpus envoyé. L'institution **doit identifier en amont**
|
| 63 |
+
si le corpus contient :
|
| 64 |
+
|
| 65 |
+
- Données nominatives (noms, prénoms, dates de naissance/décès…).
|
| 66 |
+
- Données sensibles au sens RGPD §9 (origine raciale ou ethnique,
|
| 67 |
+
opinions politiques, convictions religieuses, données de santé,
|
| 68 |
+
orientation sexuelle…).
|
| 69 |
+
|
| 70 |
+
Pour les corpus sensibles, l'institution **doit privilégier les
|
| 71 |
+
adapters locaux** (Tesseract, Pero OCR, Ollama) ou anonymiser le
|
| 72 |
+
corpus avant envoi.
|
| 73 |
+
|
| 74 |
+
### 4. Catégories de personnes concernées
|
| 75 |
+
|
| 76 |
+
- Personnes citées dans les documents historiques (typiquement
|
| 77 |
+
défuntes, sauf mention contraire).
|
| 78 |
+
- Auteurs ou correspondants des documents.
|
| 79 |
+
|
| 80 |
+
### 5. Obligations du sous-traitant
|
| 81 |
+
|
| 82 |
+
Le sous-traitant cloud s'engage à :
|
| 83 |
+
|
| 84 |
+
a) ne traiter les données que sur **instruction documentée** du
|
| 85 |
+
responsable (l'institution). Pas de réutilisation pour
|
| 86 |
+
entraînement de modèles, sauf consentement explicite (cf. §10).
|
| 87 |
+
|
| 88 |
+
b) garantir que les **personnes autorisées** à traiter les données
|
| 89 |
+
sont soumises à une obligation de confidentialité.
|
| 90 |
+
|
| 91 |
+
c) mettre en œuvre les **mesures de sécurité** énumérées au RGPD
|
| 92 |
+
§32 (chiffrement en transit, contrôle d'accès, journalisation,
|
| 93 |
+
tests réguliers).
|
| 94 |
+
|
| 95 |
+
d) ne pas recourir à un **autre sous-traitant** sans autorisation
|
| 96 |
+
écrite préalable et spécifique du responsable.
|
| 97 |
+
|
| 98 |
+
e) **assister** le responsable dans la réponse aux demandes
|
| 99 |
+
d'exercice de droits (accès, rectification, effacement…) et dans
|
| 100 |
+
les obligations de notification de violations.
|
| 101 |
+
|
| 102 |
+
f) **supprimer ou retourner** les données à la fin de la prestation,
|
| 103 |
+
sauf obligation légale de conservation.
|
| 104 |
+
|
| 105 |
+
g) mettre à disposition du responsable toutes les **informations
|
| 106 |
+
nécessaires** pour démontrer la conformité au §28.
|
| 107 |
+
|
| 108 |
+
### 6. Localisation des traitements
|
| 109 |
+
|
| 110 |
+
L'institution **doit privilégier** les fournisseurs offrant un
|
| 111 |
+
hébergement et un traitement strictement dans l'Espace économique
|
| 112 |
+
européen (EEE).
|
| 113 |
+
|
| 114 |
+
| Adapter | Localisation par défaut | Disponibilité EEE |
|
| 115 |
+
|---------|------------------------|-------------------|
|
| 116 |
+
| Mistral OCR / chat | France (cf. [Mistral Trust](https://mistral.ai/security/)) | Oui |
|
| 117 |
+
| OpenAI | États-Unis | EU residency dispo via Enterprise |
|
| 118 |
+
| Anthropic Claude | États-Unis | EU residency limitée |
|
| 119 |
+
| Google Vision | Multi-régions | EEE configurable |
|
| 120 |
+
| Azure Document Intelligence | Multi-régions | EEE configurable |
|
| 121 |
+
|
| 122 |
+
Pour un transfert hors EEE, **clauses contractuelles types** (CCT)
|
| 123 |
+
2021/914/UE applicables OBLIGATOIRES.
|
| 124 |
+
|
| 125 |
+
### 7. Sécurité
|
| 126 |
+
|
| 127 |
+
Mesures minimales :
|
| 128 |
+
|
| 129 |
+
- Chiffrement TLS 1.2+ en transit.
|
| 130 |
+
- Pas d'enregistrement des prompts/réponses pour entraînement
|
| 131 |
+
(option à activer côté fournisseur, cf. §10).
|
| 132 |
+
- Logs d'accès conservés < 30 jours sauf incident de sécurité.
|
| 133 |
+
- Tests de pénétration au moins annuels (à charge du sous-traitant).
|
| 134 |
+
|
| 135 |
+
### 8. Sous-sous-traitance
|
| 136 |
+
|
| 137 |
+
Liste des sous-sous-traitants autorisés à fournir au démarrage et à
|
| 138 |
+
chaque modification. L'institution dispose d'un droit d'objection
|
| 139 |
+
à toute nouvelle sous-sous-traitance.
|
| 140 |
+
|
| 141 |
+
### 9. Audit
|
| 142 |
+
|
| 143 |
+
L'institution se réserve le droit, à ses frais et avec préavis
|
| 144 |
+
raisonnable (30 jours), de conduire un audit du sous-traitant ou de
|
| 145 |
+
mandater un tiers indépendant pour vérifier la conformité des
|
| 146 |
+
mesures techniques et organisationnelles.
|
| 147 |
+
|
| 148 |
+
### 10. Réutilisation pour entraînement de modèles
|
| 149 |
+
|
| 150 |
+
**Disposition critique** pour le patrimoine numérique : les
|
| 151 |
+
documents envoyés sont la propriété intellectuelle de l'institution
|
| 152 |
+
(et parfois du domaine public) ; les fournisseurs ne doivent **PAS**
|
| 153 |
+
les utiliser pour entraîner leurs modèles sans accord écrit.
|
| 154 |
+
|
| 155 |
+
Configuration recommandée par fournisseur :
|
| 156 |
+
|
| 157 |
+
| Fournisseur | Comment opt-out |
|
| 158 |
+
|-------------|------------------|
|
| 159 |
+
| OpenAI | Compte Enterprise ou via API avec `data_retention=zero` |
|
| 160 |
+
| Anthropic | Compte Enterprise ; pas d'option opt-out sur API standard |
|
| 161 |
+
| Mistral | API Enterprise tier ; opt-out par défaut sur certains plans |
|
| 162 |
+
| Google Vision | Activer Workspace Data Loss Prevention |
|
| 163 |
+
| Azure | Activer "Customer-Managed Keys" + opt-out training |
|
| 164 |
+
|
| 165 |
+
### 11. Notification de violation
|
| 166 |
+
|
| 167 |
+
Le sous-traitant s'engage à notifier l'institution **dans les 24
|
| 168 |
+
heures** de la connaissance d'une violation de données à caractère
|
| 169 |
+
personnel les concernant, par e-mail ET courrier signé.
|
| 170 |
+
|
| 171 |
+
### 12. Effacement à fin de prestation
|
| 172 |
+
|
| 173 |
+
À la fin du marché ou à la résiliation, le sous-traitant restitue
|
| 174 |
+
ou supprime toutes les données dans un délai de 30 jours, et
|
| 175 |
+
fournit une **attestation de destruction**.
|
| 176 |
+
|
| 177 |
+
## Annexes
|
| 178 |
+
|
| 179 |
+
### Annexe 1 — Description du traitement
|
| 180 |
+
|
| 181 |
+
À compléter par l'institution :
|
| 182 |
+
|
| 183 |
+
- [ ] Nom du corpus traité
|
| 184 |
+
- [ ] Volume estimé (nombre de documents, taille en GB)
|
| 185 |
+
- [ ] Période de traitement (du / au)
|
| 186 |
+
- [ ] Liste des adapters cloud activés
|
| 187 |
+
- [ ] Volume de PII estimé dans le corpus
|
| 188 |
+
|
| 189 |
+
### Annexe 2 — Mesures de sécurité
|
| 190 |
+
|
| 191 |
+
À compléter par le sous-traitant — référence :
|
| 192 |
+
[ANSSI Référentiel Général de Sécurité](https://www.ssi.gouv.fr/).
|
| 193 |
+
|
| 194 |
+
### Annexe 3 — Liste des sous-sous-traitants autorisés
|
| 195 |
+
|
| 196 |
+
À compléter par le sous-traitant.
|
| 197 |
+
|
| 198 |
+
## Procédure de signature
|
| 199 |
+
|
| 200 |
+
1. L'institution remplit les annexes en fonction du corpus prévu.
|
| 201 |
+
2. Le DPO de l'institution valide la liste des adapters cloud
|
| 202 |
+
activés (`AdapterRegistry`).
|
| 203 |
+
3. Le contrat est signé par les deux parties (institution +
|
| 204 |
+
fournisseur cloud) AVANT activation de l'adapter en production.
|
| 205 |
+
4. Une copie est conservée dans le dossier de conformité du
|
| 206 |
+
traitement (durée minimale : 5 ans après la fin du traitement).
|
| 207 |
+
|
| 208 |
+
## Référence légale
|
| 209 |
+
|
| 210 |
+
- [Règlement (UE) 2016/679 — RGPD](https://eur-lex.europa.eu/eli/reg/2016/679/oj)
|
| 211 |
+
- [Lignes directrices CEPD sur les sous-traitants](https://edpb.europa.eu/our-work-tools/our-documents/guidelines/guidelines-072020-concepts-controller-and-processor-gdpr_fr)
|
| 212 |
+
- [Décision d'adéquation EU-US Data Privacy Framework (2023)](https://commission.europa.eu/document/fa09cbad-dd7d-4684-ace5-c1e932f3eda7_en)
|
| 213 |
+
|
| 214 |
+
## Révisions
|
| 215 |
+
|
| 216 |
+
| Version | Date | Changements |
|
| 217 |
+
|---------|------|-------------|
|
| 218 |
+
| 1.0 | 2026-05 | Création initiale (S60), modèle aligné RGPD §28 |
|
docs/migration/SESSION_HANDOVER.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Handover entre sessions Claude Code
|
| 2 |
+
|
| 3 |
+
> Ce document est lu en premier par chaque nouvelle session pour
|
| 4 |
+
> reprendre le travail sans se tromper. Il pointe vers les
|
| 5 |
+
> sources de vérité, signale les pièges connus, et donne la
|
| 6 |
+
> prochaine action concrète.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## 0. Principe directeur (mis à jour 2026-05)
|
| 11 |
+
|
| 12 |
+
**Suppression agressive, pas de shim qui survit à son usage.**
|
| 13 |
+
|
| 14 |
+
- Le projet est en stand-by jusqu'à la fin de la migration
|
| 15 |
+
complète. Personne (ni externe ni HuggingFace Space) ne
|
| 16 |
+
consommera l'API legacy avant cette fin.
|
| 17 |
+
- Pas de préservation de l'API publique : breaking changes
|
| 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 |
+
|
| 33 |
+
## 1. Sources de vérité (par ordre de priorité)
|
| 34 |
+
|
| 35 |
+
1. **[`legacy-retirement-plan.md`](legacy-retirement-plan.md)** —
|
| 36 |
+
plan maître des Phases 0-11 du retrait du legacy. Chaque
|
| 37 |
+
phase a un statut explicite (✅ terminée / ⏳ en cours / 📋 à
|
| 38 |
+
venir).
|
| 39 |
+
2. **[`pipeline-convergence-plan.md`](pipeline-convergence-plan.md)** —
|
| 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
|
| 50 |
+
donnent l'état réel. Le dernier commit message décrit
|
| 51 |
+
souvent la prochaine sub-phase à exécuter.
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## 2. Vérifications avant de toucher au code
|
| 56 |
+
|
| 57 |
+
```bash
|
| 58 |
+
# 1. Bonne branche ?
|
| 59 |
+
git branch --show-current
|
| 60 |
+
# → doit retourner: claude/repo-analysis-cukvm
|
| 61 |
+
|
| 62 |
+
# 2. Working tree propre ?
|
| 63 |
+
git status
|
| 64 |
+
# → doit retourner: nothing to commit, working tree clean
|
| 65 |
+
|
| 66 |
+
# 3. Tests verts à l'état initial ?
|
| 67 |
+
python -m pytest tests/ -q --no-header --tb=line
|
| 68 |
+
# → doit retourner: 5085 passed (au moment de la pause de session)
|
| 69 |
+
|
| 70 |
+
# 4. Lint vert ?
|
| 71 |
+
ruff check picarones/ tests/
|
| 72 |
+
# → doit retourner: All checks passed!
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
Si l'une de ces vérifications échoue : **NE PAS** continuer le
|
| 76 |
+
sprint. Investiguer d'abord pourquoi l'état initial diverge de
|
| 77 |
+
celui annoncé dans CLAUDE.md.
|
| 78 |
+
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
## 3. Pièges connus (apprentissages des phases précédentes)
|
| 82 |
+
|
| 83 |
+
### 3.A Architecture des couches
|
| 84 |
+
|
| 85 |
+
Voir CLAUDE.md section « Règles d'architecture critiques ».
|
| 86 |
+
Résumé :
|
| 87 |
+
|
| 88 |
+
- ``evaluation/`` ne peut pas importer ``pipeline.types`` —
|
| 89 |
+
c'est l'autre sens.
|
| 90 |
+
- ``evaluation/`` whitelist limitée : pas de pytesseract /
|
| 91 |
+
mistralai / azure / google / pero_ocr. Ces libs externes
|
| 92 |
+
vont dans ``adapters/``.
|
| 93 |
+
- ``reports_v2/`` ne peut importer que les canoniques
|
| 94 |
+
(``evaluation/metrics/``), pas les shims legacy
|
| 95 |
+
(``measurements/X.py``).
|
| 96 |
+
|
| 97 |
+
### 3.B Pattern shim — UNIQUEMENT TRANSITOIRE
|
| 98 |
+
|
| 99 |
+
⚠️ **Principe** : un shim n'existe que pour la durée d'un
|
| 100 |
+
sprint. Dès que tous ses consommateurs ont migré, il est
|
| 101 |
+
**supprimé**.
|
| 102 |
+
|
| 103 |
+
Pour un shim minimal (transitoire) :
|
| 104 |
+
|
| 105 |
+
```python
|
| 106 |
+
"""``picarones.X.Y`` — shim re-export (déprécié, suppression imminente).
|
| 107 |
+
|
| 108 |
+
Canonique : :mod:`picarones.canonical.path`. Phase X.Y du
|
| 109 |
+
retrait du legacy. Ce shim disparaît dès que tous les callers
|
| 110 |
+
auront migré (généralement dans le commit suivant).
|
| 111 |
+
"""
|
| 112 |
+
|
| 113 |
+
from __future__ import annotations
|
| 114 |
+
|
| 115 |
+
import warnings
|
| 116 |
+
|
| 117 |
+
from picarones.canonical.path import * # noqa: F401, F403
|
| 118 |
+
# Si des callers consomment des noms privés (_FOO, etc.),
|
| 119 |
+
# les ré-exporter explicitement :
|
| 120 |
+
from picarones.canonical.path import _FOO # noqa: F401
|
| 121 |
+
|
| 122 |
+
warnings.warn(
|
| 123 |
+
"picarones.X.Y is deprecated and will be removed in 2.0. "
|
| 124 |
+
"Import from picarones.canonical.path instead.",
|
| 125 |
+
DeprecationWarning,
|
| 126 |
+
stacklevel=2,
|
| 127 |
+
)
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
**Avant de créer un shim**, demandez-vous : « est-ce que je peux
|
| 131 |
+
juste migrer tous les callers maintenant et supprimer le legacy
|
| 132 |
+
en bloc ? » Si oui, faites-le — pas de shim intermédiaire.
|
| 133 |
+
|
| 134 |
+
### 3.C ``test_module_coverage::TEST_ONLY_BASELINE``
|
| 135 |
+
|
| 136 |
+
Quand un shim ``measurements/X.py`` n'a plus de consommateur
|
| 137 |
+
production (parce qu'un renderer a migré vers le canonique
|
| 138 |
+
direct), ajouter ``"X"`` à ``TEST_ONLY_BASELINE`` dans
|
| 139 |
+
``tests/architecture/test_module_coverage.py``. Sinon le test
|
| 140 |
+
``test_no_new_test_only_modules`` échoue.
|
| 141 |
+
|
| 142 |
+
### 3.D ``test_file_budgets``
|
| 143 |
+
|
| 144 |
+
Tout fichier ≥ 400 LOC doit avoir une entrée dans
|
| 145 |
+
``FILE_BUDGETS`` avec budget = LOC actuel + ~15 %. Quand on
|
| 146 |
+
relocalise un fichier, retirer l'entrée du chemin legacy et
|
| 147 |
+
en créer une au chemin canonique avec le même budget.
|
| 148 |
+
|
| 149 |
+
### 3.E ``test_doc_paths::BROKEN_PATHS_BASELINE``
|
| 150 |
+
|
| 151 |
+
Si un sub-plan ou doc référence un futur chemin Python
|
| 152 |
+
(``picarones/X/Y.py``) qui n'existe pas encore, le test
|
| 153 |
+
``test_broken_doc_paths_below_baseline`` détecte la
|
| 154 |
+
référence cassée. Soit :
|
| 155 |
+
|
| 156 |
+
- Bumper ``BROKEN_PATHS_BASELINE`` du même montant.
|
| 157 |
+
- Ou reformuler la référence en code/backticks pour échapper
|
| 158 |
+
au pattern (``picarones/X/Y.py``).
|
| 159 |
+
|
| 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 |
+
|
| 186 |
+
Le compteur de tests dans `README.md` et `CLAUDE.md` est
|
| 187 |
+
synchronisé par `scripts/gen_readme_tables.py`. À chaque
|
| 188 |
+
fois que le nombre de tests change (ajout/retrait), lancer :
|
| 189 |
+
|
| 190 |
+
```bash
|
| 191 |
+
python scripts/gen_readme_tables.py
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
Sinon le test ``test_readme_tables_consistent_with_code``
|
| 195 |
+
échoue.
|
| 196 |
+
|
| 197 |
+
---
|
| 198 |
+
|
| 199 |
+
## 4. Inventaire actuel — quel legacy reste à migrer ?
|
| 200 |
+
|
| 201 |
+
(Snapshot au moment de la pause de session, mesuré via AST,
|
| 202 |
+
fiable.)
|
| 203 |
+
|
| 204 |
+
### 4.A Imports legacy dans les tests
|
| 205 |
+
|
| 206 |
+
**62 fichiers** avec **361 statements** d'import depuis les
|
| 207 |
+
paquets legacy (``measurements``, ``llm``, ``pipelines``) —
|
| 208 |
+
Lots A à G terminés (cf. 4.D ci-dessous). Les paquets
|
| 209 |
+
``engines/``, ``modules/``, ``report/`` et ``core/`` ont été
|
| 210 |
+
**entièrement supprimés**. Restent uniquement
|
| 211 |
+
``measurements/`` (~25 modules de catégorie B/C/D),
|
| 212 |
+
``llm/``, ``pipelines/`` et les sous-paquets d'interfaces
|
| 213 |
+
(``cli/``, ``web/``, ``extras/``).
|
| 214 |
+
|
| 215 |
+
Top chemins consommés :
|
| 216 |
+
|
| 217 |
+
| Imports | Chemin legacy |
|
| 218 |
+
|---------|---------------------------------------------------------------|
|
| 219 |
+
| 29 | ``from picarones.measurements.runner import run_benchmark`` |
|
| 220 |
+
| 18 | ``from picarones.measurements.metrics import MetricsResult`` |
|
| 221 |
+
| 16 | ``from picarones.measurements.statistics import wilcoxon_test`` |
|
| 222 |
+
| 13 | ``from picarones.measurements.metrics import compute_metrics`` |
|
| 223 |
+
| 10 | ``from picarones.measurements.robustness import degrade_image_bytes`` |
|
| 224 |
+
|
| 225 |
+
**Pourquoi c'est important** : ces tests passent par les shims
|
| 226 |
+
au lieu de pointer vers le canonique. Tant que ces imports
|
| 227 |
+
existent, on **ne peut pas supprimer les shims** (le test casse).
|
| 228 |
+
|
| 229 |
+
**Stratégie** : sed batch par chemin, valider les tests,
|
| 230 |
+
commit, avancer. Shims supprimés dans les Lots A
|
| 231 |
+
(``core.modules`` + ``core.facts``), B
|
| 232 |
+
(``core.metric_registry`` + ``core.metric_hooks`` +
|
| 233 |
+
``core.metrics``), C (``core.results`` + ``core.corpus`` +
|
| 234 |
+
``core.pipeline``) et D (34 shims plats de ``measurements/``
|
| 235 |
+
vers ``evaluation.metrics/``) sur la branche
|
| 236 |
+
``claude/migrate-core-to-domain-8ubIT``.
|
| 237 |
+
|
| 238 |
+
### 4.B Imports legacy en production (hors shims eux-mêmes)
|
| 239 |
+
|
| 240 |
+
**12 fichiers** avec **41 statements** dans des paquets
|
| 241 |
+
non-legacy qui pointent encore vers le legacy. À résoudre
|
| 242 |
+
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 |
+
|
| 260 |
+
L'ordre recommandé, par lots de symboles cohérents :
|
| 261 |
+
|
| 262 |
+
1. ✅ **Lot A — domain** (~40 imports migrés, shims supprimés) :
|
| 263 |
+
- ``core.modules.{ArtifactType, BaseModule, ExecutionMode}``
|
| 264 |
+
→ ``domain.{artifacts, module_protocol}``
|
| 265 |
+
- ``core.facts.*`` → ``domain.facts.*``
|
| 266 |
+
- Shims ``picarones.core.modules`` + ``picarones.core.facts``
|
| 267 |
+
supprimés ; doc utilisateur (tutorials/, developer/,
|
| 268 |
+
reference/api-stable.md, explanation/narrative-engine.en.md)
|
| 269 |
+
pointe maintenant vers les canoniques.
|
| 270 |
+
2. ✅ **Lot B — evaluation/metric_*** (~45 imports migrés, shims
|
| 271 |
+
supprimés) :
|
| 272 |
+
- ``core.metric_registry.*`` → ``evaluation.metric_registry.*``
|
| 273 |
+
- ``core.metric_hooks.*`` → ``evaluation.metric_hooks.*``
|
| 274 |
+
- ``core.metrics.*`` → ``evaluation.metric_result.*``
|
| 275 |
+
- Shims ``picarones.core.metric_registry`` +
|
| 276 |
+
``picarones.core.metric_hooks`` + ``picarones.core.metrics``
|
| 277 |
+
supprimés ; ``docs/reference/normalization-profiles.md`` et
|
| 278 |
+
``docs/reference/api-stable.md`` migrés vers les chemins
|
| 279 |
+
canoniques.
|
| 280 |
+
3. ✅ **Lot C — evaluation/{benchmark_result, corpus, pipeline}**
|
| 281 |
+
(~75 imports migrés, shims supprimés) :
|
| 282 |
+
- ``core.results.*`` → ``evaluation.benchmark_result.*``
|
| 283 |
+
- ``core.corpus.*`` → ``evaluation.corpus.*``
|
| 284 |
+
- ``core.pipeline.*`` → ``evaluation.pipeline.*``
|
| 285 |
+
- Shims ``picarones.core.{results, corpus, pipeline}``
|
| 286 |
+
supprimés ; sections de ``docs/reference/api-stable.md``
|
| 287 |
+
migrées vers les chemins canoniques ; logger filter dans
|
| 288 |
+
``test_sprint32_multi_level_gt`` aligné sur
|
| 289 |
+
``picarones.evaluation.corpus``.
|
| 290 |
+
4. ✅ **Lot D — evaluation/metrics/*** (~100 imports + 44
|
| 291 |
+
prod migrés, 34 shims supprimés en bloc) :
|
| 292 |
+
- ``measurements.{baseline_comparison, calibration,
|
| 293 |
+
char_scores, confusion, cost_projection, difficulty,
|
| 294 |
+
error_absorption, hallucination, image_predictive,
|
| 295 |
+
image_quality, incremental_comparison, inter_engine,
|
| 296 |
+
layout, levers, lexical_modernization, line_metrics,
|
| 297 |
+
longitudinal, marginal_cost, module_policy, ner_backends,
|
| 298 |
+
normalization, numerical_sequences, pricing, rare_tokens,
|
| 299 |
+
robustness_projection, roman_numerals, specialization,
|
| 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
|
| 310 |
+
de 16 → 4 entrées.
|
| 311 |
+
- ``test_file_budgets::FILE_BUDGETS`` débarrassé des
|
| 312 |
+
entrées orphelines (inter_engine, levers,
|
| 313 |
+
normalization).
|
| 314 |
+
5. ✅ **Lot E — adapters/legacy_*** (8 shims supprimés en bloc,
|
| 315 |
+
0 import à migrer) :
|
| 316 |
+
- ``engines.*`` → ``adapters.legacy_engines.*``
|
| 317 |
+
- ``modules.alto_text_to_mono_region`` →
|
| 318 |
+
``adapters.legacy_modules.alto_text_to_mono_region``
|
| 319 |
+
- Tous les callers tests + production avaient déjà été
|
| 320 |
+
migrés en amont (Lots A-D), donc le Lot E n'a fait que
|
| 321 |
+
supprimer les 8 shims orphelins.
|
| 322 |
+
- ``LEGACY_PACKAGES`` réduit (retrait d'``engines`` et
|
| 323 |
+
``modules``) dans
|
| 324 |
+
``test_no_legacy_imports_in_rewrite.py`` et
|
| 325 |
+
``test_legacy_canonical_parity.py``.
|
| 326 |
+
- ``ENGINES_DIR`` dans
|
| 327 |
+
``tests/docs/test_readme_consistency.py`` redirigé vers
|
| 328 |
+
``picarones/adapters/legacy_engines/``.
|
| 329 |
+
6. ✅ **Lot F — reports_v2** (37 shims supprimés en bloc, 7
|
| 330 |
+
imports tests à migrer + ``scripts/gen_readme_tables.py``
|
| 331 |
+
redirigé) :
|
| 332 |
+
- ``report.*_render`` → ``reports_v2.html.renderers.*`` (29 shims)
|
| 333 |
+
- ``report.{generator, comparison, snapshot}`` →
|
| 334 |
+
``reports_v2.html.*`` (3 shims)
|
| 335 |
+
- ``report.{assets, colors, render_helpers}`` →
|
| 336 |
+
``reports_v2._helpers.*`` (3 shims)
|
| 337 |
+
- ``report.diff_utils`` → ``evaluation._diff_utils`` (1 shim)
|
| 338 |
+
- ``report.glossary`` → ``reports_v2.glossary`` (sous-package)
|
| 339 |
+
- ``scripts/gen_readme_tables.py`` redirigé vers
|
| 340 |
+
``picarones/adapters/legacy_engines/`` ;
|
| 341 |
+
``docs/reference/views.md`` migré en place vers
|
| 342 |
+
``picarones/reports_v2/html/{views, generator, renderers,
|
| 343 |
+
templates}``.
|
| 344 |
+
7. ⏳ **Lot G — measurements/runner et co.** (reporté car
|
| 345 |
+
canonique absent — phase 6 du plan maître).
|
| 346 |
+
Réalisé partiellement : suppression des 2 derniers shims
|
| 347 |
+
de ``picarones/core/`` (``diff_utils``, ``xml_utils``).
|
| 348 |
+
Le sous-paquet ``core/`` n'existe plus du tout.
|
| 349 |
+
|
| 350 |
+
La part majeure du Lot G originel (``measurements/runner``
|
| 351 |
+
+ ``pipelines/``) reste à faire ; elle nécessite **d'abord
|
| 352 |
+
la création** des canoniques ``app/services/run_orchestrator``
|
| 353 |
+
et ``adapters/llm/pipeline`` (couvrant ``OCRLLMPipeline``,
|
| 354 |
+
``PipelineMode``, ``over_normalization``, ``run_benchmark``,
|
| 355 |
+
``_compute_document_result``). Sans ces canoniques, un
|
| 356 |
+
simple sed est impossible — il faudrait migrer les 76
|
| 357 |
+
imports vers des modules qui n'existent pas encore.
|
| 358 |
+
|
| 359 |
+
8. ✅ **Lot H — measurements.statistics → evaluation.statistics**
|
| 360 |
+
(~70 imports migrés, 9 shims supprimés en bloc) :
|
| 361 |
+
- ``measurements.statistics.{bootstrap, cdd_render,
|
| 362 |
+
clustering, correlation, distributions, friedman_nemenyi,
|
| 363 |
+
pareto, wilcoxon}`` → ``evaluation.statistics.{...}``.
|
| 364 |
+
- ``measurements/statistics/`` (sous-paquet entier)
|
| 365 |
+
supprimé.
|
| 366 |
+
|
| 367 |
+
9. ✅ **Lot I — extras.importers → adapters.corpus**
|
| 368 |
+
(3 shims supprimés, ~15 imports migrés) :
|
| 369 |
+
- ``extras.importers.htr_united`` →
|
| 370 |
+
``adapters.corpus.htr_united``.
|
| 371 |
+
- ``extras.importers.huggingface`` →
|
| 372 |
+
``adapters.corpus.huggingface``.
|
| 373 |
+
- ``extras.importers._fallback_log`` →
|
| 374 |
+
``adapters.corpus._fallback_log``.
|
| 375 |
+
|
| 376 |
+
10. ✅ **Lot J — measurements.metrics.{MetricsResult,
|
| 377 |
+
aggregate_metrics} → evaluation.metric_result** (~25
|
| 378 |
+
imports migrés, 0 shim supprimé) :
|
| 379 |
+
- Migration partielle uniquement des symboles canoniquement
|
| 380 |
+
migrés (``MetricsResult``, ``aggregate_metrics``).
|
| 381 |
+
- ``compute_metrics`` reste dans
|
| 382 |
+
``picarones.measurements.metrics`` car aucun canonique
|
| 383 |
+
n'existe pour cette fonction (sera traité avec le Lot G
|
| 384 |
+
reporté).
|
| 385 |
+
|
| 386 |
+
À chaque lot : sed → tests → commit. Les shims devenus
|
| 387 |
+
orphelins après le lot peuvent être **supprimés** dans le même
|
| 388 |
+
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. **Lire** ``picarones/pipeline/_legacy_module_adapter.py``
|
| 408 |
+
en entier pour comprendre les outils disponibles
|
| 409 |
+
(``_BaseModuleAdapter``, ``_PayloadRegistry``,
|
| 410 |
+
``wrap_initial_inputs``).
|
| 411 |
+
|
| 412 |
+
3. **Écrire** un nouveau corps de ``PipelineRunner.run`` qui :
|
| 413 |
+
- Crée un ``_PayloadRegistry`` par appel.
|
| 414 |
+
- Wrappe les ``initial_inputs`` legacy via
|
| 415 |
+
``wrap_initial_inputs(...)``.
|
| 416 |
+
- Convertit la ``PipelineSpec`` legacy en ``PipelineSpec``
|
| 417 |
+
canonique (``picarones.domain.pipeline_spec.PipelineSpec``).
|
| 418 |
+
Chaque ``PipelineStep.module: BaseModule`` devient un
|
| 419 |
+
``adapter_name: str``, et l'adapter est
|
| 420 |
+
``_BaseModuleAdapter(module, registry)``.
|
| 421 |
+
- Construit un ``adapter_resolver`` qui retourne le
|
| 422 |
+
wrapper de chaque module.
|
| 423 |
+
- Construit un ``RunContext``.
|
| 424 |
+
- Convertit le ``Document`` legacy en ``DocumentRef``.
|
| 425 |
+
- Invoque ``PipelineExecutor.run(canonical_spec,
|
| 426 |
+
document_ref, canonical_inputs, context)``.
|
| 427 |
+
- Reconvertit le ``PipelineResult`` canonique en
|
| 428 |
+
``PipelineResult`` legacy.
|
| 429 |
+
- Calcule ``junction_metrics`` en post-étape (parcourt
|
| 430 |
+
les ``StepResult.produced_artifacts``, lit le payload
|
| 431 |
+
du registre, appelle ``compute_at_junction`` contre la
|
| 432 |
+
GT du document si ``GTLevel`` correspond).
|
| 433 |
+
|
| 434 |
+
4. **Tester** : tous les tests existants doivent toujours
|
| 435 |
+
passer (les 7 fichiers axe B + ``test_sprint63_pipeline_runner``,
|
| 436 |
+
etc.). C'est l'invariant de la sub-phase 7.B.2.
|
| 437 |
+
|
| 438 |
+
5. **Lint** : ``ruff check picarones/ tests/``.
|
| 439 |
+
|
| 440 |
+
6. **Commit + push** avec message décrivant ce qui a été
|
| 441 |
+
fait + pointer vers la sub-phase 7.B.3 comme prochaine
|
| 442 |
+
étape.
|
| 443 |
+
|
| 444 |
+
### Alternative pragmatique
|
| 445 |
+
|
| 446 |
+
Si le refactor 7.B.2 est trop gros pour une session,
|
| 447 |
+
**commencer par le Lot A de la section 4.D** (migrer les ~30
|
| 448 |
+
imports tests qui consomment ``core.modules`` et
|
| 449 |
+
``core.facts`` vers leur canonique ``domain/``). Cela vide
|
| 450 |
+
une portion de la table de parité et permet de **supprimer les
|
| 451 |
+
shims** ``core.modules.py`` et ``core.facts.py`` en bloc —
|
| 452 |
+
résultat tangible et bien aligné avec le principe
|
| 453 |
+
« suppression agressive ».
|
| 454 |
+
|
| 455 |
+
Pareil pour Lots B-F : chaque lot est indépendant, fait
|
| 456 |
+
progresser la migration, et démontre concrètement la
|
| 457 |
+
suppression du legacy.
|
| 458 |
+
|
| 459 |
+
### Pièges anticipés pour 7.B.2
|
| 460 |
+
|
| 461 |
+
- **Sémantique différente des inputs entre legacy et canonique** :
|
| 462 |
+
le legacy passe ``Document.image_path`` comme un string
|
| 463 |
+
pur dans ``initial_inputs[ArtifactType.IMAGE]`` ; le canonique
|
| 464 |
+
attend un ``Artifact(uri=...)``. ``wrap_initial_inputs``
|
| 465 |
+
fait la conversion mais il faut s'assurer que les modules
|
| 466 |
+
consomment bien le ``uri`` côté `_BaseModuleAdapter`.
|
| 467 |
+
|
| 468 |
+
- **``junction_metrics`` calcul** : le legacy
|
| 469 |
+
``PipelineRunner.run`` calcule ``junction_metrics`` à
|
| 470 |
+
chaque step (cf. ligne 519-540 actuellement). Le canonique
|
| 471 |
+
``PipelineExecutor`` ne le fait pas. Il faut donc faire
|
| 472 |
+
ce calcul **après** l'exécution canonique, en parcourant
|
| 473 |
+
les artefacts produits et en lisant les payloads via le
|
| 474 |
+
registre.
|
| 475 |
+
|
| 476 |
+
- **``output_types`` partial** : si un module produit un
|
| 477 |
+
output type non déclaré, le legacy le tolère (on remplit
|
| 478 |
+
``StepResult.output_types`` avec ce qui est effectivement
|
| 479 |
+
produit, pas ce qui est déclaré). Le canonique
|
| 480 |
+
``PipelineExecutor`` rejette en ``error="missing_output: ..."``.
|
| 481 |
+
Vérifier la sémantique attendue par les tests.
|
| 482 |
+
|
| 483 |
+
- **Spec conversion** : ``PipelineStep`` legacy a
|
| 484 |
+
``inputs_from: dict[ArtifactType, str]`` (mapping
|
| 485 |
+
type→step_name). ``PipelineStep`` canonique a
|
| 486 |
+
``inputs_from: tuple[InputBinding, ...]``. Conversion
|
| 487 |
+
attentive nécessaire.
|
| 488 |
+
|
| 489 |
+
---
|
| 490 |
+
|
| 491 |
+
## 6. Commande de démarrage de la nouvelle session
|
| 492 |
+
|
| 493 |
+
Le user envoie simplement :
|
| 494 |
+
|
| 495 |
+
```
|
| 496 |
+
Reprends la migration. Lis docs/migration/SESSION_HANDOVER.md
|
| 497 |
+
en entier d'abord, puis commence par les vérifications de la
|
| 498 |
+
section 2.
|
| 499 |
+
```
|
| 500 |
+
|
| 501 |
+
Ou pour aller direct à l'action :
|
| 502 |
+
|
| 503 |
+
```
|
| 504 |
+
Continue la sub-phase 7.B.2.
|
| 505 |
+
```
|
| 506 |
+
|
| 507 |
+
(Claude Code va automatiquement lire CLAUDE.md à l'init, qui
|
| 508 |
+
pointera vers ce SESSION_HANDOVER.md et les plans détaillés.)
|
docs/migration/legacy-retirement-plan.md
ADDED
|
@@ -0,0 +1,1239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 ✅ terminée
|
| 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 |
+
- [x] `docs/migration/regression-tolerances.md` — table des
|
| 51 |
+
tolérances acceptables par métrique et type d'output (CER ε=0,
|
| 52 |
+
Wilcoxon ε=1e-9, HTML diff sémantique, narrative facts égalité
|
| 53 |
+
ensembliste, etc.).
|
| 54 |
+
- [x] `tests/regression/legacy_vs_rewrite/` — harness scaffolding :
|
| 55 |
+
fixtures de corpus synthétique (small=3 docs, medium=30 docs,
|
| 56 |
+
large laissé pour ajout opportuniste) + gestion golden snapshot
|
| 57 |
+
avec flag `--regen-golden` + comparateurs sémantiques (floats,
|
| 58 |
+
sets, JSON). Marker `regression` enregistré et exclu de
|
| 59 |
+
``addopts`` par défaut (opt-in via `pytest -m regression`).
|
| 60 |
+
Smoke test couvre les 16 invariants du harness lui-même.
|
| 61 |
+
- [x] `tests/architecture/test_no_legacy_imports_in_rewrite.py` —
|
| 62 |
+
garantit qu'aucun fichier des paquets `domain/`, `formats/`,
|
| 63 |
+
`evaluation/`, `pipeline/`, `adapters/`, `app/`, `reports_v2/`,
|
| 64 |
+
`interfaces/` n'importe depuis un paquet legacy. AST-based,
|
| 65 |
+
pas regex syntaxique. État initial : **vert** — le rewrite est
|
| 66 |
+
déjà clean.
|
| 67 |
+
|
| 68 |
+
**Acceptance** : ✅ remplie. Le harness est prêt à recevoir les
|
| 69 |
+
tests de régression de chaque phase suivante (`test_phase1_*.py`,
|
| 70 |
+
`test_phase2_*.py`, etc.). Toute fonctionnalité migrée DOIT
|
| 71 |
+
avoir son test de régression ajouté ici en même temps que le
|
| 72 |
+
code.
|
| 73 |
+
|
| 74 |
+
### Phase 1 — Foundation conceptuelle (`core/`, `domain/`) — partielle ✅
|
| 75 |
+
|
| 76 |
+
**Audit de migrabilité réelle** : 5 modules `core/` sur 9 dépendent
|
| 77 |
+
de `core/modules.py` (legacy `BaseModule` + `ArtifactType` 6 valeurs,
|
| 78 |
+
incompatible avec le superset `domain/artifacts.ArtifactType` 10
|
| 79 |
+
valeurs). Les migrer ferait dériver le comportement des callers
|
| 80 |
+
legacy — à reporter en **Phase 4** quand le runner et les métriques
|
| 81 |
+
seront rewrités.
|
| 82 |
+
|
| 83 |
+
**Migrés en Phase 1 — 3 modules** (sans dépendance à `core/modules`) :
|
| 84 |
+
|
| 85 |
+
| Legacy | Canonique rewrite | Statut |
|
| 86 |
+
|--------|-------------------|--------|
|
| 87 |
+
| `core/xml_utils.py` (44 LOC) | `formats/_xml_utils.py` + re-export `picarones.formats.safe_parse_xml` | ✅ shim posé |
|
| 88 |
+
| `core/diff_utils.py` (89 LOC) | `evaluation/_diff_utils.py` + re-export `picarones.evaluation.{compute_word_diff,compute_char_diff,diff_stats}` | ✅ shim posé |
|
| 89 |
+
| `core/facts.py` (229 LOC) | `domain/facts.py` + re-export `picarones.domain.{Fact,FactType,FactImportance,DetectorRegistry,detect_all}` | ✅ shim posé |
|
| 90 |
+
|
| 91 |
+
**Reportés en Phase 4** (couplage à `core/modules.ArtifactType` legacy
|
| 92 |
+
ou au modèle du runner legacy) :
|
| 93 |
+
|
| 94 |
+
| Legacy | Bloqueur |
|
| 95 |
+
|--------|----------|
|
| 96 |
+
| `core/results.py` (677 LOC, `BenchmarkResult` + 30 champs agrégés) | Modèle central du runner legacy ; convergence avec `app.results.RunResult` en Phase 4 (rewrite de `measurements/runner/`) |
|
| 97 |
+
| `core/pipeline.py` (571 LOC, legacy `PipelineSpec` + `BaseModule`) | Concept différent du `domain.pipeline_spec.PipelineSpec` ; convergence en Phase 6 (`pipelines/` legacy) |
|
| 98 |
+
| `core/corpus.py` (511 LOC, `Document` avec payloads typés) | Modèle data legacy ≠ `DocumentRef` du rewrite ; convergence en Phase 4 |
|
| 99 |
+
| `core/modules.py` (173 LOC, `BaseModule` + `ArtifactType` 6 valeurs) | Type legacy partagé par 50+ modules ; déprécation en Phase 4 |
|
| 100 |
+
| `core/metric_registry.py` + `metric_hooks.py` (686 LOC) | Importe `core.modules.ArtifactType` ; convergence en Phase 4 |
|
| 101 |
+
| `core/metrics.py` (144 LOC, `MetricsResult`) | Schéma legacy ≠ `ViewResult.metric_values` du rewrite ; convergence en Phase 4 |
|
| 102 |
+
|
| 103 |
+
**Effort consommé Phase 1** : ~1 jour (3 modules + audit + tests).
|
| 104 |
+
**Effort restant — reporté en Phase 4** : ~5-7 jours.
|
| 105 |
+
|
| 106 |
+
**Acceptance Phase 1 partielle** : 3 modules `core/` sont des shims
|
| 107 |
+
re-export propres avec `DeprecationWarning`. Le test architectural
|
| 108 |
+
`test_no_legacy_imports_in_rewrite.py` reste vert. `picarones/__init__.py`
|
| 109 |
+
top-level pointe désormais vers le canonique pour les modules
|
| 110 |
+
migrés (pas de spam de warning à `import picarones`). Les 6 autres
|
| 111 |
+
modules `core/` fonctionnent inchangés ; ils seront migrés au
|
| 112 |
+
moment de la migration de leurs callers.
|
| 113 |
+
|
| 114 |
+
### Phase 2 — Statistics (`measurements/statistics/`) — ✅ terminée
|
| 115 |
+
|
| 116 |
+
**Modules migrés** : 8 modules (`wilcoxon.py`, `friedman_nemenyi.py`,
|
| 117 |
+
`bootstrap.py`, `pareto.py`, `clustering.py`, `correlation.py`,
|
| 118 |
+
`distributions.py`, `cdd_render.py`).
|
| 119 |
+
|
| 120 |
+
**Canonique** : `picarones/evaluation/statistics/`.
|
| 121 |
+
|
| 122 |
+
**Travaux** :
|
| 123 |
+
|
| 124 |
+
- 8 modules copiés bit-for-bit dans `evaluation/statistics/`.
|
| 125 |
+
- 1 import legacy dans `clustering.py` migré
|
| 126 |
+
(`picarones.core.diff_utils.compute_word_diff`
|
| 127 |
+
→ `picarones.evaluation.compute_word_diff`).
|
| 128 |
+
- 1 import auto-référencé dans `friedman_nemenyi.py` migré
|
| 129 |
+
(`picarones.measurements.statistics.wilcoxon._normal_sf`
|
| 130 |
+
→ `picarones.evaluation.statistics.wilcoxon._normal_sf`).
|
| 131 |
+
- `evaluation/statistics/__init__.py` ré-exporte 14 symboles
|
| 132 |
+
publics (`bootstrap_ci`, `wilcoxon_test`, `compute_pairwise_stats`,
|
| 133 |
+
`friedman_test`, `nemenyi_posthoc`, `build_critical_difference_svg`,
|
| 134 |
+
`compute_pareto_front`, `ErrorCluster`, `cluster_errors`,
|
| 135 |
+
`compute_correlation_matrix`, `compute_reliability_curve`,
|
| 136 |
+
`compute_venn_data`, `_SCIPY_AVAILABLE`, `_chi_square_sf`,
|
| 137 |
+
`_nemenyi_critical_value`, `_normal_sf`, `_rank_row`).
|
| 138 |
+
- 8 shims `measurements/statistics/<X>.py` + 1 shim
|
| 139 |
+
`measurements/statistics/__init__.py` avec `DeprecationWarning`,
|
| 140 |
+
`__all__` complet pour rétrocompat des callers (5 fichiers
|
| 141 |
+
`report/`, 6 fichiers tests).
|
| 142 |
+
|
| 143 |
+
**Effort réel** : ~1 jour (vs estimation 5-7 j — code mathématique
|
| 144 |
+
pur, pas de couplage applicatif comme prévu, mais aussi script
|
| 145 |
+
de génération de shims qui a accéléré).
|
| 146 |
+
|
| 147 |
+
**Acceptance** : suite par défaut 5019+ passed (inchangée), tests
|
| 148 |
+
ciblés sur les statistiques 226 passed, test architectural
|
| 149 |
+
anti-imports legacy reste vert (3 passed). Pas de régression
|
| 150 |
+
détectée — les algorithmes scipy/numpy sont déterministes par
|
| 151 |
+
construction (seed=42 partout) ; le rendu SVG est strictement
|
| 152 |
+
identique parce que c'est le même fichier.
|
| 153 |
+
|
| 154 |
+
### Phase 3 — Narrative engine (`measurements/narrative/`) — ✅ terminée
|
| 155 |
+
|
| 156 |
+
**Modules migrés** : 11 modules + 2 templates YAML.
|
| 157 |
+
|
| 158 |
+
| Legacy | Canonique |
|
| 159 |
+
|--------|-----------|
|
| 160 |
+
| `measurements/narrative/__init__.py` | `reports_v2/narrative/__init__.py` |
|
| 161 |
+
| `measurements/narrative/arbiter.py` | `reports_v2/narrative/arbiter.py` |
|
| 162 |
+
| `measurements/narrative/registry.py` | `reports_v2/narrative/registry.py` |
|
| 163 |
+
| `measurements/narrative/renderer.py` | `reports_v2/narrative/renderer.py` |
|
| 164 |
+
| `measurements/narrative/detectors/__init__.py` | `reports_v2/narrative/detectors/__init__.py` |
|
| 165 |
+
| `measurements/narrative/detectors/_helpers.py` | `reports_v2/narrative/detectors/_helpers.py` |
|
| 166 |
+
| `measurements/narrative/detectors/ensemble.py` | `reports_v2/narrative/detectors/ensemble.py` (1 détecteur) |
|
| 167 |
+
| `measurements/narrative/detectors/history.py` | `reports_v2/narrative/detectors/history.py` (3 détecteurs) |
|
| 168 |
+
| `measurements/narrative/detectors/pareto.py` | `reports_v2/narrative/detectors/pareto.py` (2 détecteurs) |
|
| 169 |
+
| `measurements/narrative/detectors/quality.py` | `reports_v2/narrative/detectors/quality.py` (4 détecteurs) |
|
| 170 |
+
| `measurements/narrative/detectors/ranking.py` | `reports_v2/narrative/detectors/ranking.py` (5 détecteurs) |
|
| 171 |
+
| `measurements/narrative/detectors/stratum.py` | `reports_v2/narrative/detectors/stratum.py` (3 détecteurs) |
|
| 172 |
+
| `measurements/narrative/templates/fr.yaml` | `reports_v2/narrative/templates/fr.yaml` |
|
| 173 |
+
| `measurements/narrative/templates/en.yaml` | `reports_v2/narrative/templates/en.yaml` |
|
| 174 |
+
|
| 175 |
+
Total : **18 détecteurs en 6 familles + arbitre + renderer + 36
|
| 176 |
+
templates YAML FR/EN** migrés.
|
| 177 |
+
|
| 178 |
+
**Cible architecturale** : `picarones/reports_v2/narrative/` (le
|
| 179 |
+
narratif est de la **présentation**, pas du domaine — il vit du
|
| 180 |
+
côté rapport, pas de l'évaluation).
|
| 181 |
+
|
| 182 |
+
**Travaux** :
|
| 183 |
+
|
| 184 |
+
- 14 fichiers (11 .py + 1 _helpers.py + 2 .yaml) copiés depuis le
|
| 185 |
+
legacy vers le canonique.
|
| 186 |
+
- Tous les imports `picarones.core.facts` (11 occurrences) migrés
|
| 187 |
+
vers `picarones.domain.facts` (Phase 1 a déjà migré ce module).
|
| 188 |
+
- Tous les imports auto-référencés `picarones.measurements.narrative`
|
| 189 |
+
réécrits en `picarones.reports_v2.narrative`.
|
| 190 |
+
- Path des templates YAML auto-ajusté (relatif à `__file__`).
|
| 191 |
+
- 12 shims `measurements/narrative/*.py` + `_helpers.py` shim
|
| 192 |
+
manuel (privé, pas d'`__all__`).
|
| 193 |
+
- `_DEFAULT_REGISTRY` (singleton du registre des détecteurs)
|
| 194 |
+
ré-exporté explicitement par le shim `__init__.py` pour la
|
| 195 |
+
rétrocompat des tests S19.
|
| 196 |
+
|
| 197 |
+
**Effort réel** : ~1 jour (vs estimation 8-12 j — script de
|
| 198 |
+
génération de shims a fortement accéléré ; pas d'aléatoire ni
|
| 199 |
+
d'I/O dans les détecteurs, donc régression triviale par
|
| 200 |
+
construction).
|
| 201 |
+
|
| 202 |
+
**Acceptance** : tous les tests narratifs passent — Sprints 16,
|
| 203 |
+
19, 23, 29, 36, 44, 46, 73, 90, 92, baseline_comparison, chantier
|
| 204 |
+
5, reproducibility_ops. 322 tests ciblés passed. Test architectural
|
| 205 |
+
anti-imports legacy : 3 passed (le rewrite reste autonome).
|
| 206 |
+
Garde-fou anti-hallucination préservé (les détecteurs lisent
|
| 207 |
+
toujours le dict JSON d'entrée, pas une source externe).
|
| 208 |
+
|
| 209 |
+
### Phase 4 — 35 mesures legacy (`measurements/*.py`) — partielle ✅
|
| 210 |
+
|
| 211 |
+
**Audit de migrabilité** : sur 35 mesures legacy, **24 étaient
|
| 212 |
+
déjà des re-exports** (Phase 4 partielle pré-existante avec un
|
| 213 |
+
canonique `evaluation/metrics/X.py`). Sur les 11 modules réellement
|
| 214 |
+
"contenu" :
|
| 215 |
+
|
| 216 |
+
- **9 sont migrés en Phase 4 (cette session)** sans toucher à
|
| 217 |
+
`core.modules` : autonomes ou en cascade vers d'autres modules
|
| 218 |
+
migrables.
|
| 219 |
+
- **13 modules réels** restent bloqués par
|
| 220 |
+
`core.modules.ArtifactType` (enum legacy 6 valeurs incompatible
|
| 221 |
+
avec le superset `domain.artifacts.ArtifactType` 10 valeurs ;
|
| 222 |
+
`TEXT` ≠ `RAW_TEXT`, `ALTO` ≠ `ALTO_XML`, `PAGE` ≠ `PAGE_XML`).
|
| 223 |
+
Substitution non transparente — exigerait un travail de
|
| 224 |
+
remapping sémantique sur chaque caller.
|
| 225 |
+
|
| 226 |
+
**Migrés en Phase 4 — 9 modules** :
|
| 227 |
+
|
| 228 |
+
| Legacy | Canonique | Notes |
|
| 229 |
+
|--------|-----------|-------|
|
| 230 |
+
| `measurements/char_scores.py` (307) | `evaluation/metrics/char_scores.py` | Autonome |
|
| 231 |
+
| `measurements/difficulty.py` (161) | `evaluation/metrics/difficulty.py` | Autonome |
|
| 232 |
+
| `measurements/ner_backends.py` (186) | `evaluation/metrics/ner_backends.py` | Autonome |
|
| 233 |
+
| `measurements/normalization.py` (51) | `evaluation/metrics/normalization.py` | Autonome |
|
| 234 |
+
| `measurements/structure.py` (182) | `evaluation/metrics/structure.py` | Autonome |
|
| 235 |
+
| `measurements/cost_projection.py` (140) | `evaluation/metrics/cost_projection.py` | dep `pricing` (déjà migré) |
|
| 236 |
+
| `measurements/specialization.py` (158) | `evaluation/metrics/specialization.py` | dep `inter_engine` (re-export déjà) |
|
| 237 |
+
| `measurements/taxonomy.py` (294) | `evaluation/metrics/taxonomy.py` | dep `char_scores` (en cascade) |
|
| 238 |
+
| `measurements/taxonomy_intra_doc.py` (178) | `evaluation/metrics/taxonomy_intra_doc.py` | dep `taxonomy` (en cascade) |
|
| 239 |
+
|
| 240 |
+
Total : **1657 lignes de code migrées + 9 shims legacy**.
|
| 241 |
+
|
| 242 |
+
**Bloqués — 13 modules + 1 sous-package + 6 modules `core/`** :
|
| 243 |
+
|
| 244 |
+
Reportés à une phase dédiée **« Phase 4-bis : ArtifactType
|
| 245 |
+
migration »** dont le périmètre est :
|
| 246 |
+
|
| 247 |
+
1. Décider le mapping sémantique TEXT → RAW_TEXT vs CORRECTED_TEXT
|
| 248 |
+
(par module, en lisant le contexte d'usage).
|
| 249 |
+
2. Migrer `core/modules.py` (`BaseModule` + `ArtifactType` 6
|
| 250 |
+
valeurs) vers `domain/module_protocol.py`.
|
| 251 |
+
3. Migrer `core/metric_registry.py` + `core/metric_hooks.py` vers
|
| 252 |
+
`evaluation/registry/`.
|
| 253 |
+
4. Adapter chaque module bloqué : `mufi.py`, `abbreviations.py`,
|
| 254 |
+
`early_modern_typography.py`, `modern_archives.py`,
|
| 255 |
+
`roman_numerals.py`, `unicode_blocks.py`, `equivalence_profile.py`,
|
| 256 |
+
`philological_hooks.py`, `ner.py`, `readability.py`,
|
| 257 |
+
`readability_hooks.py`, `searchability.py`,
|
| 258 |
+
`searchability_hooks.py`, `reading_order.py`, `alto_metrics.py`,
|
| 259 |
+
`numerical_sequences.py`, `numerical_sequences_hooks.py`,
|
| 260 |
+
`builtin_hooks.py`, `builtin_metrics.py`, `metrics.py`,
|
| 261 |
+
`pipeline_benchmark.py`, `pipeline_comparison.py`,
|
| 262 |
+
`pipeline_spec_loader.py`, `robustness.py`, `reliability.py`,
|
| 263 |
+
`history.py`.
|
| 264 |
+
5. Migrer le sous-package `measurements/runner/` (orchestrateur
|
| 265 |
+
legacy → fondre dans `pipeline/` + `app/services/`).
|
| 266 |
+
6. Migrer `core/results.py` (`BenchmarkResult` + 30 champs agrégés
|
| 267 |
+
→ typed Artifacts dans `domain/`).
|
| 268 |
+
7. Migrer `core/corpus.py` (`Document`/`Corpus`/`GTLevel` → modèle
|
| 269 |
+
convergent avec `domain.corpus`).
|
| 270 |
+
|
| 271 |
+
**Effort estimé Phase 4-bis** : 18-22 jours (vs 23-28 j initialement
|
| 272 |
+
estimés pour Phase 4 complète — la moitié déjà faite par les
|
| 273 |
+
re-exports pré-existants et les 9 modules de cette session).
|
| 274 |
+
|
| 275 |
+
**Acceptance Phase 4 partielle** : 9 modules migrés, 1191 tests
|
| 276 |
+
mesures passent (inchangés), test architectural anti-imports
|
| 277 |
+
legacy reste vert. Les 13 modules réels + 6 modules `core/`
|
| 278 |
+
restants sont documentés comme dépendant d'une migration
|
| 279 |
+
ArtifactType.
|
| 280 |
+
|
| 281 |
+
#### Tentative Phase 4-bis (avortée — diagnostic posé)
|
| 282 |
+
|
| 283 |
+
Une tentative de migration coordonnée de l'``ArtifactType`` a été
|
| 284 |
+
explorée puis revertée :
|
| 285 |
+
|
| 286 |
+
**Stratégie testée** : exploiter le mécanisme natif d'aliases
|
| 287 |
+
d'``Enum`` Python (un membre avec la même valeur qu'un autre devient
|
| 288 |
+
un alias). Ajout de ``TEXT = "raw_text"``, ``ALTO = "alto_xml"``,
|
| 289 |
+
``PAGE = "page_xml"`` à ``domain.artifacts.ArtifactType`` + hook
|
| 290 |
+
``_missing_`` pour accepter les valeurs string legacy. Puis
|
| 291 |
+
transformation de ``core/modules.py`` en shim qui ré-exporte
|
| 292 |
+
``ArtifactType`` et ``BaseModule`` depuis le canonique.
|
| 293 |
+
|
| 294 |
+
**Conservé en place** : les aliases + ``_missing_`` dans
|
| 295 |
+
``domain.artifacts.ArtifactType``. Inoffensif — aucun code legacy
|
| 296 |
+
ne les voit puisqu'aucun module legacy n'importe encore depuis le
|
| 297 |
+
canonique.
|
| 298 |
+
|
| 299 |
+
**Reverté** : le shim ``core/modules.py``. Cause : passer le
|
| 300 |
+
``core.modules.ArtifactType`` du legacy enum 6 valeurs au superset
|
| 301 |
+
canonique change silencieusement ``ArtifactType.TEXT.value`` de
|
| 302 |
+
``"text"`` à ``"raw_text"``. Or 27 tests legacy
|
| 303 |
+
(``test_sprint63_pipeline_runner``, ``test_sprint65_pipeline_comparison``,
|
| 304 |
+
``test_sprint68_pipeline_comparison_html`` etc.) reposent sur le
|
| 305 |
+
fait que les clés des dicts ``junction_metrics`` produites par le
|
| 306 |
+
runner legacy sont les valeurs string legacy. Quand le runner
|
| 307 |
+
utilise ``at.value`` pour stocker, il stocke maintenant ``"raw_text"``,
|
| 308 |
+
et les tests qui cherchaient ``junction_metrics["text"]`` cassent.
|
| 309 |
+
|
| 310 |
+
Le diagnostic est plus profond qu'un simple rename : le legacy
|
| 311 |
+
``BenchmarkResult.junction_metrics`` est un ``dict[str, dict]``
|
| 312 |
+
indexé par valeur string ; sa stabilité de format est implicitement
|
| 313 |
+
testée. Migrer ``core.modules.ArtifactType`` exige un travail
|
| 314 |
+
**par module** d'identification des dicts indexés par valeur
|
| 315 |
+
string, et soit (a) double-clé pour rétrocompat, (b) migration
|
| 316 |
+
ordonnée tests-en-même-temps.
|
| 317 |
+
|
| 318 |
+
**Plan rectifié pour Phase 4-bis** :
|
| 319 |
+
|
| 320 |
+
1. Lister exhaustivement les dicts indexés par ``ArtifactType.value``
|
| 321 |
+
dans le legacy (``core/results.py``, ``core/pipeline.py``,
|
| 322 |
+
``measurements/runner/``, ``measurements/pipeline_*``).
|
| 323 |
+
2. Décider la stratégie par module : double-clé pendant la
|
| 324 |
+
migration vs migration coordonnée tests + code.
|
| 325 |
+
3. Migrer un cluster à la fois en validant la suite après chaque.
|
| 326 |
+
|
| 327 |
+
**Effort rectifié** : 25-30 jours (vs 18-22 estimés initialement —
|
| 328 |
+
le couplage implicite des dicts indexés par valeur string n'avait
|
| 329 |
+
pas été vu à l'audit).
|
| 330 |
+
|
| 331 |
+
**Statut Phase 4-bis** : analyse posée, exécution reportée à un
|
| 332 |
+
sprint dédié de plusieurs sessions.
|
| 333 |
+
|
| 334 |
+
#### Phase 4-bis — Reprise et complétion (2026-05)
|
| 335 |
+
|
| 336 |
+
La reprise s'appuie sur le **diagnostic de la tentative avortée**
|
| 337 |
+
en adoptant la stratégie « double-clé » : on garde les aliases
|
| 338 |
+
legacy ``TEXT``/``ALTO``/``PAGE`` dans
|
| 339 |
+
``domain.artifacts.ArtifactType``, et on s'engage à ce que tout
|
| 340 |
+
dict indexé par ``ArtifactType.value`` présente en parallèle la
|
| 341 |
+
clé canonique (``"raw_text"``) **et** la clé legacy (``"text"``)
|
| 342 |
+
quand un alias existe.
|
| 343 |
+
|
| 344 |
+
**Ajouts dans le canonique** :
|
| 345 |
+
|
| 346 |
+
- ``LEGACY_VALUE_ALIASES = {"raw_text": "text", "alto_xml": "alto",
|
| 347 |
+
"page_xml": "page"}`` dans ``domain.artifacts``.
|
| 348 |
+
- ``expand_legacy_keys(d)`` qui mute un dict pour y copier les
|
| 349 |
+
valeurs canoniques sous les alias legacy.
|
| 350 |
+
- ``BaseModule`` canonique dans ``domain/module_protocol.py``
|
| 351 |
+
(10 valeurs vs 6 legacy).
|
| 352 |
+
|
| 353 |
+
**Sites mis à jour** :
|
| 354 |
+
|
| 355 |
+
- ``core/pipeline.py`` : ``StepResult.junction_metrics`` enrichi
|
| 356 |
+
via ``expand_legacy_keys`` à la production.
|
| 357 |
+
``PipelineResult.junction_metrics_for`` essaie la clé canonique
|
| 358 |
+
puis l'alias legacy.
|
| 359 |
+
``_artifact_type_to_gt_level`` utilise une map explicite
|
| 360 |
+
``ArtifactType → GTLevel`` (les valeurs canoniques
|
| 361 |
+
``"raw_text"``/``"alto_xml"``/``"page_xml"`` ne matchent plus
|
| 362 |
+
``GTLevel`` qui garde ``"text"``/``"alto"``/``"page"``).
|
| 363 |
+
- ``measurements/pipeline_benchmark.py`` :
|
| 364 |
+
``StepAggregate.junction_metrics`` enrichi via
|
| 365 |
+
``expand_legacy_keys`` après agrégation.
|
| 366 |
+
- ``measurements/pipeline_comparison.py`` :
|
| 367 |
+
``_final_metric_value`` essaie canonique puis legacy.
|
| 368 |
+
- ``evaluation/metrics/module_policy.py`` : la comparaison
|
| 369 |
+
manifest vs déclaration normalise via les aliases (``"text"``
|
| 370 |
+
match ``"raw_text"``).
|
| 371 |
+
|
| 372 |
+
**Migration des callers** : 16 modules ``measurements/`` + 6
|
| 373 |
+
modules `core/`/`engines/`/`modules/`/`cli/`/`report/` migrés
|
| 374 |
+
de ``from picarones.core.modules import ArtifactType`` vers
|
| 375 |
+
``from picarones.domain.artifacts import ArtifactType`` (et
|
| 376 |
+
``from picarones.domain.module_protocol import BaseModule``
|
| 377 |
+
quand applicable).
|
| 378 |
+
|
| 379 |
+
**``core/modules.py``** : transformé en shim avec
|
| 380 |
+
``DeprecationWarning`` à l'import.
|
| 381 |
+
|
| 382 |
+
**Tests adaptés** :
|
| 383 |
+
|
| 384 |
+
- ``test_public_api.py::test_artifact_type_values`` —
|
| 385 |
+
asserte le set canonique 10 valeurs.
|
| 386 |
+
- ``test_sprint33_module_interface.py::test_repr_shows_io_types``
|
| 387 |
+
— asserte ``"raw_text→raw_text"``.
|
| 388 |
+
- ``test_sprint68_pipeline_comparison_html.py::test_display_label_default``
|
| 389 |
+
— asserte ``"raw_text.cer"``.
|
| 390 |
+
|
| 391 |
+
**Acceptance Phase 4-bis** : 5019 tests passent (vs 5008 avant la
|
| 392 |
+
reprise — 11 tests étaient cassés par la première tentative et
|
| 393 |
+
sont maintenant verts). Les 24 fichiers de tests qui importent
|
| 394 |
+
encore ``from picarones.core.modules`` continuent à fonctionner via
|
| 395 |
+
le shim — ils ne deviendront erronés que quand le shim sera retiré
|
| 396 |
+
en 2.0.
|
| 397 |
+
|
| 398 |
+
**Reportés à Phase 4-ter** :
|
| 399 |
+
|
| 400 |
+
- ``core/metric_registry.py`` (263 l) et ``core/metric_hooks.py``
|
| 401 |
+
(423 l) restent en place : ils sont consommés par 30+ modules
|
| 402 |
+
``measurements/`` via le décorateur ``@register_metric`` et les
|
| 403 |
+
hooks ``register_document_metric``/``register_corpus_aggregator``.
|
| 404 |
+
Le canonique existant ``evaluation/registry/registry.py`` utilise
|
| 405 |
+
un design **instance-based** (``MetricRegistry()`` explicite,
|
| 406 |
+
pas de décorateur module-level) qui est incompatible avec le
|
| 407 |
+
pattern legacy. La migration exige un choix de design (garder
|
| 408 |
+
les deux, fondre dans une API unique, etc.) qui dépasse Phase
|
| 409 |
+
4-bis.
|
| 410 |
+
- ``core/metrics.py`` (144 l, ``MetricsResult`` +
|
| 411 |
+
``aggregate_metrics``) reste en place : pas d'équivalent
|
| 412 |
+
canonique dans ``domain/`` ou ``evaluation/`` à ce jour. La
|
| 413 |
+
conversion nécessitera d'abord de créer le destinataire dans
|
| 414 |
+
``domain.measurements`` (typé Pydantic au lieu de dataclass) ou
|
| 415 |
+
``evaluation.aggregation``.
|
| 416 |
+
- ``core/results.py`` (``BenchmarkResult`` + champs agrégés) :
|
| 417 |
+
même statut.
|
| 418 |
+
- ``core/corpus.py`` (``Document``/``Corpus``/``GTLevel``) :
|
| 419 |
+
même statut. Note : ``GTLevel`` étant intentionnellement
|
| 420 |
+
conservé en parallèle d'``ArtifactType``, son retrait dépend
|
| 421 |
+
de la fin de migration des callers qui parsent les types de GT
|
| 422 |
+
par leur valeur string.
|
| 423 |
+
|
| 424 |
+
#### Phase 4-ter — Relocalisation Cercle 1 → Cercle 2 (2026-05)
|
| 425 |
+
|
| 426 |
+
Stratégie « relocaliser sans redessiner » : on déplace
|
| 427 |
+
verbatim les modules legacy de ``core/`` (Cercle 1) vers
|
| 428 |
+
``evaluation/`` (Cercle 2) où ils appartiennent sémantiquement,
|
| 429 |
+
sans toucher à leur API publique. Le pattern module-level
|
| 430 |
+
décorateur (``@register_metric``, ``@register_document_metric``,
|
| 431 |
+
``@register_corpus_aggregator``) est **conservé** — sa
|
| 432 |
+
convergence avec l'instance-based ``evaluation.registry.MetricRegistry``
|
| 433 |
+
(Sprint A14-S5) est laissée à un futur sprint dédié quand un
|
| 434 |
+
caller institutionnel le demandera.
|
| 435 |
+
|
| 436 |
+
**Migrations effectuées (A-D)** :
|
| 437 |
+
|
| 438 |
+
| Source legacy | Destination canonique | Lignes |
|
| 439 |
+
|---|---|---|
|
| 440 |
+
| ``core/metric_registry.py`` | ``evaluation/metric_registry.py`` | 264 |
|
| 441 |
+
| ``core/metric_hooks.py`` | ``evaluation/metric_hooks.py`` | 427 |
|
| 442 |
+
| ``core/metrics.py`` | ``evaluation/metric_result.py`` | 145 |
|
| 443 |
+
| ``core/results.py`` | ``evaluation/benchmark_result.py``| 702 |
|
| 444 |
+
|
| 445 |
+
Total : **1538 lignes** déplacées du Cercle 1 vers le Cercle 2.
|
| 446 |
+
Les chemins ``core/X.py`` deviennent des shims minimaux
|
| 447 |
+
(< 30 lignes chacun) avec ``DeprecationWarning`` à l'import.
|
| 448 |
+
|
| 449 |
+
**Adaptations transverses** :
|
| 450 |
+
|
| 451 |
+
- ``evaluation/benchmark_result.py`` ne peut pas importer
|
| 452 |
+
``picarones.__version__`` (cycle d'import via
|
| 453 |
+
``measurements/``). La résolution de version utilise
|
| 454 |
+
désormais ``importlib.metadata`` directement avec fallback
|
| 455 |
+
``"1.0.0"``.
|
| 456 |
+
- ``tests/architecture/test_file_budgets.py`` mis à jour
|
| 457 |
+
pour pointer vers les nouveaux chemins canoniques.
|
| 458 |
+
- ``tests/core/test_public_api.py::TestCercle1IsLean.EXPECTED_CERCLE1``
|
| 459 |
+
ne contient plus que ``corpus.py`` et ``pipeline.py``
|
| 460 |
+
(les seuls modules ``core/`` réels qui restent).
|
| 461 |
+
|
| 462 |
+
**Reporté à Phase 4-quater** :
|
| 463 |
+
|
| 464 |
+
``core/corpus.py`` (511 l, ``Document``/``Corpus``/``GTLevel`` +
|
| 465 |
+
payloads + ``load_corpus_from_directory``) reste en place.
|
| 466 |
+
Raison : il y a déjà ``domain.corpus.CorpusSpec`` (Pydantic,
|
| 467 |
+
immutable, structural) et ``domain.documents.DocumentRef``
|
| 468 |
+
en parallèle. La convergence des deux modèles
|
| 469 |
+
(``Document``/``Corpus`` historiques riches en behavior vs
|
| 470 |
+
``CorpusSpec``/``DocumentRef`` purement déclaratifs) est un
|
| 471 |
+
choix de design (fondre, garder les deux, marquer l'un comme
|
| 472 |
+
view-de-l'autre…) qui dépasse Phase 4-ter. L'objectif Phase
|
| 473 |
+
4-quater est de produire un RFC qui tranche cette question
|
| 474 |
+
puis migre les 14 callers en une fois.
|
| 475 |
+
|
| 476 |
+
**Acceptance Phase 4-ter (A-D)** : 5019 tests passent, lint
|
| 477 |
+
vert, architecture vérifiée (anti-cycles, file budgets,
|
| 478 |
+
EXPECTED_CERCLE1 mis à jour). Les 24+ fichiers de tests qui
|
| 479 |
+
importent encore via les chemins ``core/`` continuent à
|
| 480 |
+
fonctionner via les shims — déprécation visible mais
|
| 481 |
+
non-bloquante.
|
| 482 |
+
|
| 483 |
+
#### Phase 4-quater — Relocalisation de ``core/corpus.py`` (2026-05)
|
| 484 |
+
|
| 485 |
+
Décision RFC : **garder les deux modèles en parallèle**, sans
|
| 486 |
+
fusion. ``evaluation.corpus`` (riche en behavior, dataclass,
|
| 487 |
+
chargé en mémoire, runner-friendly) et
|
| 488 |
+
``domain.corpus.CorpusSpec`` (Pydantic, immutable, déclaratif,
|
| 489 |
+
pipeline-executor-friendly) sont des projections différentes
|
| 490 |
+
d'un même domaine ; un convertisseur explicite
|
| 491 |
+
``CorpusSpec ↔ Corpus`` viendra quand un caller institutionnel
|
| 492 |
+
l'exigera concrètement. Tenter une convergence prématurée
|
| 493 |
+
casserait soit le runner historique (qui consomme
|
| 494 |
+
``Document.get_gt(level)`` + ``Corpus.has_ocr_text``), soit le
|
| 495 |
+
pipeline executor canonique (qui consomme l'immutabilité de
|
| 496 |
+
``CorpusSpec`` pour la sérialisation YAML).
|
| 497 |
+
|
| 498 |
+
Migration effectuée
|
| 499 |
+
-------------------
|
| 500 |
+
|
| 501 |
+
| Source legacy | Destination canonique | Lignes |
|
| 502 |
+
|----------------------|-------------------------------|--------|
|
| 503 |
+
| ``core/corpus.py`` | ``evaluation/corpus.py`` | 533 |
|
| 504 |
+
|
| 505 |
+
Le chemin ``core/corpus.py`` devient un shim minimal
|
| 506 |
+
(< 30 lignes) avec ``DeprecationWarning`` à l'import. Les 14
|
| 507 |
+
callers de production (``cli/_pipeline``, ``cli/_robustness``,
|
| 508 |
+
``cli/_workflows``, ``web/benchmark_utils``,
|
| 509 |
+
``measurements/pipeline_benchmark``,
|
| 510 |
+
``measurements/pipeline_comparison``,
|
| 511 |
+
``measurements/robustness``, ``measurements/runner/orchestration``,
|
| 512 |
+
``measurements/runner/ner_attach``,
|
| 513 |
+
``extras/importers/{iiif,gallica,escriptorium}``,
|
| 514 |
+
``core/pipeline``, et ``picarones/__init__.py``) sont migrés
|
| 515 |
+
vers ``picarones.evaluation.corpus``.
|
| 516 |
+
|
| 517 |
+
Note : ``GTLevel`` reste consommé en parallèle d'``ArtifactType``
|
| 518 |
+
par le runner — la convergence de ces deux énumérations est
|
| 519 |
+
liée au retrait du runner legacy lui-même (Phase 6+ du plan).
|
| 520 |
+
|
| 521 |
+
Adaptations transverses
|
| 522 |
+
-----------------------
|
| 523 |
+
|
| 524 |
+
- ``test_file_budgets.py`` : entrée ``core/corpus.py`` retirée,
|
| 525 |
+
remplacée par ``evaluation/corpus.py`` (budget identique 600).
|
| 526 |
+
- ``test_public_api.py::EXPECTED_CERCLE1`` : ``corpus.py``
|
| 527 |
+
retiré de la liste — il ne reste plus que ``pipeline.py``
|
| 528 |
+
comme module Cercle 1 réel.
|
| 529 |
+
|
| 530 |
+
État final de ``core/`` après Phase 4-quater
|
| 531 |
+
--------------------------------------------
|
| 532 |
+
|
| 533 |
+
Le répertoire ``picarones/core/`` ne contient désormais qu'**un
|
| 534 |
+
seul module réel** :
|
| 535 |
+
|
| 536 |
+
- ``pipeline.py`` (~570 l) — ``PipelineRunner`` + ``PipelineSpec``
|
| 537 |
+
+ ``StepResult`` + ``PipelineResult``.
|
| 538 |
+
|
| 539 |
+
Tous les autres fichiers (``corpus.py``, ``modules.py``,
|
| 540 |
+
``metric_registry.py``, ``metric_hooks.py``, ``metrics.py``,
|
| 541 |
+
``results.py``, ``facts.py``, ``diff_utils.py``,
|
| 542 |
+
``xml_utils.py``) sont des shims < 30 lignes avec
|
| 543 |
+
``DeprecationWarning``.
|
| 544 |
+
|
| 545 |
+
**Acceptance Phase 4-quater** : 5019 tests passent (inchangé
|
| 546 |
+
depuis Phase 4-ter), lint vert, architecture vérifiée. Le
|
| 547 |
+
``__init__.py`` racine (``picarones/__init__.py``) importe
|
| 548 |
+
maintenant directement depuis les chemins canoniques (Cercle
|
| 549 |
+
1 ``domain/`` + Cercle 2 ``evaluation/``), seul ``core.pipeline``
|
| 550 |
+
reste référencé pour ses types.
|
| 551 |
+
|
| 552 |
+
**Reporté à Phase 5** :
|
| 553 |
+
|
| 554 |
+
- ``core/pipeline.py`` (``PipelineRunner``) — convergence avec
|
| 555 |
+
le pipeline executor canonique
|
| 556 |
+
(``picarones/pipeline/executor.py``, ``planner.py``,
|
| 557 |
+
``runner.py``). C'est le dernier module ``core/`` réel ;
|
| 558 |
+
son retrait suppose que tous les callers passent par le
|
| 559 |
+
pipeline executor, ce qui implique l'écriture du sucre
|
| 560 |
+
syntaxique pour les benchmarks OCR mono-étape (typique
|
| 561 |
+
``run_benchmark(corpus, [engine_a, engine_b])``).
|
| 562 |
+
- Convergence ``GTLevel`` ↔ ``ArtifactType`` (en attente du
|
| 563 |
+
retrait du runner legacy).
|
| 564 |
+
|
| 565 |
+
### Phase 5 — Reports HTML (`report/`)
|
| 566 |
+
|
| 567 |
+
**Modules** :
|
| 568 |
+
|
| 569 |
+
- 22 renderers thématiques : `baseline_render.py`, `calibration_render.py`,
|
| 570 |
+
`difficulty_render.py`, `error_absorption_render.py`,
|
| 571 |
+
`image_predictive_render.py`, `incremental_comparison_render.py`,
|
| 572 |
+
`inter_engine_render.py`, `levers_render.py`,
|
| 573 |
+
`lexical_modernization_render.py`, `longitudinal_render.py`,
|
| 574 |
+
`marginal_cost_render.py`, `module_audit_render.py`,
|
| 575 |
+
`multirun_stability_render.py`, `ner_render.py`,
|
| 576 |
+
`numerical_sequences_render.py`, `philological_render.py`,
|
| 577 |
+
`pipeline_dag_render.py`, `pipeline_render.py`,
|
| 578 |
+
`rare_token_recall_render.py`, `readability_render.py`,
|
| 579 |
+
`robustness_projection_render.py`, `searchability_render.py`,
|
| 580 |
+
`specialization_render.py`, `stratification_render.py`,
|
| 581 |
+
`taxonomy_comparison_render.py`, `taxonomy_cooccurrence_render.py`,
|
| 582 |
+
`taxonomy_intra_doc_render.py`, `throughput_render.py`,
|
| 583 |
+
`worst_lines_render.py`.
|
| 584 |
+
- 5 vues : `views/{advanced_taxonomy,diagnostics,economics,
|
| 585 |
+
pipeline,robustness}.py`.
|
| 586 |
+
- `generator.py` (orchestrateur), `comparison.py`, `snapshot.py`,
|
| 587 |
+
`assets.py`, `colors.py`, `render_helpers.py`, `report_data/`.
|
| 588 |
+
- `templates/` (~10 fichiers Jinja2), `glossary/` (2 YAML, 25
|
| 589 |
+
entrées), `i18n/`, `vendor/`.
|
| 590 |
+
|
| 591 |
+
**Cible** : `picarones/reports_v2/html/views/<theme>.py` + helpers
|
| 592 |
+
partagés dans `reports_v2/html/_helpers/`. Glossaire dans
|
| 593 |
+
`reports_v2/html/glossary/`. Templates Jinja2 dans
|
| 594 |
+
`reports_v2/html/templates/`.
|
| 595 |
+
|
| 596 |
+
**Effort** : 12-18 jours.
|
| 597 |
+
|
| 598 |
+
**Acceptance** : régression bit-for-bit sur le HTML produit pour
|
| 599 |
+
les 3 corpus de référence. Aucun renderer legacy laissé.
|
| 600 |
+
|
| 601 |
+
#### Phase 5.A+B — Helpers + glossary + i18n (2026-05)
|
| 602 |
+
|
| 603 |
+
Première tranche du retrait du legacy ``report/`` : les utilitaires
|
| 604 |
+
purs et les ressources statiques, sans toucher aux 22 renderers
|
| 605 |
+
thématiques (qui consomment ``BenchmarkResult`` legacy et seront
|
| 606 |
+
migrés au fil des phases ultérieures, par lots).
|
| 607 |
+
|
| 608 |
+
**Migrations effectuées** :
|
| 609 |
+
|
| 610 |
+
| Source legacy | Destination canonique |
|
| 611 |
+
|--------------------------------------|------------------------------------------------|
|
| 612 |
+
| ``report/colors.py`` | ``reports_v2/_helpers/colors.py`` |
|
| 613 |
+
| ``report/render_helpers.py`` | ``reports_v2/_helpers/render_helpers.py`` |
|
| 614 |
+
| ``report/assets.py`` + ``vendor/`` | ``reports_v2/_helpers/assets.py`` + ``vendor/``|
|
| 615 |
+
| ``report/glossary/{fr,en}.yaml`` | ``reports_v2/glossary/{fr,en}.yaml`` |
|
| 616 |
+
| ``report/i18n/{fr,en}.json`` | ``reports_v2/i18n/{fr,en}.json`` |
|
| 617 |
+
|
| 618 |
+
``report/diff_utils.py`` redirige désormais directement vers
|
| 619 |
+
``picarones.evaluation`` (au lieu du double-shim via
|
| 620 |
+
``core.diff_utils``).
|
| 621 |
+
|
| 622 |
+
**Shims** : tous les chemins legacy ``report/X`` restent disponibles
|
| 623 |
+
via des shims minimaux (< 25 lignes) avec ``DeprecationWarning``
|
| 624 |
+
à l'import.
|
| 625 |
+
|
| 626 |
+
**Adaptations transverses** :
|
| 627 |
+
|
| 628 |
+
- ``picarones/i18n.py`` : ``_I18N_DIR`` pointe désormais vers
|
| 629 |
+
``reports_v2/i18n/``.
|
| 630 |
+
- 22 renderers ``report/*_render.py`` migrés sur leurs imports
|
| 631 |
+
internes vers ``picarones.reports_v2._helpers.*``.
|
| 632 |
+
- 28 fichiers de tests mis à jour (chemins ``picarones/report/i18n/*``
|
| 633 |
+
remplacés par ``picarones/reports_v2/i18n/*``).
|
| 634 |
+
- ``test_layer_dependencies.py::EXTERNAL_ALLOWED["reports_v2"]``
|
| 635 |
+
étendu à ``PIL`` (Pillow utilisé par ``_helpers/assets.py``
|
| 636 |
+
pour le redimensionnement d'images).
|
| 637 |
+
- ``test_file_budgets.py`` : entrée ``report/render_helpers.py``
|
| 638 |
+
remplacée par ``reports_v2/_helpers/render_helpers.py``
|
| 639 |
+
(budget 480 inchangé).
|
| 640 |
+
|
| 641 |
+
**Acceptance Phase 5.A+B** : 5019 tests passent, lint vert,
|
| 642 |
+
architecture vérifiée (anti-cycles, file budgets). Aucune
|
| 643 |
+
régression sur les renderers thématiques (toujours legacy).
|
| 644 |
+
|
| 645 |
+
#### Phase 5.C.batch1 — Lot 1 : 5 renderers les plus petits (2026-05)
|
| 646 |
+
|
| 647 |
+
Première vague de migration des 22 renderers thématiques. On
|
| 648 |
+
relocalise verbatim, sans toucher au contrat avec
|
| 649 |
+
``BenchmarkResult`` legacy — la convergence avec ``RunResult``
|
| 650 |
+
canonique reste un sprint à part entière (5.D ou 5.E selon
|
| 651 |
+
priorité).
|
| 652 |
+
|
| 653 |
+
Convention de nommage : ``picarones.report.<theme>_render`` →
|
| 654 |
+
``picarones.reports_v2.html.renderers.<theme>``. Le suffixe
|
| 655 |
+
``_render`` est retiré (déjà implicite dans la position).
|
| 656 |
+
|
| 657 |
+
**Migrations effectuées** :
|
| 658 |
+
|
| 659 |
+
| Source legacy | Destination canonique |
|
| 660 |
+
|------------------------------------------|------------------------------------------------------|
|
| 661 |
+
| ``report/searchability_render.py`` (103) | ``reports_v2/html/renderers/searchability.py`` |
|
| 662 |
+
| ``report/specialization_render.py`` (113)| ``reports_v2/html/renderers/specialization.py`` |
|
| 663 |
+
| ``report/marginal_cost_render.py`` (111) | ``reports_v2/html/renderers/marginal_cost.py`` |
|
| 664 |
+
| ``report/rare_token_recall_render.py`` (116)| ``reports_v2/html/renderers/rare_token_recall.py``|
|
| 665 |
+
| ``report/readability_render.py`` (126) | ``reports_v2/html/renderers/readability.py`` |
|
| 666 |
+
|
| 667 |
+
Total : ~569 lignes relocalisées. Les chemins ``report/X_render.py``
|
| 668 |
+
deviennent des shims minimaux (< 20 lignes) avec
|
| 669 |
+
``DeprecationWarning``.
|
| 670 |
+
|
| 671 |
+
**Adaptations transverses** :
|
| 672 |
+
|
| 673 |
+
- ``reports_v2/html/renderers/specialization.py`` import canonique
|
| 674 |
+
``picarones.evaluation.metrics.specialization`` (au lieu du shim
|
| 675 |
+
legacy ``picarones.measurements.specialization``) pour respecter
|
| 676 |
+
la règle layer-dependencies (interdiction d'importer du legacy
|
| 677 |
+
depuis ``reports_v2/``).
|
| 678 |
+
- ``test_module_coverage.py::TEST_ONLY_BASELINE`` étendu à
|
| 679 |
+
``"specialization"`` : son shim legacy n'a plus de consommateur
|
| 680 |
+
production (le renderer est désormais dans ``reports_v2/``).
|
| 681 |
+
- 3 tests (``test_extra_metrics.py``,
|
| 682 |
+
``test_sprint86_aii5_html.py``,
|
| 683 |
+
``test_sprint87_readability_html.py``,
|
| 684 |
+
``test_sprint89_specialization.py``) mis à jour pour pointer
|
| 685 |
+
vers les nouveaux chemins canoniques.
|
| 686 |
+
- ``picarones/report/generator.py`` mis à jour pour importer les
|
| 687 |
+
5 renderers depuis ``reports_v2/html/renderers/``.
|
| 688 |
+
|
| 689 |
+
**Acceptance batch 1** : 5019 tests passent, lint vert,
|
| 690 |
+
architecture vérifiée.
|
| 691 |
+
|
| 692 |
+
**Reporté aux batches suivants** :
|
| 693 |
+
|
| 694 |
+
- Batch 2 ✅ (cf. ci-dessous) — 5 renderers (45-165 LOC).
|
| 695 |
+
- Batch 3 ✅ (cf. ci-dessous) — 5 renderers (173-222 LOC).
|
| 696 |
+
- Batch 4 ✅ (cf. ci-dessous) — 5 renderers (188-321 LOC).
|
| 697 |
+
- Batch 5 ✅ (cf. ci-dessous) — 5 renderers (148-314 LOC).
|
| 698 |
+
- Batch 6 ✅ (cf. ci-dessous) — 2 renderers (``levers``, ``philological``).
|
| 699 |
+
- Batch 7 ✅ (cf. ci-dessous) — pré-requis migrés
|
| 700 |
+
(``roman_numerals``, ``numerical_sequences``,
|
| 701 |
+
``pipeline_benchmark``, ``pipeline_comparison``,
|
| 702 |
+
``core/pipeline``) puis 2 renderers
|
| 703 |
+
(``numerical_sequences``, ``pipeline``).
|
| 704 |
+
- Phase 5.D ✅ — 5 vues (``views/*.py``).
|
| 705 |
+
- Phase 5.E ✅ — ``generator.py``, ``comparison.py``,
|
| 706 |
+
``snapshot.py``, ``report_data/`` (8 fichiers), templates
|
| 707 |
+
Jinja2 (13 fichiers), ``picarones/i18n.py``.
|
| 708 |
+
|
| 709 |
+
Phase 5 est **terminée** côté ``report/``.
|
| 710 |
+
|
| 711 |
+
**Note sur ``core/pipeline.py``** : la Phase 5.C.batch7 a
|
| 712 |
+
*relocalisé* le ``PipelineRunner`` legacy de ``core/pipeline.py``
|
| 713 |
+
vers ``evaluation/pipeline.py``, mais **n'a pas effectué la
|
| 714 |
+
convergence** avec le canonique ``picarones.pipeline.executor``
|
| 715 |
+
(designs incompatibles : ``BaseModule`` vs ``StepExecutor``,
|
| 716 |
+
payloads bruts vs ``Artifact`` typés, dataclass mutable vs
|
| 717 |
+
Pydantic immutable, ...). L'audit détaillé + sub-plan est dans
|
| 718 |
+
``docs/migration/pipeline-convergence-plan.md``. La
|
| 719 |
+
recommandation est la stratégie « wrapper legacy → canonique »
|
| 720 |
+
(3-4 sessions) qui préserve l'API publique des callers tout en
|
| 721 |
+
unifiant le moteur. Décision sur quand exécuter la convergence
|
| 722 |
+
laissée au prochain sprint dédié.
|
| 723 |
+
|
| 724 |
+
#### Phase 5.C.batch2 — Lot 2 : 5 renderers moyens (2026-05)
|
| 725 |
+
|
| 726 |
+
Deuxième vague. Substitution dans la sélection initiale :
|
| 727 |
+
``numerical_sequences_render`` reporté au batch 3 (sa dépendance
|
| 728 |
+
``measurements/numerical_sequences.py`` dépend elle-même de
|
| 729 |
+
``measurements/roman_numerals.py``, deux modules legacy non
|
| 730 |
+
migrés vers ``evaluation/metrics/`` ; le renderer ne peut donc pas
|
| 731 |
+
les importer depuis le canonique). Remplacé par
|
| 732 |
+
``longitudinal_render`` qui n'a pas de dépendance legacy.
|
| 733 |
+
|
| 734 |
+
**Migrations effectuées** :
|
| 735 |
+
|
| 736 |
+
| Source legacy | Destination canonique |
|
| 737 |
+
|----------------------------------------------|------------------------------------------------------|
|
| 738 |
+
| ``report/difficulty_render.py`` (45) | ``reports_v2/html/renderers/difficulty.py`` |
|
| 739 |
+
| ``report/lexical_modernization_render.py`` (114) | ``reports_v2/html/renderers/lexical_modernization.py`` |
|
| 740 |
+
| ``report/multirun_stability_render.py`` (151)| ``reports_v2/html/renderers/multirun_stability.py`` |
|
| 741 |
+
| ``report/throughput_render.py`` (154) | ``reports_v2/html/renderers/throughput.py`` |
|
| 742 |
+
| ``report/longitudinal_render.py`` (165) | ``reports_v2/html/renderers/longitudinal.py`` |
|
| 743 |
+
|
| 744 |
+
Total : ~629 lignes relocalisées. 5 nouveaux shims minimaux
|
| 745 |
+
(< 20 lignes) avec ``DeprecationWarning``.
|
| 746 |
+
|
| 747 |
+
**Adaptations transverses** :
|
| 748 |
+
|
| 749 |
+
- ``reports_v2/html/renderers/lexical_modernization.py`` import
|
| 750 |
+
canonique ``picarones.evaluation.metrics.lexical_modernization``
|
| 751 |
+
(au lieu du shim legacy ``picarones.measurements.lexical_modernization``).
|
| 752 |
+
- ``test_module_coverage.py::TEST_ONLY_BASELINE`` étendu à
|
| 753 |
+
``"lexical_modernization"`` (même rationale que ``specialization``
|
| 754 |
+
au batch 1).
|
| 755 |
+
- Tests + ``picarones/report/generator.py`` mis à jour pour les
|
| 756 |
+
5 chemins canoniques.
|
| 757 |
+
|
| 758 |
+
**Acceptance batch 2** : 5019 tests passent, lint vert,
|
| 759 |
+
architecture vérifiée.
|
| 760 |
+
|
| 761 |
+
**Cumul Phase 5.C** (batches 1+2) : 10 / 22 renderers migrés
|
| 762 |
+
(~1198 lignes). 12 renderers restants.
|
| 763 |
+
|
| 764 |
+
#### Phase 5.C.batch3 — Lot 3 : 5 renderers moyens (2026-05)
|
| 765 |
+
|
| 766 |
+
Troisième vague. Tous les renderers sélectionnés sont
|
| 767 |
+
**purs sur le contrat** : import depuis ``_helpers/`` uniquement,
|
| 768 |
+
pas de dépendance sur des modules legacy non-migrés.
|
| 769 |
+
|
| 770 |
+
**Migrations effectuées** :
|
| 771 |
+
|
| 772 |
+
| Source legacy | Destination canonique |
|
| 773 |
+
|------------------------------------------------|--------------------------------------------------------|
|
| 774 |
+
| ``report/module_audit_render.py`` (173) | ``reports_v2/html/renderers/module_audit.py`` |
|
| 775 |
+
| ``report/incremental_comparison_render.py`` (201)| ``reports_v2/html/renderers/incremental_comparison.py``|
|
| 776 |
+
| ``report/image_predictive_render.py`` (207) | ``reports_v2/html/renderers/image_predictive.py`` |
|
| 777 |
+
| ``report/error_absorption_render.py`` (210) | ``reports_v2/html/renderers/error_absorption.py`` |
|
| 778 |
+
| ``report/ner_render.py`` (222) | ``reports_v2/html/renderers/ner.py`` |
|
| 779 |
+
|
| 780 |
+
Total : ~1013 lignes relocalisées. 5 nouveaux shims minimaux
|
| 781 |
+
(< 20 lignes) avec ``DeprecationWarning``.
|
| 782 |
+
|
| 783 |
+
**Adaptations transverses** :
|
| 784 |
+
|
| 785 |
+
- Tests + ``picarones/report/generator.py`` mis à jour pour les
|
| 786 |
+
5 chemins canoniques.
|
| 787 |
+
|
| 788 |
+
**Acceptance batch 3** : 5019 tests passent, lint vert,
|
| 789 |
+
architecture vérifiée.
|
| 790 |
+
|
| 791 |
+
**Cumul Phase 5.C** (batches 1+2+3) : 15 / 22 renderers migrés
|
| 792 |
+
(~2211 lignes). 7 renderers restants.
|
| 793 |
+
|
| 794 |
+
#### Phase 5.C.batch4 — Lot 4 : 5 renderers moyens-gros (2026-05)
|
| 795 |
+
|
| 796 |
+
Quatrième vague. Tous les renderers sélectionnés sont **purs sur
|
| 797 |
+
le contrat externe** (import depuis ``_helpers/`` uniquement).
|
| 798 |
+
``robustness_projection`` avait un import lazy interne vers
|
| 799 |
+
``picarones.measurements.robustness_projection`` qui a été redirigé
|
| 800 |
+
vers le canonique ``picarones.evaluation.metrics.robustness_projection``.
|
| 801 |
+
|
| 802 |
+
**Migrations effectuées** :
|
| 803 |
+
|
| 804 |
+
| Source legacy | Destination canonique |
|
| 805 |
+
|------------------------------------------------|--------------------------------------------------------|
|
| 806 |
+
| ``report/stratification_render.py`` (188) | ``reports_v2/html/renderers/stratification.py`` |
|
| 807 |
+
| ``report/baseline_render.py`` (238) | ``reports_v2/html/renderers/baseline.py`` |
|
| 808 |
+
| ``report/inter_engine_render.py`` (245) | ``reports_v2/html/renderers/inter_engine.py`` |
|
| 809 |
+
| ``report/robustness_projection_render.py`` (252) | ``reports_v2/html/renderers/robustness_projection.py``|
|
| 810 |
+
| ``report/calibration_render.py`` (321) | ``reports_v2/html/renderers/calibration.py`` |
|
| 811 |
+
|
| 812 |
+
Total : ~1244 lignes relocalisées. 5 nouveaux shims minimaux
|
| 813 |
+
(< 20 lignes) avec ``DeprecationWarning``.
|
| 814 |
+
|
| 815 |
+
**Adaptations transverses** :
|
| 816 |
+
|
| 817 |
+
- ``test_module_coverage.py::TEST_ONLY_BASELINE`` étendu à
|
| 818 |
+
``"robustness_projection"`` (même rationale que les batches
|
| 819 |
+
précédents).
|
| 820 |
+
- Tests + ``picarones/report/generator.py`` mis à jour pour les
|
| 821 |
+
5 chemins canoniques.
|
| 822 |
+
|
| 823 |
+
**Acceptance batch 4** : 5019 tests passent, lint vert,
|
| 824 |
+
architecture vérifiée.
|
| 825 |
+
|
| 826 |
+
**Cumul Phase 5.C** (batches 1+2+3+4) : 20 / 22 renderers migrés
|
| 827 |
+
(~3455 lignes). 2 renderers restants : ``pipeline_render`` (707 l)
|
| 828 |
+
et ``philological_render`` (595 l) — les XXL — auront leur propre
|
| 829 |
+
batch dédié.
|
| 830 |
+
|
| 831 |
+
#### Phase 5.C.batch5 — Lot 5 : 5 renderers moyens-gros (2026-05)
|
| 832 |
+
|
| 833 |
+
Cinquième vague. Inclut les 3 renderers de la famille
|
| 834 |
+
``taxonomy``, ``worst_lines`` et ``pipeline_dag``. Restera ensuite
|
| 835 |
+
batch 6 (XXL + ``levers``) et la migration des 5 vues
|
| 836 |
+
(``views/*.py``).
|
| 837 |
+
|
| 838 |
+
**Migrations effectuées** :
|
| 839 |
+
|
| 840 |
+
| Source legacy | Destination canonique |
|
| 841 |
+
|-------------------------------------------------|------------------------------------------------------|
|
| 842 |
+
| ``report/taxonomy_intra_doc_render.py`` (148) | ``reports_v2/html/renderers/taxonomy_intra_doc.py`` |
|
| 843 |
+
| ``report/taxonomy_cooccurrence_render.py`` (161)| ``reports_v2/html/renderers/taxonomy_cooccurrence.py``|
|
| 844 |
+
| ``report/worst_lines_render.py`` (164) | ``reports_v2/html/renderers/worst_lines.py`` |
|
| 845 |
+
| ``report/taxonomy_comparison_render.py`` (233) | ``reports_v2/html/renderers/taxonomy_comparison.py`` |
|
| 846 |
+
| ``report/pipeline_dag_render.py`` (314) | ``reports_v2/html/renderers/pipeline_dag.py`` |
|
| 847 |
+
|
| 848 |
+
Total : ~1020 lignes relocalisées.
|
| 849 |
+
|
| 850 |
+
**Adaptations transverses** :
|
| 851 |
+
|
| 852 |
+
- ``reports_v2/html/renderers/worst_lines.py`` :
|
| 853 |
+
- import ``WorstLineEntry`` redirigé vers
|
| 854 |
+
``picarones.evaluation.metrics.worst_lines``
|
| 855 |
+
- import ``compute_char_diff`` redirigé vers
|
| 856 |
+
``picarones.evaluation`` (au lieu de ``picarones.core.diff_utils``,
|
| 857 |
+
rejeté par la règle layer-dependencies sur ``reports_v2``).
|
| 858 |
+
|
| 859 |
+
**Cumul Phase 5.C** (batches 1+2+3+4+5) : 20 + 5 = **25 renderers
|
| 860 |
+
migrés**, soit l'intégralité moins ``pipeline_render`` et
|
| 861 |
+
``philological_render`` (XXL) et ``levers`` (oublié dans le plan
|
| 862 |
+
initial). Reste batch 6 (3 renderers) puis Phase 5.D (5 vues).
|
| 863 |
+
|
| 864 |
+
#### Phase 5.C.batch6 — Lot 6 : levers + philological (2026-05)
|
| 865 |
+
|
| 866 |
+
Sixième vague. Inclut le plus gros renderer non-bloqué
|
| 867 |
+
(``philological``, 527 LOC) et ``levers`` (249 LOC).
|
| 868 |
+
``pipeline_render`` (707 l) reporté à un batch 7 dédié car il
|
| 869 |
+
dépend de ``measurements/pipeline_benchmark`` et
|
| 870 |
+
``measurements/pipeline_comparison`` non encore migrés vers
|
| 871 |
+
``evaluation/`` (rejetés par layer-dependencies).
|
| 872 |
+
``numerical_sequences_render`` (149 l) reporté pour la même
|
| 873 |
+
raison (dépendance vers ``measurements/numerical_sequences``
|
| 874 |
+
qui dépend de ``measurements/roman_numerals``).
|
| 875 |
+
|
| 876 |
+
**Migrations effectuées** :
|
| 877 |
+
|
| 878 |
+
| Source legacy | Destination canonique |
|
| 879 |
+
|---------------------------------------------|------------------------------------------------------|
|
| 880 |
+
| ``report/levers_render.py`` (249) | ``reports_v2/html/renderers/levers.py`` |
|
| 881 |
+
| ``report/philological_render.py`` (527) | ``reports_v2/html/renderers/philological.py`` |
|
| 882 |
+
|
| 883 |
+
Total : ~776 lignes relocalisées.
|
| 884 |
+
|
| 885 |
+
**Adaptations transverses** :
|
| 886 |
+
|
| 887 |
+
- ``test_sprint82_levers.py`` : monkeypatch sur `_FORMATTERS`
|
| 888 |
+
pointe désormais vers le module canonique
|
| 889 |
+
``picarones.reports_v2.html.renderers.levers``.
|
| 890 |
+
- ``test_file_budgets.py`` : entrée
|
| 891 |
+
``report/philological_render.py`` retirée, remplacée par
|
| 892 |
+
``reports_v2/html/renderers/philological.py`` (budget
|
| 893 |
+
inchangé à 700).
|
| 894 |
+
|
| 895 |
+
**Cumul Phase 5.C** (batches 1-6) : 27 / 29 renderers migrés
|
| 896 |
+
(~5232 lignes). 2 renderers restants pour batch 7 :
|
| 897 |
+
``pipeline_render`` (707) et ``numerical_sequences_render`` (149).
|
| 898 |
+
|
| 899 |
+
**Acceptance batch 6** : 5019 tests passent, lint vert,
|
| 900 |
+
architecture vérifiée.
|
| 901 |
+
|
| 902 |
+
#### Phase 5.C.batch7 — Lot 7 : pré-requis + 2 derniers renderers (2026-05)
|
| 903 |
+
|
| 904 |
+
Le batch 7 finalise Phase 5.C en migrant **d'abord** les
|
| 905 |
+
modules de mesure dont dépendent les renderers
|
| 906 |
+
``numerical_sequences`` et ``pipeline`` :
|
| 907 |
+
|
| 908 |
+
| Source legacy | Destination canonique |
|
| 909 |
+
|------------------------------------------------|------------------------------------------------|
|
| 910 |
+
| ``measurements/roman_numerals.py`` (478) | ``evaluation/metrics/roman_numerals.py`` |
|
| 911 |
+
| ``measurements/numerical_sequences.py`` (422) | ``evaluation/metrics/numerical_sequences.py`` |
|
| 912 |
+
| ``measurements/pipeline_benchmark.py`` (367) | ``evaluation/pipeline_benchmark.py`` |
|
| 913 |
+
| ``measurements/pipeline_comparison.py`` (301) | ``evaluation/pipeline_comparison.py`` |
|
| 914 |
+
| ``core/pipeline.py`` (607) | ``evaluation/pipeline.py`` |
|
| 915 |
+
|
| 916 |
+
Puis les 2 derniers renderers :
|
| 917 |
+
|
| 918 |
+
| Source legacy | Destination canonique |
|
| 919 |
+
|------------------------------------------------|------------------------------------------------------|
|
| 920 |
+
| ``report/numerical_sequences_render.py`` (149) | ``reports_v2/html/renderers/numerical_sequences.py`` |
|
| 921 |
+
| ``report/pipeline_render.py`` (707) | ``reports_v2/html/renderers/pipeline.py`` |
|
| 922 |
+
|
| 923 |
+
Total : ~3031 lignes relocalisées dans ce batch. 7 nouveaux
|
| 924 |
+
shims minimaux (< 25 lignes) avec ``DeprecationWarning``.
|
| 925 |
+
|
| 926 |
+
État final de ``picarones/core/``
|
| 927 |
+
---------------------------------
|
| 928 |
+
|
| 929 |
+
Le répertoire ``picarones/core/`` est désormais **entièrement
|
| 930 |
+
constitué de shims** (10 fichiers, tous < 30 lignes). Aucun
|
| 931 |
+
module Cercle 1 réel ne subsiste — les abstractions vivent dans
|
| 932 |
+
``domain/`` (Pydantic immutable) et ``evaluation/`` (riche en
|
| 933 |
+
behavior). ``EXPECTED_CERCLE1`` du test
|
| 934 |
+
``test_public_api.py::TestCercle1IsLean`` est désormais un set
|
| 935 |
+
vide, documentant explicitement que la Phase 1 du retrait du
|
| 936 |
+
legacy est complète au niveau ``core/``.
|
| 937 |
+
|
| 938 |
+
Adaptations transverses
|
| 939 |
+
-----------------------
|
| 940 |
+
|
| 941 |
+
- Imports internes mis à jour entre modules canoniques
|
| 942 |
+
(``evaluation/metrics/numerical_sequences.py`` → canonique
|
| 943 |
+
``roman_numerals``, ``evaluation/pipeline_comparison.py`` →
|
| 944 |
+
canonique ``pipeline_benchmark``, etc.).
|
| 945 |
+
- ``test_module_coverage.py::TEST_ONLY_BASELINE`` étendu à
|
| 946 |
+
``"numerical_sequences"``, ``"numerical_sequences_hooks"``,
|
| 947 |
+
``"pipeline_benchmark"``, ``"pipeline_comparison"``.
|
| 948 |
+
- ``test_file_budgets.py`` : 4 entrées legacy retirées,
|
| 949 |
+
remplacées par les chemins canoniques.
|
| 950 |
+
- ``test_public_api.py::EXPECTED_CERCLE1`` : ``pipeline.py``
|
| 951 |
+
retiré (set désormais vide).
|
| 952 |
+
- ``docs/tutorials/writing-a-pipeline-module.md`` : tous les
|
| 953 |
+
imports mis à jour vers les chemins canoniques.
|
| 954 |
+
|
| 955 |
+
**Cumul Phase 5.C** (batches 1-7) : **29 / 29 renderers migrés**
|
| 956 |
+
(~8263 lignes au total). Phase 5.C est terminée.
|
| 957 |
+
|
| 958 |
+
**Acceptance batch 7** : 5019 tests passent, lint vert,
|
| 959 |
+
architecture vérifiée (anti-cycles, file budgets,
|
| 960 |
+
EXPECTED_CERCLE1 vide).
|
| 961 |
+
|
| 962 |
+
Restantes pour Phase 5
|
| 963 |
+
----------------------
|
| 964 |
+
|
| 965 |
+
- Phase 5.D ✅ — 5 vues (``views/*.py``) migrées vers
|
| 966 |
+
``reports_v2/html/views/``.
|
| 967 |
+
- Phase 5.E : ``generator.py``, ``comparison.py``,
|
| 968 |
+
``snapshot.py``, ``report_data/``, templates Jinja2.
|
| 969 |
+
|
| 970 |
+
#### Phase 5.D — Migration des 5 vues thématiques (2026-05)
|
| 971 |
+
|
| 972 |
+
Phase 5.D migre les 5 vues thématiques (orchestrateurs des
|
| 973 |
+
renderers) vers ``reports_v2/html/views/``. Ces vues prennent un
|
| 974 |
+
``report_data`` dict et composent plusieurs renderers en blocs
|
| 975 |
+
``<details>`` collapsibles, avec adaptive masking.
|
| 976 |
+
|
| 977 |
+
**Migrations effectuées** :
|
| 978 |
+
|
| 979 |
+
| Source legacy | Destination canonique |
|
| 980 |
+
|------------------------------------------------|----------------------------------------------------|
|
| 981 |
+
| ``report/views/__init__.py`` (65) | ``reports_v2/html/views/__init__.py`` |
|
| 982 |
+
| ``report/views/advanced_taxonomy.py`` (245) | ``reports_v2/html/views/advanced_taxonomy.py`` |
|
| 983 |
+
| ``report/views/diagnostics.py`` (247) | ``reports_v2/html/views/diagnostics.py`` |
|
| 984 |
+
| ``report/views/economics.py`` (219) | ``reports_v2/html/views/economics.py`` |
|
| 985 |
+
| ``report/views/pipeline.py`` (237) | ``reports_v2/html/views/pipeline.py`` |
|
| 986 |
+
| ``report/views/robustness.py`` (101) | ``reports_v2/html/views/robustness.py`` |
|
| 987 |
+
|
| 988 |
+
Total : ~1114 lignes relocalisées. 6 nouveaux shims minimaux
|
| 989 |
+
(< 25 lignes) avec ``DeprecationWarning``.
|
| 990 |
+
|
| 991 |
+
**Adaptations transverses** :
|
| 992 |
+
|
| 993 |
+
- 6 imports de modules de mesure dans les vues redirigés vers
|
| 994 |
+
leurs canoniques ``evaluation/metrics/`` :
|
| 995 |
+
``taxonomy_comparison``, ``incremental_comparison``,
|
| 996 |
+
``levers``, ``image_predictive``, ``worst_lines``,
|
| 997 |
+
``throughput``.
|
| 998 |
+
- ``test_module_coverage.py::TEST_ONLY_BASELINE`` étendu de 6
|
| 999 |
+
modules supplémentaires (mêmes raisons que les renderers).
|
| 1000 |
+
- Renderers ``reports_v2/html/renderers/`` cross-référencés
|
| 1001 |
+
par les vues — toujours au canonique.
|
| 1002 |
+
|
| 1003 |
+
**Acceptance Phase 5.D** : 5019 tests passent, lint vert,
|
| 1004 |
+
architecture vérifiée.
|
| 1005 |
+
|
| 1006 |
+
#### Phase 5.E — Migration generator + comparison + snapshot + report_data + templates + i18n (2026-05)
|
| 1007 |
+
|
| 1008 |
+
Phase 5.E finalise Phase 5 en migrant les derniers composants
|
| 1009 |
+
``report/`` :
|
| 1010 |
+
|
| 1011 |
+
**Migrations effectuées** :
|
| 1012 |
+
|
| 1013 |
+
| Source legacy | Destination canonique |
|
| 1014 |
+
|------------------------------------------------|----------------------------------------------------|
|
| 1015 |
+
| ``report/generator.py`` (466) | ``reports_v2/html/generator.py`` |
|
| 1016 |
+
| ``report/comparison.py`` (409) | ``reports_v2/html/comparison.py`` |
|
| 1017 |
+
| ``report/snapshot.py`` (266) | ``reports_v2/html/snapshot.py`` |
|
| 1018 |
+
| ``report/report_data/__init__.py`` (132) | ``reports_v2/html/data/__init__.py`` |
|
| 1019 |
+
| ``report/report_data/_helpers.py`` (30) | ``reports_v2/html/data/_helpers.py`` |
|
| 1020 |
+
| ``report/report_data/documents.py`` (167) | ``reports_v2/html/data/documents.py`` |
|
| 1021 |
+
| ``report/report_data/engines.py`` (103) | ``reports_v2/html/data/engines.py`` |
|
| 1022 |
+
| ``report/report_data/extra_metrics.py`` (272) | ``reports_v2/html/data/extra_metrics.py`` |
|
| 1023 |
+
| ``report/report_data/pareto.py`` (159) | ``reports_v2/html/data/pareto.py`` |
|
| 1024 |
+
| ``report/report_data/scatter.py`` (56) | ``reports_v2/html/data/scatter.py`` |
|
| 1025 |
+
| ``report/report_data/statistics.py`` (216) | ``reports_v2/html/data/statistics.py`` |
|
| 1026 |
+
| ``report/templates/`` (13 fichiers) | ``reports_v2/html/templates/`` (13 fichiers) |
|
| 1027 |
+
| ``picarones/i18n.py`` (124) | ``picarones/reports_v2/i18n/__init__.py`` |
|
| 1028 |
+
| ``report/__init__.py`` (3) | shim re-export |
|
| 1029 |
+
|
| 1030 |
+
Total : ~2400 lignes relocalisées + 13 templates Jinja2 + le
|
| 1031 |
+
loader i18n. Au total **12 nouveaux shims minimaux** (< 25
|
| 1032 |
+
lignes) avec ``DeprecationWarning``.
|
| 1033 |
+
|
| 1034 |
+
**Adaptations transverses** :
|
| 1035 |
+
|
| 1036 |
+
- ``reports_v2/html/snapshot.py`` ne peut pas importer
|
| 1037 |
+
``picarones.__version__`` (interdit par layer-deps) : utilise
|
| 1038 |
+
``importlib.metadata`` avec fallback (idem qu'au Phase 4-ter).
|
| 1039 |
+
- ``reports_v2/html/snapshot.py`` import ``pricing`` redirigé
|
| 1040 |
+
vers le canonique ``evaluation/metrics/pricing``.
|
| 1041 |
+
- ``reports_v2/html/generator.py`` toutes les ~30 imports
|
| 1042 |
+
internes redirigés vers ``reports_v2/html/{data,renderers,
|
| 1043 |
+
views,snapshot}`` et ``evaluation/{statistics,metric_result,
|
| 1044 |
+
benchmark_result}``.
|
| 1045 |
+
- ``reports_v2/html/data/`` : 7 imports vers
|
| 1046 |
+
``measurements/{statistics,difficulty,pricing,marginal_cost,
|
| 1047 |
+
rare_tokens,taxonomy_cooccurrence,taxonomy_intra_doc}``
|
| 1048 |
+
redirigés vers ``evaluation/{statistics,metrics/...}``.
|
| 1049 |
+
- ``reports_v2/html/views/`` : 6 imports vers
|
| 1050 |
+
``measurements/{taxonomy_comparison,incremental_comparison,
|
| 1051 |
+
levers,image_predictive,worst_lines,throughput}`` redirigés
|
| 1052 |
+
vers ``evaluation/metrics/...``.
|
| 1053 |
+
- ``picarones/reports_v2/__init__.py`` : nouveau loader
|
| 1054 |
+
``from picarones.reports_v2.html.generator import ReportGenerator``.
|
| 1055 |
+
- ``test_module_coverage.py::TEST_ONLY_BASELINE`` étendu à 3
|
| 1056 |
+
modules : ``statistics``, ``pricing``, ``difficulty``.
|
| 1057 |
+
- ``test_file_budgets.py`` : 2 entrées legacy retirées,
|
| 1058 |
+
remplacées par les chemins canoniques ; templates dir
|
| 1059 |
+
référencé via ``reports_v2/html/templates/``.
|
| 1060 |
+
- 28+ chemins de templates dans les tests redirigés vers
|
| 1061 |
+
``reports_v2/html/templates/``.
|
| 1062 |
+
- Tests qui faisaient ``from picarones import i18n`` redirigés
|
| 1063 |
+
vers ``from picarones.reports_v2 import i18n`` (le shim ne
|
| 1064 |
+
ré-exporte pas ``_get_labels_cached`` — privé).
|
| 1065 |
+
|
| 1066 |
+
État final de ``picarones/report/``
|
| 1067 |
+
-----------------------------------
|
| 1068 |
+
|
| 1069 |
+
Le répertoire ``picarones/report/`` ne contient désormais
|
| 1070 |
+
**que des shims** (~30 fichiers). Aucun module avec du
|
| 1071 |
+
contenu réel ne subsiste. Le canonique vit intégralement
|
| 1072 |
+
dans ``picarones/reports_v2/html/`` (générateur + renderers
|
| 1073 |
+
+ vues + données + templates + comparaison + snapshot).
|
| 1074 |
+
|
| 1075 |
+
**Acceptance Phase 5.E + Phase 5 entière** : 5019 tests
|
| 1076 |
+
passent, lint vert, architecture vérifiée (anti-cycles,
|
| 1077 |
+
file budgets, module coverage).
|
| 1078 |
+
|
| 1079 |
+
### Phase 6 — Pipelines OCR+LLM (`pipelines/`)
|
| 1080 |
+
|
| 1081 |
+
**Modules** : `pipelines/base.OCRLLMPipeline` (3 modes), `pipelines/
|
| 1082 |
+
over_normalization.detect_over_normalization`.
|
| 1083 |
+
|
| 1084 |
+
**Cible** :
|
| 1085 |
+
|
| 1086 |
+
- Les 3 modes deviennent des `PipelineSpec` YAML composés (OCR
|
| 1087 |
+
adapter → LLM adapter avec `inputs_from`).
|
| 1088 |
+
- `over_normalization` devient une métrique enregistrée dans
|
| 1089 |
+
`evaluation/metrics/over_normalization.py`.
|
| 1090 |
+
|
| 1091 |
+
**Effort** : 3-5 jours.
|
| 1092 |
+
|
| 1093 |
+
**Acceptance** : les 3 callers internes (`web/benchmark_utils.py`,
|
| 1094 |
+
`measurements/runner/document.py`, `fixtures.py`) consomment des
|
| 1095 |
+
`PipelineSpec` YAML rewrite.
|
| 1096 |
+
|
| 1097 |
+
### Phase 7 — Modules officiels (`modules/`)
|
| 1098 |
+
|
| 1099 |
+
**Module** : `modules/alto_text_to_mono_region.TextToAltoMonoRegion`
|
| 1100 |
+
(310 LOC) — baseline TEXT → ALTO.
|
| 1101 |
+
|
| 1102 |
+
**Cible** : `picarones.formats.alto.baseline_reconstruction` ou
|
| 1103 |
+
`picarones.evaluation.projectors.text_to_alto` (selon où la
|
| 1104 |
+
sémantique colle le mieux).
|
| 1105 |
+
|
| 1106 |
+
**Effort** : 1 jour.
|
| 1107 |
+
|
| 1108 |
+
### Phase 8 — Importers (`extras/importers/`)
|
| 1109 |
+
|
| 1110 |
+
**Modules** : `iiif.py`, `gallica.py`, `escriptorium.py`, `_http.py`,
|
| 1111 |
+
`_fallback_log.py`.
|
| 1112 |
+
|
| 1113 |
+
**Cible** : `picarones/adapters/corpus/{iiif,gallica,escriptorium}.py`
|
| 1114 |
+
+ helpers partagés dans `adapters/corpus/_http.py`.
|
| 1115 |
+
|
| 1116 |
+
**Effort** : 3-5 jours.
|
| 1117 |
+
|
| 1118 |
+
### Phase 9 — Web UI riche (`web/`)
|
| 1119 |
+
|
| 1120 |
+
**Modules** : 9 routers (`config`, `engines`, `history`, `home`,
|
| 1121 |
+
`importers`, `normalization`, `reports`, `synthesis`, `system`) +
|
| 1122 |
+
utilitaires (`benchmark_utils.py`, `engine_utils.py`,
|
| 1123 |
+
`corpus_utils.py`, `config_utils.py`, `state.py`, `security.py`,
|
| 1124 |
+
`models.py`, `jobs.py`, `maintenance.py`, `app.py`) + templates
|
| 1125 |
+
Jinja2.
|
| 1126 |
+
|
| 1127 |
+
**Cible** : `picarones/interfaces/web/routers/<router>.py` + utils
|
| 1128 |
+
partagés dans `interfaces/web/_utils/` + templates dans
|
| 1129 |
+
`interfaces/web/templates/`.
|
| 1130 |
+
|
| 1131 |
+
**Effort** : 8-12 jours.
|
| 1132 |
+
|
| 1133 |
+
**Acceptance** : régression sur tous les `tests/web/test_sprint*.py`
|
| 1134 |
+
existants. L'UI riche (sélecteur moteurs dynamique, gallery,
|
| 1135 |
+
stratification, narrative inline, browse corpus) doit produire les
|
| 1136 |
+
mêmes pages HTML.
|
| 1137 |
+
|
| 1138 |
+
### Phase 10 — CLI complète (`cli/`)
|
| 1139 |
+
|
| 1140 |
+
**Commandes** : 13 commandes legacy non couvertes (`metrics`,
|
| 1141 |
+
`engines`, `info`, `demo`, `diagnose`, `economics`, `edition`,
|
| 1142 |
+
`compare`, `import` group, `serve`, `history`, `robustness`,
|
| 1143 |
+
`pipeline` group avec sous-commandes `run` et `compare`).
|
| 1144 |
+
|
| 1145 |
+
**Cible** : `picarones/interfaces/cli/<command>.py`. L'entry point
|
| 1146 |
+
`console_scripts` du `pyproject.toml` doit pointer sur
|
| 1147 |
+
`picarones.interfaces.cli:cli` (à la place de `picarones.cli:cli`).
|
| 1148 |
+
|
| 1149 |
+
**Effort** : 4-6 jours.
|
| 1150 |
+
|
| 1151 |
+
### Phase 11 — Retrait final + release 2.0
|
| 1152 |
+
|
| 1153 |
+
- Suppression des 10 packages legacy.
|
| 1154 |
+
- Suppression des shims `DeprecationWarning` introduits aux phases
|
| 1155 |
+
précédentes.
|
| 1156 |
+
- Mise à jour du `pyproject.toml` (`console_scripts`,
|
| 1157 |
+
`[project.urls]`).
|
| 1158 |
+
- Rédaction du CHANGELOG 2.0 final avec liste exhaustive des
|
| 1159 |
+
breaking changes (les utilisateurs externes ont eu
|
| 1160 |
+
`DeprecationWarning` à chaque phase).
|
| 1161 |
+
- Génération SBOM + signature SLSA Level 3 (cf.
|
| 1162 |
+
`docs/operations/supply-chain.md`).
|
| 1163 |
+
- Bump `_version.py` et tag `v2.0.0`.
|
| 1164 |
+
|
| 1165 |
+
**Effort** : 3-5 jours.
|
| 1166 |
+
|
| 1167 |
+
## Estimation totale
|
| 1168 |
+
|
| 1169 |
+
| Phase | Effort min | Effort max |
|
| 1170 |
+
|-------|------------|------------|
|
| 1171 |
+
| 0 | 2 j | 3 j |
|
| 1172 |
+
| 1 | 5 j | 8 j |
|
| 1173 |
+
| 2 | 5 j | 7 j |
|
| 1174 |
+
| 3 | 8 j | 12 j |
|
| 1175 |
+
| 4 | 23 j | 28 j |
|
| 1176 |
+
| 5 | 12 j | 18 j |
|
| 1177 |
+
| 6 | 3 j | 5 j |
|
| 1178 |
+
| 7 | 1 j | 1 j |
|
| 1179 |
+
| 8 | 3 j | 5 j |
|
| 1180 |
+
| 9 | 8 j | 12 j |
|
| 1181 |
+
| 10 | 4 j | 6 j |
|
| 1182 |
+
| 11 | 3 j | 5 j |
|
| 1183 |
+
| **Total** | **77 j** | **110 j** |
|
| 1184 |
+
|
| 1185 |
+
Soit **3,5 à 5 mois** d'effort focalisé en mode développeur unique.
|
| 1186 |
+
Aucune contrainte de date — on livre quand c'est propre.
|
| 1187 |
+
|
| 1188 |
+
## Stratégie de régression — invariant non négociable
|
| 1189 |
+
|
| 1190 |
+
À chaque phase :
|
| 1191 |
+
|
| 1192 |
+
1. **Avant** : exécuter le harness legacy sur 3 corpus de référence
|
| 1193 |
+
(small / medium / large) → capture des outputs en JSON / HTML
|
| 1194 |
+
bit-for-bit.
|
| 1195 |
+
2. **Pendant** : réécrire la fonctionnalité dans le rewrite.
|
| 1196 |
+
3. **Après** : exécuter le harness rewrite et **diff** vs. snapshot
|
| 1197 |
+
legacy.
|
| 1198 |
+
4. **Tolérance** : explicite par métrique dans
|
| 1199 |
+
`docs/migration/regression-tolerances.md`. Tout écart non
|
| 1200 |
+
tolerance = régression à corriger avant merge.
|
| 1201 |
+
|
| 1202 |
+
Cela évite le piège classique du rewrite : *« ça compile, ça tourne,
|
| 1203 |
+
mais le CER a glissé de 0,002 par doc »*.
|
| 1204 |
+
|
| 1205 |
+
## Anti-bricolage — règles
|
| 1206 |
+
|
| 1207 |
+
1. **Pas de double API** : pendant la migration d'un module, on ne
|
| 1208 |
+
garde **pas** le legacy en parallèle dans le code de production.
|
| 1209 |
+
Soit on importe l'ancien, soit le nouveau. Le harness de
|
| 1210 |
+
régression suffit pour valider.
|
| 1211 |
+
2. **Pas de shim sans date de retrait** : tout `DeprecationWarning`
|
| 1212 |
+
introduit doit être inscrit dans le CHANGELOG avec date de
|
| 1213 |
+
retrait (la 2.0).
|
| 1214 |
+
3. **Pas de TODO dans le code mergé** : un TODO = une issue ouverte
|
| 1215 |
+
référencée par numéro.
|
| 1216 |
+
4. **Pas de copié-collé** : si une logique apparaît dans deux
|
| 1217 |
+
modules, extraire en helper partagé dès la deuxième occurrence.
|
| 1218 |
+
5. **Pas de god-module** : `tests/architecture/test_file_budgets.py`
|
| 1219 |
+
reste l'autorité.
|
| 1220 |
+
|
| 1221 |
+
## Statut
|
| 1222 |
+
|
| 1223 |
+
| Phase | Statut |
|
| 1224 |
+
|-------|--------|
|
| 1225 |
+
| 0 | ✅ Terminée |
|
| 1226 |
+
| 1 | ✅ Partielle (3/9 modules ; les 6 autres → Phase 4-bis) |
|
| 1227 |
+
| 2 | ✅ Terminée (8/8 modules statistics migrés) |
|
| 1228 |
+
| 3 | ✅ Terminée (11 modules narrative + 2 templates + 18 détecteurs migrés) |
|
| 1229 |
+
| 4 | ✅ Partielle (9 modules autonomes/cascade ; 13 modules + 6 modules `core/` + 1 sous-package → Phase 4-bis) |
|
| 1230 |
+
| 4-bis | 🟡 Diagnostic posé, exécution reportée (couplage dicts-string plus complexe que prévu — voir détail Phase 4) |
|
| 1231 |
+
| 5-11 | ⚪ À démarrer |
|
| 1232 |
+
|
| 1233 |
+
**Dernière mise à jour** : 2026-05 (Phase 4-bis tentative + revert + diagnostic).
|
| 1234 |
+
|
| 1235 |
+
**Reste en place suite à la tentative Phase 4-bis** : aliases
|
| 1236 |
+
``TEXT``/``ALTO``/``PAGE`` dans ``domain.artifacts.ArtifactType``
|
| 1237 |
+
(inoffensif) + hook ``_missing_`` pour accepter les valeurs string
|
| 1238 |
+
legacy. Préparation pour la session future qui complétera Phase
|
| 1239 |
+
4-bis.
|
docs/migration/pipeline-convergence-plan.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Audit & sub-plan — Convergence ``PipelineRunner`` legacy ↔ ``PipelineExecutor`` canonique
|
| 2 |
+
|
| 3 |
+
> **Note** : ce document est l'audit demandé en conclusion de Phase 5
|
| 4 |
+
> du plan de retrait du legacy
|
| 5 |
+
> (cf. ``docs/migration/legacy-retirement-plan.md``). Il identifie
|
| 6 |
+
> les différences entre les deux designs de pipeline, inventaire les
|
| 7 |
+
> callers, propose 3 stratégies de convergence et recommande un
|
| 8 |
+
> sub-plan d'exécution.
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## 1. État des lieux
|
| 13 |
+
|
| 14 |
+
Deux designs cohabitent :
|
| 15 |
+
|
| 16 |
+
### 1.A Legacy — ``picarones.evaluation.pipeline`` (ex-``core/pipeline.py``)
|
| 17 |
+
|
| 18 |
+
Sprint 63 (axe B), 607 lignes. Relocalisé en Phase 5.C.batch7
|
| 19 |
+
mais **non refactoré**.
|
| 20 |
+
|
| 21 |
+
**Caractéristiques** :
|
| 22 |
+
|
| 23 |
+
- ``PipelineRunner`` : classe statique avec ``.run(spec, document, initial_inputs) -> PipelineResult``.
|
| 24 |
+
- ``PipelineSpec`` : dataclass mutable.
|
| 25 |
+
- ``PipelineStep`` : dataclass avec ``module: BaseModule`` (instance Python).
|
| 26 |
+
- ``StepResult`` : dataclass avec ``junction_metrics: dict[str, dict[str, Any]]``.
|
| 27 |
+
- ``PipelineResult`` : dataclass avec ``steps: list[StepResult]``.
|
| 28 |
+
- Modules : ``BaseModule`` ABC consommant des **payloads bruts**
|
| 29 |
+
(``{ArtifactType: str | dict | list | ...}``).
|
| 30 |
+
- Évaluation : ``compute_at_junction`` automatique à chaque étape
|
| 31 |
+
contre la GT du document si ``GTLevel`` correspond.
|
| 32 |
+
- Pas de cache d'artefacts.
|
| 33 |
+
- Pas de ``ExecutionPlan`` séparé — résolution implicite des
|
| 34 |
+
inputs au runtime via un bag versionné.
|
| 35 |
+
|
| 36 |
+
### 1.B Canonique — ``picarones.pipeline.executor`` + ``planner`` + ``protocols``
|
| 37 |
+
|
| 38 |
+
Sprints S6-S7-S28, design rewrite ciblé.
|
| 39 |
+
|
| 40 |
+
**Caractéristiques** :
|
| 41 |
+
|
| 42 |
+
- ``PipelineExecutor`` : classe instanciable avec
|
| 43 |
+
``adapter_resolver`` injecté + ``planner`` optionnel +
|
| 44 |
+
``artifact_store`` optionnel.
|
| 45 |
+
- Méthode ``run(spec, document, initial_inputs, context) ->
|
| 46 |
+
PipelineResult`` (compat S7) qui plan-then-execute.
|
| 47 |
+
- Méthode canonique ``run_plan(plan, document, initial_inputs,
|
| 48 |
+
context)`` qui consomme un ``ExecutionPlan`` pré-calculé.
|
| 49 |
+
- ``PipelineSpec`` : Pydantic immutable
|
| 50 |
+
(``picarones.domain.pipeline_spec``), sérialisable YAML.
|
| 51 |
+
- ``PipelineStep`` : Pydantic immutable avec ``adapter_name: str``
|
| 52 |
+
(pas d'instance — résolution applicative).
|
| 53 |
+
- ``ExecutionPlan`` : produit du ``PipelinePlanner`` — porte
|
| 54 |
+
``StepInputBinding`` explicites + ``MetricJunction`` détectées.
|
| 55 |
+
- ``StepResult`` : Pydantic immutable avec
|
| 56 |
+
``produced_artifacts: dict[str, str]`` (map ArtifactType.value →
|
| 57 |
+
Artifact.id).
|
| 58 |
+
- ``PipelineResult`` : Pydantic immutable avec
|
| 59 |
+
``artifacts: tuple[Artifact, ...]``.
|
| 60 |
+
- Adapters : ``StepExecutor`` Protocol (runtime-checkable)
|
| 61 |
+
consommant des **``Artifact`` typés**
|
| 62 |
+
(``{ArtifactType: Artifact(uri, content_hash, provenance)}``).
|
| 63 |
+
- Cache d'artefacts via ``ArtifactCachePort`` (Sprint S29 + S47).
|
| 64 |
+
- ``RunContext`` Pydantic injecté à chaque ``execute()`` —
|
| 65 |
+
document_id, code_version, pipeline_name, workspace_uri.
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## 2. Différences API détaillées
|
| 70 |
+
|
| 71 |
+
| Dimension | Legacy (``evaluation.pipeline``) | Canonique (``pipeline.executor``) |
|
| 72 |
+
|----------------------------|------------------------------------------|-----------------------------------------------|
|
| 73 |
+
| Construction | classe statique | instance avec deps injectées |
|
| 74 |
+
| Spec | dataclass mutable | Pydantic immutable, YAML-sérialisable |
|
| 75 |
+
| Step | porte ``module: BaseModule`` | porte ``adapter_name: str`` |
|
| 76 |
+
| Résolution adapters | implicite (instance dans spec) | explicite (``adapter_resolver`` callable) |
|
| 77 |
+
| Résolution inputs | implicite (last-producer-wins) | explicite (``StepInputBinding``) |
|
| 78 |
+
| Validation spec | au runtime | au planning (``PipelinePlanner``) |
|
| 79 |
+
| Type passé aux modules | payload brut (str, dict, list…) | ``Artifact`` typé |
|
| 80 |
+
| Provenance | absente | ``ProvenanceRecord`` automatique |
|
| 81 |
+
| Hash de contenu | absent | ``Artifact.content_hash`` SHA-256 |
|
| 82 |
+
| Cache inter-runs | absent | ``ArtifactCachePort`` |
|
| 83 |
+
| ``RunContext`` | absent | injecté à chaque step |
|
| 84 |
+
| Évaluation auto vs GT | oui, à chaque step | non (sortie : artefacts seulement) |
|
| 85 |
+
| ``junction_metrics`` | dans ``StepResult`` | absent du runtime, calculé à part |
|
| 86 |
+
| Représentation des étapes | objets Python uniquement | YAML + Python |
|
| 87 |
+
|
| 88 |
+
---
|
| 89 |
+
|
| 90 |
+
## 3. Inventaire des callers
|
| 91 |
+
|
| 92 |
+
### 3.A Legacy (``evaluation.pipeline``)
|
| 93 |
+
|
| 94 |
+
**Production** (4 fichiers) :
|
| 95 |
+
|
| 96 |
+
- ``picarones/__init__.py`` — re-export de ``PipelineRunner``,
|
| 97 |
+
``PipelineSpec``, ``PipelineStep``, ``StepResult``,
|
| 98 |
+
``PipelineResult`` dans l'API publique.
|
| 99 |
+
- ``picarones/evaluation/pipeline_benchmark.py`` — orchestre
|
| 100 |
+
l'exécution corpus-wide via ``PipelineRunner.run()``.
|
| 101 |
+
- ``picarones/evaluation/pipeline_comparison.py`` — compare N
|
| 102 |
+
``PipelineSpec`` via ``run_pipeline_benchmark``.
|
| 103 |
+
- ``picarones/measurements/pipeline_spec_loader.py`` — charge des
|
| 104 |
+
YAML legacy en ``PipelineSpec`` + ``PipelineStep`` legacy
|
| 105 |
+
(avec instanciation des modules par ``adapter_name``).
|
| 106 |
+
|
| 107 |
+
**Tests** : 7 fichiers de tests directs (``test_sprint63_*``,
|
| 108 |
+
``test_sprint64_*``, ``test_sprint65_*``, ``test_sprint66_*``,
|
| 109 |
+
``test_sprint67_*``, ``test_sprint68_*``, etc.).
|
| 110 |
+
|
| 111 |
+
### 3.B Canonique (``pipeline.executor``)
|
| 112 |
+
|
| 113 |
+
**Production** : 0 caller production (le rewrite n'a pas encore
|
| 114 |
+
de service applicatif qui consomme l'executor canonique en
|
| 115 |
+
mode mono-document).
|
| 116 |
+
|
| 117 |
+
**Tests** : 9 fichiers de tests directs. Tests-only à ce jour.
|
| 118 |
+
|
| 119 |
+
**Conclusion sur les callers** : le legacy est en production,
|
| 120 |
+
le canonique est test-only. La convergence doit migrer le
|
| 121 |
+
legacy **sans casser les 7+4 = 11 fichiers tests/prod
|
| 122 |
+
existants**.
|
| 123 |
+
|
| 124 |
+
---
|
| 125 |
+
|
| 126 |
+
## 4. Stratégies de convergence
|
| 127 |
+
|
| 128 |
+
### 4.A Wrapper legacy → canonique
|
| 129 |
+
|
| 130 |
+
Le legacy ``PipelineRunner.run(spec, document, initial_inputs)``
|
| 131 |
+
devient un **adaptateur** qui :
|
| 132 |
+
|
| 133 |
+
1. Convertit la ``PipelineSpec`` legacy (dataclass + module
|
| 134 |
+
instance) en ``PipelineSpec`` canonique (Pydantic +
|
| 135 |
+
adapter_name).
|
| 136 |
+
2. Wrappe chaque ``BaseModule`` en ``StepExecutor`` Protocol.
|
| 137 |
+
3. Convertit les payloads bruts en ``Artifact`` (uri inline,
|
| 138 |
+
content_hash calculé).
|
| 139 |
+
4. Injecte un ``adapter_resolver`` ad hoc qui retourne les
|
| 140 |
+
wrappers.
|
| 141 |
+
5. Invoque ``PipelineExecutor.run(spec, document, initial_inputs,
|
| 142 |
+
context)``.
|
| 143 |
+
6. Reconvertit le ``PipelineResult`` canonique en ``PipelineResult``
|
| 144 |
+
legacy avec ``junction_metrics`` calculées à partir des
|
| 145 |
+
artefacts produits.
|
| 146 |
+
|
| 147 |
+
**Avantages** :
|
| 148 |
+
- Préserve l'API legacy → 0 caller cassé en production.
|
| 149 |
+
- Unifie le moteur d'exécution → 1 seul code path à maintenir.
|
| 150 |
+
- Cohérent avec la philosophie "no breaking change for callers".
|
| 151 |
+
|
| 152 |
+
**Inconvénients** :
|
| 153 |
+
- 200-400 LOC de glue (conversion bidirectionnelle de types).
|
| 154 |
+
- Coût de performance : double conversion à chaque step.
|
| 155 |
+
- Le double modèle ``Artifact``/payload reste visible côté
|
| 156 |
+
modules (le wrapper masque mais le concept demeure).
|
| 157 |
+
|
| 158 |
+
**Effort** : 2-3 sessions.
|
| 159 |
+
|
| 160 |
+
### 4.B Migration complète
|
| 161 |
+
|
| 162 |
+
Migrer chaque caller legacy vers l'API canonique :
|
| 163 |
+
|
| 164 |
+
1. ``pipeline_benchmark`` : passe de ``PipelineRunner.run`` à
|
| 165 |
+
``PipelineExecutor.run_plan``. Les ``StepAggregate`` doivent
|
| 166 |
+
accepter la nouvelle structure ``StepResult`` (Pydantic).
|
| 167 |
+
2. ``pipeline_comparison`` : idem.
|
| 168 |
+
3. ``pipeline_spec_loader`` : produit des ``PipelineSpec`` Pydantic
|
| 169 |
+
au lieu de dataclass. Plus de ``module`` instance — juste
|
| 170 |
+
``adapter_name``.
|
| 171 |
+
4. ``__init__.py`` : ré-exporte le canonique.
|
| 172 |
+
5. Tests : 7 fichiers à refactorer (mock adapters → ``StepExecutor``
|
| 173 |
+
Protocol, payloads → ``Artifact``).
|
| 174 |
+
|
| 175 |
+
**Avantages** :
|
| 176 |
+
- 1 seul design. Le legacy disparaît complètement.
|
| 177 |
+
- Pas de glue ni de double conversion.
|
| 178 |
+
- Conforme à la cible architecturale du rewrite.
|
| 179 |
+
|
| 180 |
+
**Inconvénients** :
|
| 181 |
+
- Massive : ~2500 LOC à toucher entre prod + tests.
|
| 182 |
+
- Le contrat des modules tiers (``BaseModule`` → ``StepExecutor``)
|
| 183 |
+
change. Un caller externe (BnF, HF Space) qui utilise
|
| 184 |
+
``PipelineRunner.run`` casse silencieusement.
|
| 185 |
+
- Risque de régression non détectée sur les ~7 tests sprints
|
| 186 |
+
axe B (les fixtures sont volumineuses).
|
| 187 |
+
- Évaluation auto vs GT (legacy : à chaque step) doit être
|
| 188 |
+
ré-implémentée comme une post-étape canonique.
|
| 189 |
+
|
| 190 |
+
**Effort** : 5-7 sessions.
|
| 191 |
+
|
| 192 |
+
### 4.C Cohabitation documentée
|
| 193 |
+
|
| 194 |
+
État actuel. Document explicitement que les deux designs sont
|
| 195 |
+
volontaires. Convergence reportée à un sprint dédié quand un
|
| 196 |
+
caller institutionnel l'exigera (BnF demande un YAML déclaratif
|
| 197 |
+
non-instanciable, ou HF Space veut le cache d'artefacts).
|
| 198 |
+
|
| 199 |
+
**Avantages** :
|
| 200 |
+
- 0 risque de régression maintenant.
|
| 201 |
+
- Permet de continuer le retrait du legacy sur les autres
|
| 202 |
+
paquets (Phases 6-11) sans buter sur ce sujet complexe.
|
| 203 |
+
- Le canonique reste prêt pour le jour où il sera vraiment
|
| 204 |
+
nécessaire.
|
| 205 |
+
|
| 206 |
+
**Inconvénients** :
|
| 207 |
+
- 2 designs à maintenir.
|
| 208 |
+
- L'objectif "core/ vide" du retrait du legacy n'est pas
|
| 209 |
+
totalement atteint : ``evaluation/pipeline.py`` reste un module
|
| 210 |
+
"legacy-style" en cercle 2.
|
| 211 |
+
- Risque que le canonique reste mort-né si personne ne le
|
| 212 |
+
réclame.
|
| 213 |
+
|
| 214 |
+
**Effort** : 0 (juste documentation).
|
| 215 |
+
|
| 216 |
+
---
|
| 217 |
+
|
| 218 |
+
## 5. Recommandation
|
| 219 |
+
|
| 220 |
+
> **Mise à jour 2026-05** : l'utilisateur a précisé que le projet
|
| 221 |
+
> est en stand-by jusqu'à la fin de la migration complète et que
|
| 222 |
+
> la rétrocompat de l'API publique n'est pas une contrainte. Cela
|
| 223 |
+
> élimine l'avantage principal de la stratégie 4.A (wrapper) et
|
| 224 |
+
> rend la stratégie 4.B (migration complète) recommandée :
|
| 225 |
+
|
| 226 |
+
**Stratégie 4.B — Migration complète** est la voie cible.
|
| 227 |
+
|
| 228 |
+
Bénéfices avec contrainte API levée :
|
| 229 |
+
|
| 230 |
+
- 1 seul design final, plus de wrapper interne à maintenir.
|
| 231 |
+
- Le contrat des modules tiers (``BaseModule`` → ``StepExecutor``)
|
| 232 |
+
peut changer sans gérer la rétrocompat.
|
| 233 |
+
- Les ``Artifact`` typés (provenance, content_hash, uri) deviennent
|
| 234 |
+
natifs partout — pas de double conversion.
|
| 235 |
+
|
| 236 |
+
Risques résiduels :
|
| 237 |
+
|
| 238 |
+
- ~2500 LOC à toucher entre prod + tests.
|
| 239 |
+
- L'évaluation auto vs GT (legacy : à chaque step) doit être
|
| 240 |
+
ré-implémentée comme une post-étape canonique.
|
| 241 |
+
- Risque de régression sur les ~7 tests sprints axe B
|
| 242 |
+
(fixtures volumineuses).
|
| 243 |
+
- Plusieurs sessions de travail nécessaires (5-7 sessions).
|
| 244 |
+
|
| 245 |
+
---
|
| 246 |
+
|
| 247 |
+
## 6. Découvertes additionnelles (audit complémentaire)
|
| 248 |
+
|
| 249 |
+
L'audit initial parlait de 4 callers de production de
|
| 250 |
+
``PipelineRunner``. Une investigation plus poussée révèle un
|
| 251 |
+
écosystème legacy plus large, qui doit être inclus dans le plan :
|
| 252 |
+
|
| 253 |
+
### 6.A Legacy engines (`picarones/engines/`, ~1500 LOC)
|
| 254 |
+
|
| 255 |
+
5 modules OCR legacy qui héritent de ``BaseOCREngine`` (lui-même
|
| 256 |
+
extension de ``BaseModule``) :
|
| 257 |
+
|
| 258 |
+
- ``engines/base.py:BaseOCREngine``
|
| 259 |
+
- ``engines/tesseract.py:TesseractEngine`` (177 l)
|
| 260 |
+
- ``engines/pero_ocr.py:PeroOCREngine`` (182 l)
|
| 261 |
+
- ``engines/mistral_ocr.py:MistralOCREngine`` (231 l)
|
| 262 |
+
- ``engines/google_vision.py:GoogleVisionEngine`` (256 l)
|
| 263 |
+
- ``engines/azure_doc_intel.py:AzureDocIntelEngine``
|
| 264 |
+
|
| 265 |
+
**Équivalents canoniques existent** dans
|
| 266 |
+
``picarones/adapters/ocr/`` (TesseractAdapter, PeroOCRAdapter,
|
| 267 |
+
etc.) et implémentent déjà ``StepExecutor``. Mais les noms de
|
| 268 |
+
classes et les APIs publiques **diffèrent** — pas un simple shim.
|
| 269 |
+
|
| 270 |
+
Callers production des engines legacy :
|
| 271 |
+
- ``picarones/web/benchmark_utils.py``
|
| 272 |
+
- ``picarones/pipelines/base.py`` (lui-même legacy, Phase 6)
|
| 273 |
+
|
| 274 |
+
### 6.B Legacy LLM (``picarones/llm/``, ~67 LOC)
|
| 275 |
+
|
| 276 |
+
**Déjà migré** : tous les fichiers sont des shims qui
|
| 277 |
+
ré-exportent depuis ``picarones/adapters/llm/``. Rien à faire.
|
| 278 |
+
|
| 279 |
+
### 6.C Legacy modules officiels (``picarones/modules/``)
|
| 280 |
+
|
| 281 |
+
- ``modules/alto_text_to_mono_region.py:TextToAltoMonoRegion``
|
| 282 |
+
(310 LOC) — extension de ``BaseModule``.
|
| 283 |
+
|
| 284 |
+
**Pas d'équivalent canonique** à ce jour. Cible documentée :
|
| 285 |
+
``picarones/formats/alto/baseline_reconstruction.py`` ou
|
| 286 |
+
``picarones/evaluation/projectors/text_to_alto.py``
|
| 287 |
+
(cf. Phase 7 du plan de retrait).
|
| 288 |
+
|
| 289 |
+
### 6.D Sémantique des payloads vs Artifacts
|
| 290 |
+
|
| 291 |
+
La conversion ``BaseModule.process`` ↔ ``StepExecutor.execute``
|
| 292 |
+
n'est pas triviale parce que :
|
| 293 |
+
|
| 294 |
+
- Le legacy passe des **payloads bruts** :
|
| 295 |
+
- ``ArtifactType.IMAGE`` → ``str`` (chemin du fichier image)
|
| 296 |
+
- ``ArtifactType.RAW_TEXT`` → ``str`` (contenu textuel inline)
|
| 297 |
+
- ``ArtifactType.ALTO_XML`` → ``str`` (contenu XML inline)
|
| 298 |
+
- ``ArtifactType.ENTITIES`` → ``list[dict]``
|
| 299 |
+
- Le canonique passe des ``Artifact`` Pydantic immutables :
|
| 300 |
+
- ``uri`` (filesystem ou URI distant)
|
| 301 |
+
- ``content_hash`` (SHA-256)
|
| 302 |
+
- ``provenance`` (``ProvenanceRecord``)
|
| 303 |
+
- **pas de champ ``content`` direct** — le contenu se lit via
|
| 304 |
+
``uri``.
|
| 305 |
+
|
| 306 |
+
Pour les tests legacy qui injectent du contenu inline (mock
|
| 307 |
+
modules retournant ``"hello"``), il faut **soit** :
|
| 308 |
+
|
| 309 |
+
1. Persister le contenu dans un fichier temporaire et pointer
|
| 310 |
+
``artifact.uri`` dessus.
|
| 311 |
+
2. Ajouter une convention ``data:`` URI pour le contenu inline.
|
| 312 |
+
3. Étendre ``Artifact`` avec un champ ``inline_payload: bytes |
|
| 313 |
+
None`` optionnel.
|
| 314 |
+
|
| 315 |
+
Décision recommandée : **option 1** (fichier temporaire), parce
|
| 316 |
+
qu'elle préserve la sémantique « un artefact a toujours un
|
| 317 |
+
identifiant filesystem » et permet le cache/provenance proprement.
|
| 318 |
+
|
| 319 |
+
---
|
| 320 |
+
|
| 321 |
+
## 7. Sub-plan d'exécution révisé (stratégie 4.B)
|
| 322 |
+
|
| 323 |
+
### Sub-phase 7.A — Migration des adapters concrets
|
| 324 |
+
|
| 325 |
+
Bouclage de la migration des adapters legacy (engines/llm/modules)
|
| 326 |
+
vers les canoniques avant de toucher aux pipeline runners.
|
| 327 |
+
|
| 328 |
+
**Étapes** :
|
| 329 |
+
|
| 330 |
+
1. ``engines/`` → shims pointant vers ``adapters/ocr/`` (avec
|
| 331 |
+
alias de classes : ``TesseractEngine = TesseractAdapter``,
|
| 332 |
+
etc.).
|
| 333 |
+
2. Mise à jour des callers de ``engines/`` à utiliser
|
| 334 |
+
``adapters/ocr/`` directement.
|
| 335 |
+
3. ``modules/alto_text_to_mono_region.py`` → migré vers
|
| 336 |
+
``picarones/evaluation/projectors/text_to_alto.py`` (canonique
|
| 337 |
+
en ``StepExecutor``).
|
| 338 |
+
4. Suppression du shim ``engines/``.
|
| 339 |
+
|
| 340 |
+
**Effort** : 2-3 sessions.
|
| 341 |
+
|
| 342 |
+
### Sub-phase 7.B — Migration des callers ``PipelineRunner``
|
| 343 |
+
|
| 344 |
+
Une fois les adapters unifiés sur ``StepExecutor`` :
|
| 345 |
+
|
| 346 |
+
1. ``pipeline_spec_loader`` : produit des ``picarones.domain.pipeline_spec.PipelineSpec``
|
| 347 |
+
(Pydantic) avec ``adapter_name: str`` au lieu d'instances.
|
| 348 |
+
2. ``pipeline_benchmark`` : consomme ``PipelineExecutor.run_plan``.
|
| 349 |
+
``StepAggregate`` accepte ``StepResult`` Pydantic canonique.
|
| 350 |
+
3. ``pipeline_comparison`` : idem.
|
| 351 |
+
4. ``__init__.py`` : ré-exporte les canoniques.
|
| 352 |
+
|
| 353 |
+
**Effort** : 2 sessions.
|
| 354 |
+
|
| 355 |
+
### Sub-phase 7.C — Refactor des tests
|
| 356 |
+
|
| 357 |
+
Les 7 fichiers de tests legacy axe B (sprints 63-68 + 95) :
|
| 358 |
+
|
| 359 |
+
- Mocks ``BaseModule`` → mocks ``StepExecutor`` Protocol.
|
| 360 |
+
- Payloads bruts → ``Artifact`` (avec helper
|
| 361 |
+
``make_inline_artifact(content, type_)`` pour réduire le
|
| 362 |
+
boilerplate).
|
| 363 |
+
- ``Document`` legacy → ``DocumentRef`` canonique.
|
| 364 |
+
- Fixtures ``junction_metrics`` → ré-implémentation via
|
| 365 |
+
post-étape canonique.
|
| 366 |
+
|
| 367 |
+
**Effort** : 1-2 sessions.
|
| 368 |
+
|
| 369 |
+
### Sub-phase 7.D — Suppression du legacy
|
| 370 |
+
|
| 371 |
+
1. Suppression de ``evaluation/pipeline.PipelineRunner``,
|
| 372 |
+
``PipelineSpec``, ``PipelineStep``, ``StepResult``,
|
| 373 |
+
``PipelineResult`` (le legacy).
|
| 374 |
+
2. Suppression de ``domain/module_protocol.BaseModule``.
|
| 375 |
+
3. Le module ``evaluation/pipeline.py`` réduit à
|
| 376 |
+
``_artifact_type_to_gt_level`` ou supprimé totalement.
|
| 377 |
+
4. ``core/pipeline.py`` (shim) supprimé.
|
| 378 |
+
5. ``core/modules.py`` (shim) supprimé.
|
| 379 |
+
|
| 380 |
+
**Effort** : 0.5 session (suppression mécanique).
|
| 381 |
+
|
| 382 |
+
---
|
| 383 |
+
|
| 384 |
+
## 8. Total effort révisé (stratégie 4.B)
|
| 385 |
+
|
| 386 |
+
| Sub-phase | Description | Effort |
|
| 387 |
+
|-----------|--------------------------------------------|------------------|
|
| 388 |
+
| 7.A | Migration adapters concrets | 2-3 sessions |
|
| 389 |
+
| 7.B | Migration callers PipelineRunner | 2 sessions |
|
| 390 |
+
| 7.C | Refactor des tests | 1-2 sessions |
|
| 391 |
+
| 7.D | Suppression du legacy | 0.5 session |
|
| 392 |
+
| **Total** | **Migration complète** | **5-8 sessions** |
|
| 393 |
+
|
| 394 |
+
---
|
| 395 |
+
|
| 396 |
+
## 9. Ordre d'exécution recommandé
|
| 397 |
+
|
| 398 |
+
L'ordre **bottom-up** est plus sûr : à chaque étape, les tests
|
| 399 |
+
restent verts.
|
| 400 |
+
|
| 401 |
+
```
|
| 402 |
+
Sub-phase 7.A (adapters) → Sub-phase 7.B (orchestration) →
|
| 403 |
+
Sub-phase 7.C (tests) → Sub-phase 7.D (suppression)
|
| 404 |
+
```
|
| 405 |
+
|
| 406 |
+
L'ordre **top-down** (start by removing PipelineRunner, then
|
| 407 |
+
fix everything that breaks) est plus risqué mais plus rapide
|
| 408 |
+
si on accepte une période de tests rouges.
|
| 409 |
+
|
| 410 |
+
Recommandation : **bottom-up**, par étapes verticales testables.
|
docs/migration/regression-tolerances.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tolérances de régression — legacy ↔ rewrite
|
| 2 |
+
|
| 3 |
+
> **Audience** : développeur qui migre une fonctionnalité legacy
|
| 4 |
+
> vers le rewrite, reviewer qui relit la PR.
|
| 5 |
+
>
|
| 6 |
+
> **Référence** : [`legacy-retirement-plan.md`](legacy-retirement-plan.md).
|
| 7 |
+
>
|
| 8 |
+
> **Contrat** : le harness `tests/regression/legacy_vs_rewrite/`
|
| 9 |
+
> exécute legacy + rewrite sur les mêmes corpus de référence et
|
| 10 |
+
> compare leurs sorties. Toute divergence au-delà de la tolérance
|
| 11 |
+
> ε définie ici est une **régression à corriger avant merge**.
|
| 12 |
+
>
|
| 13 |
+
> Une régression peut être :
|
| 14 |
+
>
|
| 15 |
+
> - **Intentionnelle** : la phase de migration corrige un bug
|
| 16 |
+
> historique → la tolérance est temporairement relâchée AVEC
|
| 17 |
+
> commentaire pointant vers l'issue.
|
| 18 |
+
> - **Inattendue** : c'est ce que ce document est censé empêcher.
|
| 19 |
+
|
| 20 |
+
## Principe général
|
| 21 |
+
|
| 22 |
+
Pour une fonctionnalité donnée, la sortie du rewrite **doit être
|
| 23 |
+
égale** à celle du legacy à la tolérance ε près. L'égalité est :
|
| 24 |
+
|
| 25 |
+
- **Bit-for-bit** quand l'output est déterministe (texte, hash, JSON).
|
| 26 |
+
- **Sémantique** quand l'output structurel a des libertés (ordre des
|
| 27 |
+
éléments d'un set, indentation HTML, ordre des facts narratifs
|
| 28 |
+
équivalents).
|
| 29 |
+
|
| 30 |
+
## Table des tolérances par type d'output
|
| 31 |
+
|
| 32 |
+
### Métriques numériques
|
| 33 |
+
|
| 34 |
+
| Métrique | ε | Justification |
|
| 35 |
+
|----------|---|---------------|
|
| 36 |
+
| `cer_raw`, `cer_nfc`, `cer_caseless`, `cer_diplomatic` | **0** (bit-for-bit) | jiwer est déterministe ; toute différence = changement de pré/post-processing |
|
| 37 |
+
| `wer`, `mer`, `wil` | **0** | idem |
|
| 38 |
+
| `bleu`, `chrf` | **1e-9** | flottants — réordonnancements internes acceptables |
|
| 39 |
+
| `precision`, `recall`, `f1` (NER) | **1e-9** | flottants |
|
| 40 |
+
| `mufi_coverage`, `abbreviation_expansion_score` | **0** | comptage entier sur ensembles fermés |
|
| 41 |
+
| `roman_numerals_accuracy` | **0** | parsing déterministe |
|
| 42 |
+
| `unicode_blocks_accuracy` | **0** | tables Unicode déterministes |
|
| 43 |
+
| `reading_order_f1` (ICDAR 2015) | **1e-9** | algorithme déterministe, flottants |
|
| 44 |
+
| `layout_f1` | **1e-9** | flottants |
|
| 45 |
+
| `confusion_matrix.entries` | **0** | comptage entier |
|
| 46 |
+
| `taxonomy.error_class_*` | **0** | classification déterministe sur règles |
|
| 47 |
+
|
| 48 |
+
### Tests statistiques
|
| 49 |
+
|
| 50 |
+
| Test | ε | Justification |
|
| 51 |
+
|------|---|---------------|
|
| 52 |
+
| Wilcoxon `p_value` | **1e-9** | scipy `wilcoxon` est déterministe à entrée constante |
|
| 53 |
+
| Friedman `chi2`, `p_value` | **1e-9** | idem |
|
| 54 |
+
| Nemenyi (matrice p-values) | **1e-9** | dérivé de Friedman |
|
| 55 |
+
| Bootstrap CI 95 % | **1e-3** | random seed FIXÉ explicitement (cf. `bootstrap.py` du legacy : `seed=42`) ; la tolérance laisse une marge minuscule pour les ré-implémentations qui itéreraient dans un ordre différent à seed identique |
|
| 56 |
+
| Pareto front (set d'engines dominants) | **0** (bit-for-bit en tant qu'ensemble) | dominance Pareto stable sur entrées identiques |
|
| 57 |
+
| CDD (Critical Difference Diagram) coordonnées SVG | **1e-3** sur les positions (px) | rendu Matplotlib peut varier sur des sub-pixels selon backend |
|
| 58 |
+
| Clustering (labels) | **0** sur l'**ensemble** des classes (l'étiquetage interne 0/1/2 peut différer mais la partition doit être identique) | un test custom compare les partitions, pas les labels |
|
| 59 |
+
| Corrélation Spearman / Pearson | **1e-9** | flottants |
|
| 60 |
+
|
| 61 |
+
### Calibration
|
| 62 |
+
|
| 63 |
+
| Output | ε | Justification |
|
| 64 |
+
|--------|---|---------------|
|
| 65 |
+
| ECE, MCE | **1e-9** | flottants, pas d'aléatoire |
|
| 66 |
+
| Reliability diagram (bins, freq, conf) | **0** sur les bins, **1e-9** sur les valeurs | binning déterministe |
|
| 67 |
+
|
| 68 |
+
### Confidences sidecar (S50 sur Tesseract)
|
| 69 |
+
|
| 70 |
+
| Output | ε |
|
| 71 |
+
|--------|---|
|
| 72 |
+
| `tokens[].text` | **0** (string identique) |
|
| 73 |
+
| `tokens[].confidence` | **0** | Tesseract retourne un entier 0-100 ; division exacte par 100 → flottant binairement identique en IEEE-754 |
|
| 74 |
+
| `extractor`, `model_version` | **0** |
|
| 75 |
+
|
| 76 |
+
### HTML (rapport `reports_v2/html/render.py`)
|
| 77 |
+
|
| 78 |
+
Le diff HTML est **structurel**, pas lexical :
|
| 79 |
+
|
| 80 |
+
- Mêmes éléments DOM avec mêmes attributs sémantiques (`data-*`,
|
| 81 |
+
`aria-*`, `id`, `class`).
|
| 82 |
+
- Mêmes valeurs textuelles dans les nœuds de texte.
|
| 83 |
+
- L'**ordre** des sections doit être identique.
|
| 84 |
+
- L'indentation et le whitespace inter-éléments sont **ignorés**.
|
| 85 |
+
- Le contenu d'un `<script>` est comparé après normalisation
|
| 86 |
+
d'espace blanc.
|
| 87 |
+
|
| 88 |
+
Implémenté via une fonction `assert_html_semantically_equal(a, b)`
|
| 89 |
+
qui parse les deux HTML avec `lxml` (ou `html.parser` fallback) et
|
| 90 |
+
compare l'arbre.
|
| 91 |
+
|
| 92 |
+
### CSV (`reports_v2/csv/render.py`)
|
| 93 |
+
|
| 94 |
+
| Output | ε |
|
| 95 |
+
|--------|---|
|
| 96 |
+
| Header row | **0** (identique exact) |
|
| 97 |
+
| Data rows (set non ordonné) | **0** sur l'ensemble |
|
| 98 |
+
| Ordre des lignes | autorisé à différer | les renderers triaient parfois différemment ; seule l'égalité ensembliste est exigée |
|
| 99 |
+
| Format des nombres | **0** (le rewrite formate à 6 décimales `f"{v:.6f}"`) | déterministe |
|
| 100 |
+
|
| 101 |
+
### JSON (`reports_v2/json/render.py`)
|
| 102 |
+
|
| 103 |
+
| Output | ε |
|
| 104 |
+
|--------|---|
|
| 105 |
+
| Bit-for-bit identique | **0** | le rewrite utilise `model_dump(mode="json")` Pydantic + `json.dumps(sort_keys=True, indent=2, ensure_ascii=False)` ; le legacy doit être amené au même contrat dans la phase concernée |
|
| 106 |
+
|
| 107 |
+
### Narrative facts (Phase 3)
|
| 108 |
+
|
| 109 |
+
| Aspect | ε |
|
| 110 |
+
|--------|---|
|
| 111 |
+
| Ensemble des `Fact` produits (par `FactType`) | **0** sur l'ensemble | l'arbitre peut réordonner mais pas inventer ni rater un fact |
|
| 112 |
+
| Payload de chaque fact (les valeurs numériques citées) | **0** (bit-for-bit) | garde-fou anti-hallucination |
|
| 113 |
+
| Templates rendus FR + EN | **0** sur le texte | déterministe par `str.format_map` |
|
| 114 |
+
| Ordre final des facts dans la synthèse | **autorisé à différer** | l'arbitre du rewrite peut choisir un ordre différent si la priorité est respectée — un test custom valide « les facts HIGH apparaissent avant les MEDIUM » plutôt que l'ordre exact |
|
| 115 |
+
|
| 116 |
+
### Rapport HTML — sections legacy spécifiques (Phase 5)
|
| 117 |
+
|
| 118 |
+
Pour chaque renderer migré (calibration, NER, Pareto, narrative,
|
| 119 |
+
philological, etc.), un cas-test de régression dédié vit dans
|
| 120 |
+
`tests/regression/legacy_vs_rewrite/test_phase5_<renderer>.py`.
|
| 121 |
+
Le snapshot legacy est figé en début de phase.
|
| 122 |
+
|
| 123 |
+
## Aléatoire — politique
|
| 124 |
+
|
| 125 |
+
Tout module qui utilise `random` doit :
|
| 126 |
+
|
| 127 |
+
1. Accepter un argument `seed: int` ou utiliser une seed fixée
|
| 128 |
+
explicitement.
|
| 129 |
+
2. Documenter la seed dans son docstring.
|
| 130 |
+
3. Le harness de régression utilise toujours **seed=42**.
|
| 131 |
+
|
| 132 |
+
Modules concernés au legacy :
|
| 133 |
+
|
| 134 |
+
- `measurements/statistics/bootstrap.py` (seed=42)
|
| 135 |
+
- `measurements/runner/workers.py` (pas d'aléatoire — confirmé)
|
| 136 |
+
- `core/results.py` (pas d'aléatoire — confirmé)
|
| 137 |
+
|
| 138 |
+
## Adaptateurs cloud (Mistral, OpenAI, Anthropic, Google, Azure)
|
| 139 |
+
|
| 140 |
+
Les appels réseau ne sont **pas** rejoués pendant la régression —
|
| 141 |
+
le test serait non-déterministe et coûteux. Stratégie :
|
| 142 |
+
|
| 143 |
+
1. Le harness utilise des **fixtures de réponses figées** (JSON
|
| 144 |
+
capturé en local lors de la création du corpus de référence).
|
| 145 |
+
2. Le legacy et le rewrite reçoivent **la même fixture** ; le test
|
| 146 |
+
vérifie que tous deux produisent le même output structurel.
|
| 147 |
+
3. Si une dépendance SDK change la sérialisation (rare), le test
|
| 148 |
+
pète bruyamment et la PR doit re-frigorifier la fixture.
|
| 149 |
+
|
| 150 |
+
Aucune tolérance non triviale n'est nécessaire — l'égalité
|
| 151 |
+
bit-for-bit est tenable parce que l'aléatoire vient du cloud, pas
|
| 152 |
+
du parser.
|
| 153 |
+
|
| 154 |
+
## Procédure d'exception (régression intentionnelle)
|
| 155 |
+
|
| 156 |
+
Quand une migration corrige un bug historique légitime :
|
| 157 |
+
|
| 158 |
+
1. Ouvrir une issue GitHub avec le label `regression-intentional`.
|
| 159 |
+
2. Référencer le numéro d'issue dans le commit qui modifie la
|
| 160 |
+
tolérance.
|
| 161 |
+
3. Ajouter une entrée dans la section *« Régressions intentionnelles
|
| 162 |
+
acceptées »* ci-dessous, **avant** le merge.
|
| 163 |
+
4. La tolérance peut être relâchée temporairement ; au merge, soit
|
| 164 |
+
le snapshot legacy est mis à jour pour refléter le nouveau
|
| 165 |
+
comportement (correct), soit la tolérance reste serrée pour les
|
| 166 |
+
prochaines migrations.
|
| 167 |
+
|
| 168 |
+
## Régressions intentionnelles acceptées
|
| 169 |
+
|
| 170 |
+
| Date | Issue | Phase | Module | Description |
|
| 171 |
+
|------|-------|-------|--------|-------------|
|
| 172 |
+
| (aucune à ce jour) | | | | |
|
| 173 |
+
|
| 174 |
+
## Révisions
|
| 175 |
+
|
| 176 |
+
| Version | Date | Changements |
|
| 177 |
+
|---------|------|-------------|
|
| 178 |
+
| 1.0 | 2026-05 | Création initiale (Phase 0 du plan de retrait legacy) |
|
docs/operations/deployment-institutional.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
| 8 |
> plutôt que sur HuggingFace Space public.
|
| 9 |
>
|
| 10 |
> Pour le déploiement HuggingFace Space ou un usage local rapide,
|
| 11 |
-
> voir [`
|
| 12 |
|
| 13 |
## Pré-requis
|
| 14 |
|
|
|
|
| 8 |
> plutôt que sur HuggingFace Space public.
|
| 9 |
>
|
| 10 |
> Pour le déploiement HuggingFace Space ou un usage local rapide,
|
| 11 |
+
> voir [`how-to/install.md`](../how-to/install.md).
|
| 12 |
|
| 13 |
## Pré-requis
|
| 14 |
|
docs/operations/observability.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Observabilité — Picarones
|
| 2 |
+
|
| 3 |
+
> **Audience** : opérateur (DSI institutionnelle, SRE). Décrit
|
| 4 |
+
> comment instrumenter Picarones pour qu'il soit observable depuis
|
| 5 |
+
> Prometheus, Grafana, Loki, Datadog, etc.
|
| 6 |
+
>
|
| 7 |
+
> Pour la réponse aux incidents, voir [`runbook.md`](runbook.md).
|
| 8 |
+
> Pour le déploiement, voir [`deployment-institutional.md`](deployment-institutional.md).
|
| 9 |
+
|
| 10 |
+
## Principes
|
| 11 |
+
|
| 12 |
+
Picarones expose trois types de signaux :
|
| 13 |
+
|
| 14 |
+
1. **Logs structurés** (stdlib `logging`). Tous les modules
|
| 15 |
+
utilisent `logger = logging.getLogger(__name__)`. Niveaux
|
| 16 |
+
conventionnels : DEBUG, INFO, WARNING, ERROR. Aucun `print` en
|
| 17 |
+
production.
|
| 18 |
+
2. **Audit trail** spécifique : `[audit] <event> <key=value>`
|
| 19 |
+
(par convention). Émis par les endpoints sensibles
|
| 20 |
+
(`POST/DELETE /api/jobs`).
|
| 21 |
+
3. **Endpoints de santé** : `GET /health`, `GET /version`.
|
| 22 |
+
|
| 23 |
+
L'export vers une plateforme observabilité (Prometheus, Datadog, ELK)
|
| 24 |
+
est laissé au déploiement institutionnel — Picarones ne pousse rien
|
| 25 |
+
de lui-même.
|
| 26 |
+
|
| 27 |
+
## Logs structurés
|
| 28 |
+
|
| 29 |
+
### Format recommandé
|
| 30 |
+
|
| 31 |
+
Configurer le root logger en JSON pour l'ingestion automatique :
|
| 32 |
+
|
| 33 |
+
```python
|
| 34 |
+
# /etc/picarones/logging.yaml
|
| 35 |
+
version: 1
|
| 36 |
+
disable_existing_loggers: false
|
| 37 |
+
formatters:
|
| 38 |
+
json:
|
| 39 |
+
format: '{"ts":"%(asctime)s","lvl":"%(levelname)s","logger":"%(name)s","msg":"%(message)s"}'
|
| 40 |
+
handlers:
|
| 41 |
+
stdout:
|
| 42 |
+
class: logging.StreamHandler
|
| 43 |
+
stream: ext://sys.stdout
|
| 44 |
+
formatter: json
|
| 45 |
+
loggers:
|
| 46 |
+
picarones:
|
| 47 |
+
level: INFO
|
| 48 |
+
handlers: [stdout]
|
| 49 |
+
propagate: false
|
| 50 |
+
root:
|
| 51 |
+
level: WARNING
|
| 52 |
+
handlers: [stdout]
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
Activer au démarrage :
|
| 56 |
+
|
| 57 |
+
```bash
|
| 58 |
+
PICARONES_LOG_CONFIG=/etc/picarones/logging.yaml \
|
| 59 |
+
uvicorn picarones.interfaces.web:create_app --factory ...
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
### Niveaux par module
|
| 63 |
+
|
| 64 |
+
| Module | Niveau prod recommandé |
|
| 65 |
+
|--------|------------------------|
|
| 66 |
+
| `picarones.adapters.*` | INFO |
|
| 67 |
+
| `picarones.app.services.*` | INFO |
|
| 68 |
+
| `picarones.interfaces.web.*` | INFO |
|
| 69 |
+
| `picarones.pipeline.*` | INFO (DEBUG si chasse à un bug d'orchestration) |
|
| 70 |
+
| `picarones.evaluation.*` | WARNING (très verbeux en INFO) |
|
| 71 |
+
| `picarones.adapters._retry` | WARNING (déjà bavard sur les retries) |
|
| 72 |
+
|
| 73 |
+
### Exemples de lignes utiles à monitorer
|
| 74 |
+
|
| 75 |
+
| Pattern | Signification | Alerte |
|
| 76 |
+
|---------|---------------|--------|
|
| 77 |
+
| `[adapter] erreur retryable.*` | Cloud API instable | > 10/min sur 5 min → page |
|
| 78 |
+
| `OCRAdapterError` | Échec définitif d'OCR | > 5/min → warning |
|
| 79 |
+
| `[job_runner] job .* en échec` | Job s'est terminé en error | track per-IP |
|
| 80 |
+
| `[audit] job_submitted` | Soumission de job | tracker pour audit RGPD |
|
| 81 |
+
| `[audit] job_cancelled` | Annulation de job | tracker pour audit RGPD |
|
| 82 |
+
| `WinError 87` | Filename Windows invalide | DEVRAIT être 0 (corrigé S59) — sinon régression |
|
| 83 |
+
| `database is locked` | SQLite contention | > 1/min → page |
|
| 84 |
+
|
| 85 |
+
## Audit trail
|
| 86 |
+
|
| 87 |
+
Les opérations sensibles produisent un log INFO normalisé :
|
| 88 |
+
|
| 89 |
+
```
|
| 90 |
+
INFO [audit] job_submitted job_id=abc123 corpus=bnf_xviii from=10.0.0.42
|
| 91 |
+
INFO [audit] job_cancelled job_id=abc123 from=10.0.0.42
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
Ces lignes sont **destinées à être conservées** selon la politique
|
| 95 |
+
RGPD de l'institution (cf. [`data-retention-rgpd.md`](data-retention-rgpd.md)).
|
| 96 |
+
Stockage minimum recommandé : 90 jours (audit interne) ; 5 ans si
|
| 97 |
+
soumis aux Archives nationales.
|
| 98 |
+
|
| 99 |
+
Pour ingestion SIEM :
|
| 100 |
+
|
| 101 |
+
```
|
| 102 |
+
filter '[audit] '
|
| 103 |
+
extract job_id, corpus, from
|
| 104 |
+
forward to siem.bnf.fr:514 (syslog)
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
## Endpoints de santé
|
| 108 |
+
|
| 109 |
+
### `GET /health`
|
| 110 |
+
|
| 111 |
+
Réponse `200 OK` si le process est en mesure de servir. Vérifie :
|
| 112 |
+
|
| 113 |
+
- `JobStore` accessible (lecture)
|
| 114 |
+
- `WorkspaceManager` accessible (écriture sandbox)
|
| 115 |
+
- Pas de check sur les API cloud (un cloud down ne doit pas planter
|
| 116 |
+
les health probes locales)
|
| 117 |
+
|
| 118 |
+
```json
|
| 119 |
+
{
|
| 120 |
+
"status": "ok",
|
| 121 |
+
"version": "1.3.0-dev",
|
| 122 |
+
"job_store": "ok",
|
| 123 |
+
"workspace": "ok"
|
| 124 |
+
}
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
À utiliser comme **liveness probe** (Kubernetes) ou **healthcheck**
|
| 128 |
+
(Docker). Recommandation : every 30s, fail after 3 consecutive.
|
| 129 |
+
|
| 130 |
+
### `GET /version`
|
| 131 |
+
|
| 132 |
+
Réponse :
|
| 133 |
+
|
| 134 |
+
```json
|
| 135 |
+
{
|
| 136 |
+
"version": "1.3.0-dev",
|
| 137 |
+
"code_version": "git-sha-abc1234",
|
| 138 |
+
"python": "3.11.15"
|
| 139 |
+
}
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
Utile pour déterminer la version déployée sans accès au filesystem.
|
| 143 |
+
|
| 144 |
+
## Métriques (à venir)
|
| 145 |
+
|
| 146 |
+
Picarones n'expose pas encore d'endpoint Prometheus `/metrics`.
|
| 147 |
+
Recommandation immédiate : monitorer les logs.
|
| 148 |
+
|
| 149 |
+
**Backlog** (cf. [`/docs/roadmap/backlog.md`](../roadmap/backlog.md)) :
|
| 150 |
+
|
| 151 |
+
- Compteur `picarones_jobs_total{status="complete|error|cancelled"}`
|
| 152 |
+
- Histogramme `picarones_job_duration_seconds`
|
| 153 |
+
- Compteur `picarones_adapter_calls_total{adapter, status}`
|
| 154 |
+
- Histogramme `picarones_adapter_latency_seconds{adapter}`
|
| 155 |
+
- Gauge `picarones_jobs_running` (instantané)
|
| 156 |
+
|
| 157 |
+
Implémentation visée : `prometheus_client` middleware FastAPI optionnel.
|
| 158 |
+
|
| 159 |
+
## Tracing distribué
|
| 160 |
+
|
| 161 |
+
Pour les institutions qui orchestrent Picarones avec d'autres services
|
| 162 |
+
(ETL, cataloguing), le tracing OpenTelemetry est recommandé.
|
| 163 |
+
|
| 164 |
+
État actuel : pas d'instrumentation native. Une instrumentation
|
| 165 |
+
opportuniste via `opentelemetry-instrumentation-fastapi` peut être
|
| 166 |
+
activée par le déploiement sans modifier Picarones :
|
| 167 |
+
|
| 168 |
+
```python
|
| 169 |
+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
| 170 |
+
from picarones.interfaces.web import create_app
|
| 171 |
+
|
| 172 |
+
app = create_app(state=...)
|
| 173 |
+
FastAPIInstrumentor.instrument_app(app)
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
## Dashboards Grafana — squelette
|
| 177 |
+
|
| 178 |
+
Les panels recommandés pour un dashboard Picarones :
|
| 179 |
+
|
| 180 |
+
1. **Jobs throughput** — courbes par status (complete/error/cancelled),
|
| 181 |
+
stack area, 24 h.
|
| 182 |
+
2. **Adapter latency p50/p95/p99** par adapter (Tesseract, Pero,
|
| 183 |
+
Mistral OCR, Google Vision, Azure DI, OpenAI, Anthropic, Mistral
|
| 184 |
+
chat, Ollama).
|
| 185 |
+
3. **Error rate par adapter** — % d'erreurs sur la dernière heure.
|
| 186 |
+
4. **Concurrence** — `picarones_jobs_running` actuel, comparé à
|
| 187 |
+
`PICARONES_MAX_CONCURRENT_JOBS`.
|
| 188 |
+
5. **Workspace size** — `du -sh /var/lib/picarones/workspaces` via
|
| 189 |
+
exporter node.
|
| 190 |
+
6. **Heap RSS** du process Picarones (via node_exporter ou
|
| 191 |
+
process_exporter).
|
| 192 |
+
|
| 193 |
+
## SLOs suggérés
|
| 194 |
+
|
| 195 |
+
Pour un déploiement institutionnel ouvert aux chercheurs :
|
| 196 |
+
|
| 197 |
+
| Métrique | SLO 30j | Action si dépassé |
|
| 198 |
+
|----------|---------|-------------------|
|
| 199 |
+
| Disponibilité `/health` | 99.5 % | Investiguer infra |
|
| 200 |
+
| Job completion rate | > 95 % | Examiner taux d'erreurs adapter |
|
| 201 |
+
| API p95 latency (CRUD jobs) | < 500 ms | Profiler le `JobStore` |
|
| 202 |
+
| Cloud adapter retry rate | < 5 % | Demander quota plus haut |
|
| 203 |
+
|
| 204 |
+
## Révisions
|
| 205 |
+
|
| 206 |
+
| Version | Date | Changements |
|
| 207 |
+
|---------|------|-------------|
|
| 208 |
+
| 1.0 | 2026-05 | Création initiale (S60) |
|
docs/operations/runbook.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Runbook — réponse aux incidents Picarones
|
| 2 |
+
|
| 3 |
+
> **Audience** : opérateur (DSI institutionnelle, SRE) en garde
|
| 4 |
+
> active. Ce document liste les incidents prévisibles et les
|
| 5 |
+
> procédures de mitigation. Pour le déploiement initial, voir
|
| 6 |
+
> [`deployment-institutional.md`](deployment-institutional.md) ;
|
| 7 |
+
> pour l'observabilité, voir [`observability.md`](observability.md).
|
| 8 |
+
>
|
| 9 |
+
> **Convention** : chaque scénario suit le format
|
| 10 |
+
> `Symptôme → Diagnostic → Mitigation → Suivi`.
|
| 11 |
+
|
| 12 |
+
## Index des scénarios
|
| 13 |
+
|
| 14 |
+
| ID | Scénario | Sévérité | Page |
|
| 15 |
+
|----|----------|----------|------|
|
| 16 |
+
| INC-01 | Job stuck en `running` | MAJOR | [§INC-01](#inc-01--job-stuck-en-running) |
|
| 17 |
+
| INC-02 | Disk full sur le workspace | BLOCKER | [§INC-02](#inc-02--disk-full-sur-le-workspace) |
|
| 18 |
+
| INC-03 | Cloud API rate limit / quota dépassé | MAJOR | [§INC-03](#inc-03--cloud-api-rate-limit) |
|
| 19 |
+
| INC-04 | SQLite `database is locked` | MAJOR | [§INC-04](#inc-04--sqlite-database-is-locked) |
|
| 20 |
+
| INC-05 | Memory leak (RSS qui croît continûment) | MAJOR | [§INC-05](#inc-05--memory-leak) |
|
| 21 |
+
| INC-06 | Compromission d'une clé API cloud | BLOCKER | [§INC-06](#inc-06--compromission-de-cl%C3%A9-api) |
|
| 22 |
+
| INC-07 | Rapport HTML corrompu / non-déterministe | MEDIUM | [§INC-07](#inc-07--rapport-html-corrompu) |
|
| 23 |
+
| INC-08 | CI bloquée > 30 min (déjà vu) | MEDIUM | [§INC-08](#inc-08--ci-bloqu%C3%A9e) |
|
| 24 |
+
| INC-09 | Upgrade qui casse les jobs en cours | MAJOR | [§INC-09](#inc-09--upgrade-casse-jobs) |
|
| 25 |
+
| INC-10 | Restauration depuis backup | MEDIUM | [§INC-10](#inc-10--restauration-backup) |
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## INC-01 — Job stuck en `running`
|
| 30 |
+
|
| 31 |
+
**Symptôme**. `GET /api/jobs/{job_id}` retourne `status=running`
|
| 32 |
+
depuis > 1 heure alors que le corpus tient en quelques minutes.
|
| 33 |
+
|
| 34 |
+
**Diagnostic**.
|
| 35 |
+
|
| 36 |
+
```bash
|
| 37 |
+
# 1. Le thread daemon existe-t-il encore ?
|
| 38 |
+
curl -s http://localhost:7860/api/jobs/{job_id} | jq '.status, .progress'
|
| 39 |
+
|
| 40 |
+
# 2. Les logs montrent-ils une activité récente ?
|
| 41 |
+
journalctl -u picarones -n 200 | grep "{job_id}"
|
| 42 |
+
|
| 43 |
+
# 3. Y a-t-il un appel cloud bloqué ?
|
| 44 |
+
ss -tnp | grep :443 # connexions TLS sortantes
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
Causes typiques :
|
| 48 |
+
|
| 49 |
+
- Appel cloud qui hang sans timeout (anciens adapters).
|
| 50 |
+
- Workspace en read-only (impossible d'écrire le résultat).
|
| 51 |
+
- Process daemon mort sans avoir mis à jour le statut.
|
| 52 |
+
|
| 53 |
+
**Mitigation**.
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
# Forcer l'annulation (dégrade en cancelled, pas en error).
|
| 57 |
+
curl -X DELETE http://localhost:7860/api/jobs/{job_id}
|
| 58 |
+
|
| 59 |
+
# Si le service ne répond plus :
|
| 60 |
+
systemctl restart picarones
|
| 61 |
+
# Au boot, le lifespan hook ``mark_orphaned_jobs_interrupted`` bascule
|
| 62 |
+
# automatiquement les jobs ``running`` en ``interrupted``.
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
**Suivi**. Vérifier que le `JobRunner` n'a pas d'autres threads
|
| 66 |
+
zombies via `len(runner._threads)` (devrait redescendre). Si
|
| 67 |
+
récurrent, instrumenter avec un timeout de soft-cap par job.
|
| 68 |
+
|
| 69 |
+
---
|
| 70 |
+
|
| 71 |
+
## INC-02 — Disk full sur le workspace
|
| 72 |
+
|
| 73 |
+
**Symptôme**. Les jobs échouent en `error` avec
|
| 74 |
+
`OSError: [Errno 28] No space left on device`. L'API web peut
|
| 75 |
+
elle-même planter au boot (`JobStore` ne peut plus persister).
|
| 76 |
+
|
| 77 |
+
**Diagnostic**.
|
| 78 |
+
|
| 79 |
+
```bash
|
| 80 |
+
df -h /var/lib/picarones/workspaces # ou le path configuré
|
| 81 |
+
du -sh /var/lib/picarones/workspaces/*
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
Coupable typique : caches d'artefacts non purgés (`InMemoryArtifactStore`
|
| 85 |
+
n'a pas de TTL ; `FilesystemArtifactStore` non plus).
|
| 86 |
+
|
| 87 |
+
**Mitigation**.
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
# 1. Identifier les workspaces les plus gros.
|
| 91 |
+
du -sh /var/lib/picarones/workspaces/* | sort -rh | head -10
|
| 92 |
+
|
| 93 |
+
# 2. Purger les workspaces dont aucun job actif ne dépend (lookup
|
| 94 |
+
# via JobStore).
|
| 95 |
+
sqlite3 /var/lib/picarones/jobs.db \
|
| 96 |
+
"SELECT job_id, status, payload FROM jobs WHERE status NOT IN ('pending', 'running');" \
|
| 97 |
+
| jq -r '.payload | fromjson | .output_dir'
|
| 98 |
+
|
| 99 |
+
# 3. Pour chaque output_dir terminé, archiver puis supprimer.
|
| 100 |
+
tar czf /backup/picarones-archive-$(date +%F).tar.gz <output_dirs>
|
| 101 |
+
rm -rf <output_dirs>
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
**Suivi**. Établir une politique de rétention dans
|
| 105 |
+
[`data-retention-rgpd.md`](data-retention-rgpd.md). Recommandation :
|
| 106 |
+
purger les workspaces > 30 jours sans accès.
|
| 107 |
+
|
| 108 |
+
---
|
| 109 |
+
|
| 110 |
+
## INC-03 — Cloud API rate limit
|
| 111 |
+
|
| 112 |
+
**Symptôme**. Logs WARN : `[adapter] erreur retryable (tentative 3/4,
|
| 113 |
+
attente 8s) : 429 Too Many Requests`. Job se termine en error après
|
| 114 |
+
épuisement des retries.
|
| 115 |
+
|
| 116 |
+
**Diagnostic**.
|
| 117 |
+
|
| 118 |
+
```bash
|
| 119 |
+
# Compter les 429 dans la dernière heure.
|
| 120 |
+
journalctl -u picarones --since "1 hour ago" \
|
| 121 |
+
| grep "429" | wc -l
|
| 122 |
+
|
| 123 |
+
# Identifier les jobs concernés.
|
| 124 |
+
journalctl -u picarones --since "1 hour ago" \
|
| 125 |
+
| grep -B2 "429" | grep "job_runner"
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
Causes typiques : un benchmark de 5000 documents lance 5000 appels
|
| 129 |
+
en parallèle, dépasse la quota de l'organisation cloud.
|
| 130 |
+
|
| 131 |
+
**Mitigation immédiate**.
|
| 132 |
+
|
| 133 |
+
```bash
|
| 134 |
+
# 1. Réduire le parallélisme du runner (env var).
|
| 135 |
+
sed -i 's/PICARONES_RUNNER_MAX_WORKERS=8/PICARONES_RUNNER_MAX_WORKERS=2/' /etc/picarones/.env
|
| 136 |
+
systemctl restart picarones
|
| 137 |
+
|
| 138 |
+
# 2. Re-soumettre les jobs en error qui se sont arrêtés au milieu.
|
| 139 |
+
# (Picarones ne fait pas de resume automatique sur erreur cloud — le
|
| 140 |
+
# cache d'artefacts du PipelineExecutor évite de re-exécuter les
|
| 141 |
+
# steps déjà terminés au prochain run.)
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
**Mitigation long terme**. Demander une quota plus haute au
|
| 145 |
+
fournisseur cloud, ou ajouter un throttle au niveau adapter (token
|
| 146 |
+
bucket par adapter).
|
| 147 |
+
|
| 148 |
+
---
|
| 149 |
+
|
| 150 |
+
## INC-04 — SQLite `database is locked`
|
| 151 |
+
|
| 152 |
+
**Symptôme**. Logs ERROR : `sqlite3.OperationalError: database is
|
| 153 |
+
locked`. Touche typiquement le `JobStore`.
|
| 154 |
+
|
| 155 |
+
**Diagnostic**.
|
| 156 |
+
|
| 157 |
+
```bash
|
| 158 |
+
# 1. Compter les processes qui ont la DB ouverte.
|
| 159 |
+
lsof /var/lib/picarones/jobs.db
|
| 160 |
+
|
| 161 |
+
# 2. Vérifier le mode WAL.
|
| 162 |
+
sqlite3 /var/lib/picarones/jobs.db "PRAGMA journal_mode;"
|
| 163 |
+
# Devrait répondre "wal". Si "delete" ou "rollback", le WAL n'a pas
|
| 164 |
+
# pris.
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
Causes : un process autre que Picarones a ouvert la DB (backup
|
| 168 |
+
maladroit), ou le filesystem ne supporte pas WAL (FAT32, NFS sans
|
| 169 |
+
verrous).
|
| 170 |
+
|
| 171 |
+
**Mitigation**.
|
| 172 |
+
|
| 173 |
+
```bash
|
| 174 |
+
# 1. Stopper l'autre process si identifié.
|
| 175 |
+
# 2. Si NFS : remonter avec ``-o nolock`` côté serveur ne marche PAS
|
| 176 |
+
# (WAL exige des verrous). Solution : déplacer ``jobs.db`` sur un
|
| 177 |
+
# filesystem local et exporter le résultat via NFS read-only.
|
| 178 |
+
# 3. Si filesystem ne supporte vraiment pas WAL, le code retombe sur
|
| 179 |
+
# ``rollback journal`` (cf. job_store.py:185-189) — fonctionnel
|
| 180 |
+
# mais bloquant en lecture pendant les écritures.
|
| 181 |
+
|
| 182 |
+
# Test de santé.
|
| 183 |
+
sqlite3 /var/lib/picarones/jobs.db "PRAGMA integrity_check;"
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
**Suivi**. Configurer un monitoring du `journal_mode` au boot.
|
| 187 |
+
|
| 188 |
+
---
|
| 189 |
+
|
| 190 |
+
## INC-05 — Memory leak
|
| 191 |
+
|
| 192 |
+
**Symptôme**. RSS du process Picarones croît continûment au-delà
|
| 193 |
+
de 2 GB après plusieurs heures.
|
| 194 |
+
|
| 195 |
+
**Diagnostic**.
|
| 196 |
+
|
| 197 |
+
```bash
|
| 198 |
+
# Profiling minimal sans installer d'outil.
|
| 199 |
+
ps -o pid,rss,cmd -p $(pgrep picarones) | tail -1
|
| 200 |
+
|
| 201 |
+
# Si py-spy disponible :
|
| 202 |
+
py-spy dump --pid $(pgrep picarones)
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
Causes connues :
|
| 206 |
+
|
| 207 |
+
- `JobRunner._threads` non nettoyé (FIXÉ en S58).
|
| 208 |
+
- `RateLimitMiddleware._buckets` non borné (FIXÉ en S58 — eviction LRU).
|
| 209 |
+
- Caches d'artefacts in-memory accumulés (cf. INC-02).
|
| 210 |
+
|
| 211 |
+
**Mitigation**.
|
| 212 |
+
|
| 213 |
+
```bash
|
| 214 |
+
systemctl restart picarones
|
| 215 |
+
# Le lifespan hook nettoie les jobs orphelins ; les caches in-memory
|
| 216 |
+
# sont vidés par redémarrage.
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
**Suivi**. Si récurrent, exporter `picarones._mem_audit` (à
|
| 220 |
+
implémenter — backlog) et corréler avec les jobs actifs.
|
| 221 |
+
|
| 222 |
+
---
|
| 223 |
+
|
| 224 |
+
## INC-06 — Compromission de clé API
|
| 225 |
+
|
| 226 |
+
**Symptôme**. Facturation cloud anormale, ou notification du
|
| 227 |
+
fournisseur (« nous avons détecté une utilisation suspecte de votre
|
| 228 |
+
clé »).
|
| 229 |
+
|
| 230 |
+
**Mitigation immédiate** (dans l'ordre).
|
| 231 |
+
|
| 232 |
+
```bash
|
| 233 |
+
# 1. Révoquer la clé chez le fournisseur (console cloud).
|
| 234 |
+
# 2. Stopper Picarones pour éviter qu'il ne tente de relancer avec
|
| 235 |
+
# la clé invalidée.
|
| 236 |
+
systemctl stop picarones
|
| 237 |
+
# 3. Rotater la clé dans le secret store.
|
| 238 |
+
vault kv put secret/picarones OPENAI_API_KEY=sk-NEW...
|
| 239 |
+
# 4. Reload + redémarrage.
|
| 240 |
+
systemctl start picarones
|
| 241 |
+
# 5. Audit des jobs récents pour identifier les exfiltrations.
|
| 242 |
+
sqlite3 /var/lib/picarones/jobs.db \
|
| 243 |
+
"SELECT job_id, payload, created_at FROM jobs ORDER BY created_at DESC LIMIT 100;"
|
| 244 |
+
```
|
| 245 |
+
|
| 246 |
+
**Suivi**. Notifier le DPO institutionnel sous 24 h si des
|
| 247 |
+
documents avec PII (registres, état civil) ont été envoyés à l'API
|
| 248 |
+
compromise. Voir [`data-retention-rgpd.md`](data-retention-rgpd.md).
|
| 249 |
+
|
| 250 |
+
---
|
| 251 |
+
|
| 252 |
+
## INC-07 — Rapport HTML corrompu
|
| 253 |
+
|
| 254 |
+
**Symptôme**. Deux runs identiques produisent des rapports HTML
|
| 255 |
+
différents byte-for-byte.
|
| 256 |
+
|
| 257 |
+
**Diagnostic**.
|
| 258 |
+
|
| 259 |
+
```bash
|
| 260 |
+
# Comparer les hashes de manifests.
|
| 261 |
+
sha256sum run-A/run_manifest.json run-B/run_manifest.json
|
| 262 |
+
|
| 263 |
+
# Si différents : un des paramètres canoniques a divergé.
|
| 264 |
+
diff <(jq -S . run-A/run_manifest.json) <(jq -S . run-B/run_manifest.json)
|
| 265 |
+
```
|
| 266 |
+
|
| 267 |
+
Causes typiques : un adapter cloud (gpt-4o, claude) qui a une
|
| 268 |
+
température > 0 → non-déterminisme natif. Vérifier les
|
| 269 |
+
`adapter_kwargs` dans le manifest.
|
| 270 |
+
|
| 271 |
+
**Mitigation**. Forcer `temperature: 0.0` dans la `RunSpec` YAML.
|
| 272 |
+
Pour les benchmarks de reproductibilité, exclure les adapters
|
| 273 |
+
non-déterministes.
|
| 274 |
+
|
| 275 |
+
---
|
| 276 |
+
|
| 277 |
+
## INC-08 — CI bloquée
|
| 278 |
+
|
| 279 |
+
**Symptôme**. Un job GitHub Actions reste en `queued` ou
|
| 280 |
+
`in_progress` > 30 minutes pour ce qui devrait être un test < 5 min.
|
| 281 |
+
|
| 282 |
+
**Diagnostic**. Vérifier dans cet ordre :
|
| 283 |
+
|
| 284 |
+
1. **Codecov upload hang** (déjà vu — 50+ min) → couvert par
|
| 285 |
+
`timeout-minutes: 5` sur l'étape Codecov + `fail_ci_if_error: false`
|
| 286 |
+
depuis le S59.
|
| 287 |
+
2. **Live tests qui s'exécutent** au lieu d'être deselected → le
|
| 288 |
+
marker `live` doit être dans `addopts` de `pyproject.toml`
|
| 289 |
+
(vérifié par les tests dual-lang).
|
| 290 |
+
3. **Codespaces / runner épuisé** → annuler manuellement le job,
|
| 291 |
+
relancer.
|
| 292 |
+
|
| 293 |
+
**Mitigation**. Annuler le workflow run (UI GitHub Actions),
|
| 294 |
+
relancer. Si récurrent, élever un incident infra GitHub.
|
| 295 |
+
|
| 296 |
+
---
|
| 297 |
+
|
| 298 |
+
## INC-09 — Upgrade casse jobs
|
| 299 |
+
|
| 300 |
+
**Symptôme**. Après `git pull && pip install -e .`, les jobs
|
| 301 |
+
soumis avant l'upgrade échouent en `error`.
|
| 302 |
+
|
| 303 |
+
**Diagnostic**. Le `JobStore` utilise une table `schema_version` ;
|
| 304 |
+
une bump de SCHEMA_VERSION sans migration livre `JobStoreError` au
|
| 305 |
+
boot.
|
| 306 |
+
|
| 307 |
+
**Mitigation**.
|
| 308 |
+
|
| 309 |
+
```bash
|
| 310 |
+
# 1. Stopper le service AVANT l'upgrade.
|
| 311 |
+
systemctl stop picarones
|
| 312 |
+
|
| 313 |
+
# 2. Backup du JobStore.
|
| 314 |
+
cp /var/lib/picarones/jobs.db /var/lib/picarones/jobs.db.bak
|
| 315 |
+
|
| 316 |
+
# 3. Upgrade.
|
| 317 |
+
git pull && pip install -e ".[dev,web]"
|
| 318 |
+
|
| 319 |
+
# 4. Vérifier le schéma.
|
| 320 |
+
sqlite3 /var/lib/picarones/jobs.db "SELECT version FROM schema_version;"
|
| 321 |
+
|
| 322 |
+
# 5. Démarrer. Le dispatcher applique automatiquement les
|
| 323 |
+
# migrations enregistrées dans ``_MIGRATIONS``.
|
| 324 |
+
systemctl start picarones
|
| 325 |
+
```
|
| 326 |
+
|
| 327 |
+
**Suivi**. Tester chaque upgrade en staging avant prod.
|
| 328 |
+
|
| 329 |
+
---
|
| 330 |
+
|
| 331 |
+
## INC-10 — Restauration depuis backup
|
| 332 |
+
|
| 333 |
+
**Symptôme**. Corruption ou perte du workspace ou de la DB jobs.
|
| 334 |
+
|
| 335 |
+
**Pré-requis**. Backup récent (recommandé : snapshot quotidien du
|
| 336 |
+
volume `/var/lib/picarones/`).
|
| 337 |
+
|
| 338 |
+
**Mitigation**.
|
| 339 |
+
|
| 340 |
+
```bash
|
| 341 |
+
# 1. Stopper le service.
|
| 342 |
+
systemctl stop picarones
|
| 343 |
+
|
| 344 |
+
# 2. Restaurer.
|
| 345 |
+
rsync -av /backup/picarones-2026-05-XX/ /var/lib/picarones/
|
| 346 |
+
|
| 347 |
+
# 3. Vérifier l'intégrité SQLite.
|
| 348 |
+
sqlite3 /var/lib/picarones/jobs.db "PRAGMA integrity_check;"
|
| 349 |
+
|
| 350 |
+
# 4. Démarrer. Les jobs ``running`` au moment du backup seront
|
| 351 |
+
# automatiquement marqués ``interrupted`` par le lifespan hook.
|
| 352 |
+
systemctl start picarones
|
| 353 |
+
```
|
| 354 |
+
|
| 355 |
+
**Suivi**. Communiquer aux utilisateurs que les jobs en cours au
|
| 356 |
+
moment du backup sont à relancer.
|
| 357 |
+
|
| 358 |
+
---
|
| 359 |
+
|
| 360 |
+
## Escalade
|
| 361 |
+
|
| 362 |
+
Si un incident dépasse les procédures ci-dessus :
|
| 363 |
+
|
| 364 |
+
1. Documenter l'observation dans un fichier `incidents/<date>.md`
|
| 365 |
+
(snapshot du symptôme + commandes lancées + résultat).
|
| 366 |
+
2. Ouvrir une issue GitHub avec le label `incident`.
|
| 367 |
+
3. Pour une vulnérabilité de sécurité, suivre la procédure de
|
| 368 |
+
[`/SECURITY.md`](../../SECURITY.md) (canal privé).
|
| 369 |
+
|
| 370 |
+
## Révisions
|
| 371 |
+
|
| 372 |
+
| Version | Date | Changements |
|
| 373 |
+
|---------|------|-------------|
|
| 374 |
+
| 1.0 | 2026-05 | Création initiale (S60), 10 scénarios |
|
docs/operations/supply-chain.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Supply chain — SBOM, SLSA, signatures
|
| 2 |
+
|
| 3 |
+
> **Audience** : DSI institutionnelle et conformité réglementaire
|
| 4 |
+
> (EU CRA — Cyber Resilience Act, exigible à partir de 2027 pour les
|
| 5 |
+
> livraisons à des organismes publics européens).
|
| 6 |
+
>
|
| 7 |
+
> Décrit comment Picarones documente sa chaîne d'approvisionnement
|
| 8 |
+
> logicielle et permet à une institution de vérifier l'intégrité
|
| 9 |
+
> d'un wheel ou d'une image Docker avant déploiement.
|
| 10 |
+
|
| 11 |
+
## SBOM (Software Bill of Materials)
|
| 12 |
+
|
| 13 |
+
### Format CycloneDX
|
| 14 |
+
|
| 15 |
+
Picarones produit un SBOM au format **CycloneDX 1.5 JSON** à chaque
|
| 16 |
+
release. Le SBOM liste l'intégralité des paquets Python installés
|
| 17 |
+
dans l'environnement de build avec :
|
| 18 |
+
|
| 19 |
+
- `name`, `version`, `purl` (package URL canonique).
|
| 20 |
+
- `licenses` (SPDX expression).
|
| 21 |
+
- `hashes` (SHA-256 du wheel).
|
| 22 |
+
- `dependencies` (graphe de dépendance complet).
|
| 23 |
+
|
| 24 |
+
Génération locale :
|
| 25 |
+
|
| 26 |
+
```bash
|
| 27 |
+
pip install cyclonedx-bom
|
| 28 |
+
python scripts/gen_sbom.py --output sbom.json
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
Génération automatique dans la CI : voir
|
| 32 |
+
[`.github/workflows/release.yml`](../../.github/workflows/release.yml)
|
| 33 |
+
qui attache `sbom.json` à chaque GitHub Release.
|
| 34 |
+
|
| 35 |
+
### Image Docker
|
| 36 |
+
|
| 37 |
+
L'image Docker `ghcr.io/maribakulj/picarones:<version>` embarque son
|
| 38 |
+
propre SBOM (couche métadonnées BuildKit) :
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
docker buildx imagetools inspect \
|
| 42 |
+
ghcr.io/maribakulj/picarones:<version> \
|
| 43 |
+
--format '{{ json .SBOM }}'
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
## SLSA Provenance
|
| 47 |
+
|
| 48 |
+
[SLSA](https://slsa.dev/) (Supply-chain Levels for Software Artifacts)
|
| 49 |
+
formalise le niveau de confiance qu'on peut accorder à un artefact
|
| 50 |
+
livré.
|
| 51 |
+
|
| 52 |
+
### État actuel : SLSA Level 2
|
| 53 |
+
|
| 54 |
+
- **Build** isolé sur GitHub-hosted runners, traçable au commit SHA.
|
| 55 |
+
- **Provenance** générée automatiquement par
|
| 56 |
+
[`docker/build-push-action@v5`](https://github.com/docker/build-push-action)
|
| 57 |
+
avec `provenance: true`.
|
| 58 |
+
|
| 59 |
+
Inspection :
|
| 60 |
+
|
| 61 |
+
```bash
|
| 62 |
+
docker buildx imagetools inspect \
|
| 63 |
+
ghcr.io/maribakulj/picarones:<version> \
|
| 64 |
+
--format '{{ json .Provenance }}'
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### Trajectoire vers SLSA Level 3
|
| 68 |
+
|
| 69 |
+
Pour atteindre le niveau 3 (signature non-falsifiable), prochaines
|
| 70 |
+
étapes (cf. [`/docs/roadmap/backlog.md`](../roadmap/backlog.md)) :
|
| 71 |
+
|
| 72 |
+
1. Signer chaque wheel PyPI avec [Sigstore](https://www.sigstore.dev/)
|
| 73 |
+
via `pypi-attestations` (PEP 740).
|
| 74 |
+
2. Signer le SBOM avec `cosign sign-blob` lors de la release.
|
| 75 |
+
3. Publier les attestations sur Rekor (transparency log).
|
| 76 |
+
|
| 77 |
+
## Vérification côté institution
|
| 78 |
+
|
| 79 |
+
Avant déploiement, l'institution peut vérifier qu'un wheel n'a pas
|
| 80 |
+
été altéré entre le build CI et le download :
|
| 81 |
+
|
| 82 |
+
```bash
|
| 83 |
+
# 1. Téléchargement.
|
| 84 |
+
pip download picarones==<version> --no-deps -d /tmp/audit/
|
| 85 |
+
|
| 86 |
+
# 2. Vérification du hash contre le SBOM.
|
| 87 |
+
sha256sum /tmp/audit/picarones-*.whl
|
| 88 |
+
jq -r '.components[] | select(.name == "picarones") | .hashes[0].content' sbom.json
|
| 89 |
+
# Les deux valeurs doivent matcher.
|
| 90 |
+
|
| 91 |
+
# 3. (Future, SLSA L3) Vérification de la signature Sigstore.
|
| 92 |
+
# cosign verify-blob --bundle picarones-<version>.whl.sigstore picarones-<version>.whl
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
## Politique de mise à jour des dépendances
|
| 96 |
+
|
| 97 |
+
- **CVE critique** (CVSS ≥ 9.0) : patch release sous 7 jours.
|
| 98 |
+
- **CVE élevée** (7.0 ≤ CVSS < 9.0) : minor release sous 30 jours.
|
| 99 |
+
- **CVE moyenne** : prise en compte au prochain cycle de release.
|
| 100 |
+
|
| 101 |
+
Surveillance :
|
| 102 |
+
|
| 103 |
+
- `pip-audit` exécuté en CI sur chaque push (cf.
|
| 104 |
+
[`/.github/workflows/precommit.yml`](../../.github/workflows/precommit.yml)).
|
| 105 |
+
- Dependabot / Renovate sur `pyproject.toml` pour les minor / patch.
|
| 106 |
+
|
| 107 |
+
## Conformité EU CRA (anticipation)
|
| 108 |
+
|
| 109 |
+
L'EU Cyber Resilience Act, applicable à partir de 2027 pour les
|
| 110 |
+
produits livrés à des entités publiques de l'UE, exigera :
|
| 111 |
+
|
| 112 |
+
| Exigence CRA | Statut Picarones |
|
| 113 |
+
|--------------|------------------|
|
| 114 |
+
| SBOM machine-readable | ✅ CycloneDX 1.5 |
|
| 115 |
+
| Vulnerability disclosure policy | ✅ [`/SECURITY.md`](../../SECURITY.md) + RFC 9116 [`/.well-known/security.txt`](../../.well-known/security.txt) |
|
| 116 |
+
| Coordinated vulnerability disclosure | ✅ GitHub Security Advisories |
|
| 117 |
+
| Cryptographic signing of releases | 🔧 SLSA L2 actuel, L3 prévu |
|
| 118 |
+
| Vulnerability handling within reasonable timeframes | ✅ Politique documentée ci-dessus |
|
| 119 |
+
| Security updates for at least 5 years | 🔧 Politique LTS à définir avant 1.0 GA |
|
| 120 |
+
|
| 121 |
+
## Révisions
|
| 122 |
+
|
| 123 |
+
| Version | Date | Changements |
|
| 124 |
+
|---------|------|-------------|
|
| 125 |
+
| 1.0 | 2026-05 | Création initiale (S60) |
|
docs/{views → reference}/alto-view.md
RENAMED
|
File without changes
|
docs/{api-stable.md → reference/api-stable.md}
RENAMED
|
@@ -1,26 +1,34 @@
|
|
| 1 |
-
# API publique stable de Picarones
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
## Définition
|
| 7 |
|
| 8 |
-
L'API publique de Picarones est constituée des classes,
|
| 9 |
-
constantes et types listés ci-dessous,
|
| 10 |
-
|
| 11 |
-
de stabilité** : nous nous engageons à ne pas le casser entre versions
|
| 12 |
-
mineures (semver `1.x.0`).
|
| 13 |
|
| 14 |
-
Ce qui n'est pas dans cette liste
|
| 15 |
-
qui ont été déplacés vers `picarones.measurements/`, `picarones.extras/`
|
| 16 |
-
et accessibles via shims rétrocompat — peut évoluer à tout moment
|
| 17 |
sans bump majeur.
|
| 18 |
|
| 19 |
-
Les imports historiques
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
ce sont des aliases rétrocompat. Pour de la nouveauté, préférer
|
| 23 |
-
`from picarones.measurements.confusion import ...`.
|
| 24 |
|
| 25 |
## Test automatique
|
| 26 |
|
|
@@ -30,7 +38,7 @@ ou change de forme.
|
|
| 30 |
|
| 31 |
## Liste exhaustive
|
| 32 |
|
| 33 |
-
### `picarones.
|
| 34 |
|
| 35 |
```python
|
| 36 |
class GTLevel(str, Enum):
|
|
@@ -51,12 +59,18 @@ GT_SUFFIXES: dict[GTLevel, str] # mapping niveau → suffixe fichier
|
|
| 51 |
def load_corpus_from_directory(path) -> Corpus
|
| 52 |
```
|
| 53 |
|
| 54 |
-
### `picarones.
|
| 55 |
|
| 56 |
```python
|
| 57 |
class ArtifactType(str, Enum):
|
| 58 |
-
IMAGE,
|
|
|
|
|
|
|
|
|
|
| 59 |
|
|
|
|
|
|
|
|
|
|
| 60 |
class BaseModule(ABC):
|
| 61 |
input_types: tuple[ArtifactType, ...]
|
| 62 |
output_types: tuple[ArtifactType, ...]
|
|
@@ -71,7 +85,7 @@ class BaseModule(ABC):
|
|
| 71 |
ExecutionMode = Literal["io", "cpu"]
|
| 72 |
```
|
| 73 |
|
| 74 |
-
### `picarones.
|
| 75 |
|
| 76 |
```python
|
| 77 |
class DocumentResult: # résultat moteur sur un doc (CER, métriques, taxonomy…)
|
|
@@ -105,7 +119,7 @@ def run_benchmark(
|
|
| 105 |
) -> BenchmarkResult
|
| 106 |
```
|
| 107 |
|
| 108 |
-
### `picarones.
|
| 109 |
|
| 110 |
```python
|
| 111 |
class PipelineStep:
|
|
@@ -144,7 +158,7 @@ def load_comparison_specs_from_yaml(path) -> tuple[list[PipelineSpec], dict]
|
|
| 144 |
def load_comparison_specs_from_dict(data: dict) -> tuple[list[PipelineSpec], dict]
|
| 145 |
```
|
| 146 |
|
| 147 |
-
### `picarones.
|
| 148 |
|
| 149 |
```python
|
| 150 |
class MetricSpec: # frozen dataclass : name, func, input_types, ...
|
|
@@ -156,7 +170,7 @@ def select_metrics(input_types) -> list[MetricSpec]
|
|
| 156 |
def compute_at_junction(reference, hypothesis, input_types, *, skip_on_error=True) -> dict
|
| 157 |
```
|
| 158 |
|
| 159 |
-
### `picarones.
|
| 160 |
|
| 161 |
```python
|
| 162 |
# Profils — constantes
|
|
@@ -241,11 +255,11 @@ def reset_default_store(...)
|
|
| 241 |
reflètent ces changements.
|
| 242 |
- **Modules `picarones.extras/`** : statut variable selon le
|
| 243 |
sous-package (academic / governance / historical / importers).
|
| 244 |
-
Voir `docs/architecture.md`.
|
| 245 |
- **Comportement des renderers HTML** : la structure des fichiers HTML
|
| 246 |
peut évoluer entre versions mineures. Nous gardons les noms des
|
| 247 |
vues principales.
|
| 248 |
-
- **Internes des modules
|
| 249 |
ne font pas partie de l'API publique. Les tests Sprints
|
| 250 |
historiques qui les importent (Sprint 13/42) sont préservés mais
|
| 251 |
par effort, pas par contrat.
|
|
@@ -258,9 +272,9 @@ Un bump majeur sera nécessaire pour :
|
|
| 258 |
- Changer la signature d'une fonction publique de manière non
|
| 259 |
rétrocompatible.
|
| 260 |
- Casser le format de sérialisation du `BenchmarkResult.to_json()`.
|
| 261 |
-
- Renommer un module
|
| 262 |
|
| 263 |
-
## Modules historiques rétrocompat (non
|
| 264 |
|
| 265 |
Les imports suivants continuent à fonctionner mais ne font pas partie
|
| 266 |
de l'API publique stable. Ils peuvent évoluer ou être retirés en
|
|
@@ -275,7 +289,7 @@ from picarones.measurements.calibration import compute_calibration_metrics
|
|
| 275 |
|
| 276 |
# Moteur narratif (déplacé vers picarones.measurements.narrative/)
|
| 277 |
from picarones.measurements.narrative import build_synthesis
|
| 278 |
-
from picarones.
|
| 279 |
from picarones.measurements.narrative.detectors import detect_global_leader_cer
|
| 280 |
|
| 281 |
# Plugins (déplacés vers picarones.extras/)
|
|
@@ -296,8 +310,8 @@ Pour les **nouvelles** intégrations, préférer les chemins canoniques :
|
|
| 296 |
|
| 297 |
## Voir aussi
|
| 298 |
|
| 299 |
-
- [`docs/architecture.md`](architecture.md) — cartographie
|
| 300 |
des 3 cercles + critères d'assignation.
|
| 301 |
-
- [`docs/architecture.md`](architecture.md) — vue d'ensemble post-chantiers.
|
| 302 |
- [`tests/test_public_api.py`](../tests/test_public_api.py) — test
|
| 303 |
automatique qui échoue si un nom listé ici disparaît.
|
|
|
|
| 1 |
+
# API publique stable de Picarones
|
| 2 |
+
|
| 3 |
+
> **Statut** : ce document décrivait l'API publique du Cercle 1
|
| 4 |
+
> historique (`picarones.core/`). Le projet est en cours de
|
| 5 |
+
> retrait du legacy vers une **architecture 8 couches**
|
| 6 |
+
> (`domain → formats → evaluation → pipeline → adapters → app
|
| 7 |
+
> → reports_v2 → interfaces`, cf.
|
| 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 |
|
| 22 |
+
L'API publique stable de Picarones est constituée des classes,
|
| 23 |
+
fonctions, constantes et types listés ci-dessous, désormais
|
| 24 |
+
exportés depuis l'arborescence canonique.
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
Ce qui n'est pas dans cette liste peut évoluer à tout moment
|
|
|
|
|
|
|
| 27 |
sans bump majeur.
|
| 28 |
|
| 29 |
+
Les imports historiques restent fonctionnels via shims pendant
|
| 30 |
+
la migration ; ils ne font **pas** partie de l'API publique
|
| 31 |
+
stable et émettent un `DeprecationWarning`.
|
|
|
|
|
|
|
| 32 |
|
| 33 |
## Test automatique
|
| 34 |
|
|
|
|
| 38 |
|
| 39 |
## Liste exhaustive
|
| 40 |
|
| 41 |
+
### `picarones.evaluation.corpus`
|
| 42 |
|
| 43 |
```python
|
| 44 |
class GTLevel(str, Enum):
|
|
|
|
| 59 |
def load_corpus_from_directory(path) -> Corpus
|
| 60 |
```
|
| 61 |
|
| 62 |
+
### `picarones.domain.artifacts`
|
| 63 |
|
| 64 |
```python
|
| 65 |
class ArtifactType(str, Enum):
|
| 66 |
+
IMAGE, RAW_TEXT, CORRECTED_TEXT, ALTO_XML, PAGE_XML,
|
| 67 |
+
CANONICAL_DOCUMENT, ENTITIES, READING_ORDER, ALIGNMENT, CONFIDENCES
|
| 68 |
+
# Aliases legacy pour rétrocompat : TEXT, ALTO, PAGE
|
| 69 |
+
```
|
| 70 |
|
| 71 |
+
### `picarones.domain.module_protocol`
|
| 72 |
+
|
| 73 |
+
```python
|
| 74 |
class BaseModule(ABC):
|
| 75 |
input_types: tuple[ArtifactType, ...]
|
| 76 |
output_types: tuple[ArtifactType, ...]
|
|
|
|
| 85 |
ExecutionMode = Literal["io", "cpu"]
|
| 86 |
```
|
| 87 |
|
| 88 |
+
### `picarones.evaluation.benchmark_result`
|
| 89 |
|
| 90 |
```python
|
| 91 |
class DocumentResult: # résultat moteur sur un doc (CER, métriques, taxonomy…)
|
|
|
|
| 119 |
) -> BenchmarkResult
|
| 120 |
```
|
| 121 |
|
| 122 |
+
### `picarones.evaluation.pipeline`
|
| 123 |
|
| 124 |
```python
|
| 125 |
class PipelineStep:
|
|
|
|
| 158 |
def load_comparison_specs_from_dict(data: dict) -> tuple[list[PipelineSpec], dict]
|
| 159 |
```
|
| 160 |
|
| 161 |
+
### `picarones.evaluation.metric_registry`
|
| 162 |
|
| 163 |
```python
|
| 164 |
class MetricSpec: # frozen dataclass : name, func, input_types, ...
|
|
|
|
| 170 |
def compute_at_junction(reference, hypothesis, input_types, *, skip_on_error=True) -> dict
|
| 171 |
```
|
| 172 |
|
| 173 |
+
### `picarones.evaluation.metric_hooks`
|
| 174 |
|
| 175 |
```python
|
| 176 |
# Profils — constantes
|
|
|
|
| 255 |
reflètent ces changements.
|
| 256 |
- **Modules `picarones.extras/`** : statut variable selon le
|
| 257 |
sous-package (academic / governance / historical / importers).
|
| 258 |
+
Voir `docs/explanation/architecture.md`.
|
| 259 |
- **Comportement des renderers HTML** : la structure des fichiers HTML
|
| 260 |
peut évoluer entre versions mineures. Nous gardons les noms des
|
| 261 |
vues principales.
|
| 262 |
+
- **Internes des modules canoniques** : les noms commençant par `_`
|
| 263 |
ne font pas partie de l'API publique. Les tests Sprints
|
| 264 |
historiques qui les importent (Sprint 13/42) sont préservés mais
|
| 265 |
par effort, pas par contrat.
|
|
|
|
| 272 |
- Changer la signature d'une fonction publique de manière non
|
| 273 |
rétrocompatible.
|
| 274 |
- Casser le format de sérialisation du `BenchmarkResult.to_json()`.
|
| 275 |
+
- Renommer un module de l'arborescence canonique.
|
| 276 |
|
| 277 |
+
## Modules historiques rétrocompat (non canoniques)
|
| 278 |
|
| 279 |
Les imports suivants continuent à fonctionner mais ne font pas partie
|
| 280 |
de l'API publique stable. Ils peuvent évoluer ou être retirés en
|
|
|
|
| 289 |
|
| 290 |
# Moteur narratif (déplacé vers picarones.measurements.narrative/)
|
| 291 |
from picarones.measurements.narrative import build_synthesis
|
| 292 |
+
from picarones.domain.facts import Fact, FactType, FactImportance
|
| 293 |
from picarones.measurements.narrative.detectors import detect_global_leader_cer
|
| 294 |
|
| 295 |
# Plugins (déplacés vers picarones.extras/)
|
|
|
|
| 310 |
|
| 311 |
## Voir aussi
|
| 312 |
|
| 313 |
+
- [`docs/explanation/architecture.md`](architecture.md) — cartographie
|
| 314 |
des 3 cercles + critères d'assignation.
|
| 315 |
+
- [`docs/explanation/architecture.md`](architecture.md) — vue d'ensemble post-chantiers.
|
| 316 |
- [`tests/test_public_api.py`](../tests/test_public_api.py) — test
|
| 317 |
automatique qui échoue si un nom listé ici disparaît.
|
docs/{views → reference}/comparing-views.md
RENAMED
|
File without changes
|
docs/{profiles.md → reference/normalization-profiles.md}
RENAMED
|
@@ -4,7 +4,7 @@ Picarones expose **7 profils de calcul** qui modulent les métriques
|
|
| 4 |
calculées par le runner selon le use case. Chaque profil active un
|
| 5 |
sous-ensemble des **12 hooks document-level** et **12 agrégateurs
|
| 6 |
corpus-level** du registre central
|
| 7 |
-
([`picarones/
|
| 8 |
|
| 9 |
## Synoptique
|
| 10 |
|
|
@@ -21,7 +21,7 @@ corpus-level** du registre central
|
|
| 21 |
> **Note rétrocompat** : aujourd'hui les profils `philological`, `diagnostics`,
|
| 22 |
> `economics`, `pipeline` et `full` activent **le même ensemble** que `standard`
|
| 23 |
> côté hooks calculés. Ce qui change, c'est la **vue HTML rendue** : chaque
|
| 24 |
-
> profil active des sous-sections différentes du rapport (cf. `docs/views.md`).
|
| 25 |
> Les profils sont volontairement génériques pour permettre aux contributeurs
|
| 26 |
> futurs d'ajouter des hooks spécifiques sans casser l'API.
|
| 27 |
|
|
@@ -127,11 +127,11 @@ reproductibilité scientifique maximale.
|
|
| 127 |
|
| 128 |
## Comment ajouter un hook personnalisé
|
| 129 |
|
| 130 |
-
Voir [`docs/
|
| 131 |
pour le détail. Pattern de base :
|
| 132 |
|
| 133 |
```python
|
| 134 |
-
from picarones.
|
| 135 |
register_document_metric, PROFILE_DIAGNOSTICS, PROFILE_FULL,
|
| 136 |
)
|
| 137 |
|
|
@@ -148,7 +148,7 @@ def my_hook(*, ground_truth, hypothesis, image_path, corpus_lang, ocr_result):
|
|
| 148 |
|
| 149 |
## Code source
|
| 150 |
|
| 151 |
-
- [`picarones/
|
| 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.
|
|
|
|
| 4 |
calculées par le runner selon le use case. Chaque profil active un
|
| 5 |
sous-ensemble des **12 hooks document-level** et **12 agrégateurs
|
| 6 |
corpus-level** du registre central
|
| 7 |
+
([`picarones/evaluation/metric_hooks.py`](../picarones/evaluation/metric_hooks.py)).
|
| 8 |
|
| 9 |
## Synoptique
|
| 10 |
|
|
|
|
| 21 |
> **Note rétrocompat** : aujourd'hui les profils `philological`, `diagnostics`,
|
| 22 |
> `economics`, `pipeline` et `full` activent **le même ensemble** que `standard`
|
| 23 |
> côté hooks calculés. Ce qui change, c'est la **vue HTML rendue** : chaque
|
| 24 |
+
> profil active des sous-sections différentes du rapport (cf. `docs/reference/views.md`).
|
| 25 |
> Les profils sont volontairement génériques pour permettre aux contributeurs
|
| 26 |
> futurs d'ajouter des hooks spécifiques sans casser l'API.
|
| 27 |
|
|
|
|
| 127 |
|
| 128 |
## Comment ajouter un hook personnalisé
|
| 129 |
|
| 130 |
+
Voir [`docs/explanation/narrative-engine.md`](developer/narrative-engine.md)
|
| 131 |
pour le détail. Pattern de base :
|
| 132 |
|
| 133 |
```python
|
| 134 |
+
from picarones.evaluation.metric_hooks import (
|
| 135 |
register_document_metric, PROFILE_DIAGNOSTICS, PROFILE_FULL,
|
| 136 |
)
|
| 137 |
|
|
|
|
| 148 |
|
| 149 |
## Code source
|
| 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.
|
docs/{reproducibility-snapshots.md → reference/reproducibility-snapshots.md}
RENAMED
|
File without changes
|
docs/{views → reference}/text-view.md
RENAMED
|
File without changes
|
docs/{views.md → reference/views.md}
RENAMED
|
@@ -62,7 +62,7 @@ orphelins** identifiés dans l'audit initial :
|
|
| 62 |
|
| 63 |
#### Vue « Coût et performance » (`build_economics_view_html`)
|
| 64 |
|
| 65 |
-
Module : [`picarones/
|
| 66 |
Activée si :
|
| 67 |
- `engine_reports` fournis avec durations non nulles.
|
| 68 |
- (Optionnel) `extra_html_blocks` pour cost projection / marginal cost.
|
|
@@ -73,7 +73,7 @@ Sous-sections :
|
|
| 73 |
|
| 74 |
#### Vue « Taxonomie avancée » (`build_advanced_taxonomy_view_html`)
|
| 75 |
|
| 76 |
-
Module : [`picarones/
|
| 77 |
Activée si ≥ 2 moteurs ont une `aggregated_taxonomy`.
|
| 78 |
|
| 79 |
Sous-sections :
|
|
@@ -85,7 +85,7 @@ Sous-sections :
|
|
| 85 |
|
| 86 |
#### Vue « Diagnostic approfondi » (`build_diagnostics_view_html`)
|
| 87 |
|
| 88 |
-
Module : [`picarones/
|
| 89 |
Activée si `detect_levers()` produit au moins un levier (typique sur
|
| 90 |
un bench standard) ou si données opt-in fournies.
|
| 91 |
|
|
@@ -106,7 +106,7 @@ servent à composer des **rapports autonomes** :
|
|
| 106 |
|
| 107 |
### Vue « Pipeline composée » (`build_pipeline_view_html`)
|
| 108 |
|
| 109 |
-
Module : [`picarones/
|
| 110 |
|
| 111 |
Utilisée par `picarones pipeline run` (ou par tout outil qui consomme un
|
| 112 |
`PipelineBenchmarkResult`). Sous-sections :
|
|
@@ -122,7 +122,7 @@ Utilisée par `picarones pipeline run` (ou par tout outil qui consomme un
|
|
| 122 |
|
| 123 |
### Vue « Robustesse projetée » (`build_robustness_view_html`)
|
| 124 |
|
| 125 |
-
Module : [`picarones/
|
| 126 |
|
| 127 |
Utilisée par le workflow `picarones robustness`. Sous-sections :
|
| 128 |
|
|
@@ -141,14 +141,14 @@ défini dans `economics.py` :
|
|
| 141 |
|
| 142 |
## Code source
|
| 143 |
|
| 144 |
-
- [`picarones/
|
| 145 |
— orchestrateur Jinja2 qui appelle les renderers et passe leurs sorties
|
| 146 |
au template.
|
| 147 |
-
- [`picarones/
|
| 148 |
composition (chantier 3).
|
| 149 |
-
- [`picarones/
|
| 150 |
atomiques.
|
| 151 |
-
- [`picarones/
|
| 152 |
— template Jinja2 qui inclut les blocs.
|
| 153 |
- [`tests/test_views.py`](../tests/test_views.py) — tests d'intégration
|
| 154 |
des 5 vues du chantier 3.
|
|
|
|
| 62 |
|
| 63 |
#### Vue « Coût et performance » (`build_economics_view_html`)
|
| 64 |
|
| 65 |
+
Module : [`picarones/reports_v2/html/views/economics.py`](../picarones/reports_v2/html/views/economics.py).
|
| 66 |
Activée si :
|
| 67 |
- `engine_reports` fournis avec durations non nulles.
|
| 68 |
- (Optionnel) `extra_html_blocks` pour cost projection / marginal cost.
|
|
|
|
| 73 |
|
| 74 |
#### Vue « Taxonomie avancée » (`build_advanced_taxonomy_view_html`)
|
| 75 |
|
| 76 |
+
Module : [`picarones/reports_v2/html/views/advanced_taxonomy.py`](../picarones/reports_v2/html/views/advanced_taxonomy.py).
|
| 77 |
Activée si ≥ 2 moteurs ont une `aggregated_taxonomy`.
|
| 78 |
|
| 79 |
Sous-sections :
|
|
|
|
| 85 |
|
| 86 |
#### Vue « Diagnostic approfondi » (`build_diagnostics_view_html`)
|
| 87 |
|
| 88 |
+
Module : [`picarones/reports_v2/html/views/diagnostics.py`](../picarones/reports_v2/html/views/diagnostics.py).
|
| 89 |
Activée si `detect_levers()` produit au moins un levier (typique sur
|
| 90 |
un bench standard) ou si données opt-in fournies.
|
| 91 |
|
|
|
|
| 106 |
|
| 107 |
### Vue « Pipeline composée » (`build_pipeline_view_html`)
|
| 108 |
|
| 109 |
+
Module : [`picarones/reports_v2/html/views/pipeline.py`](../picarones/reports_v2/html/views/pipeline.py).
|
| 110 |
|
| 111 |
Utilisée par `picarones pipeline run` (ou par tout outil qui consomme un
|
| 112 |
`PipelineBenchmarkResult`). Sous-sections :
|
|
|
|
| 122 |
|
| 123 |
### Vue « Robustesse projetée » (`build_robustness_view_html`)
|
| 124 |
|
| 125 |
+
Module : [`picarones/reports_v2/html/views/robustness.py`](../picarones/reports_v2/html/views/robustness.py).
|
| 126 |
|
| 127 |
Utilisée par le workflow `picarones robustness`. Sous-sections :
|
| 128 |
|
|
|
|
| 141 |
|
| 142 |
## Code source
|
| 143 |
|
| 144 |
+
- [`picarones/reports_v2/html/generator.py`](../picarones/reports_v2/html/generator.py)
|
| 145 |
— orchestrateur Jinja2 qui appelle les renderers et passe leurs sorties
|
| 146 |
au template.
|
| 147 |
+
- [`picarones/reports_v2/html/views/`](../picarones/reports_v2/html/views/) — 5 modules de
|
| 148 |
composition (chantier 3).
|
| 149 |
+
- [`picarones/reports_v2/html/renderers/`](../picarones/reports_v2/html/renderers/) — 26 renderers
|
| 150 |
atomiques.
|
| 151 |
+
- [`picarones/reports_v2/html/templates/view_analyses.html`](../picarones/reports_v2/html/templates/view_analyses.html)
|
| 152 |
— template Jinja2 qui inclut les blocs.
|
| 153 |
- [`tests/test_views.py`](../tests/test_views.py) — tests d'intégration
|
| 154 |
des 5 vues du chantier 3.
|
BACKLOG_POST_LIVRAISON.md → docs/roadmap/backlog.md
RENAMED
|
File without changes
|
docs/roadmap/rewrite-2026.md
CHANGED
|
@@ -43,37 +43,38 @@ Le rewrite ciblé attaque ces trois problèmes ensemble.
|
|
| 43 |
|
| 44 |
```
|
| 45 |
picarones/
|
| 46 |
-
domain/ #
|
| 47 |
# EvaluationSpec, DocumentRef, Provenance)
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
views/
|
| 50 |
projectors/
|
| 51 |
metrics/
|
| 52 |
registry.py
|
| 53 |
-
pipeline/ #
|
| 54 |
executor.py
|
| 55 |
cache.py
|
| 56 |
spec.py
|
| 57 |
-
|
| 58 |
-
alto/
|
| 59 |
-
pagexml/
|
| 60 |
-
text/
|
| 61 |
-
adapters/ # Cercle 3 — moteurs OCR/LLM/VLM, importers, storage
|
| 62 |
ocr/
|
| 63 |
llm/
|
| 64 |
vlm/
|
| 65 |
corpus/
|
| 66 |
storage/
|
| 67 |
-
app/ #
|
| 68 |
services/
|
| 69 |
schemas/
|
| 70 |
-
|
| 71 |
-
cli/
|
| 72 |
-
web/
|
| 73 |
-
reports/
|
| 74 |
html/
|
| 75 |
json/
|
| 76 |
csv/
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
```
|
| 78 |
|
| 79 |
Pivot mental : l'objet central n'est plus `Engine + BenchmarkResult`,
|
|
|
|
| 43 |
|
| 44 |
```
|
| 45 |
picarones/
|
| 46 |
+
domain/ # Couche 1 — types purs (Artifact, PipelineSpec,
|
| 47 |
# EvaluationSpec, DocumentRef, Provenance)
|
| 48 |
+
formats/ # Couche 2 — ALTO, PAGE, normalisation texte
|
| 49 |
+
alto/
|
| 50 |
+
pagexml/
|
| 51 |
+
text/
|
| 52 |
+
evaluation/ # Couche 3 — vues, projecteurs, métriques
|
| 53 |
views/
|
| 54 |
projectors/
|
| 55 |
metrics/
|
| 56 |
registry.py
|
| 57 |
+
pipeline/ # Couche 4 — exécution canonique
|
| 58 |
executor.py
|
| 59 |
cache.py
|
| 60 |
spec.py
|
| 61 |
+
adapters/ # Couche 5 — moteurs OCR/LLM/VLM, importers, storage
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
ocr/
|
| 63 |
llm/
|
| 64 |
vlm/
|
| 65 |
corpus/
|
| 66 |
storage/
|
| 67 |
+
app/ # Couche 6 — services applicatifs
|
| 68 |
services/
|
| 69 |
schemas/
|
| 70 |
+
reports_v2/ # Couche 7 — rendu HTML / JSON / CSV
|
|
|
|
|
|
|
|
|
|
| 71 |
html/
|
| 72 |
json/
|
| 73 |
csv/
|
| 74 |
+
narrative/
|
| 75 |
+
interfaces/ # Couche 8 — CLI, web
|
| 76 |
+
json/
|
| 77 |
+
csv/
|
| 78 |
```
|
| 79 |
|
| 80 |
Pivot mental : l'objet central n'est plus `Engine + BenchmarkResult`,
|
docs/security/threat-model.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Threat model — Picarones
|
| 2 |
+
|
| 3 |
+
> **Audience** : DSI institutionnelle (BnF, LoC, BL), auditeur
|
| 4 |
+
> sécurité, mainteneur. Ce document complète
|
| 5 |
+
> [`/SECURITY.md`](../../SECURITY.md) en formalisant le modèle de
|
| 6 |
+
> menace. Méthodologie : **STRIDE** (Microsoft) + adaptation
|
| 7 |
+
> patrimoine numérique.
|
| 8 |
+
>
|
| 9 |
+
> **Périmètre** : déploiement institutionnel — Picarones tourne sur
|
| 10 |
+
> une infrastructure interne (NAS, cluster Kubernetes), un workspace
|
| 11 |
+
> partagé entre chercheurs, des clés API cloud côté serveur.
|
| 12 |
+
>
|
| 13 |
+
> **Hors périmètre** : déploiement public HuggingFace Space (mode
|
| 14 |
+
> ouvert anonymisé, sans secrets), CLI mono-utilisateur en local
|
| 15 |
+
> (modèle de menace = celui de la machine de l'utilisateur).
|
| 16 |
+
>
|
| 17 |
+
> **Statut** : v1, 2026-05. À réviser à chaque release majeure ou
|
| 18 |
+
> incident sécurité.
|
| 19 |
+
|
| 20 |
+
## Acteurs
|
| 21 |
+
|
| 22 |
+
| Acteur | Confiance | Capacités |
|
| 23 |
+
|--------|-----------|-----------|
|
| 24 |
+
| **Utilisateur authentifié** (chercheur, archiviste BnF) | Modéré | Upload corpus, lance benchmark, lit rapport, télécharge artefacts |
|
| 25 |
+
| **Utilisateur invité** (lecteur d'un rapport publié) | Bas | Lit un rapport HTML produit |
|
| 26 |
+
| **Opérateur** (DSI institutionnelle) | Élevé | Déploie, configure, accède aux logs, gère les clés API |
|
| 27 |
+
| **Mainteneur** (équipe Picarones) | Élevé sur le code | Push code, release, accès limité aux instances de production |
|
| 28 |
+
| **Attaquant externe** | Aucune | Internet public ou utilisateur malveillant |
|
| 29 |
+
|
| 30 |
+
## Actifs à protéger
|
| 31 |
+
|
| 32 |
+
| Actif | Sensibilité | Pourquoi |
|
| 33 |
+
|-------|-------------|----------|
|
| 34 |
+
| **Corpus uploadés** | RGPD (peut contenir PII : registres d'état civil) | Article 4 RGPD — données personnelles si nominatives |
|
| 35 |
+
| **Vérités terrain (GT)** | Propriété intellectuelle de l'institution | Investissement humain coûteux ; secret de fait |
|
| 36 |
+
| **Clés API cloud** (`OPENAI_API_KEY`, etc.) | Secret crédential | Compromission = facturation arbitraire + exfiltration de données |
|
| 37 |
+
| **Résultats de benchmark** | Faible (résultats agrégés) | Sauf si attribués nominativement à un transcripteur |
|
| 38 |
+
| **Logs applicatifs** | Modéré (PII collatéral, métadonnées corpus) | Audit trail = preuve juridique mais aussi cible |
|
| 39 |
+
| **Code source** | Public (OSS) | Intégrité supply-chain (signed releases, SBOM, SLSA) |
|
| 40 |
+
| **Base SQLite des jobs** | Modéré (historique des runs, paramètres) | Permet de reconstituer l'activité d'un utilisateur |
|
| 41 |
+
|
| 42 |
+
## Surfaces d'attaque
|
| 43 |
+
|
| 44 |
+
```
|
| 45 |
+
┌──────────────────────────────────────────────────────────┐
|
| 46 |
+
│ Internet / Intranet │
|
| 47 |
+
└─────────────────────┬────────────────────────────────────┘
|
| 48 |
+
│
|
| 49 |
+
▼
|
| 50 |
+
┌───────────────────────────────────────┐
|
| 51 |
+
│ FastAPI (interfaces/web) │ ← S1 (HTTP), S2 (auth)
|
| 52 |
+
│ - SecurityHeadersMiddleware │
|
| 53 |
+
│ - BodySizeLimitMiddleware │
|
| 54 |
+
│ - RateLimitMiddleware │
|
| 55 |
+
│ - AuthenticationMiddleware (opt-in) │
|
| 56 |
+
└────────────────────┬──────────────────┘
|
| 57 |
+
│
|
| 58 |
+
▼
|
| 59 |
+
┌───────────────────────────────────────┐
|
| 60 |
+
│ RunOrchestrator + JobRunner │ ← S3 (job exec)
|
| 61 |
+
│ - WorkspaceManager (sandbox) │
|
| 62 |
+
│ - ZIP extraction (zip-slip safe) │
|
| 63 |
+
└────────────────────┬──────────────────┘
|
| 64 |
+
│
|
| 65 |
+
┌───────────────┼─────────────────┐
|
| 66 |
+
▼ ▼ ▼
|
| 67 |
+
┌──────────┐ ┌───────────┐ ┌─────────────┐
|
| 68 |
+
│ Adapters │ │ Adapters │ │ Storage │ ← S4 (cloud)
|
| 69 |
+
│ OCR cloud│ │ LLM cloud │ │ filesystem │ ← S5 (FS)
|
| 70 |
+
│ (HTTPS) │ │ (HTTPS) │ │ + SQLite │ ← S6 (DB)
|
| 71 |
+
└──────────┘ └───────────┘ └─────────────┘
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
## Menaces — analyse STRIDE
|
| 75 |
+
|
| 76 |
+
### S — Spoofing (usurpation d'identité)
|
| 77 |
+
|
| 78 |
+
| ID | Menace | Mitigation |
|
| 79 |
+
|----|--------|------------|
|
| 80 |
+
| S1 | Un attaquant se fait passer pour un utilisateur authentifié | `AuthenticationMiddleware` opt-in avec `AuthenticationBackend` Protocol — l'institution branche son SSO/LDAP/JWT. Les endpoints `/health` et `/version` restent publics pour les sondes. |
|
| 81 |
+
| S2 | Un client forge `X-Forwarded-For` pour spoofer son IP dans le rate limit | `RateLimitMiddleware.trust_proxy_count: int` (défaut 0 = XFF ignoré). Lecture du Nème IP en partant de la fin de la chaîne XFF. Test `tests/interfaces/web/test_rate_limit_xff.py` (7 cas). |
|
| 82 |
+
| S3 | Un attaquant publie un faux package `picarones` sur PyPI | Le projet n'est pas encore sur PyPI public. À la publication : signer les wheels avec Sigstore et publier le SLSA provenance level 3 (cf. backlog). |
|
| 83 |
+
|
| 84 |
+
### T — Tampering (altération)
|
| 85 |
+
|
| 86 |
+
| ID | Menace | Mitigation |
|
| 87 |
+
|----|--------|------------|
|
| 88 |
+
| T1 | Un utilisateur uploade un ZIP avec des chemins zip-slip pour écrire hors workspace | `WorkspaceManager` sandboxe par session, extraction ZIP filtre les chemins absolus et `..`. |
|
| 89 |
+
| T2 | Un caller construit `DocumentRef(id="../../etc/passwd")` programmatiquement | `_DOC_ID_RE` regex `^[A-Za-z0-9_.\-/]+$` + validateur Pydantic explicite qui rejette tout segment `..` (S59 #M3). |
|
| 90 |
+
| T3 | Un attaquant altère le schéma SQLite `jobs.db` entre deux démarrages | `JobStore.SCHEMA_VERSION` + dispatcher `_MIGRATIONS` qui rejette dur les schémas downgrade. Pas de mitigation contre une altération en place — c'est au filesystem. |
|
| 91 |
+
| T4 | Un cache d'artefact corrompu ferait diverger un run | `ArtifactKey.hash_hex()` multi-paramètres (inputs hash + step + code_version + params + projection_spec) — un cache pollué est rejeté à la lecture parce que la clé ne match plus. |
|
| 92 |
+
| T5 | Une fonte / modèle local est remplacé par un fichier malveillant | Picarones ne charge aucun modèle automatiquement. Les modèles Tesseract et Pero sont pointés explicitement par l'utilisateur ; à charge à lui de vérifier les hashes. |
|
| 93 |
+
|
| 94 |
+
### R — Repudiation (non-répudiation)
|
| 95 |
+
|
| 96 |
+
| ID | Menace | Mitigation |
|
| 97 |
+
|----|--------|------------|
|
| 98 |
+
| R1 | Un utilisateur lance un job coûteux puis nie l'avoir fait | `[audit]` log INFO sur `POST /api/jobs` et `DELETE /api/jobs/{id}` avec IP source (S59 #M2). Logs structurés à conserver côté ops selon la politique RGPD. |
|
| 99 |
+
| R2 | Un attaquant modifie un rapport persisté pour falsifier les chiffres | Le `RunManifest` est byte-déterministe (`model_dump_json` Pydantic ordered). Le hash SHA-256 du manifest peut être cité dans une publication pour ancrer la version. Signature cryptographique : non implémentée, à arbitrer (cf. backlog). |
|
| 100 |
+
| R3 | Un mainteneur publie une release sans laisser de trace | GitHub Actions `release.yml` enregistre l'identité GitHub du déclencheur ; SLSA provenance (à venir) attestera la chaîne build → wheel. |
|
| 101 |
+
|
| 102 |
+
### I — Information disclosure
|
| 103 |
+
|
| 104 |
+
| ID | Menace | Mitigation |
|
| 105 |
+
|----|--------|------------|
|
| 106 |
+
| I1 | Une clé API cloud (`OPENAI_API_KEY`, etc.) fuit dans un log applicatif | Les adapters ne logent jamais la clé — vérifié par revue de code. Les exceptions cloud sont catchées et le message reformulé sans inclure de header. À durcir : un test `bandit` dans la CI sur les patterns `api_key` en variable de log. |
|
| 107 |
+
| I2 | Un rapport HTML embarque un CSP permissif et leak via XSS | `CSP: default-src 'self'`, pas de `unsafe-inline`, vérifié par `tests/interfaces/web/test_sprint_a14_s49_security.py`. Le moteur narratif rend les chiffres via templates YAML (pas de injection HTML). |
|
| 108 |
+
| I3 | Le workspace partagé fait fuiter le corpus d'un chercheur à un autre | `WorkspaceManager` sandboxe par `session_id` ; aucun caller ne peut sortir de son workspace via `resolve_output_path`. |
|
| 109 |
+
| I4 | Un endpoint `GET /api/jobs/{job_id}` divulgue les paramètres d'un autre utilisateur | Pas d'isolation multi-tenants à ce jour — défaut documenté. Le déploiement institutionnel doit ajouter une couche d'autorisation par utilisateur (cf. `AuthenticationMiddleware`). |
|
| 110 |
+
| I5 | Un attaquant lit `dependencies_lock` du `RunManifest` pour cibler une CVE | Acceptable — `dependencies_lock` est public par design (reproductibilité). La défense est de patcher rapidement les CVE via `pip-audit` en CI. |
|
| 111 |
+
|
| 112 |
+
### D — Denial of Service
|
| 113 |
+
|
| 114 |
+
| ID | Menace | Mitigation |
|
| 115 |
+
|----|--------|------------|
|
| 116 |
+
| D1 | Upload ZIP géant qui sature le disque | `BodySizeLimitMiddleware` (défaut 100 MiB). **Limite connue** : ne couvre pas `Transfer-Encoding: chunked` — recommandation = nginx `client_max_body_size` en amont (cf. [`operations/runbook.md`](../operations/runbook.md)). |
|
| 117 |
+
| D2 | Flood de requêtes saturant le rate limit en mémoire | `RateLimitMiddleware` avec eviction LRU `max_clients=10000` (S58). Pas atomique sous très haute concurrence — best-effort assumé. |
|
| 118 |
+
| D3 | Job qui hang sur appel cloud (timeout réseau) | `pytest-timeout 5 min` par test ; `urllib.request.urlopen(timeout=)` configurable par adapter ; `call_with_retry` partagé (3 retries 2/4/8s) qui FAIL fast si non-retryable. |
|
| 119 |
+
| D4 | DAG cyclique ou infini dans une `PipelineSpec` | Validation statique avec détection de cycle dans `pipeline/validation.py` ; rejet `PipelineSpecError` au load. |
|
| 120 |
+
| D5 | XML billion-laughs / XXE sur upload ALTO/PAGE | `defusedxml` exclusif dans `formats/alto/parser.py` et `formats/pagexml/parser.py`. |
|
| 121 |
+
|
| 122 |
+
### E — Elevation of privilege
|
| 123 |
+
|
| 124 |
+
| ID | Menace | Mitigation |
|
| 125 |
+
|----|--------|------------|
|
| 126 |
+
| E1 | Un module contribué tiers s'exécute avec des privilèges qu'il ne devrait pas | `BaseModule` interface stricte ; `module_policy.audit_module` valide qu'un module externe ne dérive que de `BaseModule` et déclare ses `input_types`/`output_types` proprement. Pas de sandboxing process — un module malicieux peut faire `os.system`. |
|
| 127 |
+
| E2 | Un utilisateur web arrive à exécuter du code arbitraire via l'API | `RunSpec` est validé par Pydantic ; `adapter_class` est un dotted-path résolu via `importlib.import_module` mais filtré contre une liste explicite via `RegistryService.bootstrap_defaults()`. Une release institutionnelle doit verrouiller cette liste. |
|
| 128 |
+
|
| 129 |
+
## Risques résiduels acceptés
|
| 130 |
+
|
| 131 |
+
| ID | Risque | Pourquoi accepté |
|
| 132 |
+
|----|--------|------------------|
|
| 133 |
+
| RR1 | Le rate limit n'est pas atomique sous très haute concurrence | Best-effort suffit pour usage institutionnel ; un Redis-backed rate limiter est l'évolution si besoin |
|
| 134 |
+
| RR2 | Un module Python contribué peut faire des `os.system` arbitraires | Le modèle de confiance est *« le mainteneur a revu le code »* — pas de sandbox process. Pour un usage institutionnel multi-tenant, déployer dans un conteneur isolé par tenant. |
|
| 135 |
+
| RR3 | Les clés API cloud sont en variables d'environnement, pas en HSM | Standard de l'industrie ; un Vault-backed secret store est l'évolution si la DSI l'exige. |
|
| 136 |
+
| RR4 | Pas d'isolation multi-tenants par user dans le workspace web | Documentée explicitement ; déploiement multi-tenants doit ajouter sa propre couche d'autorisation. |
|
| 137 |
+
|
| 138 |
+
## Procédure de signalement
|
| 139 |
+
|
| 140 |
+
Voir [`/SECURITY.md`](../../SECURITY.md) pour le canal de
|
| 141 |
+
divulgation responsable. La version anglaise est dans
|
| 142 |
+
[`/SECURITY.en.md`](../../SECURITY.en.md).
|
| 143 |
+
|
| 144 |
+
## Révisions
|
| 145 |
+
|
| 146 |
+
| Version | Date | Changements |
|
| 147 |
+
|---------|------|-------------|
|
| 148 |
+
| 1.0 | 2026-05 | Création initiale (S60), méthodologie STRIDE |
|
docs/{user → tutorials}/reading-a-report.en.md
RENAMED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
<!-- translation: machine + human review pending -->
|
| 2 |
-
<!-- canonical: docs/
|
| 3 |
|
| 4 |
# Reading a Picarones report
|
| 5 |
|
|
@@ -98,6 +98,6 @@ relative path and loaded by the browser on-demand
|
|
| 98 |
## Further reading
|
| 99 |
|
| 100 |
- [Glossary] (embedded in report, accessible via `?` icons)
|
| 101 |
-
- [docs/
|
| 102 |
- [docs/developer/extending-glossary.en.md](../developer/extending-glossary.en.md) — enriching the glossary
|
| 103 |
- [SPECS.md](../../SPECS.md) — full project specifications
|
|
|
|
| 1 |
<!-- translation: machine + human review pending -->
|
| 2 |
+
<!-- canonical: docs/tutorials/reading-a-report.md (FR) -->
|
| 3 |
|
| 4 |
# Reading a Picarones report
|
| 5 |
|
|
|
|
| 98 |
## Further reading
|
| 99 |
|
| 100 |
- [Glossary] (embedded in report, accessible via `?` icons)
|
| 101 |
+
- [docs/explanation/narrative-engine.en.md](../developer/narrative-engine.en.md) — adding a detector
|
| 102 |
- [docs/developer/extending-glossary.en.md](../developer/extending-glossary.en.md) — enriching the glossary
|
| 103 |
- [SPECS.md](../../SPECS.md) — full project specifications
|
docs/{user → tutorials}/reading-a-report.md
RENAMED
|
@@ -24,7 +24,7 @@ Visible dès l'ouverture, sans navigation. Contient :
|
|
| 24 |
1. **Synthèse factuelle** — 3 à 5 phrases générées mécaniquement à
|
| 25 |
partir des résultats. Aucun LLM dans la chaîne, donc le texte est
|
| 26 |
reproductible bit-à-bit. Chaque nombre cité est traçable au JSON
|
| 27 |
-
de résultats. Voir [docs/
|
| 28 |
complète des faits que le moteur peut détecter.
|
| 29 |
2. **Critical Difference Diagram** (Friedman-Nemenyi) — un graphique
|
| 30 |
horizontal qui place chaque moteur sur un axe de rang moyen. Les
|
|
@@ -133,7 +133,7 @@ LibreOffice.
|
|
| 133 |
## Pour aller plus loin
|
| 134 |
|
| 135 |
- [Glossaire complet] (intégré dans le rapport, accessible via les `?`)
|
| 136 |
-
- [docs/
|
| 137 |
- [docs/developer/extending-glossary.md] — comment enrichir le glossaire
|
| 138 |
- [SPECS.md] — spécifications complètes du projet
|
| 139 |
|
|
|
|
| 24 |
1. **Synthèse factuelle** — 3 à 5 phrases générées mécaniquement à
|
| 25 |
partir des résultats. Aucun LLM dans la chaîne, donc le texte est
|
| 26 |
reproductible bit-à-bit. Chaque nombre cité est traçable au JSON
|
| 27 |
+
de résultats. Voir [docs/explanation/narrative-engine.md] pour la liste
|
| 28 |
complète des faits que le moteur peut détecter.
|
| 29 |
2. **Critical Difference Diagram** (Friedman-Nemenyi) — un graphique
|
| 30 |
horizontal qui place chaque moteur sur un axe de rang moyen. Les
|
|
|
|
| 133 |
## Pour aller plus loin
|
| 134 |
|
| 135 |
- [Glossaire complet] (intégré dans le rapport, accessible via les `?`)
|
| 136 |
+
- [docs/explanation/narrative-engine.md] — comment ajouter un détecteur
|
| 137 |
- [docs/developer/extending-glossary.md] — comment enrichir le glossaire
|
| 138 |
- [SPECS.md] — spécifications complètes du projet
|
| 139 |
|
docs/{user → tutorials}/writing-a-pipeline-module.md
RENAMED
|
@@ -17,8 +17,9 @@
|
|
| 17 |
## TL;DR
|
| 18 |
|
| 19 |
```python
|
| 20 |
-
from picarones.
|
| 21 |
-
from picarones.
|
|
|
|
| 22 |
PipelineRunner, PipelineSpec, PipelineStep,
|
| 23 |
)
|
| 24 |
|
|
@@ -150,7 +151,7 @@ class NERExtractor(BaseModule):
|
|
| 150 |
### 3.a Mono-document (Sprint 63)
|
| 151 |
|
| 152 |
```python
|
| 153 |
-
from picarones.
|
| 154 |
PipelineRunner, PipelineSpec, PipelineStep,
|
| 155 |
)
|
| 156 |
|
|
@@ -178,7 +179,7 @@ que `Document.ground_truths` porte une `TextGT` (ou `AltoGT`,
|
|
| 178 |
### 3.b Corpus complet (Sprint 64)
|
| 179 |
|
| 180 |
```python
|
| 181 |
-
from picarones.
|
| 182 |
|
| 183 |
bench = run_pipeline_benchmark(spec, my_corpus)
|
| 184 |
print(bench.n_pipelines_succeeded, "/", bench.n_docs)
|
|
@@ -203,7 +204,7 @@ bench = run_pipeline_benchmark(spec, corpus, initial_inputs_factory=my_factory)
|
|
| 203 |
### 3.c Comparer N pipelines (Sprint 65)
|
| 204 |
|
| 205 |
```python
|
| 206 |
-
from picarones.
|
| 207 |
|
| 208 |
comparison = compare_pipelines(
|
| 209 |
[spec_baseline, spec_with_correcteur_a, spec_with_correcteur_b],
|
|
@@ -259,7 +260,7 @@ Sans `inputs_from`, `correct_b` aurait reçu la sortie de
|
|
| 259 |
|
| 260 |
```python
|
| 261 |
from pathlib import Path
|
| 262 |
-
from picarones.
|
| 263 |
|
| 264 |
bench = run_pipeline_benchmark(spec, corpus)
|
| 265 |
Path("rapport_pipeline.html").write_text(
|
|
@@ -270,8 +271,8 @@ Path("rapport_pipeline.html").write_text(
|
|
| 270 |
### 4.b Comparaison de N pipelines (Sprint 68)
|
| 271 |
|
| 272 |
```python
|
| 273 |
-
from picarones.
|
| 274 |
-
from picarones.
|
| 275 |
RankingSpec, build_pipeline_comparison_report_html,
|
| 276 |
)
|
| 277 |
|
|
|
|
| 17 |
## TL;DR
|
| 18 |
|
| 19 |
```python
|
| 20 |
+
from picarones.domain.artifacts import ArtifactType
|
| 21 |
+
from picarones.domain.module_protocol import BaseModule
|
| 22 |
+
from picarones.evaluation.pipeline import (
|
| 23 |
PipelineRunner, PipelineSpec, PipelineStep,
|
| 24 |
)
|
| 25 |
|
|
|
|
| 151 |
### 3.a Mono-document (Sprint 63)
|
| 152 |
|
| 153 |
```python
|
| 154 |
+
from picarones.evaluation.pipeline import (
|
| 155 |
PipelineRunner, PipelineSpec, PipelineStep,
|
| 156 |
)
|
| 157 |
|
|
|
|
| 179 |
### 3.b Corpus complet (Sprint 64)
|
| 180 |
|
| 181 |
```python
|
| 182 |
+
from picarones.evaluation.pipeline_benchmark import run_pipeline_benchmark
|
| 183 |
|
| 184 |
bench = run_pipeline_benchmark(spec, my_corpus)
|
| 185 |
print(bench.n_pipelines_succeeded, "/", bench.n_docs)
|
|
|
|
| 204 |
### 3.c Comparer N pipelines (Sprint 65)
|
| 205 |
|
| 206 |
```python
|
| 207 |
+
from picarones.evaluation.pipeline_comparison import compare_pipelines
|
| 208 |
|
| 209 |
comparison = compare_pipelines(
|
| 210 |
[spec_baseline, spec_with_correcteur_a, spec_with_correcteur_b],
|
|
|
|
| 260 |
|
| 261 |
```python
|
| 262 |
from pathlib import Path
|
| 263 |
+
from picarones.reports_v2.html.renderers.pipeline import build_pipeline_report_html
|
| 264 |
|
| 265 |
bench = run_pipeline_benchmark(spec, corpus)
|
| 266 |
Path("rapport_pipeline.html").write_text(
|
|
|
|
| 271 |
### 4.b Comparaison de N pipelines (Sprint 68)
|
| 272 |
|
| 273 |
```python
|
| 274 |
+
from picarones.domain.artifacts import ArtifactType
|
| 275 |
+
from picarones.reports_v2.html.renderers.pipeline import (
|
| 276 |
RankingSpec, build_pipeline_comparison_report_html,
|
| 277 |
)
|
| 278 |
|