File size: 8,103 Bytes
1acc64c
d2df0b9
 
1acc64c
 
 
d2df0b9
 
1acc64c
 
 
 
 
 
 
d2df0b9
 
1acc64c
d2df0b9
1acc64c
 
d2df0b9
1acc64c
 
 
 
 
 
 
 
 
d2df0b9
1acc64c
 
d2df0b9
1acc64c
 
 
 
 
 
 
 
7e28f42
1acc64c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d2df0b9
1acc64c
 
7e28f42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Architecture Picarones — manifeste

Picarones est un **banc d'essai** pour pipelines OCR/HTR sur documents
patrimoniaux. Le code est organisé en **3 cercles concentriques** avec
une règle de dépendance stricte : les flèches d'import vont uniquement
de l'extérieur vers l'intérieur.

```
   Cercle 3 (extras, report, cli, web)


   Cercle 2 (measurements, engines, llm, pipelines, modules)


   Cercle 1 (core)
```

## Cercle 1 — `picarones/core/` : abstractions de domaine

Pas de logique métier, pas d'I/O. Uniquement des **contrats** que les
cercles supérieurs implémentent.

| Module | Contenu |
|---|---|
| `modules.py` | `BaseModule`, `ArtifactType`, `validate_inputs`/`validate_outputs` |
| `corpus.py` | `Document`, `Corpus`, `GTLevel`, payloads typés (`TextGT`, `AltoGT`, `PageGT`, `EntitiesGT`, `ReadingOrderGT`) |
| `results.py` | `DocumentResult`, `EngineReport`, `BenchmarkResult` |
| `metric_registry.py` | `MetricSpec`, `register_metric`, `select_metrics`, `compute_at_junction` |
| `metric_hooks.py` | `register_document_metric`, `register_corpus_aggregator`, profils de calcul |
| `pipeline.py` | `PipelineRunner`, `PipelineSpec`, `PipelineStep` (DAG de modules) |
| `facts.py` | `Fact`, `FactType`, `FactImportance`, `DetectorRegistry` |

**Règle** : un module du cercle 1 peut importer un autre module du
cercle 1. Il ne peut **rien** importer des cercles 2 ou 3.

## Cercle 2 — implémentations officielles

Les implémentations distribuées par défaut dans le package `picarones`.

### `picarones/measurements/` — métriques (~50 modules)

