Spaces:
Running
feat: Sprint A14-S57 — Wave F clôture audit (issues #15 #16 #21 #23 #24 #25 #26 #30)
Browse filesDernière vague de remédiation des 30 dettes identifiées en audit
institutional readiness 2026-05. Tous les issues sont désormais adressés.
Issues #15 (lazy imports SDK), #21 (claim rewrite complet), #23 (+406
tests), #24 (parallel rewrite), #25 (file budgets), #30 (CER fix) :
rectifications documentaires dans CHANGELOG.md et docs/migration/
rewrite-status-s46.md — formulations qualifiées et justifications
explicites.
Issue #16 (i18n prompts FR/EN/LA) : BaseLLMAdapter.DEFAULT_CORRECTION_PROMPTS
et BaseVLMAdapter.DEFAULT_TRANSCRIPTION_PROMPTS sont désormais des dicts
indexés par code langue ; sélection via config["correction_prompt"]/
["transcription_prompt"] > config["lang"] (fr/en/la) > fallback FR.
Issue #26 (DeprecationWarning legacy spec.py) : import depuis
picarones.pipeline.spec émet désormais un DeprecationWarning pointant
vers picarones.domain.pipeline_spec (chemin canonique). Tous les callers
internes (10 fichiers picarones/, 5 fichiers tests/) migrés vers le
chemin canonique ; seul le test S40 dédié à la rétrocompat conserve
l'import legacy + un nouveau test_legacy_pipeline_path_emits_warning
qui valide explicitement l'émission du warning. Suppression effective
prévue S60.
Annexes :
- ArtifactType.CONFIDENCES (S50) ajouté à test_canonical_values.
- picarones/adapters/storage/job_store.py (421 lignes après S56)
ajouté à FILE_BUDGETS avec budget 500.
- README régénéré via scripts/gen_readme_tables.py.
Tests : 4990 passed, 11 skipped, 0 failed.
Lint : ruff check picarones/ tests/ clean.
- CHANGELOG.md +111 -0
- README.md +1 -1
- docs/migration/rewrite-status-s46.md +96 -29
- picarones/adapters/llm/base.py +41 -11
- picarones/adapters/vlm/base.py +34 -3
- picarones/app/schemas/run_spec.py +1 -1
- picarones/app/services/benchmark_service.py +1 -1
- picarones/pipeline/__init__.py +1 -1
- picarones/pipeline/cache.py +1 -1
- picarones/pipeline/cache_helpers.py +1 -1
- picarones/pipeline/executor.py +1 -1
- picarones/pipeline/planner.py +1 -1
- picarones/pipeline/runner.py +1 -1
- picarones/pipeline/spec.py +17 -1
- picarones/pipeline/validation.py +1 -1
- picarones/pipeline/yaml_io.py +1 -1
- tests/adapters/llm/test_sprint_a14_s44_llm_step_executor.py +1 -1
- tests/adapters/vlm/test_sprint_a14_s45_vlm_adapters.py +1 -1
- tests/app/schemas/test_sprint_a14_s39_run_spec_extended.py +1 -1
- tests/architecture/test_file_budgets.py +4 -0
- tests/domain/test_sprint_a14_s40_pipeline_spec_in_domain.py +23 -1
- tests/domain/test_sprint_a14_s4_artifacts.py +5 -2
- tests/pipeline/test_sprint_a14_s28_planner.py +1 -1
- tests/pipeline/test_sprint_a14_s47_artifact_store_resume.py +1 -1
|
@@ -7,6 +7,117 @@ La numérotation de version suit [Semantic Versioning](https://semver.org/lang/f
|
|
| 7 |
|
| 8 |
---
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
## [Unreleased] — fix CI perf_regression — 2026-05
|
| 11 |
|
| 12 |
### ⚠️ BREAKING CHANGE — sémantique `--fail-if-cer-above`
|
|
|
|
| 7 |
|
| 8 |
---
|
| 9 |
|
| 10 |
+
## [Unreleased] — rewrite A14 (S27-S46) + audit remediation (S47-S57) — 2026-05
|
| 11 |
+
|
| 12 |
+
> Cette section couvre la phase **rewrite ciblé** (S27-S46) puis les
|
| 13 |
+
> **6 vagues de remédiation** des dettes identifiées en audit
|
| 14 |
+
> *institutional readiness 2026-05* (S47-S57). Détail complet dans
|
| 15 |
+
> `docs/migration/rewrite-status-s46.md` et
|
| 16 |
+
> `docs/audits/remediation-plan-2026-05.md`.
|
| 17 |
+
|
| 18 |
+
### Phase rewrite (S27-S46) — partial rewrite
|
| 19 |
+
|
| 20 |
+
20 sprints sur la directive *« rewrite tout, le plus solide, sans dette
|
| 21 |
+
technique »*. Stratégie : **rewrite parallèle**, pas full rewrite — le
|
| 22 |
+
nouveau monde (`picarones/{domain,formats,evaluation,pipeline,adapters,
|
| 23 |
+
app,reports_v2,interfaces}/`) cohabite avec le legacy
|
| 24 |
+
(`picarones/{cli,web,engines,llm,pipelines,report}/`) le temps que la
|
| 25 |
+
parité fonctionnelle soit atteinte sur le rendu rapport et que les
|
| 26 |
+
callers externes migrent.
|
| 27 |
+
|
| 28 |
+
**Fondations** : `ProjectionEngine` + `EvaluationEngine` séparés,
|
| 29 |
+
`PipelinePlanner` + `ExecutionPlan`, `ArtifactStore` filesystem +
|
| 30 |
+
hash multi-paramètres.
|
| 31 |
+
|
| 32 |
+
**Adapters natifs** (NO SHIM) : 5 OCR (Tesseract, Pero, Mistral,
|
| 33 |
+
Google Vision, Azure DI), 4 LLM (Anthropic, OpenAI, Mistral, Ollama),
|
| 34 |
+
4 VLM dérivés via MRO multiple.
|
| 35 |
+
|
| 36 |
+
**Web app native** : skeleton FastAPI + DI, 3 routers (corpus,
|
| 37 |
+
benchmark, jobs), JobStore SQLite, UI Jinja2 + i18n FR/EN.
|
| 38 |
+
|
| 39 |
+
**Reports v2** : CSV, JSON ; HTML canonique (TextView, AltoView,
|
| 40 |
+
SearchView). Vues thématiques legacy (Pareto, narrative, glossary,
|
| 41 |
+
case-studies) à porter une à une post-livraison.
|
| 42 |
+
|
| 43 |
+
### Phase remédiation (S47-S57) — 30 dettes adressées en 6 vagues
|
| 44 |
+
|
| 45 |
+
| Vague | Sprint | Issues | Thème |
|
| 46 |
+
|-------|--------|--------|-------|
|
| 47 |
+
| Pré-audit | S47-S48 | #1, #2 | `ArtifactStore` wired to `PipelineExecutor` (resume by hash), `JobRunner` threading + lifespan hook |
|
| 48 |
+
| A | S49-S51 | #3-#7 | Web security middlewares (`SecurityHeadersMiddleware`, `BodySizeLimitMiddleware`, `RateLimitMiddleware`, `AuthenticationMiddleware`), confidences sidecar JSON, `resolve_output_path` workspace propagation |
|
| 49 |
+
| B | S52-S53 | #8-#11 | `AdapterStepError` hierarchy (parent commun OCR/LLM/VLM), Mistral routing strict (`.lower().startswith("mistral-ocr")`), `normalize_llm_content` sur le chemin chat |
|
| 50 |
+
| C | S54 | #6 | MRO guard `__init_subclass__` sur `BaseVLMAdapter` — détecte `class X(LLM, VLM)` au lieu de `class X(VLM, LLM)` à la définition |
|
| 51 |
+
| D | S55 | #14 | Tests d'intégration live `tests/integration/live/` avec marker `live` (pytest.importorskip pour SDK absents) |
|
| 52 |
+
| E | S56 | #12, #13, #17, #18, #19, #20, #22, #27, #28, #29 | `JobStore` `schema_version` table + `busy_timeout 30s`, WAL mode, `model_dump(mode="json")`, `_infer_pipeline_name` via préfixe `doc_id`, `MAX_RUNS_DISPLAYED=20`, etc. |
|
| 53 |
+
| F | S57 | #15, #16, #21, #23, #24, #25, #26, #30 | i18n prompts FR/EN/LA dans `BaseLLMAdapter`/`BaseVLMAdapter`, `DeprecationWarning` sur `picarones.pipeline.spec`, rectifications doc CHANGELOG + audit |
|
| 54 |
+
|
| 55 |
+
**Tous les 30 issues sont adressés au S57**.
|
| 56 |
+
|
| 57 |
+
### S57 — détail des rectifications
|
| 58 |
+
|
| 59 |
+
- **#15 Lazy imports SDK tiers** : confirmé intentionnel — `mistralai`,
|
| 60 |
+
`anthropic`, `openai`, `ollama` sont importés à l'intérieur des
|
| 61 |
+
méthodes plutôt qu'au top du module. Raison : ces SDK sont des
|
| 62 |
+
dépendances optionnelles (extras `[mistral]`, `[anthropic]`…) — un
|
| 63 |
+
import top-level ferait planter `import picarones` sur un
|
| 64 |
+
environnement minimal.
|
| 65 |
+
|
| 66 |
+
- **#16 i18n prompts FR/EN/LA** : `BaseLLMAdapter.DEFAULT_CORRECTION_PROMPTS`
|
| 67 |
+
et `BaseVLMAdapter.DEFAULT_TRANSCRIPTION_PROMPTS` sont désormais des
|
| 68 |
+
`dict[str, str]` indexés par code langue (`fr`, `en`, `la`).
|
| 69 |
+
Sélection : override explicite via `config["correction_prompt"]` /
|
| 70 |
+
`config["transcription_prompt"]` > `config["lang"]` (fr/en/la) >
|
| 71 |
+
fallback FR. Les anciennes constantes `DEFAULT_CORRECTION_PROMPT` /
|
| 72 |
+
`DEFAULT_TRANSCRIPTION_PROMPT` (singulier) restent pour rétrocompat
|
| 73 |
+
des callers qui les lisent directement.
|
| 74 |
+
|
| 75 |
+
- **#21 Rectification *« rewrite fonctionnellement complet »*** :
|
| 76 |
+
formulation initiale trop forte. La parité fonctionnelle cible
|
| 77 |
+
est atteinte sur **les contrats et l'architecture**, pas sur le
|
| 78 |
+
**rendu rapport** (vues thématiques legacy non encore portées) ni
|
| 79 |
+
sur la **CLI** (commandes `history`, `compare`, `pipeline`,
|
| 80 |
+
`diagnose` à porter). Cf.
|
| 81 |
+
`docs/migration/rewrite-status-s46.md` pour le détail.
|
| 82 |
+
|
| 83 |
+
- **#23 Qualification *« +406 tests »*** : nombre concernait
|
| 84 |
+
spécifiquement les **nouveaux tests écrits pour le new world** sur
|
| 85 |
+
S27-S45 (`tests/{adapters,pipeline,evaluation,reports_v2,app,
|
| 86 |
+
interfaces}/`), pas une supposée hausse de la couverture totale du
|
| 87 |
+
repo. Les tests legacy ont été conservés intacts — la couverture
|
| 88 |
+
nette du rewrite est **additive**, pas substitutive.
|
| 89 |
+
|
| 90 |
+
- **#24 Rewrite parallèle** : documenté explicitement dans
|
| 91 |
+
`rewrite-status-s46.md` — `picarones/{cli,web,engines,llm,
|
| 92 |
+
pipelines,report}/` reste exécutable et un caller externe peut
|
| 93 |
+
encore importer depuis n'importe lequel. Cette coexistence est
|
| 94 |
+
volontaire le temps de la migration des callers, mais doit être
|
| 95 |
+
tenue pour ce qu'elle est : un **rewrite parallèle**, pas un *full
|
| 96 |
+
rewrite*.
|
| 97 |
+
|
| 98 |
+
- **#25 File budgets** : la règle interne *« tout fichier ≥ 400
|
| 99 |
+
lignes est budgété »* est un garde-fou pragmatique, pas une
|
| 100 |
+
doctrine ; elle force à expliciter la justification lorsqu'un
|
| 101 |
+
module dépasse ce seuil. Aucun fichier ne dépasse 800 lignes
|
| 102 |
+
après S46.
|
| 103 |
+
|
| 104 |
+
- **#26 DeprecationWarning sur `picarones.pipeline.spec`** : import
|
| 105 |
+
depuis ce module émet désormais un `DeprecationWarning` pointant
|
| 106 |
+
vers `picarones.domain.pipeline_spec` (chemin canonique). Tous
|
| 107 |
+
les callers internes (`picarones/`) et les tests sauf le test
|
| 108 |
+
S40 dédié à la rétrocompat ont été migrés vers le chemin
|
| 109 |
+
canonique. Suppression effective du re-export prévue S60.
|
| 110 |
+
|
| 111 |
+
- **#30 Commit hygiene CER fix** : le seuil de régression CER en CI
|
| 112 |
+
(`perf_regression.yml`) est passé de `0.10` à `0.20` (cf. section
|
| 113 |
+
`[Unreleased] — fix CI perf_regression`). Justification métier :
|
| 114 |
+
les corpus patrimoniaux ont des CER bruts qui peuvent légitimement
|
| 115 |
+
varier de 5-15 points selon le tirage de validation (segmentation,
|
| 116 |
+
qualité d'image, présence de notes marginales). Un seuil à 10
|
| 117 |
+
points faisait échouer la CI sur du bruit légitime.
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
## [Unreleased] — fix CI perf_regression — 2026-05
|
| 122 |
|
| 123 |
### ⚠️ BREAKING CHANGE — sémantique `--fail-if-cer-above`
|
|
@@ -396,7 +396,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
|
|
|
|
| 396 |
python -m mypy picarones/core/
|
| 397 |
```
|
| 398 |
|
| 399 |
+
**Test suite**: ~5010 tests, ~3 min on a modern laptop. Coverage
|
| 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
|
|
@@ -1,28 +1,41 @@
|
|
| 1 |
-
# État du rewrite —
|
| 2 |
|
| 3 |
Ce document synthétise l'état du rewrite du Picarones après les 20 sprints
|
| 4 |
S27-S46 réalisés sur la directive *« rewrite tout, le plus solide, sans
|
| 5 |
-
dette technique »*
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
## Inventaire des modules legacy
|
| 28 |
|
|
@@ -87,18 +100,72 @@ Pour chaque module legacy à supprimer, il faut :
|
|
| 87 |
4. **Autorisation utilisateur explicite** : un commit qui supprime
|
| 88 |
~4000 lignes de code en production exige une revue formelle.
|
| 89 |
|
| 90 |
-
## Statistiques globales du rewrite (S1-
|
| 91 |
-
|
| 92 |
-
- **Tests** : ~4910 tests, 11 skipped, 0 failed (vs 4504 au
|
| 93 |
-
rewrite, S26).
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
- **Lint** : `ruff check picarones/ tests/` clean.
|
| 96 |
-
- **File budgets**
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
- **Layer dependencies** : domain → formats → evaluation → pipeline
|
| 99 |
→ adapters → app → reports_v2 → interfaces, vérifié par test
|
| 100 |
d'architecture.
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
## Prochaines étapes possibles (post-rewrite)
|
| 103 |
|
| 104 |
1. **Confidences typées** : créer un `ConfidenceArtifact` typé pour
|
|
|
|
| 1 |
+
# État du rewrite — Sprints A14-S46 puis S47-S57 (audit + remédiation)
|
| 2 |
|
| 3 |
Ce document synthétise l'état du rewrite du Picarones après les 20 sprints
|
| 4 |
S27-S46 réalisés sur la directive *« rewrite tout, le plus solide, sans
|
| 5 |
+
dette technique »*, puis les 11 sprints S47-S57 d'audit/remédiation des
|
| 6 |
+
30 dettes identifiées en revue de fin de rewrite (audit 2026-05).
|
| 7 |
+
|
| 8 |
+
## Statut réel — partial rewrite, pas full rewrite (S57, audit #21 + #24)
|
| 9 |
+
|
| 10 |
+
Le rewrite est **fonctionnellement complet sur le périmètre des contrats
|
| 11 |
+
et de l'architecture cible** (circles propres `domain → formats →
|
| 12 |
+
evaluation → pipeline → adapters → app → reports_v2 → interfaces`,
|
| 13 |
+
services applicatifs, adapters natifs OCR/LLM/VLM, pipeline planner,
|
| 14 |
+
artifact store, web UI native). La formulation initiale *« rewrite
|
| 15 |
+
fonctionnellement complet »* était trop forte sur deux dimensions
|
| 16 |
+
relevées par l'audit :
|
| 17 |
+
|
| 18 |
+
1. **Parité fonctionnelle non encore atteinte côté rendu rapport** : le
|
| 19 |
+
legacy `picarones/report/` contient ~22 vues HTML thématiques
|
| 20 |
+
(Pareto, narrative, glossary, case-studies, etc.) que `reports_v2/`
|
| 21 |
+
ne reproduit pas intégralement. Les vues canoniques (TextView,
|
| 22 |
+
AltoView, SearchView) sont en place ; les vues additionnelles seront
|
| 23 |
+
portées une à une selon les besoins BnF, pas en bloc.
|
| 24 |
+
|
| 25 |
+
2. **Coexistence legacy + new world** : `picarones/{cli,web,engines,
|
| 26 |
+
llm,pipelines,report}/` reste en place et exécutable. Un caller
|
| 27 |
+
externe peut encore importer depuis n'importe lequel. Cette
|
| 28 |
+
coexistence est volontaire (cf. *Critères pour la suppression future
|
| 29 |
+
du legacy* plus bas) mais doit être tenue pour ce qu'elle est : un
|
| 30 |
+
**rewrite parallèle**, pas un *full rewrite*. Les usages production
|
| 31 |
+
sont à migrer caller par caller.
|
| 32 |
+
|
| 33 |
+
3. **Tests legacy non migrés** : ~200+ tests legacy valident le
|
| 34 |
+
comportement historique (`tests/web/`, `tests/measurements/`,
|
| 35 |
+
`tests/cli/_workflows/`, `tests/integration/test_chantier*.py`,
|
| 36 |
+
etc.). Ils protègent le legacy contre les régressions le temps
|
| 37 |
+
que la migration des callers s'achève ; les supprimer prématurément
|
| 38 |
+
perdrait la couverture.
|
| 39 |
|
| 40 |
## Inventaire des modules legacy
|
| 41 |
|
|
|
|
| 100 |
4. **Autorisation utilisateur explicite** : un commit qui supprime
|
| 101 |
~4000 lignes de code en production exige une revue formelle.
|
| 102 |
|
| 103 |
+
## Statistiques globales du rewrite (S1-S57)
|
| 104 |
+
|
| 105 |
+
- **Tests** : ~4910 tests, 11 skipped, 0 failed au S46 (vs 4504 au
|
| 106 |
+
début du rewrite, S26). Sprint S57 (audit #23) : la formulation
|
| 107 |
+
*« +406 nouveaux tests »* concernait spécifiquement les **nouveaux
|
| 108 |
+
tests écrits pour le new world** sur S27-S45 (`tests/{adapters,
|
| 109 |
+
pipeline,evaluation,reports_v2,app,interfaces}/`) ; elle ne dit
|
| 110 |
+
rien d'une supposée hausse de la couverture totale du repo. Les
|
| 111 |
+
tests legacy (`tests/{web,cli,engines,measurements,...}/`) ont été
|
| 112 |
+
conservés intacts — la couverture nette du rewrite est donc
|
| 113 |
+
**additive**, pas substitutive.
|
| 114 |
- **Lint** : `ruff check picarones/ tests/` clean.
|
| 115 |
+
- **File budgets** (audit #25) : la règle interne *« tout fichier
|
| 116 |
+
≥ 400 lignes est budgété »* est un garde-fou pragmatique, pas une
|
| 117 |
+
doctrine ; elle force à expliciter la justification lorsqu'un
|
| 118 |
+
module dépasse ce seuil (ex. `interfaces/web/app.py` ~480 lignes
|
| 119 |
+
— composé de routes/handlers/middlewares groupés par cohérence
|
| 120 |
+
fonctionnelle). Aucun fichier ne dépasse 800 lignes après S46.
|
| 121 |
- **Layer dependencies** : domain → formats → evaluation → pipeline
|
| 122 |
→ adapters → app → reports_v2 → interfaces, vérifié par test
|
| 123 |
d'architecture.
|
| 124 |
|
| 125 |
+
## Sprints d'audit/remédiation S47-S57 (audit institutional readiness)
|
| 126 |
+
|
| 127 |
+
L'audit *institutional readiness 2026-05* a identifié 30 dettes
|
| 128 |
+
techniques résiduelles après le rewrite ciblé. Elles ont été
|
| 129 |
+
adressées en 6 vagues (S47-S57) :
|
| 130 |
+
|
| 131 |
+
| Vague | Sprint | Issues | Thème |
|
| 132 |
+
|-------|--------|--------|-------|
|
| 133 |
+
| pré-audit | S47-S48 | #1, #2 | ArtifactStore wired, JobRunner threading |
|
| 134 |
+
| A | S49-S51 | #3-#7 | Web security middlewares, confidences sidecar, output paths |
|
| 135 |
+
| B | S52-S53 | #8-#11 | AdapterStepError hierarchy, Mistral routing strict, normalize_llm_content path |
|
| 136 |
+
| C | S54 | #6 | MRO guard `__init_subclass__` BaseVLMAdapter |
|
| 137 |
+
| D | S55 | #14 | Live integration tests `tests/integration/live/` |
|
| 138 |
+
| E | S56 | #12, #13, #17, #18, #19, #20, #22, #27, #28, #29 | JobStore schema_version, busy_timeout, model_dump(mode="json"), `_infer_pipeline_name`, etc. |
|
| 139 |
+
| F | S57 | #15, #16, #21, #23, #24, #25, #26, #30 | i18n prompts FR/EN/LA, DeprecationWarning legacy spec.py, doc rectifications |
|
| 140 |
+
|
| 141 |
+
**Tous les 30 issues sont adressés au S57**. Les détails sont dans
|
| 142 |
+
`docs/audits/remediation-plan-2026-05.md`.
|
| 143 |
+
|
| 144 |
+
### Notes spécifiques (S57)
|
| 145 |
+
|
| 146 |
+
- **#15 Lazy imports SDK tiers** : les imports `mistralai`, `anthropic`,
|
| 147 |
+
`openai`, `ollama` sont **intentionnellement à l'intérieur des
|
| 148 |
+
méthodes** (`MistralOCRAdapter._call_chat_vision_api`, etc.) plutôt
|
| 149 |
+
qu'au top du module. Raison : ces SDK sont des dépendances
|
| 150 |
+
optionnelles (extras `[mistral]`, `[anthropic]`…) — un import top-level
|
| 151 |
+
ferait planter `import picarones` sur un environnement minimal.
|
| 152 |
+
Le coût (re-exécution de l'import à chaque appel) est négligé par
|
| 153 |
+
le cache d'imports Python.
|
| 154 |
+
- **#16 i18n prompts FR/EN/LA** : `BaseLLMAdapter.DEFAULT_CORRECTION_PROMPTS`
|
| 155 |
+
et `BaseVLMAdapter.DEFAULT_TRANSCRIPTION_PROMPTS` sont des
|
| 156 |
+
`dict[str, str]` indexés par code langue. Sélection : override
|
| 157 |
+
explicite via `config["correction_prompt"]`/`["transcription_prompt"]`
|
| 158 |
+
> `config["lang"]` (fr/en/la) > fallback FR.
|
| 159 |
+
- **#26 DeprecationWarning legacy spec.py** : import depuis
|
| 160 |
+
`picarones.pipeline.spec` émet désormais un `DeprecationWarning`
|
| 161 |
+
pointant vers `picarones.domain`. Suppression effective prévue S60.
|
| 162 |
+
- **#30 Commit hygiene CER fix** : la modification du seuil de
|
| 163 |
+
régression CER en CI (de 0.10 à 0.20) est documentée dans le
|
| 164 |
+
CHANGELOG sous *« CER regression check threshold rationale »*
|
| 165 |
+
avec justification métier (corpus patrimoniaux ont des CER bruts
|
| 166 |
+
qui peuvent légitimement varier de 5-15 points selon le tirage de
|
| 167 |
+
validation).
|
| 168 |
+
|
| 169 |
## Prochaines étapes possibles (post-rewrite)
|
| 170 |
|
| 171 |
1. **Confidences typées** : créer un `ConfidenceArtifact` typé pour
|
|
@@ -242,14 +242,36 @@ class BaseLLMAdapter(ABC):
|
|
| 242 |
#: surcharger en ``"cpu"``.
|
| 243 |
execution_mode: str = "io"
|
| 244 |
|
| 245 |
-
#:
|
| 246 |
-
#:
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
"
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
def __init__(
|
| 255 |
self,
|
|
@@ -387,9 +409,17 @@ class BaseLLMAdapter(ABC):
|
|
| 387 |
image_path.read_bytes(),
|
| 388 |
).decode("ascii")
|
| 389 |
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
prompt = prompt_template.format(text=original_text)
|
| 394 |
|
| 395 |
result = self.complete(prompt, image_b64=image_b64)
|
|
|
|
| 242 |
#: surcharger en ``"cpu"``.
|
| 243 |
execution_mode: str = "io"
|
| 244 |
|
| 245 |
+
#: Prompts de post-correction par défaut, indexés par code langue
|
| 246 |
+
#: ISO-639-1. Sprint S57 (audit #16) : avant ce sprint, seul le
|
| 247 |
+
#: prompt FR existait — un corpus EN/LA était sous-optimal.
|
| 248 |
+
#: Le prompt est sélectionné selon ``config["lang"]``,
|
| 249 |
+
#: défaut FR.
|
| 250 |
+
DEFAULT_CORRECTION_PROMPTS: dict[str, str] = {
|
| 251 |
+
"fr": (
|
| 252 |
+
"Corrige les erreurs OCR dans le texte suivant en "
|
| 253 |
+
"conservant fidèlement la langue, l'orthographe "
|
| 254 |
+
"historique et la ponctuation. Retourne uniquement le "
|
| 255 |
+
"texte corrigé, sans commentaire :\n\n{text}"
|
| 256 |
+
),
|
| 257 |
+
"en": (
|
| 258 |
+
"Fix OCR errors in the following text while preserving "
|
| 259 |
+
"the original language, historical spelling, and "
|
| 260 |
+
"punctuation. Return only the corrected text, with no "
|
| 261 |
+
"commentary:\n\n{text}"
|
| 262 |
+
),
|
| 263 |
+
"la": (
|
| 264 |
+
"Corrige errores OCR in textu sequenti, fideliter "
|
| 265 |
+
"servans linguam, orthographiam historicam et "
|
| 266 |
+
"interpunctionem. Redde solum textum correctum, sine "
|
| 267 |
+
"ulla glossa:\n\n{text}"
|
| 268 |
+
),
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
#: Alias rétrocompat — Sprint S44 utilisait
|
| 272 |
+
#: ``DEFAULT_CORRECTION_PROMPT`` (FR uniquement). Toujours exposé
|
| 273 |
+
#: pour ne pas casser les tests S44 ; pointe vers le prompt FR.
|
| 274 |
+
DEFAULT_CORRECTION_PROMPT: str = DEFAULT_CORRECTION_PROMPTS["fr"]
|
| 275 |
|
| 276 |
def __init__(
|
| 277 |
self,
|
|
|
|
| 409 |
image_path.read_bytes(),
|
| 410 |
).decode("ascii")
|
| 411 |
|
| 412 |
+
# Sprint S57 (audit #16) : sélection du prompt par langue.
|
| 413 |
+
# Priorité : config["correction_prompt"] (override explicite)
|
| 414 |
+
# > prompt par langue selon config["lang"] > FR par défaut.
|
| 415 |
+
custom_prompt = self.config.get("correction_prompt")
|
| 416 |
+
if custom_prompt is not None:
|
| 417 |
+
prompt_template = custom_prompt
|
| 418 |
+
else:
|
| 419 |
+
lang = (self.config.get("lang") or "fr").lower()
|
| 420 |
+
prompt_template = self.DEFAULT_CORRECTION_PROMPTS.get(
|
| 421 |
+
lang, self.DEFAULT_CORRECTION_PROMPTS["fr"],
|
| 422 |
+
)
|
| 423 |
prompt = prompt_template.format(text=original_text)
|
| 424 |
|
| 425 |
result = self.complete(prompt, image_b64=image_b64)
|
|
@@ -125,6 +125,30 @@ class BaseVLMAdapter(BaseLLMAdapter):
|
|
| 125 |
def output_types(self) -> "frozenset":
|
| 126 |
return frozenset({ArtifactType.RAW_TEXT})
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
DEFAULT_TRANSCRIPTION_PROMPT: str = (
|
| 129 |
"Transcris fidèlement le texte visible sur cette image de "
|
| 130 |
"document historique. Conserve l'orthographe historique, les "
|
|
@@ -165,9 +189,16 @@ class BaseVLMAdapter(BaseLLMAdapter):
|
|
| 165 |
image_path.read_bytes(),
|
| 166 |
).decode("ascii")
|
| 167 |
|
| 168 |
-
prompt
|
| 169 |
-
|
| 170 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
result = self.complete(prompt, image_b64=image_b64)
|
| 173 |
if not result.success:
|
|
|
|
| 125 |
def output_types(self) -> "frozenset":
|
| 126 |
return frozenset({ArtifactType.RAW_TEXT})
|
| 127 |
|
| 128 |
+
#: Prompts de transcription VLM par défaut, indexés par code
|
| 129 |
+
#: langue (Sprint S57 / audit #16).
|
| 130 |
+
DEFAULT_TRANSCRIPTION_PROMPTS: dict[str, str] = {
|
| 131 |
+
"fr": (
|
| 132 |
+
"Transcris fidèlement le texte visible sur cette image "
|
| 133 |
+
"de document historique. Conserve l'orthographe "
|
| 134 |
+
"historique, les abréviations, et la ponctuation. "
|
| 135 |
+
"Retourne uniquement le texte transcrit, sans commentaire."
|
| 136 |
+
),
|
| 137 |
+
"en": (
|
| 138 |
+
"Faithfully transcribe the text visible in this image of "
|
| 139 |
+
"a historical document. Preserve the historical "
|
| 140 |
+
"spelling, abbreviations, and punctuation. Return only "
|
| 141 |
+
"the transcribed text, with no commentary."
|
| 142 |
+
),
|
| 143 |
+
"la": (
|
| 144 |
+
"Fideliter transcribe textum in hac imagine documenti "
|
| 145 |
+
"historici visibilem. Serva orthographiam historicam, "
|
| 146 |
+
"abbreviationes, et interpunctionem. Redde solum textum "
|
| 147 |
+
"transcriptum, sine ulla glossa."
|
| 148 |
+
),
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
#: Alias rétrocompat (Sprint S45 utilisait cette constante).
|
| 152 |
DEFAULT_TRANSCRIPTION_PROMPT: str = (
|
| 153 |
"Transcris fidèlement le texte visible sur cette image de "
|
| 154 |
"document historique. Conserve l'orthographe historique, les "
|
|
|
|
| 189 |
image_path.read_bytes(),
|
| 190 |
).decode("ascii")
|
| 191 |
|
| 192 |
+
# Sprint S57 (audit #16) : sélection du prompt par langue.
|
| 193 |
+
# Override explicite > prompt par langue > FR.
|
| 194 |
+
custom = self.config.get("transcription_prompt")
|
| 195 |
+
if custom is not None:
|
| 196 |
+
prompt = custom
|
| 197 |
+
else:
|
| 198 |
+
lang = (self.config.get("lang") or "fr").lower()
|
| 199 |
+
prompt = self.DEFAULT_TRANSCRIPTION_PROMPTS.get(
|
| 200 |
+
lang, self.DEFAULT_TRANSCRIPTION_PROMPTS["fr"],
|
| 201 |
+
)
|
| 202 |
|
| 203 |
result = self.complete(prompt, image_b64=image_b64)
|
| 204 |
if not result.success:
|
|
@@ -199,7 +199,7 @@ class PipelineSpecYaml(BaseModel):
|
|
| 199 |
def _validate_inputs_from(self) -> "PipelineSpecYaml":
|
| 200 |
"""Vérifie que chaque ``inputs_from[type] = ref`` désigne soit
|
| 201 |
``__initial__``, soit un step antérieur qui produit le type."""
|
| 202 |
-
from picarones.
|
| 203 |
|
| 204 |
# Set des steps déjà vus pour vérifier l'antériorité.
|
| 205 |
seen_step_ids: set[str] = set()
|
|
|
|
| 199 |
def _validate_inputs_from(self) -> "PipelineSpecYaml":
|
| 200 |
"""Vérifie que chaque ``inputs_from[type] = ref`` désigne soit
|
| 201 |
``__initial__``, soit un step antérieur qui produit le type."""
|
| 202 |
+
from picarones.domain.pipeline_spec import INITIAL_STEP_ID
|
| 203 |
|
| 204 |
# Set des steps déjà vus pour vérifier l'antériorité.
|
| 205 |
seen_step_ids: set[str] = set()
|
|
@@ -53,7 +53,7 @@ from picarones.app.results import RunDocumentResult, RunResult
|
|
| 53 |
from picarones.evaluation.views.base import ViewResult
|
| 54 |
from picarones.evaluation.views.executor import DefaultEvaluationViewExecutor
|
| 55 |
from picarones.pipeline.runner import CorpusRunner
|
| 56 |
-
from picarones.
|
| 57 |
from picarones.pipeline.types import PipelineResult, RunContext
|
| 58 |
|
| 59 |
logger = logging.getLogger(__name__)
|
|
|
|
| 53 |
from picarones.evaluation.views.base import ViewResult
|
| 54 |
from picarones.evaluation.views.executor import DefaultEvaluationViewExecutor
|
| 55 |
from picarones.pipeline.runner import CorpusRunner
|
| 56 |
+
from picarones.domain.pipeline_spec import PipelineSpec
|
| 57 |
from picarones.pipeline.types import PipelineResult, RunContext
|
| 58 |
|
| 59 |
logger = logging.getLogger(__name__)
|
|
@@ -72,7 +72,7 @@ from picarones.pipeline.runner import (
|
|
| 72 |
DocumentOutcome,
|
| 73 |
InitialInputsFactory,
|
| 74 |
)
|
| 75 |
-
from picarones.
|
| 76 |
from picarones.pipeline.types import PipelineResult, RunContext, StepResult
|
| 77 |
from picarones.pipeline.validation import ValidationError, validate_spec
|
| 78 |
from picarones.pipeline.yaml_io import dump_spec_to_yaml, load_spec_from_yaml
|
|
|
|
| 72 |
DocumentOutcome,
|
| 73 |
InitialInputsFactory,
|
| 74 |
)
|
| 75 |
+
from picarones.domain.pipeline_spec import INITIAL_STEP_ID, PipelineSpec, PipelineStep
|
| 76 |
from picarones.pipeline.types import PipelineResult, RunContext, StepResult
|
| 77 |
from picarones.pipeline.validation import ValidationError, validate_spec
|
| 78 |
from picarones.pipeline.yaml_io import dump_spec_to_yaml, load_spec_from_yaml
|
|
@@ -31,7 +31,7 @@ import json
|
|
| 31 |
from typing import Iterable
|
| 32 |
|
| 33 |
from picarones.domain.artifacts import Artifact, ArtifactType
|
| 34 |
-
from picarones.
|
| 35 |
|
| 36 |
|
| 37 |
class ArtifactCache:
|
|
|
|
| 31 |
from typing import Iterable
|
| 32 |
|
| 33 |
from picarones.domain.artifacts import Artifact, ArtifactType
|
| 34 |
+
from picarones.domain.pipeline_spec import PipelineStep
|
| 35 |
|
| 36 |
|
| 37 |
class ArtifactCache:
|
|
@@ -64,7 +64,7 @@ from picarones.domain.artifacts import Artifact, ArtifactType
|
|
| 64 |
from picarones.pipeline.cache_protocol import ArtifactCachePort
|
| 65 |
|
| 66 |
if TYPE_CHECKING:
|
| 67 |
-
from picarones.
|
| 68 |
from picarones.pipeline.types import RunContext
|
| 69 |
|
| 70 |
logger = logging.getLogger(__name__)
|
|
|
|
| 64 |
from picarones.pipeline.cache_protocol import ArtifactCachePort
|
| 65 |
|
| 66 |
if TYPE_CHECKING:
|
| 67 |
+
from picarones.domain.pipeline_spec import PipelineStep
|
| 68 |
from picarones.pipeline.types import RunContext
|
| 69 |
|
| 70 |
logger = logging.getLogger(__name__)
|
|
@@ -81,7 +81,7 @@ from picarones.pipeline.planner import (
|
|
| 81 |
ResolvedStep,
|
| 82 |
)
|
| 83 |
from picarones.pipeline.protocols import StepExecutor
|
| 84 |
-
from picarones.
|
| 85 |
from picarones.pipeline.types import PipelineResult, RunContext, StepResult
|
| 86 |
|
| 87 |
logger = logging.getLogger(__name__)
|
|
|
|
| 81 |
ResolvedStep,
|
| 82 |
)
|
| 83 |
from picarones.pipeline.protocols import StepExecutor
|
| 84 |
+
from picarones.domain.pipeline_spec import INITIAL_STEP_ID, PipelineSpec
|
| 85 |
from picarones.pipeline.types import PipelineResult, RunContext, StepResult
|
| 86 |
|
| 87 |
logger = logging.getLogger(__name__)
|
|
@@ -51,7 +51,7 @@ from dataclasses import dataclass, field
|
|
| 51 |
from picarones.domain.artifacts import ArtifactType
|
| 52 |
from picarones.domain.errors import PicaronesError
|
| 53 |
from picarones.evaluation.registry import MetricRegistry
|
| 54 |
-
from picarones.
|
| 55 |
INITIAL_STEP_ID,
|
| 56 |
PipelineSpec,
|
| 57 |
PipelineStep,
|
|
|
|
| 51 |
from picarones.domain.artifacts import ArtifactType
|
| 52 |
from picarones.domain.errors import PicaronesError
|
| 53 |
from picarones.evaluation.registry import MetricRegistry
|
| 54 |
+
from picarones.domain.pipeline_spec import (
|
| 55 |
INITIAL_STEP_ID,
|
| 56 |
PipelineSpec,
|
| 57 |
PipelineStep,
|
|
@@ -58,7 +58,7 @@ from picarones.domain.artifacts import Artifact, ArtifactType
|
|
| 58 |
from picarones.domain.documents import DocumentRef
|
| 59 |
from picarones.domain.errors import PicaronesError
|
| 60 |
from picarones.pipeline.executor import PipelineExecutor
|
| 61 |
-
from picarones.
|
| 62 |
from picarones.pipeline.types import PipelineResult, RunContext
|
| 63 |
|
| 64 |
logger = logging.getLogger(__name__)
|
|
|
|
| 58 |
from picarones.domain.documents import DocumentRef
|
| 59 |
from picarones.domain.errors import PicaronesError
|
| 60 |
from picarones.pipeline.executor import PipelineExecutor
|
| 61 |
+
from picarones.domain.pipeline_spec import PipelineSpec
|
| 62 |
from picarones.pipeline.types import PipelineResult, RunContext
|
| 63 |
|
| 64 |
logger = logging.getLogger(__name__)
|
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""``PipelineStep`` et ``PipelineSpec`` — re-export depuis ``domain``.
|
| 2 |
|
| 3 |
Sprint A14-S40 a migré le module canonique vers
|
| 4 |
``picarones.domain.pipeline_spec`` (cercle 1, types purs). Ce
|
|
@@ -6,6 +6,11 @@ module reste un alias de chemin pour ne pas casser les callers
|
|
| 6 |
existants — ce n'est pas un shim au sens architectural
|
| 7 |
(adaptation d'une API incompatible) mais une convenance de chemin.
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
Les nouveaux callers doivent importer directement depuis
|
| 10 |
``picarones.domain`` :
|
| 11 |
|
|
@@ -16,10 +21,21 @@ Les nouveaux callers doivent importer directement depuis
|
|
| 16 |
|
| 17 |
from __future__ import annotations
|
| 18 |
|
|
|
|
|
|
|
| 19 |
from picarones.domain.pipeline_spec import (
|
| 20 |
INITIAL_STEP_ID,
|
| 21 |
PipelineSpec,
|
| 22 |
PipelineStep,
|
| 23 |
)
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
__all__ = ["PipelineStep", "PipelineSpec", "INITIAL_STEP_ID"]
|
|
|
|
| 1 |
+
"""``PipelineStep`` et ``PipelineSpec`` — re-export depuis ``domain`` (déprécié).
|
| 2 |
|
| 3 |
Sprint A14-S40 a migré le module canonique vers
|
| 4 |
``picarones.domain.pipeline_spec`` (cercle 1, types purs). Ce
|
|
|
|
| 6 |
existants — ce n'est pas un shim au sens architectural
|
| 7 |
(adaptation d'une API incompatible) mais une convenance de chemin.
|
| 8 |
|
| 9 |
+
Sprint A14-S57 (audit #26) : émission d'un ``DeprecationWarning``
|
| 10 |
+
à l'import de ce module pour signaler aux callers que le chemin
|
| 11 |
+
canonique est ``picarones.domain``. Le module sera supprimé au
|
| 12 |
+
sprint S60.
|
| 13 |
+
|
| 14 |
Les nouveaux callers doivent importer directement depuis
|
| 15 |
``picarones.domain`` :
|
| 16 |
|
|
|
|
| 21 |
|
| 22 |
from __future__ import annotations
|
| 23 |
|
| 24 |
+
import warnings
|
| 25 |
+
|
| 26 |
from picarones.domain.pipeline_spec import (
|
| 27 |
INITIAL_STEP_ID,
|
| 28 |
PipelineSpec,
|
| 29 |
PipelineStep,
|
| 30 |
)
|
| 31 |
|
| 32 |
+
warnings.warn(
|
| 33 |
+
"picarones.pipeline.spec is deprecated since S57; "
|
| 34 |
+
"import from picarones.domain instead "
|
| 35 |
+
"(`from picarones.domain import PipelineSpec, PipelineStep, "
|
| 36 |
+
"INITIAL_STEP_ID`). This re-export will be removed in S60.",
|
| 37 |
+
DeprecationWarning,
|
| 38 |
+
stacklevel=2,
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
__all__ = ["PipelineStep", "PipelineSpec", "INITIAL_STEP_ID"]
|
|
@@ -36,7 +36,7 @@ from __future__ import annotations
|
|
| 36 |
from pydantic import BaseModel, ConfigDict
|
| 37 |
|
| 38 |
from picarones.domain.artifacts import ArtifactType
|
| 39 |
-
from picarones.
|
| 40 |
|
| 41 |
|
| 42 |
class ValidationError(BaseModel):
|
|
|
|
| 36 |
from pydantic import BaseModel, ConfigDict
|
| 37 |
|
| 38 |
from picarones.domain.artifacts import ArtifactType
|
| 39 |
+
from picarones.domain.pipeline_spec import INITIAL_STEP_ID, PipelineSpec, PipelineStep
|
| 40 |
|
| 41 |
|
| 42 |
class ValidationError(BaseModel):
|
|
@@ -25,7 +25,7 @@ from __future__ import annotations
|
|
| 25 |
|
| 26 |
import yaml
|
| 27 |
|
| 28 |
-
from picarones.
|
| 29 |
|
| 30 |
|
| 31 |
def dump_spec_to_yaml(spec: PipelineSpec) -> str:
|
|
|
|
| 25 |
|
| 26 |
import yaml
|
| 27 |
|
| 28 |
+
from picarones.domain.pipeline_spec import PipelineSpec
|
| 29 |
|
| 30 |
|
| 31 |
def dump_spec_to_yaml(spec: PipelineSpec) -> str:
|
|
@@ -304,7 +304,7 @@ class TestPipelineIntegration:
|
|
| 304 |
def test_used_as_pipeline_step(self, tmp_path: Path) -> None:
|
| 305 |
"""Un adapter LLM se branche directement comme step de pipeline."""
|
| 306 |
from picarones.pipeline.executor import PipelineExecutor
|
| 307 |
-
from picarones.
|
| 308 |
from picarones.domain.documents import DocumentRef
|
| 309 |
|
| 310 |
text_path = tmp_path / "doc01.txt"
|
|
|
|
| 304 |
def test_used_as_pipeline_step(self, tmp_path: Path) -> None:
|
| 305 |
"""Un adapter LLM se branche directement comme step de pipeline."""
|
| 306 |
from picarones.pipeline.executor import PipelineExecutor
|
| 307 |
+
from picarones.domain.pipeline_spec import PipelineSpec, PipelineStep
|
| 308 |
from picarones.domain.documents import DocumentRef
|
| 309 |
|
| 310 |
text_path = tmp_path / "doc01.txt"
|
|
@@ -275,7 +275,7 @@ class TestConcreteVLMAdapters:
|
|
| 275 |
class TestVLMPipelineIntegration:
|
| 276 |
def test_used_as_pipeline_step(self, tmp_path: Path) -> None:
|
| 277 |
from picarones.pipeline.executor import PipelineExecutor
|
| 278 |
-
from picarones.
|
| 279 |
from picarones.domain.documents import DocumentRef
|
| 280 |
|
| 281 |
image_path = tmp_path / "doc01.png"
|
|
|
|
| 275 |
class TestVLMPipelineIntegration:
|
| 276 |
def test_used_as_pipeline_step(self, tmp_path: Path) -> None:
|
| 277 |
from picarones.pipeline.executor import PipelineExecutor
|
| 278 |
+
from picarones.domain.pipeline_spec import PipelineSpec, PipelineStep
|
| 279 |
from picarones.domain.documents import DocumentRef
|
| 280 |
|
| 281 |
image_path = tmp_path / "doc01.png"
|
|
@@ -23,7 +23,7 @@ from picarones.app.schemas.run_spec import (
|
|
| 23 |
load_run_spec_from_yaml,
|
| 24 |
)
|
| 25 |
from picarones.domain.artifacts import ArtifactType
|
| 26 |
-
from picarones.
|
| 27 |
|
| 28 |
|
| 29 |
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
| 23 |
load_run_spec_from_yaml,
|
| 24 |
)
|
| 25 |
from picarones.domain.artifacts import ArtifactType
|
| 26 |
+
from picarones.domain.pipeline_spec import INITIAL_STEP_ID
|
| 27 |
|
| 28 |
|
| 29 |
# ──────────────────────────────────────────────────────────────────────
|
|
@@ -90,6 +90,10 @@ FILE_BUDGETS: dict[str, int] = {
|
|
| 90 |
# hash multi-paramètres pour adresser la critique d'audit n° 14
|
| 91 |
# « hash multi-paramètres + reprise par hash ».
|
| 92 |
"picarones/adapters/storage/artifact_store.py": 580, # actuel 504
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
# Sprint A14-S41 — artifacts_index.jsonl séparé.
|
| 94 |
"picarones/app/services/benchmark_service.py": 470, # actuel 400
|
| 95 |
# Sprint A14-S44 — BaseLLMAdapter implémente le contrat StepExecutor
|
|
|
|
| 90 |
# hash multi-paramètres pour adresser la critique d'audit n° 14
|
| 91 |
# « hash multi-paramètres + reprise par hash ».
|
| 92 |
"picarones/adapters/storage/artifact_store.py": 580, # actuel 504
|
| 93 |
+
# Sprint A14-S37 + S52 + S56 — JobStore SQLite : POST/GET/DELETE,
|
| 94 |
+
# JobStoreError, schema_version table (S56) + busy_timeout 30s +
|
| 95 |
+
# WAL mode pour les jobs concurrents.
|
| 96 |
+
"picarones/adapters/storage/job_store.py": 500, # actuel 421
|
| 97 |
# Sprint A14-S41 — artifacts_index.jsonl séparé.
|
| 98 |
"picarones/app/services/benchmark_service.py": 470, # actuel 400
|
| 99 |
# Sprint A14-S44 — BaseLLMAdapter implémente le contrat StepExecutor
|
|
@@ -39,7 +39,11 @@ def test_domain_top_level_reexports() -> None:
|
|
| 39 |
|
| 40 |
|
| 41 |
def test_legacy_pipeline_path_aliased() -> None:
|
| 42 |
-
"""``picarones.pipeline.spec`` reste un alias de chemin.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
from picarones.pipeline.spec import (
|
| 44 |
INITIAL_STEP_ID,
|
| 45 |
PipelineSpec,
|
|
@@ -50,6 +54,24 @@ def test_legacy_pipeline_path_aliased() -> None:
|
|
| 50 |
assert INITIAL_STEP_ID == "__initial__"
|
| 51 |
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
def test_all_paths_resolve_to_same_classes() -> None:
|
| 54 |
"""Les imports depuis les 3 emplacements pointent vers le MÊME objet."""
|
| 55 |
from picarones.domain import PipelineSpec as DomainSpec
|
|
|
|
| 39 |
|
| 40 |
|
| 41 |
def test_legacy_pipeline_path_aliased() -> None:
|
| 42 |
+
"""``picarones.pipeline.spec`` reste un alias de chemin.
|
| 43 |
+
|
| 44 |
+
Sprint S57 (audit #26) : émet désormais un ``DeprecationWarning``
|
| 45 |
+
à l'import — vérifié dans ``test_legacy_pipeline_path_emits_warning``.
|
| 46 |
+
"""
|
| 47 |
from picarones.pipeline.spec import (
|
| 48 |
INITIAL_STEP_ID,
|
| 49 |
PipelineSpec,
|
|
|
|
| 54 |
assert INITIAL_STEP_ID == "__initial__"
|
| 55 |
|
| 56 |
|
| 57 |
+
def test_legacy_pipeline_path_emits_warning() -> None:
|
| 58 |
+
"""Sprint S57 (audit #26) : l'import via ``picarones.pipeline.spec``
|
| 59 |
+
émet un ``DeprecationWarning``.
|
| 60 |
+
"""
|
| 61 |
+
import importlib
|
| 62 |
+
import sys
|
| 63 |
+
import warnings
|
| 64 |
+
|
| 65 |
+
# Force le re-import pour déclencher le warning module-level.
|
| 66 |
+
sys.modules.pop("picarones.pipeline.spec", None)
|
| 67 |
+
with warnings.catch_warnings(record=True) as captured:
|
| 68 |
+
warnings.simplefilter("always")
|
| 69 |
+
importlib.import_module("picarones.pipeline.spec")
|
| 70 |
+
deprecation = [w for w in captured if issubclass(w.category, DeprecationWarning)]
|
| 71 |
+
assert deprecation, "DeprecationWarning attendu sur l'import legacy."
|
| 72 |
+
assert "picarones.domain" in str(deprecation[0].message)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
def test_all_paths_resolve_to_same_classes() -> None:
|
| 76 |
"""Les imports depuis les 3 emplacements pointent vers le MÊME objet."""
|
| 77 |
from picarones.domain import PipelineSpec as DomainSpec
|
|
@@ -33,12 +33,15 @@ def _prov() -> ProvenanceRecord:
|
|
| 33 |
|
| 34 |
|
| 35 |
class TestArtifactType:
|
| 36 |
-
def
|
| 37 |
-
"""Sprint A14-S4 —
|
|
|
|
|
|
|
| 38 |
expected = {
|
| 39 |
"image", "raw_text", "corrected_text",
|
| 40 |
"alto_xml", "page_xml", "canonical_document",
|
| 41 |
"entities", "reading_order", "alignment",
|
|
|
|
| 42 |
}
|
| 43 |
assert {t.value for t in ArtifactType} == expected
|
| 44 |
|
|
|
|
| 33 |
|
| 34 |
|
| 35 |
class TestArtifactType:
|
| 36 |
+
def test_canonical_values(self) -> None:
|
| 37 |
+
"""Sprint A14-S4 — valeurs canoniques (9 jusqu'au S49 ;
|
| 38 |
+
+``confidences`` ajouté au S50 pour le sidecar JSON OCR).
|
| 39 |
+
"""
|
| 40 |
expected = {
|
| 41 |
"image", "raw_text", "corrected_text",
|
| 42 |
"alto_xml", "page_xml", "canonical_document",
|
| 43 |
"entities", "reading_order", "alignment",
|
| 44 |
+
"confidences",
|
| 45 |
}
|
| 46 |
assert {t.value for t in ArtifactType} == expected
|
| 47 |
|
|
@@ -46,7 +46,7 @@ from picarones.pipeline.planner import (
|
|
| 46 |
PlanningError,
|
| 47 |
StepInputBinding,
|
| 48 |
)
|
| 49 |
-
from picarones.
|
| 50 |
INITIAL_STEP_ID,
|
| 51 |
PipelineSpec,
|
| 52 |
PipelineStep,
|
|
|
|
| 46 |
PlanningError,
|
| 47 |
StepInputBinding,
|
| 48 |
)
|
| 49 |
+
from picarones.domain.pipeline_spec import (
|
| 50 |
INITIAL_STEP_ID,
|
| 51 |
PipelineSpec,
|
| 52 |
PipelineStep,
|
|
@@ -34,7 +34,7 @@ from picarones.adapters.storage import (
|
|
| 34 |
from picarones.domain.artifacts import Artifact, ArtifactType
|
| 35 |
from picarones.domain.documents import DocumentRef
|
| 36 |
from picarones.pipeline.executor import PipelineExecutor
|
| 37 |
-
from picarones.
|
| 38 |
from picarones.pipeline.types import RunContext
|
| 39 |
|
| 40 |
|
|
|
|
| 34 |
from picarones.domain.artifacts import Artifact, ArtifactType
|
| 35 |
from picarones.domain.documents import DocumentRef
|
| 36 |
from picarones.pipeline.executor import PipelineExecutor
|
| 37 |
+
from picarones.domain.pipeline_spec import PipelineSpec, PipelineStep
|
| 38 |
from picarones.pipeline.types import RunContext
|
| 39 |
|
| 40 |
|