File size: 7,073 Bytes
fc30527
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b782d0
fc30527
 
 
 
 
 
 
2b782d0
fc30527
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416bee1
fc30527
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd38857
fc30527
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd38857
fc30527
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd38857
fc30527
 
 
 
 
 
dd38857
fc30527
 
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
# Snapshots de reproductibilité

## Pourquoi des snapshots ?

Pour qu'un benchmark Picarones soit **citable scientifiquement**, un
lecteur doit pouvoir, des années plus tard, comprendre exactement
*ce qui a été mesuré*. Un rapport HTML qui dit « Tesseract 5.3.4
obtient un CER de 4,2 % sur ce corpus » est inutilisable s'il
n'indique pas :

- la **table de pricing** utilisée (qui a évolué entre temps),
- la **version exacte des prompts** appliqués aux pipelines OCR+LLM,
- le **profil de normalisation** effectivement appliqué (avec ses
  équivalences diplomatiques),
- le **commit Picarones** utilisé pour produire le rapport,
- les **paquets Python** installés au moment du run.

Le module `picarones.reports.html.snapshot` agrège ces cinq dimensions et
les embarque **dans le JSON du rapport**, sous la clé
`report_data["snapshots"]`. Le rapport HTML reste auto-portant : un
lecteur peut tout retrouver sans accès au repo source.

## Ce qu'un snapshot contient

```python
from picarones.reports.html.snapshot import snapshot_all

snap = snapshot_all(
    lang="fr",
    normalization_profile=profile,  # Profile dataclass ou None
)
```

Retourne un dict avec quatre clés top-level :

| Clé | Contenu | Source |
|---|---|---|
| `pricing` | YAML brut intégral de `picarones/data/pricing.yaml` | `pricing_snapshot()` |
| `glossary` | Entrées du glossaire effectivement référencées dans la synthèse (langue rendue) | `glossary_snapshot()` |
| `normalization` | Profil sérialisé (`diplomatic_table`, `exclude_chars`, drapeaux NFC/case-folding…) | `normalization_snapshot()` |
| `environment` | Version Picarones, Python, plateforme OS, commit git, paquets installés (top 200) | `environment_snapshot()` |

### `pricing`

Le YAML est embarqué **verbatim**. Si le tarif d'OpenAI change demain,
le rapport d'aujourd'hui reste lisible — l'analyse Pareto coût garde
sa valeur historique car elle pointe vers la table effectivement
utilisée.

### `glossary`

Pas le glossaire complet (~25 entrées) — seulement celles qui sont
effectivement référencées par la synthèse narrative ou les vues
ouvertes au moment du snapshot. Économie de poids ; un ancien rapport
reste documenté même si le glossaire évolue.

### `normalization`

Le profil contient :

```yaml
name: medieval_french
nfc: true
casefold: false
diplomatic_table:
  ſ: s
  u: v
  i: j
  ꝑ: per
  ⁊: et
exclude_chars:
  - "·"
  - "¶"
```

Permet à un relecteur de comprendre exactement quelles équivalences
ont été appliquées au moment du calcul de CER, **sans relancer**.

### `environment`

```yaml
picarones_version: "0.9.0"
python_version: "3.11.13"
platform: "Linux 6.18.5-x86_64-with-glibc2.39"
git_commit: "17cc5474..."
installed_packages:
  - "click==8.3.3"
  - "defusedxml==0.7.1"
  - "jiwer==3.1.0"
  # ... top 200 trié alpha
```

Le `git_commit` est lu via `git rev-parse HEAD` (subprocess timeout 2 s,
sans shell). Si le repo n'est pas un dépôt git, la clé reste `None`.

## Comment rejouer un benchmark à 5 ans d'écart

### Étape 1 — Récupérer le commit Picarones d'origine

Dans le rapport HTML, ouvrir la console JS et inspecter `DATA.snapshots.environment.git_commit` :

```javascript
> DATA.snapshots.environment.git_commit
"17cc5474abc..."
```

Puis :

