File size: 17,353 Bytes
9ecaeac
 
 
 
 
 
 
 
3af8896
d2df0b9
d0a3fab
 
6d4b563
3af8896
6d4b563
d2df0b9
d0a3fab
3af8896
 
 
d0a3fab
3af8896
 
 
 
d2df0b9
 
 
9ecaeac
 
 
6d4b563
9ecaeac
 
 
 
 
 
 
6d4b563
9ecaeac
a42c174
 
9ecaeac
 
a42c174
 
 
 
3af8896
 
a42c174
 
 
 
 
 
6d4b563
a42c174
 
3af8896
a42c174
6d4b563
a42c174
3af8896
a42c174
3af8896
 
 
a42c174
 
 
 
3af8896
a42c174
6d4b563
3af8896
a42c174
 
 
3af8896
 
 
 
6d4b563
3af8896
 
a42c174
 
3af8896
 
6d4b563
a42c174
3af8896
 
6d4b563
3af8896
2c2bc0f
a42c174
2c2bc0f
a42c174
 
6d4b563
a42c174
3af8896
 
 
 
6d4b563
bfc4268
6d4b563
3af8896
9ecaeac
 
 
 
0aa159b
9ecaeac
53f4d56
2c2bc0f
 
 
 
 
 
 
 
 
 
 
 
9ecaeac
0aa159b
9ecaeac
0aa159b
 
 
 
 
 
 
9ecaeac
0aa159b
 
 
9ecaeac
 
 
 
 
 
 
 
 
 
 
 
a76711b
 
 
 
 
46bb905
9ecaeac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
df2b641
 
 
9ecaeac
df2b641
 
 
 
 
 
 
 
 
 
9ecaeac
 
 
 
 
 
 
 
 
 
 
 
 
 
d0a3fab
3af8896
 
 
 
 
 
 
df2b641
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3af8896
18919b0
 
 
 
 
3af8896
18919b0
3af8896
 
 
 
 
 
 
 
18919b0
6d4b563
0aa159b
6d4b563
a42c174
 
3af8896
0aa159b
 
9011070
6d4b563
 
 
 
2c2bc0f
 
6d4b563
 
2c2bc0f
 
6d4b563
 
 
 
2c2bc0f
 
6d4b563
0aa159b
 
b4fd5de
 
 
 
 
 
 
 
 
 
 
 
 
d28d854
6d4b563
 
 
d28d854
6d4b563
 
9ecaeac
 
 
 
 
6d4b563
2c2bc0f
 
d0a3fab
 
3af8896
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# CLAUDE.md — Picarones

Plateforme de benchmark OCR/HTR pour documents patrimoniaux.
Repo : github.com/maribakulj/Picarones
HuggingFace Space : huggingface.co/spaces/Ma-Ri-Ba-Ku/Picarones (Docker, port 7860)

---

## Architecture — 8 couches concentriques

Voir le manifeste complet dans [`docs/explanation/architecture.md`](docs/explanation/architecture.md).

```
domain → formats → evaluation → pipeline → adapters → app → reports → interfaces
```

Règle d'import stricte : les dépendances vont uniquement de l'extérieur
vers l'intérieur.  Vérifié par `tests/architecture/test_layer_dependencies.py`
+ `tests/architecture/test_no_legacy_imports_in_rewrite.py`
(`LEGACY_PACKAGES = ()` à v2.0 — plus aucun paquet legacy).

Tous les paquets legacy (`picarones/{core,measurements,engines,modules,
report,llm,pipelines,cli,web,extras}/` + `adapters/legacy_engines/` +
`adapters/legacy_pipelines/` + `interfaces/{cli,web}/_legacy/`) ont été
**supprimés** au cours des sprints A-H (mai 2026).

---

## Setup

```bash
pip install -e ".[dev,web]"          # toujours inclure [web] pour les tests
pytest tests/ -q --tb=short          # lancer les tests
picarones demo --output rapport.html # rapport démo sans moteur installé
picarones serve --port 8080          # interface web locale
```

---

## Structure

Architecture canonique en **8 couches concentriques** :