| Catégorie | Modules |
|---|---|
| Coeur | `metrics.py`, `statistics/` (sous-package), `runner.py`, `builtin_hooks.py`, `builtin_metrics.py`, `normalization.py` |
| Erreurs | `confusion.py`, `taxonomy.py`, `taxonomy_comparison.py`, `taxonomy_cooccurrence.py`, `taxonomy_intra_doc.py` |
| Lignes/structure | `line_metrics.py`, `structure.py`, `worst_lines.py`, `char_scores.py` |
| Calibration/fiabilité | `calibration.py`, `reliability.py`, `hallucination.py` |
| Image | `image_quality.py`, `image_predictive.py`, `difficulty.py` |
| Robustesse | `robustness.py`, `robustness_projection.py` |
| Inter-moteurs | `inter_engine.py`, `specialization.py` |
| Statistique avancée | `baseline_comparison.py`, `longitudinal.py`, `incremental_comparison.py` |
| Contenu | `searchability.py`, `numerical_sequences.py`, `rare_tokens.py`, `readability.py` |
| Structure ALTO | `layout.py`, `reading_order.py`, `ner.py`, `ner_backends.py`, `error_absorption.py` |
| Économie | `cost_projection.py`, `marginal_cost.py`, `pricing.py`, `throughput.py` |
| 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` |
| Pipelines composées | `pipeline_benchmark.py`, `pipeline_comparison.py`, `pipeline_spec_loader.py`, `alto_metrics.py` |
| Divers | `equivalence_profile.py`, `levers.py`, `module_policy.py`, `history.py` |
| Runners adaptifs | `readability_runner.py`, `searchability_runner.py`, `numerical_sequences_runner.py` |
| Narratif | `narrative/` (arbiter, renderer, registry, 18 détecteurs en 6 familles) |

### `picarones/engines/` — adapters OCR (5)

`tesseract.py`, `pero_ocr.py`, `mistral_ocr.py`, `google_vision.py`,
`azure_doc_intel.py`. Tous héritent de `picarones.core.engine.BaseOCREngine`
(qui vit dans `engines/base.py` pour la lisibilité).

### `picarones/llm/` — adapters LLM (4)

`mistral_adapter.py`, `openai_adapter.py`, `anthropic_adapter.py`,
`ollama_adapter.py`. Interface commune dans `base.py`.

### `picarones/pipelines/` — pipelines OCR+LLM intégrés

`base.py` (`OCRLLMPipeline`, qui hérite de `BaseOCREngine`),
`over_normalization.py`.

### `picarones/modules/` — modules `BaseModule` officiels

Démonstrateurs qui prouvent l'axe B du plan d'évolution :
`alto_text_to_mono_region.py`.

## Cercle 3 — extensions et présentation

### `picarones/extras/importers/` — connecteurs corpus

`iiif.py`, `gallica.py`, `htr_united.py`, `huggingface.py`,
`escriptorium.py`, `_http.py`. Plugins pluggable, certains expérimentaux.

### `picarones/report/` — rendu HTML

| Sous-dossier | Contenu |
|---|---|
| `generator.py` | Orchestration Jinja2 |
| `views/` | 5 vues thématiques (economics, advanced_taxonomy, diagnostics, pipeline, robustness) |
| `templates/` | Jinja2 (base, header, footer, vues, partials) |
| `i18n/` | FR/EN |
| `glossary/` | 25 entrées bilingues |
| `vendor/` | Chart.js |
| `*_render.py` | ~22 renderers (calibration, NER, Pareto, Sankey, etc.) |

Pas de sous-dossier `extras/render/` — tout le rendu est ici.

### `picarones/cli/` — Click (7 fichiers)

Point d'entrée `picarones.cli:cli` (référencé dans `pyproject.toml`).
15 sous-commandes : `run`, `diagnose`, `economics`, `edition`,
`compare`, `metrics`, `engines`, `info`, `report`, `demo`, `serve`,
`history`, `robustness`, `pipeline run/compare`, `import`.

### `picarones/web/` — FastAPI

Interface web (`app.py`).

## Données

| Dossier | Rôle |
|---|---|
| `picarones/prompts/` | Prompts LLM versionnés (8 fichiers, FR + EN) |
| `picarones/data/` | Tables indicatives (pricing, etc.) |
| `picarones/fixtures.py` | Corpus de démonstration |

## Règles de migration

1. **Pas de shim** : un module a un seul emplacement physique. Les
   imports pointent directement vers la vraie source.
2. **Pas de double API** : une fonction a un seul nom canonique. Les
   alias historiques sont supprimés et les tests mis à jour.
3. **Frontières strictes** : si un module Y du cercle N importe le
   module X, alors le cercle de X est ≤ N. Une exception
   pragmatique : `engines/base.py` est conceptuellement cercle 1
   mais physiquement dans `engines/` pour rester avec ses
   implémentations.
4. **Les dépendances optionnelles** (`scipy`, `spacy`, etc.) sont
   gérées par try/except à l'import — pas par shim.

## Tests

Organisés par cercle : `tests/core/`, `tests/measurements/`,
`tests/engines/`, `tests/extras/`, `tests/report/`,
`tests/integration/` (tests E2E croisant plusieurs cercles).

Un test du cercle N **n'importe pas** les implémentations des
cercles > N (sauf `tests/integration/`).

## Convention de découpage des modules > 400 lignes

Quand un module Python dépasse 400 lignes ET contient plusieurs
responsabilités disjointes, le découper en **sous-package** plutôt
qu'en plusieurs modules à plat. Modèle de référence :
[`picarones/measurements/statistics/`](../picarones/measurements/statistics/)
issu du sprint « découpage de statistics.py » (mai 2026).

Convention :

1. **Renommer** `X.py` en `X/__init__.py` via `git mv` (préserve
   l'historique du fichier original).
2. **Créer** dans `X/` un sous-module par famille fonctionnelle
   (`bootstrap.py`, `wilcoxon.py`, `friedman_nemenyi.py`, etc.).
   Chaque sous-module doit faire moins de ~400 lignes ; sinon
   re-décomposer.
3. **`X/__init__.py`** ne contient QUE des ré-exports rétrocompat —
   tous les symboles publics de l'ancien `X.py` doivent rester
   importables via `from picarones.X import …`. Les symboles privés
   ré-exportés doivent être ceux **réellement** consommés par les
   tests (vérifié par grep, pas par supposition).
4. **`__all__`** explicite dans chaque sous-module et dans le
   `__init__.py`.
5. **Tests architecture** (`tests/architecture/test_*.py`) doivent
   continuer à passer : si nécessaire, étendre `_measurements_modules()`
   ou `_imports_target_*` pour reconnaître les sous-packages.
6. **Préfixer les modules de rendu** par leur domaine
   (`cdd_render.py` plutôt que `render_cdd.py`) pour cohérence avec
   `picarones/report/*_render.py`.

**Quand NE PAS découper** : si les responsabilités sont fortement
couplées (ex: un orchestrateur qui appelle 12 sous-fonctions au
même endroit), le maintien dans un seul fichier > 400 lignes est
acceptable. Le budget par fichier (`tests/architecture/test_file_budgets.py`)
documente ces dérogations conscientes.