```bash
git clone https://github.com/maribakulj/Picarones.git
cd Picarones
git checkout 17cc5474abc
```

### Étape 2 — Récréer l'environnement Python

Picarones livre des lock files :

```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.lock
```

Si vous avez besoin d'une version Python différente (par exemple un
ancien rapport rendu en Python 3.11.10), utiliser pyenv :

```bash
pyenv install 3.11.10
pyenv local 3.11.10
```

### Étape 3 — Récréer le corpus + GT

Picarones ne stocke **pas** les images du corpus dans le snapshot
(les images appartiennent au déposant). Il faut donc re-récupérer le
corpus original. Pour les imports IIIF, le manifeste est durable
(les bibliothèques nationales versionnent) ; pour les uploads ZIP,
l'utilisateur doit conserver son archive source.

Si le corpus a déjà été chargé dans Gallica / HTR-United, le
`metadata.source_url` de chaque `Document` permet le re-fetch.

### Étape 4 — Rejouer le benchmark

```bash
picarones run \
    --corpus ./corpus_recovered/ \
    --engines tesseract,pero_ocr \
    --output rerun.json \
    --normalization medieval_french
```

### Étape 5 — Vérifier la concordance

Le commit + le lock file + le profil de normalisation garantissent
que les métriques **CER / WER / MER / WIL** seront bit-à-bit
identiques.

Différences possibles légitimes :
- L'OCR cloud (Mistral / Google / Azure) peut avoir évolué côté
  serveur — les chiffres peuvent diverger même avec un client
  identique. Pour un benchmark scientifiquement reproductible,
  privilégier **Tesseract / Pero OCR** (modèles versionnés et locaux).
- Les LLMs évoluent constamment ; un pipeline OCR+LLM rejoué 6 mois
  plus tard peut donner d'autres résultats. Le snapshot des prompts
  reste utile mais ne reproduit pas le LLM lui-même.

## Snapshot et publication scientifique

Pour un papier scientifique, citer Picarones doit indiquer :

```bibtex
@misc{picarones_2026,
  title  = {Picarones: Heritage OCR/HTR/VLM Benchmarking Platform},
  author = {<auteurs>},
  year   = {2026},
  doi    = {10.5281/zenodo.<id>},
  version= {1.1.0},
  url    = {https://github.com/maribakulj/Picarones}
}
```

Et dans le matériel supplémentaire, joindre :

1. Le rapport HTML autonome (qui contient tout le snapshot).
2. `requirements-dev.lock` du moment du benchmark (pour ré-instancier
   l'environnement).
3. Le digest Docker si vous avez utilisé l'image officielle :
   `docker inspect ghcr.io/maribakulj/picarones:1.1.0 --format='{{.Id}}'`.

Un évaluateur scientifique disposera ainsi des éléments matériels pour
vérifier l'analyse — c'est l'exigence minimale d'une publication
reproductible (cf. Stodden et al., *Computational reproducibility*).

## Limites assumées

- **Le code source n'est pas embarqué dans le snapshot**. On embarque
  *l'identifiant* du commit, pas le diff. Si le repo est rendu
  privé ou supprimé, le snapshot devient orphelin. Mitigation :
  publier les versions sur Zenodo (DOI durable, garanti 20 ans).
- **Les images du corpus** ne sont pas snapshottées (poids + droits
  d'auteur). Le déposant doit conserver son corpus.
- **Le LLM cloud** ne peut pas être snapshotté — c'est une dépendance
  externe non reproductible. Pour la science, préférer les modèles
  ouverts (Llama, Phi, Mistral via Ollama).

## Tests

`tests/report/test_reproducibility_snapshots.py`
valide que `snapshot_all()` est :

- déterministe (même input → même bytes en sortie),
- complet (toutes les clés top-level présentes),
- robuste (ne crashe pas si git absent, si pricing.yaml manquant…).

`tests/test_reproducibility_ops.py` ajoute la validation
de la chaîne **lock file + Docker digest + snapshot** comme contrat
opérationnel.