Claude commited on
Commit
307d18e
·
unverified ·
1 Parent(s): 06aac6a

docs(audit): rapport d'audit institutionnel BnF (mai 2026)

Browse files

Audit transversal de l'état du repo (commit 06aac6a) pour publication
scientifique et adoption institutionnelle BnF, conduit via 6 agents
d'exploration en parallèle (architecture, sécurité, tests, docs,
CI/CD, web/a11y) puis vérification manuelle des findings critiques.

Verdict : non prêt pour estampille institutionnelle sans remédiation.
Le code est solide (ruff 0, 3356 passed/3 skipped/0 failed, archi en
3 cercles tenue à 99 %, sécurité de fond), mais 11 BLOCKERS sont
identifiés sur 3 axes — communication scientifique (CITATION+JOSS,
refs primaires Demšar/Wilcoxon, traçabilité MUFI/TEI), conformité
opérationnelle (CSRF, accessibilité WCAG niv. A, déploiement, RGPD)
et hygiène CI/CD (lock file, scanners sécurité, seuil de couverture).

S'ajoutent 18 problèmes MAJEURS et 17 MINEURS, plus 1 faux positif
écarté et 8 points forts à préserver. Feuille de route synthétique
sur 10 semaines (1 ETP) en §7.

docs/audits/institutional-readiness-2026-05.md ADDED
@@ -0,0 +1,840 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Audit institutionnel BnF — état de Picarones au 2 mai 2026
2
+
3
+ > Audit réalisé sur la branche `claude/audit-institutional-readiness-8Cw4w`
4
+ > à partir du commit `06aac6a` (merge PR #50). Méthode : 6 agents
5
+ > d'exploration en parallèle (architecture, code/sécurité, tests,
6
+ > documentation, CI/CD, web/i18n/accessibilité), suivis d'une vérification
7
+ > manuelle des findings critiques (`grep`, lecture ligne à ligne).
8
+ > Lint `ruff check picarones/ tests/` : passe. Suite complète :
9
+ > **3 356 passed, 3 skipped, 0 failed** en 3 min 04 s.
10
+ >
11
+ > **Cible** : adoption institutionnelle (BnF, BL, KBR, Archives nationales)
12
+ > et publication scientifique citable (JOSS, arXiv).
13
+ >
14
+ > **Verdict global** : **non prêt** pour estampille institutionnelle ou
15
+ > citation académique sans remédiation. Le code est solide, l'architecture
16
+ > claire, la couverture de tests inégalée. Les bloqueurs sont concentrés
17
+ > sur trois axes : **(1) communication scientifique** (CITATION, JOSS,
18
+ > citations primaires des méthodes statistiques), **(2) gouvernance et
19
+ > ops institutionnelles** (CSRF, accessibilité WCAG, déploiement, RGPD),
20
+ > **(3) hygiène d'intégration continue** (lock file, scanners de sécurité,
21
+ > seuil de couverture).
22
+ >
23
+ > **Effort estimé pour atteindre le niveau BnF** : 6 à 10 semaines
24
+ > calendaires (1 ETP), hors rédaction du papier JOSS qui suit son propre
25
+ > calendrier (8 à 12 semaines de revue par les pairs).
26
+
27
+ ---
28
+
29
+ ## 1. Résumé par sévérité
30
+
31
+ | Sévérité | Compte | Domaines |
32
+ |---|---|---|
33
+ | **BLOCKER** | 11 | Architecture (2), violation règle propre (3), publication scientifique (3), accessibilité (2), sécurité web (1) |
34
+ | **MAJOR** | 18 | CI/CD (6), documentation (5), tests (3), reproductibilité (2), web/UX (2) |
35
+ | **MINOR** | 17 | Polissage (DX, packaging, i18n résiduel, cache Docker, formats locales…) |
36
+ | **Faux positifs** | 1 | « SQL injection » dans `jobs.py:235` — détaillé en §6 |
37
+
38
+ Tous les findings sont accompagnés de la citation `fichier:ligne` exacte
39
+ et d'une esquisse de correction. Les efforts indiqués sont en
40
+ *personne-jours* (PJ) pour un ingénieur familier du repo.
41
+
42
+ ---
43
+
44
+ ## 2. Bloqueurs — à corriger avant tout estampillage institutionnel
45
+
46
+ ### B-1 — Violation Cercle 2 → Cercle 3 dans `measurements/statistics.py`
47
+
48
+ **Fichier** : `picarones/measurements/statistics.py:861`
49
+
50
+ ```python
51
+ def _extract_error_pairs(gt: str, hyp: str) -> list[tuple[str, str]]:
52
+ from picarones.report.diff_utils import compute_word_diff # ← Cercle 3 !
53
+ ```
54
+
55
+ **Problème** : violation directe de la règle architecturale documentée
56
+ dans `CLAUDE.md` et `docs/architecture.md` (« les imports vont
57
+ uniquement de l'extérieur vers l'intérieur »). Un module de mesures
58
+ (Cercle 2) ne doit jamais dépendre du rendu (Cercle 3). Le import
59
+ est *paresseux* (à l'intérieur de la fonction), donc il ne casse pas
60
+ le démarrage, mais il rend le module `statistics` inutilisable
61
+ pour quiconque consomme Picarones sans la couche `report` (par exemple
62
+ un pipeline d'analyse en notebook ou un service externe).
63
+
64
+ **Correctif** : extraire `compute_word_diff` (et toute la famille
65
+ `diff_utils`) dans `picarones/core/diff_utils.py`. Le rendu HTML peut
66
+ continuer à le ré-exporter pour rétrocompatibilité.
67
+
68
+ **Effort** : 0,5 PJ. **Risque** : faible — le module diff_utils a déjà
69
+ ses tests dans `tests/report/test_diff_utils.py`, à déplacer.
70
+
71
+ ---
72
+
73
+ ### B-2 — Violation Cercle 2 → Cercle 3 dans `measurements/difficulty.py`
74
+
75
+ **Fichier** : `picarones/measurements/difficulty.py:195`
76
+
77
+ ```python
78
+ def difficulty_color(score: float) -> str:
79
+ from picarones.report.colors import COLOR_GREEN, COLOR_YELLOW, COLOR_ORANGE, COLOR_RED
80
+ ```
81
+
82
+ **Problème** : identique à B-1. Pire : la fonction renvoie une couleur
83
+ CSS, donc c'est une logique purement de présentation qui s'est glissée
84
+ dans le module métier `difficulty`.
85
+
86
+ **Correctif** : déplacer `difficulty_color` dans
87
+ `picarones/report/difficulty_render.py` (à créer) et ne laisser dans
88
+ `difficulty.py` que la logique de scoring numérique. Les appelants du
89
+ côté `report/` font alors `from picarones.report.difficulty_render
90
+ import difficulty_color`.
91
+
92
+ **Effort** : 0,5 PJ.
93
+
94
+ ---
95
+
96
+ ### B-3 — Trois `except Exception: pass` qui violent la règle « jamais »
97
+
98
+ **Fichiers et lignes** :
99
+ - `picarones/extras/importers/huggingface.py:266` (recherche API silencieusement avalée)
100
+ - `picarones/extras/importers/huggingface.py:416` (échec de sauvegarde d'image silencieux)
101
+ - `picarones/extras/importers/htr_united.py:448` (parsing YAML silencieux → fallback démo)
102
+
103
+ **Problème** : règle écrite noir sur blanc dans `CLAUDE.md` :
104
+
105
+ > **Ne jamais mettre `except Exception: pass`** : remplacer par
106
+ > `logger.warning("[module] fonctionnalité dégradée : %s", e)`.
107
+
108
+ Conséquences concrètes pour un archiviste BnF qui importe un corpus
109
+ HTR-United : si le YAML distant est mal-formé, l'utilisateur reçoit un
110
+ catalogue de démo *sans aucun avertissement* — il croit consulter le
111
+ catalogue institutionnel. Pour une mainteneur, c'est un bug invisible
112
+ qui peut survivre des années.
113
+
114
+ **Correctif** : remplacer chaque `pass` par
115
+ `logger.warning("[importers] <opération> a échoué (mode dégradé) : %s", e)`
116
+ + ajouter un `Fact` dans la synthèse du rapport quand le fallback est
117
+ déclenché côté utilisateur.
118
+
119
+ **Effort** : 0,5 PJ. **Test** : 1 cas par site (mock l'échec → vérifier
120
+ le log).
121
+
122
+ ---
123
+
124
+ ### B-4 — Aucune `CITATION.cff` ni preprint scientifique
125
+
126
+ **Fichiers manquants** : `CITATION.cff`, `paper.md` (JOSS), pas de DOI
127
+ dans le `README`, pas d'`ORCID` listés, pas de `.zenodo.json`.
128
+
129
+ **Problème** : pour qu'un article scientifique cite Picarones, il faut
130
+ au minimum un fichier `CITATION.cff` parsable par GitHub (qui produit
131
+ alors le bouton « Cite this repository »), idéalement un DOI Zenodo, et
132
+ en pratique un papier JOSS pour un projet de cette envergure (méthodes
133
+ statistiques nouvelles, registre de métriques typées, moteur narratif
134
+ factuel anti-hallucination — chacune de ces contributions est citable).
135
+
136
+ Une bibliothèque nationale n'adoptera pas un outil scientifique non
137
+ citable. Une thèse ou un article ne peut pas s'appuyer sur Picarones
138
+ si la référence se résume à une URL GitHub mutable.
139
+
140
+ **Correctif** :
141
+ 1. Créer `CITATION.cff` (5 min), avec auteurs ORCID et version.
142
+ 2. Pousser une release GitHub taggée + obtenir un DOI Zenodo (intégration
143
+ automatique : 1 h).
144
+ 3. Rédiger un `paper.md` (format JOSS, 6 à 8 pages) résumant : philosophie
145
+ « banc d'essai », architecture en 3 cercles, contributions
146
+ méthodologiques (Friedman + Nemenyi, registre typé, moteur narratif).
147
+
148
+ **Effort** : 1 PJ pour CITATION+Zenodo. Le papier JOSS : ~10 PJ d'écriture
149
+ + 8 à 12 semaines de revue par les pairs.
150
+
151
+ ---
152
+
153
+ ### B-5 — Méthodes statistiques sans citation primaire dans le code
154
+
155
+ **Fichier** : `picarones/measurements/statistics.py` (1 127 lignes)
156
+
157
+ **Problème** : le module implémente Friedman, post-hoc Nemenyi, Wilcoxon,
158
+ bootstrap, intervalles de confiance, dominance Pareto. Or aucune
159
+ référence BibTeX/DOI n'apparaît dans les docstrings. Demšar 2006 (le
160
+ papier qui définit Friedman+Nemenyi pour la comparaison de classifieurs,
161
+ soit *exactement* ce que fait Picarones) n'est cité nulle part dans le
162
+ code. Idem Wilcoxon 1945, Efron 1979 (bootstrap).
163
+
164
+ Conséquence : un relecteur académique ou un responsable BnF en
165
+ évaluation ne peut pas vérifier que l'implémentation correspond aux
166
+ définitions canoniques. Le `glossary` (Sprint 21) mentionne les noms
167
+ des tests mais ne pointe pas vers les sources primaires.
168
+
169
+ **Correctif** : ajouter un en-tête de module dans `statistics.py` listant
170
+ les références (BibTeX), et un `:references:` dans la docstring de
171
+ chaque fonction publique. Ajouter le champ `reference` aux 25 entrées
172
+ du glossaire (déjà prévu dans le schéma — voir
173
+ `picarones/report/glossary/`, vérifier la complétude).
174
+
175
+ **Effort** : 1 PJ.
176
+
177
+ ---
178
+
179
+ ### B-6 — Profils de normalisation non tracés à des standards éditoriaux
180
+
181
+ **Fichiers** :
182
+ - `picarones/measurements/normalization.py` (420 lignes)
183
+ - `picarones/measurements/mufi.py` (cite « MUFI » sans préciser la version)
184
+ - `docs/profiles.md` (présent mais ne pointe ni vers TEI P5 ni vers
185
+ MUFI registry ni vers DEAF)
186
+
187
+ **Problème** : Picarones revendique des profils `DIPLOMATIC_FR`,
188
+ `MUFI`, `EARLY_MODERN`, etc. Pour un médiéviste ou un éditeur critique,
189
+ la première question est : « quelle version de MUFI ? quelle
190
+ recommandation TEI ? quelle politique pour ſ→s ? ». Sans cette
191
+ traçabilité, on ne peut pas comparer un benchmark Picarones à une
192
+ édition TEI conforme aux standards de la communauté.
193
+
194
+ **Correctif** : créer `docs/normalization-specs.md` qui mappe chaque
195
+ profil :
196
+ - nom du profil
197
+ - version exacte de la spec source (MUFI v4.0, TEI P5 Unicode chapter
198
+ 3.4, DEAF ortho-2024, …)
199
+ - liste exhaustive des transformations appliquées
200
+ - DOI/URL stable de la spec
201
+ - date de révision
202
+
203
+ Ajouter un test de non-régression :
204
+ `tests/measurements/test_normalization_spec_consistency.py`.
205
+
206
+ **Effort** : 2 PJ (la connaissance experte est plus rare que le code).
207
+
208
+ ---
209
+
210
+ ### B-7 — Aucun scanner de sécurité dans la CI
211
+
212
+ **Fichier** : `.github/workflows/ci.yml`
213
+
214
+ **Manquants** : `bandit` (code Python), `pip-audit` ou `safety` (CVE
215
+ des dépendances), `trivy` (scan de l'image Docker), `gitleaks`
216
+ (détection de secrets dans l'historique). Pre-commit a `detect-private-key`
217
+ seulement.
218
+
219
+ **Problème** : projet exposant un endpoint public sur HuggingFace Space
220
+ avec dépendances cloud (mistralai, anthropic, openai, google-cloud-vision,
221
+ azure-ai-formrecognizer). Une CVE non détectée dans une de ces SDK est
222
+ une porte d'entrée. Pour BnF, c'est rédhibitoire — les revues
223
+ sécurité institutionnelles l'exigent.
224
+
225
+ **Correctif** : ajouter à `ci.yml` un job `security` parallèle aux tests :
226
+
227
+ ```yaml
228
+ security:
229
+ runs-on: ubuntu-latest
230
+ steps:
231
+ - uses: actions/checkout@v4
232
+ - uses: actions/setup-python@v5
233
+ with: { python-version: "3.11" }
234
+ - run: pip install bandit pip-audit
235
+ - run: bandit -r picarones/ -ll # niveau LOW+
236
+ - run: pip-audit --strict
237
+ - uses: aquasecurity/trivy-action@master
238
+ with: { image-ref: 'picarones:latest', exit-code: '1', severity: 'HIGH,CRITICAL' }
239
+ ```
240
+
241
+ **Effort** : 1 PJ (intégration + traitement des findings initiaux).
242
+
243
+ ---
244
+
245
+ ### B-8 — Aucun seuil de couverture appliqué (`--cov-fail-under` manquant)
246
+
247
+ **Fichier** : `.github/workflows/ci.yml:78`
248
+
249
+ ```yaml
250
+ pytest tests/ -q --cov=picarones --cov-report=xml --cov-report=term-missing
251
+ ```
252
+
253
+ **Problème** : la couverture est calculée et uploadée à Codecov, mais
254
+ aucun plancher n'est imposé. Une PR peut faire baisser la couverture de
255
+ 85 % à 40 % sans qu'aucun signal CI ne se déclenche. Pour un projet
256
+ revendiquant 3 359 tests, c'est paradoxal : la rigueur affichée n'est
257
+ pas applicable.
258
+
259
+ **Correctif** : ajouter `--cov-fail-under=85` (mesurer le baseline d'abord
260
+ avec `pytest --cov` → fixer le plancher 2 points en dessous). Optionnel
261
+ mais recommandé : exporter le delta dans un commentaire de PR via
262
+ `coverage-comment-action`.
263
+
264
+ **Effort** : 0,25 PJ.
265
+
266
+ ---
267
+
268
+ ### B-9 — Graphiques Canvas inaccessibles aux lecteurs d'écran (WCAG 1.1.1 niveau A)
269
+
270
+ **Fichiers** :
271
+ - `picarones/report/templates/_app.js:1062`, `1102`, et autres
272
+ instanciations Chart.js
273
+ - `picarones/report/vendor/chart.umd.min.js`
274
+
275
+ **Problème** : Chart.js produit du `<canvas>`. Sans intervention,
276
+ *aucun* contenu n'est exposé à l'AT (assistive technology). Un usager
277
+ non-voyant utilisant NVDA/JAWS n'entend qu'« graphique vide ». Cela
278
+ viole WCAG 2.1 succès 1.1.1 (Non-text Content) au niveau A — le plus
279
+ bas, donc rédhibitoire pour toute déclaration de conformité RGAA en
280
+ France.
281
+
282
+ **Correctif** : pour chaque graphique, ajouter en parallèle :
283
+ 1. un `<table>` de données équivalentes, marqué
284
+ `aria-describedby` du canvas, masqué visuellement (`visually-hidden`)
285
+ mais lu par les AT ;
286
+ 2. un `aria-label` descriptif sur le `<canvas>` ;
287
+ 3. un bouton « Voir les données » qui révèle la table à tous (utile
288
+ aussi pour la copie).
289
+
290
+ Alternative plus profonde : remplacer Chart.js par des SVG natifs avec
291
+ `<title>` et `<desc>` (déjà la pratique dans `pipeline_dag_render.py`,
292
+ `taxonomy_cooccurrence_render.py`, etc. — Sprints 64, 75 et al.).
293
+ Cohérent avec le reste de la base.
294
+
295
+ **Effort** : 2 PJ (8 à 12 graphiques Chart.js à doubler).
296
+
297
+ ---
298
+
299
+ ### B-10 — Pas de lien « Aller au contenu » (WCAG 2.4.1)
300
+
301
+ **Fichier** : `picarones/report/templates/base.html.j2` et `_header.html`
302
+
303
+ **Problème** : aucune occurrence de `skip`, `main-content` ou équivalent
304
+ dans les templates. Un usager-clavier doit traverser toute la
305
+ navigation et le panneau latéral avant d'atteindre le rapport. Violation
306
+ WCAG 2.1 succès 2.4.1 (Bypass Blocks) au niveau A.
307
+
308
+ **Correctif** : ajouter dans `_header.html`, premier enfant du `<body>` :
309
+
310
+ ```html
311
+ <a href="#main" class="skip-link">{{ i18n.skip_to_content }}</a>
312
+ ```
313
+
314
+ + une classe CSS `.skip-link` qui reste cachée hors `:focus` ; et
315
+ ajouter `id="main"` sur le conteneur principal.
316
+
317
+ **Effort** : 0,25 PJ.
318
+
319
+ ---
320
+
321
+ ### B-11 — Aucune protection CSRF sur les endpoints POST
322
+
323
+ **Fichier** : `picarones/web/app.py` + 11 routers dans
324
+ `picarones/web/routers/`
325
+
326
+ **Problème** : tous les endpoints POST (`/api/corpus/upload`,
327
+ `/api/benchmark/start`, `/api/benchmark/run`, `/api/benchmark/{id}/cancel`,
328
+ `/api/config/save`, `/api/htr-united/import`, `/api/huggingface/import`,
329
+ `/api/lang/{code}`) acceptent les requêtes sans vérification d'origine
330
+ ni token CSRF.
331
+
332
+ Sur HuggingFace Space en mode public, l'impact est limité (pas de
333
+ session utilisateur authentifiée à voler). Mais en déploiement
334
+ institutionnel BnF (sur intranet, derrière SSO), un usager logué peut
335
+ être victime d'une page tierce qui poste vers `/api/config/save` ou
336
+ lance un benchmark coûteux à son insu.
337
+
338
+ **Correctif** : ajouter le middleware `starlette-csrf` ou équivalent,
339
+ piloté par variable d'environnement `PICARONES_CSRF_REQUIRED=1`. En
340
+ mode public HuggingFace : laissé désactivé (pas de session). En mode
341
+ institutionnel : activé d'office.
342
+
343
+ **Effort** : 1 PJ + tests.
344
+
345
+ ---
346
+
347
+ ## 3. Problèmes majeurs — à régler dans les 6 prochaines semaines
348
+
349
+ ### M-1 — Pas de fichier de verrouillage des dépendances
350
+
351
+ **Symptôme** : `pyproject.toml` déclare 11 dépendances cœur et 7 extras
352
+ en `>=` sans borne haute. `requirements.txt` à la racine est
353
+ divergent et obsolète. Aucun `requirements.lock`, `uv.lock`,
354
+ `poetry.lock`.
355
+
356
+ **Conséquence** : un build Docker du 2 mai 2026 et un build du 2 mai 2027
357
+ ne produiront pas le même artefact. Pour un dépôt patrimonial qui doit
358
+ pouvoir rejouer un benchmark à 5 ans d'intervalle, c'est inacceptable.
359
+
360
+ **Correctif** : adopter `uv` ou `pip-tools`, générer `requirements.lock`
361
+ et l'épingler dans le `Dockerfile` (`pip install -r requirements.lock`
362
+ au lieu de `pip install .`). Régénérer mensuellement via un workflow
363
+ dédié + PR automatique.
364
+
365
+ **Effort** : 1 PJ.
366
+
367
+ ---
368
+
369
+ ### M-2 — Image Docker de base non épinglée
370
+
371
+ **Fichier** : `Dockerfile:18, 43`
372
+
373
+ ```dockerfile
374
+ FROM python:3.11-slim AS builder
375
+ FROM python:3.11-slim AS runtime
376
+ ```
377
+
378
+ **Correctif** : épingler au digest SHA256 :
379
+ `FROM python:3.11.10-slim@sha256:abc123…`. Régénérer trimestriellement.
380
+
381
+ **Effort** : 0,25 PJ.
382
+
383
+ ---
384
+
385
+ ### M-3 — Endpoint `/health` absent alors que le `HEALTHCHECK` Docker l'appelle
386
+
387
+ **Fichiers** : `Dockerfile:96` (`curl -f http://localhost:7860/health`)
388
+ vs `picarones/web/routers/system.py:13` (qui expose `/api/status`).
389
+
390
+ **Correctif** : aliaser `/health` → `/api/status` ou créer un endpoint
391
+ dédié, plus minimaliste (juste 200 OK + version, sans introspecter
392
+ l'état OCR).
393
+
394
+ **Effort** : 0,25 PJ.
395
+
396
+ ---
397
+
398
+ ### M-4 — Pas de type-checking dans la CI
399
+
400
+ **État** : `Makefile:100-102` propose un target `typecheck` qui appelle
401
+ mypy avec `--ignore-missing-imports --no-strict-optional`, mais n'est
402
+ pas appelé par `ci.yml`. Aucune section `[tool.mypy]` dans
403
+ `pyproject.toml`. Pas de marqueur `py.typed`.
404
+
405
+ **Correctif** : configurer mypy dans `pyproject.toml` avec
406
+ `strict = true` sur `picarones/core/` (le plus stable), `strict = false`
407
+ ailleurs comme état initial. Ajouter un job CI `typecheck` qui devient
408
+ bloquant pour `picarones/core/` et avertissant ailleurs. Marquer
409
+ `py.typed`.
410
+
411
+ **Effort** : 2 PJ (premier passage), puis maintenance continue.
412
+
413
+ ---
414
+
415
+ ### M-5 — Pas de pipeline de release vers PyPI
416
+
417
+ **Symptôme** : `pyproject.toml` épingle `version = "1.0.0"` en dur. Pas
418
+ de `setuptools_scm`. Pas de workflow `.github/workflows/release.yml`.
419
+ Picarones n'est pas installable via `pip install picarones`.
420
+
421
+ **Conséquence** : impossible de citer une version exacte (`picarones==1.2.3`)
422
+ dans un `requirements.txt` de notebook ou de papier. Toute installation
423
+ passe par `pip install git+https://…` (mutable, fragile).
424
+
425
+ **Correctif** : adopter `setuptools_scm` (version dérivée des tags Git)
426
+ + workflow `release.yml` déclenché sur tag `v*` qui : build sdist+wheel
427
+ → test sur `testpypi` → publie sur PyPI via `pypa/gh-action-pypi-publish`
428
+ avec OIDC trust (pas de token long-lived).
429
+
430
+ **Effort** : 1 PJ.
431
+
432
+ ---
433
+
434
+ ### M-6 — Pas d'image conteneur publiée immutable
435
+
436
+ **Symptôme** : `Makefile:167` tagge `picarones:latest` et `picarones:1.0.0`
437
+ localement mais ne pousse nulle part. HuggingFace Space rebuild à chaque
438
+ merge (donc pas un *artefact*, c'est une recompilation). Pas de
439
+ publication sur ghcr.io, Docker Hub, ou Quay.
440
+
441
+ **Correctif** : ajouter un workflow qui pousse vers
442
+ `ghcr.io/maribakulj/picarones:1.0.0` et `…:latest` à chaque release.
443
+ Avec digest fixe communiqué dans le `CHANGELOG`.
444
+
445
+ **Effort** : 0,5 PJ.
446
+
447
+ ---
448
+
449
+ ### M-7 — Pas de guide de déploiement institutionnel
450
+
451
+ **Manquant** : `docs/operations/deployment.md`, `docs/operations/backup.md`,
452
+ `docs/operations/data-retention.md`.
453
+
454
+ **Conséquence** : un DSI BnF qui veut héberger Picarones doit deviner :
455
+ - Quelle BD pour `jobs.sqlite` en multi-instance ?
456
+ - Comment migrer le schéma de l'historique longitudinal entre versions ?
457
+ - Combien de temps les uploads sont-ils conservés ? Politique RGPD ?
458
+ - Comment intégrer derrière un proxy SSO (Shibboleth, CAS, OIDC) ?
459
+ - Quelle observabilité (logs JSON pour ELK, métriques Prometheus) ?
460
+ - Comment sauvegarder/restaurer l'historique ?
461
+
462
+ INSTALL.md couvre uniquement Docker mono-instance HuggingFace. C'est
463
+ insuffisant.
464
+
465
+ **Correctif** : rédiger les 3 guides ci-dessus. Ajouter une section
466
+ RGPD au `SECURITY.md` (rétention des uploads, logs, IP du
467
+ rate-limiter).
468
+
469
+ **Effort** : 3 PJ.
470
+
471
+ ---
472
+
473
+ ### M-8 — Aucune politique de rétention des données ni mention RGPD
474
+
475
+ **Manquant** : politique explicite pour les uploads ZIP/images,
476
+ les logs (qui contiennent IP via le rate-limiter), l'historique
477
+ longitudinal SQLite.
478
+
479
+ **Conséquence** : sur Space public, un visiteur qui upload une image
480
+ patrimoniale ne sait pas combien de temps elle est gardée. Sur
481
+ déploiement institutionnel BnF, l'absence de politique bloque la
482
+ mise en production.
483
+
484
+ **Correctif** : doc `docs/operations/data-retention.md` + mécanisme
485
+ de purge automatique (cron job `purge_uploads_older_than(days=7)`)
486
+ + mention RGPD dans le `README` et la home web.
487
+
488
+ **Effort** : 1,5 PJ.
489
+
490
+ ---
491
+
492
+ ### M-9 — Pas de déclaration d'accessibilité
493
+
494
+ **Manquant** : `ACCESSIBILITY.md` (recommandation gouvernementale FR
495
+ pour tout service public, RGAA 4.1 art. 47 de la loi 2005-102).
496
+
497
+ **Correctif** : déclaration explicite après audit RGAA + remédiation
498
+ des bloqueurs B-9 et B-10. Pour atteindre WCAG 2.1 niveau AA
499
+ (prérequis BnF), prévoir un audit externe après remédiation.
500
+
501
+ **Effort** : 1 PJ pour la déclaration + remédiation déjà comptée en B-9/B-10
502
+ + audit externe (hors équipe).
503
+
504
+ ---
505
+
506
+ ### M-10 — Pas de divulgation de conflits d'intérêt
507
+
508
+ **Manquant** : déclaration sur la position de l'outil vis-à-vis des
509
+ fournisseurs cloud benchmarkés (OpenAI, Anthropic, Mistral, Google,
510
+ Azure). Pricing dans `picarones/data/pricing.yaml` (`last_updated:
511
+ 2026-04-01`) sans validation indépendante ni veille automatique.
512
+
513
+ **Conséquence** : un papier qui s'appuie sur l'analyse de Pareto coût
514
+ de Picarones doit pouvoir citer une politique d'absence de COI. Sinon
515
+ un relecteur peut soupçonner un biais éditorial.
516
+
517
+ **Correctif** : ajouter une section « Conflicts of interest » dans le
518
+ `README` + `paper.md` JOSS, et un en-tête « Pricing as observed on
519
+ YYYY-MM-DD ; recompute with your own contracted rates » sur la vue
520
+ Pareto.
521
+
522
+ **Effort** : 0,5 PJ.
523
+
524
+ ---
525
+
526
+ ### M-11 — `CODEOWNERS` et politique de gouvernance absents
527
+
528
+ **Manquant** : `.github/CODEOWNERS`, `GOVERNANCE.md`, politique de
529
+ revue, cadence de release, SLO réponse aux issues.
530
+
531
+ **Conséquence** : une institution qui évalue la pérennité ne sait pas
532
+ s'il y a un mainteneur unique ou plusieurs, ni à quelle cadence elle
533
+ peut espérer un correctif.
534
+
535
+ **Correctif** : créer les deux fichiers. Cadence de release suggérée :
536
+ mensuelle pour les versions mineures, trimestrielle pour les majeures.
537
+ SLO suggéré (et tenable pour un projet de cette taille) : 5 jours
538
+ ouvrés pour un triage initial des issues.
539
+
540
+ **Effort** : 0,5 PJ + engagement de gouvernance.
541
+
542
+ ---
543
+
544
+ ### M-12 — Reproductibilité des snapshots sous-documentée
545
+
546
+ **État** : `picarones/report/snapshot.py` (266 lignes) et
547
+ `tests/report/test_sprint27_reproducibility_snapshots.py` existent.
548
+ Mais ni le `README` ni `docs/user/reading-a-report.md` n'expliquent :
549
+ - ce que contient un snapshot (versions OCR, modèles LLM, hash du code,
550
+ hash du corpus, seeds…)
551
+ - comment recharger un snapshot pour rejouer un benchmark
552
+ - comment documenter un snapshot dans une publication
553
+
554
+ **Correctif** : créer `docs/reproducibility-snapshots.md`. Inclure
555
+ exemples reproductibles. Lier depuis `README` et `paper.md`.
556
+
557
+ **Effort** : 1 PJ.
558
+
559
+ ---
560
+
561
+ ### M-13 — Tests de concurrence runner / web sous-représentés
562
+
563
+ **Findings agents tests** :
564
+ - `picarones/measurements/runner.py` (1 019 lignes) n'a pas de test
565
+ ciblant : épuisement du pool de processus, échecs partiels,
566
+ processus zombies, `PICARONES_MAX_CONCURRENT_JOBS=32` sous charge.
567
+ - `picarones/web/jobs.py` : pas de test pour SSE `Last-Event-ID`
568
+ reconnexion, écritures concurrentes SQLite (`SQLITE_BUSY`), bascule
569
+ `PICARONES_PUBLIC_MODE=1` à chaud, isolation des jobs entre IPs.
570
+
571
+ **Correctif** : ajouter `tests/integration/test_runner_concurrency.py`
572
+ (50+ cas) et `tests/web/test_sse_reconnect.py`.
573
+
574
+ **Effort** : 3 PJ.
575
+
576
+ ---
577
+
578
+ ### M-14 — Pas de garde-fou anti-régression pour le benchmark lui-même
579
+
580
+ **Findings agent tests** : un *benchmarking platform* qui ne mesure pas
581
+ sa propre dérive de performance est suspect. Le job `regression_check`
582
+ dans `ci.yml:207-226` est commenté : « optionnel — activer si vous avez
583
+ un corpus de référence ».
584
+
585
+ **Correctif** : créer un mini-corpus de référence (10 documents libres
586
+ de droits couvrant les 3 strates principales : médiéval, imprimé
587
+ ancien, moderne) dans `tests/fixtures/reference_corpus/`. Ajouter un
588
+ job CI `--fail-if-cer-above 15.0` sur Tesseract+Pero. Exécuter
589
+ hebdomadairement (cron), pas à chaque PR (coût).
590
+
591
+ **Effort** : 2 PJ + sélection corpus.
592
+
593
+ ---
594
+
595
+ ### M-15 — Pas de timeout global pytest
596
+
597
+ **Fichier** : `.github/workflows/ci.yml:74-78`. Aucun `--timeout`. Un
598
+ test bloqué (Tesseract qui freeze, API LLM qui pend) bloque le runner
599
+ GH Actions jusqu'au timeout du job (6 h par défaut).
600
+
601
+ **Correctif** : ajouter `pytest-timeout` aux deps `[dev]`, configurer
602
+ `pyproject.toml` :
603
+ ```toml
604
+ [tool.pytest.ini_options]
605
+ timeout = 300
606
+ timeout_method = "thread"
607
+ ```
608
+
609
+ **Effort** : 0,1 PJ.
610
+
611
+ ---
612
+
613
+ ### M-16 — Pas de chargement paresseux pour les rapports volumineux
614
+
615
+ **Symptôme** : `picarones/report/generator.py` produit un fichier HTML
616
+ unique, images en base64. Un corpus de 1 000 documents × 5 moteurs
617
+ peut générer un fichier > 200 MB. Le navigateur peine.
618
+
619
+ **Correctif** : pour la galerie de documents, externaliser les images
620
+ dans `report-assets/<doc_id>.png` à côté du HTML, et lazy-loader
621
+ (`loading="lazy"`). Optionnel : pagination côté client.
622
+
623
+ **Effort** : 1 PJ. Garder l'option « monolithique » pour les petits
624
+ corpus (par défaut < 50 docs).
625
+
626
+ ---
627
+
628
+ ### M-17 — Documentation déséquilibrée FR/EN
629
+
630
+ **Constat** : README bilingue ✓. UI/glossaire bilingues ✓. Mais SPECS.md,
631
+ CHANGELOG.md, `docs/user/reading-a-report.md`, `docs/case-studies/`,
632
+ les guides développeur (4 fichiers) sont en français pur. Un chercheur
633
+ britannique ou allemand qui veut contribuer ne peut pas lire les guides
634
+ développeur. Un mainteneur qui veut publier le projet sur arXiv doit
635
+ réécrire toute la documentation utilisateur en anglais.
636
+
637
+ **Correctif** : traduire prioritairement (1) `docs/user/reading-a-report.md`,
638
+ (2) `docs/developer/index.md` + les 3 sous-guides, (3) `CONTRIBUTING.md`.
639
+ Laisser CHANGELOG et SPECS en français pour l'instant — moins critique.
640
+
641
+ **Effort** : 2 PJ pour les 5 documents prioritaires.
642
+
643
+ ---
644
+
645
+ ### M-18 — Pas de `.dockerignore` ni de `.env.example`
646
+
647
+ **Symptômes** :
648
+ - Pas de `.dockerignore` à la racine → `git`, `docs/`, `tests/` copiés
649
+ inutilement dans l'image (taille +20 %, cache hit dégradé).
650
+ - `docker-compose.yml` référence `${OPENAI_API_KEY}`, `${PICARONES_PORT}`
651
+ sans `.env.example` → les utilisateurs doivent deviner.
652
+
653
+ **Correctif** : 2 fichiers, 30 lignes chacun.
654
+
655
+ **Effort** : 0,1 PJ.
656
+
657
+ ---
658
+
659
+ ## 4. Problèmes mineurs — à intégrer en backlog
660
+
661
+ | # | Item | Fichier:ligne | Effort |
662
+ |---|------|---------------|--------|
663
+ | m-1 | Hardcoded FR `'Données d'ancrage non disponibles.'` bypass i18n | `_app.js:1087` | 0,1 PJ |
664
+ | m-2 | Hardcoded FR `'Données Gini non disponibles.'` (fallback) | `_app.js:1049` | 0,1 PJ |
665
+ | m-3 | Boutons « Réinitialiser » sans clé i18n | `_header.html:25` | 0,1 PJ |
666
+ | m-4 | Tableaux HTML sans `scope="col"` sur `<th>` | templates `view_*.html` | 0,3 PJ |
667
+ | m-5 | Palette heatmap non daltonien-friendly | `_styles.css` + `colors.py` | 0,5 PJ |
668
+ | m-6 | Nombres dans tableaux non localisés (1234567 vs 1 234 567) | `_app.js` (toLocaleString) | 0,3 PJ |
669
+ | m-7 | Pre-commit non rejoué en CI (bypassable via `--no-verify`) | `.github/workflows/ci.yml` | 0,1 PJ |
670
+ | m-8 | CI ne teste pas Python 3.13 (alors que `requires-python = ">=3.11"`) | `ci.yml:34` | 0,1 PJ |
671
+ | m-9 | API stability tests ne valident pas les `default values` des signatures | `tests/core/test_public_api.py` | 0,3 PJ |
672
+ | m-10 | Tests cloud OCR sans cas d'erreur HTTP (429, 401, 503) | `tests/engines/test_engines_cloud.py` | 0,5 PJ |
673
+ | m-11 | Versionnement des testdata absent (`tests/.testdata_versions.yaml`) | `tests/` | 0,2 PJ |
674
+ | m-12 | Numérotation sprint des fichiers de tests : trous (1, 37, 41, 43…) | `tests/` | 0,1 PJ (audit + nettoyage) |
675
+ | m-13 | `requirements.txt` racine partiellement divergent de `pyproject.toml` | `requirements.txt` | 0,1 PJ |
676
+ | m-14 | Pas de staleness check automatique sur `pricing.yaml` | générateur | 0,3 PJ |
677
+ | m-15 | `picarones.spec` (PyInstaller) avec `hiddenimports` manuels | `picarones.spec:45-98` | 0,5 PJ |
678
+ | m-16 | Aucun module `extras/historical/` ni `extras/importers/` séparé en package | `pyproject.toml:84-97` | 1 PJ (refactor planifié déjà documenté) |
679
+ | m-17 | `tests/measurements/test_sprint11_i18n_english.py` importe `report.generator` | `tests/measurements/` | 0,2 PJ (déplacer en `tests/integration/`) |
680
+
681
+ ---
682
+
683
+ ## 5. Points forts — à préserver et à valoriser dans la communication
684
+
685
+ Pour qu'un audit institutionnel soit *crédible*, il doit aussi nommer
686
+ explicitement ce qui marche. Les points suivants sont **au-dessus** de
687
+ ce qu'on observe dans 90 % des projets de recherche similaires :
688
+
689
+ 1. **Architecture en 3 cercles tenue à 99 %.** Cercle 1 (`picarones/core/`)
690
+ n'a aucune dépendance vers Cercles 2 ou 3. L'API publique
691
+ (`picarones/__init__.py`) ré-exporte uniquement Cercle 1 — surface
692
+ stable, contrat clair (`docs/api-stable.md`). Les 2 violations
693
+ identifiées (B-1, B-2) sont **circonscrites et faciles à corriger**.
694
+
695
+ 2. **Discipline de code rigoureuse.** Lint `ruff` 0 erreur. Logger
696
+ nommé par module systématiquement. 0 `print()` en code métier.
697
+ 3 `TODO/FIXME` dans tout le repo (signe rare). 87 `except Exception`
698
+ au total mais **84 sont annotés `# noqa: BLE001` avec contexte
699
+ explicite**, seuls les 3 du B-3 sont des vraies violations.
700
+
701
+ 3. **Sécurité de fond solide.**
702
+ - XML défendu par `defusedxml` partout (XXE / Billion Laughs).
703
+ - Zip-slip prévenu par `Path(member.filename).name` dans
704
+ `web/corpus_utils.py:182-183`.
705
+ - Toutes les requêtes SQLite paramétrées (le `f-string` de
706
+ `jobs.py:235` est un faux positif — voir §6).
707
+ - Aucun `pickle.load()` (vecteur de RCE classique).
708
+ - `subprocess` utilisé une seule fois (`snapshot.py:186`,
709
+ `git rev-parse HEAD` — args hardcodés, `timeout=2`,
710
+ `stderr=DEVNULL`, gestion d'exception explicite).
711
+ - Mode public (`PICARONES_PUBLIC_MODE`) avec gating des moteurs
712
+ cloud, rate-limiting par IP, `Image.verify()` anti-bombe de
713
+ décompression, en-têtes CSP / X-Content-Type-Options /
714
+ X-Frame-Options.
715
+
716
+ 4. **Couverture de tests volumineuse et structurée.** 3 359 tests
717
+ collectés, organisés par cercle (`tests/core`, `tests/measurements`,
718
+ `tests/engines`, `tests/web`, `tests/integration`, etc.). Tests
719
+ d'API publique (`tests/core/test_public_api.py`) garantissant la
720
+ stabilité du contrat externe. Pas de test fantôme `assert True`.
721
+
722
+ 5. **Neutralité éditoriale exemplaire.** La règle « Picarones mesure et
723
+ classe — il ne tranche pas le débat éditorial » est tenue jusque
724
+ dans le moteur narratif (chaque nombre rendu est traçable au
725
+ `payload` du `Fact` correspondant — anti-hallucination *prouvé*
726
+ par tests). Les 5 « leviers d'amélioration » (Sprint 51) sont
727
+ explicitement factuels, pas prescriptifs. Les profils diplomatique
728
+ vs modernisant sont rapportés sans verdict.
729
+
730
+ 6. **Reproductibilité partielle déjà en place.** Snapshot bit-à-bit
731
+ identique sur même entrée (Sprint 27, vérifié par tests). Run
732
+ save/load (Sprint 25). Comparaison de runs (Sprint 26). Manque
733
+ uniquement la doc utilisateur (M-12) pour valoriser.
734
+
735
+ 7. **Documentation interne (CLAUDE.md, CHANGELOG.md, SPECS.md)
736
+ exceptionnellement détaillée.** Le journal des sprints permet à
737
+ un nouveau contributeur ou à un auditeur de comprendre l'évolution
738
+ de chaque décision.
739
+
740
+ 8. **Politique de modules contribués (Sprint 97) déjà formalisée.**
741
+ `core/module_policy.py` + `docs/developer/module-policy.md`. Picarones
742
+ a anticipé le passage à un écosystème de plugins externes — rare
743
+ pour un projet de cette taille.
744
+
745
+ ---
746
+
747
+ ## 6. Faux positifs identifiés et écartés
748
+
749
+ ### F-1 — « SQL injection » dans `picarones/web/jobs.py:235`
750
+
751
+ L'agent code-quality a flagué cette ligne :
752
+
753
+ ```python
754
+ c.execute(
755
+ f"UPDATE jobs SET {', '.join(fields)} WHERE job_id = ?",
756
+ values,
757
+ )
758
+ ```
759
+
760
+ **Vérification manuelle** (lecture des lignes 210-238) : la liste
761
+ `fields` est construite *exclusivement* à partir de littéraux Python
762
+ hardcodés (`"progress = ?"`, `"current_engine = ?"`, etc.) selon des
763
+ branches `if X is not None`. À aucun moment un input utilisateur n'y
764
+ arrive. Tous les `values` correspondants sont bien paramétrés via `?`.
765
+
766
+ **Verdict** : pas une vulnérabilité d'injection SQL. Au pire, un *style
767
+ fragile* qui pourrait inviter à l'erreur lors d'un futur refactor. À
768
+ laisser tel quel ou à refactorer en `m-18` (mineur de polissage).
769
+
770
+ ---
771
+
772
+ ### F-2 — « Pas de `--cov-fail-under` » classé blocker par certains agents
773
+
774
+ L'agent docs et l'agent CI ont tous deux insisté. C'est bloquant **pour
775
+ l'institution** (B-8) mais pas pour la communauté open-source. Je l'ai
776
+ gardé en BLOCKER vu la cible BnF.
777
+
778
+ ---
779
+
780
+ ### F-3 — Allégations de couverture de test divergentes (1 072 vs 3 354)
781
+
782
+ `CLAUDE.md` cite « 1 072 passed » dans la section *État actuel
783
+ (Sprint 16)* puis « ~3 354 passed » plus loin (*Contexte développement*).
784
+ Le second chiffre est correct (3 359 tests collectés au 2 mai 2026).
785
+ La première mention est obsolète depuis le Sprint 16 — à mettre à jour.
786
+ Effort : 0,01 PJ (un edit).
787
+
788
+ ---
789
+
790
+ ## 7. Feuille de route synthétique (10 semaines, 1 ETP)
791
+
792
+ | Semaine | Sprint d'audit | Livrables |
793
+ |---------|----------------|-----------|
794
+ | 1 | **S-A1 Architecture** | B-1, B-2, B-3 (violations + importers). Tests verts. |
795
+ | 1-2 | **S-A2 Sécurité CI** | B-7 (scanners), B-8 (cov threshold), M-15 (timeout pytest). |
796
+ | 2-3 | **S-A3 Web/Accessibilité** | B-9 (Chart.js a11y), B-10 (skip-link), B-11 (CSRF), m-1 à m-4 (i18n résiduel + scope). |
797
+ | 3-4 | **S-A4 Reproductibilité ops** | M-1 (lock file), M-2 (digest Docker), M-3 (/health), M-12 (doc snapshots), M-18 (.dockerignore + .env.example). |
798
+ | 4-5 | **S-A5 Publication scientifique** | B-4 (CITATION + Zenodo), B-5 (refs primaires statistics), B-6 (normalization specs). |
799
+ | 5-6 | **S-A6 Distribution** | M-5 (PyPI release), M-6 (image ghcr.io), M-11 (CODEOWNERS + governance). |
800
+ | 6-7 | **S-A7 Documentation institutionnelle** | M-7 (deployment guide), M-8 (data retention RGPD), M-9 (ACCESSIBILITY.md), M-10 (COI), M-17 (traduction EN). |
801
+ | 7-8 | **S-A8 Robustesse runner+web** | M-13 (tests concurrence), M-14 (anti-régression CER), M-16 (lazy loading reports). |
802
+ | 8-9 | **S-A9 Type-checking** | M-4 (mypy strict sur core, gradient ailleurs). |
803
+ | 9-10 | **S-A10 Polissage final + audit externe** | Backlog mineur restant + audit externe RGAA + audit externe sécurité. |
804
+
805
+ En parallèle (n'occupe pas le ETP) : **rédaction du papier JOSS** par
806
+ le ou les auteurs académiques (8 à 12 semaines, dont 4 à 6 de revue
807
+ par les pairs). Recommandation : démarrer dès la semaine 1.
808
+
809
+ ---
810
+
811
+ ## 8. Synthèse pour la direction
812
+
813
+ Picarones est un projet de recherche **techniquement solide, méthodologiquement
814
+ ambitieux, éditorialement neutre**. Il dispose déjà de la majorité des
815
+ briques d'une plateforme institutionnelle :
816
+ architecture cohérente, sécurité de fond, tests volumineux, snapshots
817
+ reproductibles, anti-hallucination prouvé.
818
+
819
+ Ce qui manque pour une adoption BnF / Bibliothèque nationale et pour
820
+ une citation académique se concentre sur **trois axes orthogonaux** au
821
+ code lui-même :
822
+
823
+ 1. **Communication scientifique** (CITATION, JOSS, traçabilité des
824
+ méthodes statistiques et des profils éditoriaux) — sans cela, le
825
+ projet n'est pas citable et donc pas crédible pour un papier ou
826
+ une thèse.
827
+ 2. **Conformité opérationnelle** (CSRF, accessibilité WCAG niveau A,
828
+ guides de déploiement, RGPD, gouvernance) — sans cela, aucune
829
+ institution publique française ou européenne ne peut le mettre
830
+ en production sur ses infrastructures.
831
+ 3. **Hygiène CI/CD** (lock file, scanners, seuil de couverture,
832
+ release PyPI, image immutable) — sans cela, la promesse de
833
+ « plateforme reproductible et auditable » n'est pas tenue de bout
834
+ en bout.
835
+
836
+ Avec 6 à 10 semaines d'investissement par un ingénieur senior + le
837
+ calendrier propre du papier JOSS, le projet peut atteindre un état
838
+ **publiable et adoptable institutionnellement**. Le code lui-même
839
+ nécessite peu de retouches profondes — l'essentiel du travail est
840
+ documentation, gouvernance, intégration continue et accessibilité.