```
picarones/
├── domain/                     Couche 1 — types purs (Pydantic + stdlib only)
│   ├── artifacts.py            Artifact, ArtifactType (10 types)
│   ├── corpus.py               CorpusSpec
│   ├── documents.py            DocumentRef
│   ├── pipeline_spec.py        PipelineSpec, PipelineStep
│   ├── module_protocol.py      BaseModule
│   ├── facts.py                Fact, FactType, FactImportance, DetectorRegistry
│   ├── evaluation_spec.py      MetricSpec, EvaluationView, EvaluationSpec
│   ├── projection_spec.py      ProjectionSpec
│   ├── provenance.py           ProvenanceRecord
│   ├── run_manifest.py         RunManifest
│   └── errors.py               PicaronesError, AdapterStepError, ...
│
├── formats/                    Couche 2 — parsing/sérialisation (lxml, defusedxml)
│   ├── alto/, pagexml/         ALTO 4, PAGE XML
│   ├── text/                   normalisation, profils de comparaison
│   └── _xml_utils.py           safe_parse_xml
│
├── evaluation/                 Couche 3 — métriques et calcul
│   ├── metrics/                ~37 métriques (CER/WER, MUFI, philological, NER, …)
│   ├── statistics/             Wilcoxon, Friedman/Nemenyi, bootstrap, Pareto, CDD
│   ├── views/, projectors/     EvaluationView, AltoToText, PageToText, ...
│   ├── registry/               MetricRegistry (DI)
│   ├── corpus.py               Document, Corpus, GTLevel + payloads
│   ├── benchmark_result.py     BenchmarkResult, EngineReport, DocumentResult
│   ├── metric_registry.py      MetricSpec, register_metric, compute_at_junction
│   ├── metric_hooks.py         register_document_metric, register_corpus_aggregator
│   ├── metric_result.py        MetricsResult, aggregate_metrics
│   ├── synthetic.py            generate_sample_benchmark (pour `picarones demo`)
│   └── _diff_utils.py          compute_word_diff, compute_char_diff, diff_stats

├── pipeline/                   Couche 4 — orchestration
│   ├── executor.py             PipelineExecutor (instance-based)
│   ├── planner.py              ExecutionPlan, StepInputBinding
│   ├── protocols.py            StepExecutor Protocol, ExecutionMode
│   ├── runner.py               CorpusRunner (backpressure + timeout + cancel)
│   ├── types.py                RunContext, StepResult, PipelineResult
│   ├── llm_pipeline_builder.py make_ocr_llm_pipeline_spec (3 modes)
│   └── llm_pipeline_config.py  OCRLLMPipelineConfig (container OCR+LLM)

├── adapters/                   Couche 5 — adapters externes (libs autorisées)
│   ├── ocr/                    Tesseract, Pero, Mistral OCR, Google Vision, Azure DI, Precomputed + factory
│   ├── llm/                    OpenAI, Anthropic, Mistral, Ollama (BaseLLMAdapter)
│   ├── vlm/                    Adapters VLM (zero-shot)
│   ├── corpus/                 IIIF, Gallica, HTR-United, HuggingFace, eScriptorium
│   └── storage/                ArtifactStore, JobStore

├── app/                        Couche 6 — services applicatifs
│   └── services/               BenchmarkService, RunOrchestrator, JobRunner,
│                               benchmark_runner (entry point CLI/web), partial_store

├── reports/                    Couche 7 — rendu HTML / JSON / CSV
│   ├── html/                   ReportGenerator + 28 renderers + 5 vues + templates Jinja2
│   ├── json/, csv/             exports tabulaires
│   ├── narrative/              moteur narratif (20 détecteurs)
│   ├── glossary/, i18n/        glossaire + i18n FR/EN
│   └── _helpers/               colors, render_helpers, assets

├── interfaces/                 Couche 8 — entrées utilisateur
│   ├── cli/                    Click CLI : run, diagnose, economics, edition,
│   │                           compare, robustness, history, serve, metrics,
│   │                           engines, info, demo, report, import (group)
│   └── web/                    FastAPI : UI Jinja2 + SSE benchmark + ZIP upload

├── prompts/                    16 fichiers .txt (FR/EN/DE) — médiéval, imprimé ancien, presse XIXe
├── data/                       Tables indicatives (pricing.yaml)
└── i18n.py                     Helper i18n (multi-langue rapports)
```

---

## État des tests et bugs historiques

`pytest tests/` → **5100 passed, 16 skipped, 8 deselected, 2 xfailed, 0 failed**
(post-audit code-quality, mai 2026).  Les deselected sont les markers
`live` (5 tests d'intégration contre vraie API/binaire) + `network`
(3 tests qui hit le réseau réel), opt-in en local via `pytest -m live`
ou `pytest -m network`.  Le compteur ``passed`` est synchronisé
automatiquement par `scripts/gen_readme_tables.py` (CI : job
``sync-counters`` ; local : `make sync-counters-check`).  Le détail
``skipped``/``xfailed`` peut dériver de ±2 entre éditions et n'est
pas verrouillé en CI.

NB : utiliser ``python -m pytest tests/`` plutôt que ``pytest tests/``
directement — l'installation via ``uv tool install pytest`` masque
les deps Picarones et produit ~160 collection errors trompeurs.

### Bugs documentés antérieurement — tous résolus

| Bug | Statut | Sprint de résolution |
|-----|--------|---------------------|
| Pipeline OCR+LLM sortie vide (`tesseract → ministral-3b-latest`) | ✅ Résolu | Sprint 15 — adapter Mistral logue `finish_reason`, `completion_tokens`, normalise les ContentChunk |
| CI `python-multipart` manquant | ✅ Résolu | `pyproject.toml` expose `python-multipart>=0.0.9` dans les extras `dev` ET `web`; `ci.yml:71` installe `.[dev,web]` |
| Tests fixtures post-Sprint 10 (counts moteurs, flag `is_pipeline`) | ✅ Résolu | Fixtures mises à jour |
| Test Windows SQLite `test_history_empty_db` | ✅ Résolu | `try/except OSError` + `gc.collect()` avant `unlink` |
| Test HuggingFace `test_search_language_filter` | ✅ Résolu | Assertion corrigée |

En cas de régression sur un de ces bugs, chercher les fichiers de test
correspondants (`test_sprint15_llm_pipeline_bugs.py`, `test_sprint8_escriptorium_gallica.py`,
`test_sprint6_web_interface.py`) avant de ré-ouvrir une enquête.

---

## Règles importantes — ne pas toucher

- **Ne jamais retirer `python-multipart` des dépendances** : FastAPI vérifie sa présence à
  l'import du module (décoration `@app.post` avec `UploadFile`), pas à l'exécution. Ça casse
  tous les tests web au setup.
- **Ne jamais mettre `except Exception: pass`** : remplacer par
  `logger.warning("[module] fonctionnalité dégradée : %s", e)`.
- **Toujours utiliser `logger.warning` avec message explicite** quand une fonctionnalité optionnelle
  échoue (confusion, taxonomy, structure, image_quality, etc.).
- **Avant tout push, lancer `make lint`** (ou `ruff check picarones/ tests/`).
  La config est centralisée dans `pyproject.toml` sous `[tool.ruff]`, donc
  CI, Makefile et invocation directe produisent le même résultat. Le job
  `lint` du CI est bloquant — un F401 (import inutilisé) ou un E741
  (variable ambiguë) fait échouer la PR, par design.
- **Les profils de normalisation** sont dans `picarones/formats/text/normalization.py` — l'endpoint
  `/api/normalization/profiles` doit les lire dynamiquement depuis ce fichier, pas depuis une
  liste statique.

---

## Variables d'environnement

```bash
# Clés API LLM (configurées dans HuggingFace Space Settings → Variables and secrets)
MISTRAL_API_KEY=...
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...

# OCR cloud (optionnel)
GOOGLE_APPLICATION_CREDENTIALS=/path/to/creds.json
AZURE_DOC_INTEL_ENDPOINT=https://...
AZURE_DOC_INTEL_KEY=...
```

---

## Pipelines OCR+LLM — modes

Trois modes canoniques (typés `Literal[…]` dans `PipelineConfig.pipeline_mode`
depuis Phase 2 du chantier post-rewrite — toute autre valeur est rejetée
en 422 par Pydantic, plus de fallback silencieux) :

| Mode (clé API) | Description |
|---|---|
| **`zero_shot`** | Le VLM reçoit l'image directement et transcrit sans OCR préalable (pas d'OCR amont). |
| **`text_only`** | OCR → texte brut → LLM corrige le texte sans voir l'image (modèles texte seul). |
| **`text_and_image`** | OCR → VLM reçoit image + texte brut pour correction multimodale. |

`ministral-3b-latest` = modèle texte pur → utiliser mode `text_only`
uniquement.  L'UI envoie ces clés en anglais ; les libellés français
"Post-correction texte" / "Post-correction image+texte" sont des
labels d'affichage (i18n), pas des identifiants API.

---

## CI/CD

- **CI GitHub Actions** : `.github/workflows/ci.yml` — Python 3.11/3.12, Linux/macOS/Windows
- **Sync HuggingFace** : `.github/workflows/sync_to_huggingface.yml` — push auto sur main
  (nécessite secret `HF_TOKEN` dans GitHub Settings → Secrets → Actions)
- **HuggingFace Space** : Docker sur port 7860

---

## Sprints réalisés

L'historique détaillé des **97+ sprints** du projet (de la fondation
S1 jusqu'au rewrite ciblé S27-S46, l'audit institutionnel S47-S59,
puis le retrait complet du legacy A-H jusqu'à v2.0) est dans le
CHANGELOG.md à la racine.

**v2.0 (mai 2026)** : la migration vers l'architecture 8 couches
canoniques est terminée.  Plus aucun paquet legacy.  Plus aucun shim.

**Chantier post-rewrite (mai 2026, branche `claude/fix-module-rewiring-MHssX`)** :
réconciliation des chemins UI/API/runner après audit révélant des
options ignorées, moteurs annoncés sans backend, surfaces filesystem
ouvertes et round-trip JSON appauvri (cf. CHANGELOG.md).  Cinq phases
exécutées :
- **Phase 1 (sécurité P0)** : `output_dir` validé (importers HTR-United/HF),
  `db_path` validé (`/api/history/regressions`), ZIP collision de
  basename + validation image extraite.
- **Phase 2 (méthodologie P0)** : `pipeline_mode` strict Literal,
  `BenchmarkResult.from_json_object` (round-trip JSON complet —
  taxonomy/NER/calibration/philological/searchability/hallucination
  préservés), `partial_store` fingerprint SHA-256 (engine_config +
  normalization + char_exclude + corpus mtime/size + code version).
- **Phase 3 (moteurs fantômes)** : adapters `KrakenAdapter` et
  `CalamariAdapter` implémentés (lazy imports, extras `[kraken]` /
  `[calamari]`) + matrice CLI/Web/factory unifiée.
- **Phase 4 (code zombie)** : `upload_purge_task` (RGPD) branchée au
  lifespan + payload corpus dans `JobStore.create_job`,
  `/api/benchmark/start` unifié vers le worker v2,
  `HTRUnitedCatalogue.from_remote` (avec fallback demo + champ
  `is_demo` exposé), endpoints config save/load branchés à l'UI,
  workflows CLI `diagnose`/`economics`/`edition` génèrent le HTML
  automatiquement.
- **Phase 5 (naming)** : `CompetitorConfig``PipelineConfig`,
  `ocr_engine``engine_name` (rupture API, le field accepte aussi
  des VLMs et `corpus`).

**Règles d'architecture** :

- ``evaluation/`` whitelist externe : ``PIL, annotated_types,
  jiwer, numpy, pydantic, rapidfuzz, scipy, spacy,
  typing_extensions, yaml`` — **pas** ``pytesseract``,
  ``mistralai``, ``azure``, ``google``, ``pero_ocr``.  Tout code
  qui importe ces libs externes va dans ``adapters/``.
- ``evaluation/`` ne peut pas importer depuis ``pipeline/`` :
  c'est le sens inverse de la dépendance.  Un module qui pont
  les deux contrats vit dans ``pipeline/``.
- ``reports/`` consomme uniquement ``evaluation/metrics/``.
- ``interfaces/`` (couche 8) consomme ``app.services`` (couche 6) :
  ``app.services.benchmark_runner`` est l'entry point unique pour
  CLI et web.
- ``test_file_budgets`` : si un fichier dépasse 400 LOC, ajouter
  une entrée avec budget = LOC actuel + ~15 %.

## Moteur narratif

Le modèle de données (`Fact`, `FactType`, `FactImportance`,
`DetectorRegistry`) vit en couche 1 (`domain`) dans
[`picarones/domain/facts.py`](picarones/domain/facts.py). Les détecteurs et
le rendu vivent en couche 7 (`reports`) :

```
picarones/reports/narrative/
├── __init__.py              API publique + pipeline build_synthesis
├── arbiter.py               Tri par importance, non-redondance, anti-contradiction
├── renderer.py              Rendu templates YAML par str.format_map (déterministe)
├── registry.py              Registre par défaut des détecteurs
├── templates/{fr,en}.yaml   20 templates × 2 langues
└── detectors/               20 détecteurs en 6 familles
    ├── ranking.py           5 (global_leader, statistical_tie, significant_gap,
    │                          speed_winner, median_mean_gap_warning)
    ├── pareto.py            3 (pareto_alternative, cost_outlier,
    │                          pricing_staleness_warning)
    ├── stratum.py           3 (stratum_winner, stratum_collapse,
    │                          stratification_recommended)
    ├── quality.py           4 (error_profile_outlier, llm_hallucination_flag,
    │                          robustness_fragile, confidence_warning)
    ├── history.py           4 (engine_off_baseline, engine_unstable,
    │                          regression_in_history, importer_fallback_triggered)
    └── ensemble.py          1 (ensemble_opportunity)
```

**Principe anti-hallucination** (formulation précise — audit F7) :
aucun LLM, aucune valeur aléatoire/fabriquée, rendu `str.format_map`
déterministe.  Les **noms d'entités** sont repris *verbatim* du JSON
d'entrée ; les **nombres** sont soit verbatim, soit une **fonction
déterministe et auditable** de valeurs d'entrée (écart relatif,
accélération, largeur d'IC…).  L'ancienne formulation « chaque nombre
provient du JSON » était trop forte (dérivations) et le test
historique `test_sprint19_narrative_engine.py` était *circulaire* (il
validait les nombres rendus contre le `payload` du `Fact`, lui-même
rempli par le détecteur).  La traçabilité **à la source** est désormais
vérifiée par `tests/evaluation/test_scientific_audit_2026.py`
(`TestF7NarrativeTraceability`) : reconstruction depuis le
`BenchmarkResult` d'origine + déterminisme bit-à-bit.

**Règle anti-contradiction** (arbitre) : si `SIGNIFICANT_GAP` (Wilcoxon
non corrigé) et `STATISTICAL_TIE` (Nemenyi corrigé) concernent les mêmes
moteurs, Nemenyi l'emporte.

**Pipeline** : `build_synthesis(benchmark_data, lang, max_facts=5)`
détecte, arbitre, rend.

---

## Contexte développement

- **Environnement** : GitHub Codespaces, Python 3.11+
- **Tests** : voir « État des tests et bugs historiques » plus haut
  (compteur synchronisé par ``scripts/gen_readme_tables.py``).
- **Manifeste architecture** : [`docs/explanation/architecture.md`](docs/explanation/architecture.md).
- **API publique stable** : [`docs/reference/api-stable.md`](docs/reference/api-stable.md).

### Statut v2.0 — mai 2026

L'architecture 8 couches est complète.  Tous les paquets legacy
top-level (`core/`, `measurements/`, `engines/`, `modules/`,
`report/`, `llm/`, `pipelines/`, `cli/`, `web/`, `extras/`) ainsi
que les sous-paquets transitoires (`adapters/legacy_engines/`,
`adapters/legacy_pipelines/`, `interfaces/{cli,web}/_legacy/`) ont
été supprimés.

| Phase / Sprint | Statut | Détails |
|---|---|---|
| Foundation S1-S26 | ✅ | domain, formats, evaluation, narrative engine |
| Rewrite ciblé S27-S46 | ✅ | pipeline, app.services, adapters/ocr canonique, reports |
| Audit S47-S59 | ✅ | confidences, sécurité web, registry typé, baselines |
| Plan A-H (mai 2026) | ✅ | Retrait complet du legacy : core/measurements/engines/modules/report/llm/pipelines/cli/web/extras supprimés ; interfaces/{cli,web}/_legacy promus au niveau canonique ; v2.0 release |