File size: 57,759 Bytes
307d18e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2a19401
 
 
307d18e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
df7146b
307d18e
 
 
 
 
 
 
 
df7146b
 
 
 
 
 
307d18e
df7146b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307d18e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
01bf885
307d18e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2a19401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f0362de
2a19401
f0362de
2a19401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307d18e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
# Audit institutionnel BnF — état de Picarones au 2 mai 2026

> Audit réalisé sur la branche `claude/audit-institutional-readiness-8Cw4w`
> à partir du commit `06aac6a` (merge PR #50). Méthode : 6 agents
> d'exploration en parallèle (architecture, code/sécurité, tests,
> documentation, CI/CD, web/i18n/accessibilité), suivis d'une vérification
> manuelle des findings critiques (`grep`, lecture ligne à ligne).
> Lint `ruff check picarones/ tests/` : passe. Suite complète :
> **3 356 passed, 3 skipped, 0 failed** en 3 min 04 s.
>
> **Cible** : adoption institutionnelle (BnF, BL, KBR, Archives nationales)
> et publication scientifique citable (JOSS, arXiv).
>
> **Verdict global** : **non prêt** pour estampille institutionnelle ou
> citation académique sans remédiation. Le code est solide, l'architecture
> claire, la couverture de tests inégalée. Les bloqueurs sont concentrés
> sur trois axes : **(1) communication scientifique** (CITATION, JOSS,
> citations primaires des méthodes statistiques), **(2) gouvernance et
> ops institutionnelles** (CSRF, accessibilité WCAG, déploiement, RGPD),
> **(3) hygiène d'intégration continue** (lock file, scanners de sécurité,
> seuil de couverture).
>
> **Effort estimé pour atteindre le niveau BnF** : 6 à 10 semaines
> calendaires (1 ETP), hors rédaction du papier JOSS qui suit son propre
> calendrier (8 à 12 semaines de revue par les pairs).

---

## 1. Résumé par sévérité

| Sévérité | Compte | Domaines |
|---|---|---|
| **BLOCKER** | 13 | Architecture (2), violation règle propre (3), publication scientifique (3), accessibilité (2), sécurité web (1), **documentation produit (2 — SPECS+README, voir §9)** |
| **MAJOR** | 28 | CI/CD (6), documentation (5), tests (3), reproductibilité (2), web/UX (2), **README désynchronisé (10 items, voir §9)** |
| **MINOR** | 18 | Polissage (DX, packaging, i18n résiduel, cache Docker, formats locales…) + petits items README |
| **Faux positifs** | 1 | « SQL injection » dans `jobs.py:235` — détaillé en §6 |

Tous les findings sont accompagnés de la citation `fichier:ligne` exacte
et d'une esquisse de correction. Les efforts indiqués sont en
*personne-jours* (PJ) pour un ingénieur familier du repo.

---

## 2. Bloqueurs — à corriger avant tout estampillage institutionnel

### B-1 — Violation Cercle 2 → Cercle 3 dans `measurements/statistics.py`

**Fichier** : `picarones/measurements/statistics.py:861`

```python
def _extract_error_pairs(gt: str, hyp: str) -> list[tuple[str, str]]:
    from picarones.report.diff_utils import compute_word_diff   # ← Cercle 3 !
```

**Problème** : violation directe de la règle architecturale documentée
dans `CLAUDE.md` et `docs/architecture.md` (« les imports vont
uniquement de l'extérieur vers l'intérieur »). Un module de mesures
(Cercle 2) ne doit jamais dépendre du rendu (Cercle 3). Le import
est *paresseux* (à l'intérieur de la fonction), donc il ne casse pas
le démarrage, mais il rend le module `statistics` inutilisable
pour quiconque consomme Picarones sans la couche `report` (par exemple
un pipeline d'analyse en notebook ou un service externe).

**Correctif** : extraire `compute_word_diff` (et toute la famille
`diff_utils`) dans `picarones/core/diff_utils.py`. Le rendu HTML peut
continuer à le ré-exporter pour rétrocompatibilité.

**Effort** : 0,5 PJ. **Risque** : faible — le module diff_utils a déjà
ses tests dans `tests/report/test_diff_utils.py`, à déplacer.

---

### B-2 — Violation Cercle 2 → Cercle 3 dans `measurements/difficulty.py`

**Fichier** : `picarones/measurements/difficulty.py:195`

```python
def difficulty_color(score: float) -> str:
    from picarones.report.colors import COLOR_GREEN, COLOR_YELLOW, COLOR_ORANGE, COLOR_RED
```

**Problème** : identique à B-1. Pire : la fonction renvoie une couleur
CSS, donc c'est une logique purement de présentation qui s'est glissée
dans le module métier `difficulty`.

**Correctif** : déplacer `difficulty_color` dans
`picarones/report/difficulty_render.py` (à créer) et ne laisser dans
`difficulty.py` que la logique de scoring numérique. Les appelants du
côté `report/` font alors `from picarones.report.difficulty_render
import difficulty_color`.

**Effort** : 0,5 PJ.

---

### B-3 — Trois `except Exception: pass` qui violent la règle « jamais »

**Fichiers et lignes** :
- `picarones/extras/importers/huggingface.py:266` (recherche API silencieusement avalée)
- `picarones/extras/importers/huggingface.py:416` (échec de sauvegarde d'image silencieux)
- `picarones/extras/importers/htr_united.py:448` (parsing YAML silencieux → fallback démo)

**Problème** : règle écrite noir sur blanc dans `CLAUDE.md` :

> **Ne jamais mettre `except Exception: pass`** : remplacer par
> `logger.warning("[module] fonctionnalité dégradée : %s", e)`.

Conséquences concrètes pour un archiviste BnF qui importe un corpus
HTR-United : si le YAML distant est mal-formé, l'utilisateur reçoit un
catalogue de démo *sans aucun avertissement* — il croit consulter le
catalogue institutionnel. Pour une mainteneur, c'est un bug invisible
qui peut survivre des années.

**Correctif** : remplacer chaque `pass` par
`logger.warning("[importers] <opération> a échoué (mode dégradé) : %s", e)`
+ ajouter un `Fact` dans la synthèse du rapport quand le fallback est
déclenché côté utilisateur.

**Effort** : 0,5 PJ. **Test** : 1 cas par site (mock l'échec → vérifier
le log).

---

### B-4 — Aucune `CITATION.cff` ni preprint scientifique

**Fichiers manquants** : `CITATION.cff`, `paper.md` (JOSS), pas de DOI
dans le `README`, pas d'`ORCID` listés, pas de `.zenodo.json`.

**Problème** : pour qu'un article scientifique cite Picarones, il faut
au minimum un fichier `CITATION.cff` parsable par GitHub (qui produit
alors le bouton « Cite this repository »), idéalement un DOI Zenodo, et
en pratique un papier JOSS pour un projet de cette envergure (méthodes
statistiques nouvelles, registre de métriques typées, moteur narratif
factuel anti-hallucination — chacune de ces contributions est citable).

Une bibliothèque nationale n'adoptera pas un outil scientifique non
citable. Une thèse ou un article ne peut pas s'appuyer sur Picarones
si la référence se résume à une URL GitHub mutable.

**Correctif** :
1. Créer `CITATION.cff` (5 min), avec auteurs ORCID et version.
2. Pousser une release GitHub taggée + obtenir un DOI Zenodo (intégration
   automatique : 1 h).
3. Rédiger un `paper.md` (format JOSS, 6 à 8 pages) résumant : philosophie
   « banc d'essai », architecture en 3 cercles, contributions
   méthodologiques (Friedman + Nemenyi, registre typé, moteur narratif).

**Effort** : 1 PJ pour CITATION+Zenodo. Le papier JOSS : ~10 PJ d'écriture
+ 8 à 12 semaines de revue par les pairs.

---

### B-5 — Méthodes statistiques sans citation primaire dans le code

**Fichier** : `picarones/measurements/statistics.py` (1 127 lignes)

**Problème** : le module implémente Friedman, post-hoc Nemenyi, Wilcoxon,
bootstrap, intervalles de confiance, dominance Pareto. Or aucune
référence BibTeX/DOI n'apparaît dans les docstrings. Demšar 2006 (le
papier qui définit Friedman+Nemenyi pour la comparaison de classifieurs,
soit *exactement* ce que fait Picarones) n'est cité nulle part dans le
code. Idem Wilcoxon 1945, Efron 1979 (bootstrap).

Conséquence : un relecteur académique ou un responsable BnF en
évaluation ne peut pas vérifier que l'implémentation correspond aux
définitions canoniques. Le `glossary` (Sprint 21) mentionne les noms
des tests mais ne pointe pas vers les sources primaires.

**Correctif** : ajouter un en-tête de module dans `statistics.py` listant
les références (BibTeX), et un `:references:` dans la docstring de
chaque fonction publique. Ajouter le champ `reference` aux 25 entrées
du glossaire (déjà prévu dans le schéma — voir
`picarones/report/glossary/`, vérifier la complétude).

**Effort** : 1 PJ.

---

### B-6 — Profils de normalisation non tracés à des standards éditoriaux

**Fichiers** :
- `picarones/measurements/normalization.py` (420 lignes)
- `picarones/measurements/mufi.py` (cite « MUFI » sans préciser la version)
- `docs/profiles.md` (présent mais ne pointe ni vers TEI P5 ni vers
  MUFI registry ni vers DEAF)

**Problème** : Picarones revendique des profils `DIPLOMATIC_FR`,
`MUFI`, `EARLY_MODERN`, etc. Pour un médiéviste ou un éditeur critique,
la première question est : « quelle version de MUFI ? quelle
recommandation TEI ? quelle politique pour ſ→s ? ». Sans cette
traçabilité, on ne peut pas comparer un benchmark Picarones à une
édition TEI conforme aux standards de la communauté.

**Correctif** : créer `docs/normalization-specs.md` qui mappe chaque
profil :
- nom du profil
- version exacte de la spec source (MUFI v4.0, TEI P5 Unicode chapter
  3.4, DEAF ortho-2024, …)
- liste exhaustive des transformations appliquées
- DOI/URL stable de la spec
- date de révision

Ajouter un test de non-régression :
`tests/measurements/test_normalization_spec_consistency.py`.

**Effort** : 2 PJ (la connaissance experte est plus rare que le code).

---

### B-7 — Aucun scanner de sécurité dans la CI

**Fichier** : `.github/workflows/ci.yml`

**Manquants** : `bandit` (code Python), `pip-audit` ou `safety` (CVE
des dépendances), `trivy` (scan de l'image Docker), `gitleaks`
(détection de secrets dans l'historique). Pre-commit a `detect-private-key`
seulement.

**Problème** : projet exposant un endpoint public sur HuggingFace Space
avec dépendances cloud (mistralai, anthropic, openai, google-cloud-vision,
azure-ai-formrecognizer). Une CVE non détectée dans une de ces SDK est
une porte d'entrée. Pour BnF, c'est rédhibitoire — les revues
sécurité institutionnelles l'exigent.

**Correctif** : ajouter à `ci.yml` un job `security` parallèle aux tests :

```yaml
security:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with: { python-version: "3.11" }
    - run: pip install bandit pip-audit
    - run: bandit -r picarones/ -ll  # niveau LOW+
    - run: pip-audit --strict
    - uses: aquasecurity/trivy-action@master
      with: { image-ref: 'picarones:latest', exit-code: '1', severity: 'HIGH,CRITICAL' }
```

**Effort** : 1 PJ (intégration + traitement des findings initiaux).

---

### B-8 — Aucun seuil de couverture appliqué (`--cov-fail-under` manquant)

**Fichier** : `.github/workflows/ci.yml:78`

```yaml
pytest tests/ -q --cov=picarones --cov-report=xml --cov-report=term-missing
```

**Problème** : la couverture est calculée et uploadée à Codecov, mais
aucun plancher n'est imposé. Une PR peut faire baisser la couverture de
85 % à 40 % sans qu'aucun signal CI ne se déclenche. Pour un projet
revendiquant 3 359 tests, c'est paradoxal : la rigueur affichée n'est
pas applicable.

**Correctif** : ajouter `--cov-fail-under=85` (mesurer le baseline d'abord
avec `pytest --cov` → fixer le plancher 2 points en dessous). Optionnel
mais recommandé : exporter le delta dans un commentaire de PR via
`coverage-comment-action`.

**Effort** : 0,25 PJ.

---

### B-9 — Graphiques Canvas inaccessibles aux lecteurs d'écran (WCAG 1.1.1 niveau A)

**Fichiers** :
- `picarones/report/templates/_app.js:1062`, `1102`, et autres
  instanciations Chart.js
- `picarones/report/vendor/chart.umd.min.js`

**Problème** : Chart.js produit du `<canvas>`. Sans intervention,
*aucun* contenu n'est exposé à l'AT (assistive technology). Un usager
non-voyant utilisant NVDA/JAWS n'entend qu'« graphique vide ». Cela
viole WCAG 2.1 succès 1.1.1 (Non-text Content) au niveau A — le plus
bas, donc rédhibitoire pour toute déclaration de conformité RGAA en
France.

**Correctif** : pour chaque graphique, ajouter en parallèle :
1. un `<table>` de données équivalentes, marqué
   `aria-describedby` du canvas, masqué visuellement (`visually-hidden`)
   mais lu par les AT ;
2. un `aria-label` descriptif sur le `<canvas>` ;
3. un bouton « Voir les données » qui révèle la table à tous (utile
   aussi pour la copie).

Alternative plus profonde : remplacer Chart.js par des SVG natifs avec
`<title>` et `<desc>` (déjà la pratique dans `pipeline_dag_render.py`,
`taxonomy_cooccurrence_render.py`, etc. — Sprints 64, 75 et al.).
Cohérent avec le reste de la base.

**Effort** : 2 PJ (8 à 12 graphiques Chart.js à doubler).

---

### B-10 — Pas de lien « Aller au contenu » (WCAG 2.4.1)

**Fichier** : `picarones/report/templates/base.html.j2` et `_header.html`

**Problème** : aucune occurrence de `skip`, `main-content` ou équivalent
dans les templates. Un usager-clavier doit traverser toute la
navigation et le panneau latéral avant d'atteindre le rapport. Violation
WCAG 2.1 succès 2.4.1 (Bypass Blocks) au niveau A.

**Correctif** : ajouter dans `_header.html`, premier enfant du `<body>` :

```html
<a href="#main" class="skip-link">{{ i18n.skip_to_content }}</a>
```

+ une classe CSS `.skip-link` qui reste cachée hors `:focus` ; et
ajouter `id="main"` sur le conteneur principal.

**Effort** : 0,25 PJ.

---

### B-11 — Aucune protection CSRF sur les endpoints POST

**Fichier** : `picarones/web/app.py` + 11 routers dans
`picarones/web/routers/`

**Problème** : tous les endpoints POST (`/api/corpus/upload`,
`/api/benchmark/start`, `/api/benchmark/run`, `/api/benchmark/{id}/cancel`,
`/api/config/save`, `/api/htr-united/import`, `/api/huggingface/import`,
`/api/lang/{code}`) acceptent les requêtes sans vérification d'origine
ni token CSRF.

Sur HuggingFace Space en mode public, l'impact est limité (pas de
session utilisateur authentifiée à voler). Mais en déploiement
institutionnel BnF (sur intranet, derrière SSO), un usager logué peut
être victime d'une page tierce qui poste vers `/api/config/save` ou
lance un benchmark coûteux à son insu.

**Correctif** : ajouter le middleware `starlette-csrf` ou équivalent,
piloté par variable d'environnement `PICARONES_CSRF_REQUIRED=1`. En
mode public HuggingFace : laissé désactivé (pas de session). En mode
institutionnel : activé d'office.

**Effort** : 1 PJ + tests.

---

## 3. Problèmes majeurs — à régler dans les 6 prochaines semaines

### M-1 — Pas de fichier de verrouillage des dépendances

**Symptôme** : `pyproject.toml` déclare 11 dépendances cœur et 7 extras
en `>=` sans borne haute. `requirements.txt` à la racine est
divergent et obsolète. Aucun `requirements.lock`, `uv.lock`,
`poetry.lock`.

**Conséquence** : un build Docker du 2 mai 2026 et un build du 2 mai 2027
ne produiront pas le même artefact. Pour un dépôt patrimonial qui doit
pouvoir rejouer un benchmark à 5 ans d'intervalle, c'est inacceptable.

**Correctif** : adopter `uv` ou `pip-tools`, générer `requirements.lock`
et l'épingler dans le `Dockerfile` (`pip install -r requirements.lock`
au lieu de `pip install .`). Régénérer mensuellement via un workflow
dédié + PR automatique.

**Effort** : 1 PJ.

---

### M-2 — Image Docker de base non épinglée — ✅ Résolu (Sprint A8 + Sprint A16)

**Fichier** : `Dockerfile:18, 43`

```dockerfile
FROM python:3.11-slim AS builder
FROM python:3.11-slim AS runtime
```

**Correctif appliqué** :
- Sprint A8 : épinglage au patch `python:3.11.13-slim` (au lieu de
  `python:3.11-slim` qui suit le stream).
- Sprint A16 : ajout du digest sha256 (`@sha256:9bffe43…eec4`) sur les
  deux `ARG PYTHON_BASE_IMAGE` (builder + runtime). Build reproductible
  bit-à-bit. Test anti-régression : `tests/release/test_docker_reproducibility.py`.

**Procédure de rotation** documentée dans le commentaire en tête du
`Dockerfile` (curl + auth.docker.io + registry-1.docker.io API).

---

### M-29 — Paquets `apt-get` non figés par version (reproductibilité partielle) — ⏳ Différé

**Fichier** : `Dockerfile` (étapes builder + runtime).

```dockerfile
RUN apt-get install -y --no-install-recommends \
    tesseract-ocr tesseract-ocr-fra ... libpng16-16 ...
```

**Pourquoi c'est une dette résiduelle** : après Sprint A16, l'image de
base est figée par sha256 et l'arbre Python est figé par
`requirements-docker.lock`. **Mais** chaque `apt-get install` résout
contre les repos Debian au moment du build : deux builds à 6 mois
d'écart peuvent installer des versions différentes de Tesseract,
libpng, libtiff, etc. La reproductibilité institutionnelle BnF
(« même `git checkout`, même image binaire, même empreinte ») n'est
donc pas atteinte aux 100 %.

**Options** (toutes complexes, à arbitrer en sprint dédié) :

1. **`snapshot.debian.org`** : rediriger les sources `apt` vers un
   snapshot Debian daté. Reproductible, mais casse la fenêtre des
   patches de sécurité Debian (fige les CVE non patchées au
   snapshot).

2. **Pinning explicite** : `apt-get install pkg=version` pour chaque
   paquet. Maintenance lourde (≈ 13 paquets) ; fragile (toute MAJ
   Debian invalide le pin).

3. **Migration vers une image distroless** type `chainguard/python` :
   image durcie avec paquets pré-pinnés et CVE patchées en continu
   par Chainguard. Change l'OS, blast radius large, à valider.

**Décision** : différé à un sprint dédié post-v1.2, après stabilisation
des extras `[ner]` et `[ocr-cloud]`. Documenté dans `Dockerfile`
ligne 25 (rotation trimestrielle manuelle reste l'approche actuelle).

**Effort estimé** : 2 PJ option 1 ; 3 PJ option 2 ; 5 PJ option 3.

---

### M-3 — Endpoint `/health` absent alors que le `HEALTHCHECK` Docker l'appelle

**Fichiers** : `Dockerfile:96` (`curl -f http://localhost:7860/health`)
vs `picarones/web/routers/system.py:13` (qui expose `/api/status`).

**Correctif** : aliaser `/health``/api/status` ou créer un endpoint
dédié, plus minimaliste (juste 200 OK + version, sans introspecter
l'état OCR).

**Effort** : 0,25 PJ.

---

### M-4 — Pas de type-checking dans la CI

**État** : `Makefile:100-102` propose un target `typecheck` qui appelle
mypy avec `--ignore-missing-imports --no-strict-optional`, mais n'est
pas appelé par `ci.yml`. Aucune section `[tool.mypy]` dans
`pyproject.toml`. Pas de marqueur `py.typed`.

**Correctif** : configurer mypy dans `pyproject.toml` avec
`strict = true` sur `picarones/core/` (le plus stable), `strict = false`
ailleurs comme état initial. Ajouter un job CI `typecheck` qui devient
bloquant pour `picarones/core/` et avertissant ailleurs. Marquer
`py.typed`.

**Effort** : 2 PJ (premier passage), puis maintenance continue.

---

### M-5 — Pas de pipeline de release vers PyPI

**Symptôme** : `pyproject.toml` épingle `version = "1.0.0"` en dur. Pas
de `setuptools_scm`. Pas de workflow `.github/workflows/release.yml`.
Picarones n'est pas installable via `pip install picarones`.

**Conséquence** : impossible de citer une version exacte (`picarones==1.2.3`)
dans un `requirements.txt` de notebook ou de papier. Toute installation
passe par `pip install git+https://…` (mutable, fragile).

**Correctif** : adopter `setuptools_scm` (version dérivée des tags Git)
+ workflow `release.yml` déclenché sur tag `v*` qui : build sdist+wheel
→ test sur `testpypi` → publie sur PyPI via `pypa/gh-action-pypi-publish`
avec OIDC trust (pas de token long-lived).

**Effort** : 1 PJ.

---

### M-6 — Pas d'image conteneur publiée immutable

**Symptôme** : `Makefile:167` tagge `picarones:latest` et `picarones:1.0.0`
localement mais ne pousse nulle part. HuggingFace Space rebuild à chaque
merge (donc pas un *artefact*, c'est une recompilation). Pas de
publication sur ghcr.io, Docker Hub, ou Quay.

**Correctif** : ajouter un workflow qui pousse vers
`ghcr.io/maribakulj/picarones:1.0.0` et `…:latest` à chaque release.
Avec digest fixe communiqué dans le `CHANGELOG`.

**Effort** : 0,5 PJ.

---

### M-7 — Pas de guide de déploiement institutionnel

**Manquant** : `docs/operations/deployment.md`, `docs/operations/backup.md`,
`docs/operations/data-retention.md`.

**Conséquence** : un DSI BnF qui veut héberger Picarones doit deviner :
- Quelle BD pour `jobs.sqlite` en multi-instance ?
- Comment migrer le schéma de l'historique longitudinal entre versions ?
- Combien de temps les uploads sont-ils conservés ? Politique RGPD ?
- Comment intégrer derrière un proxy SSO (Shibboleth, CAS, OIDC) ?
- Quelle observabilité (logs JSON pour ELK, métriques Prometheus) ?
- Comment sauvegarder/restaurer l'historique ?

INSTALL.md couvre uniquement Docker mono-instance HuggingFace. C'est
insuffisant.

**Correctif** : rédiger les 3 guides ci-dessus. Ajouter une section
RGPD au `SECURITY.md` (rétention des uploads, logs, IP du
rate-limiter).

**Effort** : 3 PJ.

---

### M-8 — Aucune politique de rétention des données ni mention RGPD

**Manquant** : politique explicite pour les uploads ZIP/images,
les logs (qui contiennent IP via le rate-limiter), l'historique
longitudinal SQLite.

**Conséquence** : sur Space public, un visiteur qui upload une image
patrimoniale ne sait pas combien de temps elle est gardée. Sur
déploiement institutionnel BnF, l'absence de politique bloque la
mise en production.

**Correctif** : doc `docs/operations/data-retention.md` + mécanisme
de purge automatique (cron job `purge_uploads_older_than(days=7)`)
+ mention RGPD dans le `README` et la home web.

**Effort** : 1,5 PJ.

---

### M-9 — Pas de déclaration d'accessibilité

**Manquant** : `ACCESSIBILITY.md` (recommandation gouvernementale FR
pour tout service public, RGAA 4.1 art. 47 de la loi 2005-102).

**Correctif** : déclaration explicite après audit RGAA + remédiation
des bloqueurs B-9 et B-10. Pour atteindre WCAG 2.1 niveau AA
(prérequis BnF), prévoir un audit externe après remédiation.

**Effort** : 1 PJ pour la déclaration + remédiation déjà comptée en B-9/B-10
+ audit externe (hors équipe).

---

### M-10 — Pas de divulgation de conflits d'intérêt

**Manquant** : déclaration sur la position de l'outil vis-à-vis des
fournisseurs cloud benchmarkés (OpenAI, Anthropic, Mistral, Google,
Azure). Pricing dans `picarones/data/pricing.yaml` (`last_updated:
2026-04-01`) sans validation indépendante ni veille automatique.

**Conséquence** : un papier qui s'appuie sur l'analyse de Pareto coût
de Picarones doit pouvoir citer une politique d'absence de COI. Sinon
un relecteur peut soupçonner un biais éditorial.

**Correctif** : ajouter une section « Conflicts of interest » dans le
`README` + `paper.md` JOSS, et un en-tête « Pricing as observed on
YYYY-MM-DD ; recompute with your own contracted rates » sur la vue
Pareto.

**Effort** : 0,5 PJ.

---

### M-11 — `CODEOWNERS` et politique de gouvernance absents

**Manquant** : `.github/CODEOWNERS`, `GOVERNANCE.md`, politique de
revue, cadence de release, SLO réponse aux issues.

**Conséquence** : une institution qui évalue la pérennité ne sait pas
s'il y a un mainteneur unique ou plusieurs, ni à quelle cadence elle
peut espérer un correctif.

**Correctif** : créer les deux fichiers. Cadence de release suggérée :
mensuelle pour les versions mineures, trimestrielle pour les majeures.
SLO suggéré (et tenable pour un projet de cette taille) : 5 jours
ouvrés pour un triage initial des issues.

**Effort** : 0,5 PJ + engagement de gouvernance.

---

### M-12 — Reproductibilité des snapshots sous-documentée

**État** : `picarones/report/snapshot.py` (266 lignes) et
`tests/report/test_sprint27_reproducibility_snapshots.py` existent.
Mais ni le `README` ni `docs/user/reading-a-report.md` n'expliquent :
- ce que contient un snapshot (versions OCR, modèles LLM, hash du code,
  hash du corpus, seeds…)
- comment recharger un snapshot pour rejouer un benchmark
- comment documenter un snapshot dans une publication

**Correctif** : créer `docs/reproducibility-snapshots.md`. Inclure
exemples reproductibles. Lier depuis `README` et `paper.md`.

**Effort** : 1 PJ.

---

### M-13 — Tests de concurrence runner / web sous-représentés

**Findings agents tests** :
- `picarones/measurements/runner.py` (1 019 lignes) n'a pas de test
  ciblant : épuisement du pool de processus, échecs partiels,
  processus zombies, `PICARONES_MAX_CONCURRENT_JOBS=32` sous charge.
- `picarones/web/jobs.py` : pas de test pour SSE `Last-Event-ID`
  reconnexion, écritures concurrentes SQLite (`SQLITE_BUSY`), bascule
  `PICARONES_PUBLIC_MODE=1` à chaud, isolation des jobs entre IPs.

**Correctif** : ajouter `tests/integration/test_runner_concurrency.py`
(50+ cas) et `tests/web/test_sse_reconnect.py`.

**Effort** : 3 PJ.

---

### M-14 — Pas de garde-fou anti-régression pour le benchmark lui-même

**Findings agent tests** : un *benchmarking platform* qui ne mesure pas
sa propre dérive de performance est suspect. Le job `regression_check`
dans `ci.yml:207-226` est commenté : « optionnel — activer si vous avez
un corpus de référence ».

**Correctif** : créer un mini-corpus de référence (10 documents libres
de droits couvrant les 3 strates principales : médiéval, imprimé
ancien, moderne) dans `tests/fixtures/reference_corpus/`. Ajouter un
job CI `--fail-if-cer-above 0.15` (fraction = 15 %) sur Tesseract+Pero. Exécuter
hebdomadairement (cron), pas à chaque PR (coût).

**Effort** : 2 PJ + sélection corpus.

---

### M-15 — Pas de timeout global pytest

**Fichier** : `.github/workflows/ci.yml:74-78`. Aucun `--timeout`. Un
test bloqué (Tesseract qui freeze, API LLM qui pend) bloque le runner
GH Actions jusqu'au timeout du job (6 h par défaut).

**Correctif** : ajouter `pytest-timeout` aux deps `[dev]`, configurer
`pyproject.toml` :
```toml
[tool.pytest.ini_options]
timeout = 300
timeout_method = "thread"
```

**Effort** : 0,1 PJ.

---

### M-16 — Pas de chargement paresseux pour les rapports volumineux

**Symptôme** : `picarones/report/generator.py` produit un fichier HTML
unique, images en base64. Un corpus de 1 000 documents × 5 moteurs
peut générer un fichier > 200 MB. Le navigateur peine.

**Correctif** : pour la galerie de documents, externaliser les images
dans `report-assets/<doc_id>.png` à côté du HTML, et lazy-loader
(`loading="lazy"`). Optionnel : pagination côté client.

**Effort** : 1 PJ. Garder l'option « monolithique » pour les petits
corpus (par défaut < 50 docs).

---

### M-17 — Documentation déséquilibrée FR/EN

**Constat** : README bilingue ✓. UI/glossaire bilingues ✓. Mais SPECS.md,
CHANGELOG.md, `docs/user/reading-a-report.md`, `docs/case-studies/`,
les guides développeur (4 fichiers) sont en français pur. Un chercheur
britannique ou allemand qui veut contribuer ne peut pas lire les guides
développeur. Un mainteneur qui veut publier le projet sur arXiv doit
réécrire toute la documentation utilisateur en anglais.

**Correctif** : traduire prioritairement (1) `docs/user/reading-a-report.md`,
(2) `docs/developer/index.md` + les 3 sous-guides, (3) `CONTRIBUTING.md`.
Laisser CHANGELOG et SPECS en français pour l'instant — moins critique.

**Effort** : 2 PJ pour les 5 documents prioritaires.

---

### M-18 — Pas de `.dockerignore` ni de `.env.example`

**Symptômes** :
- Pas de `.dockerignore` à la racine → `git`, `docs/`, `tests/` copiés
  inutilement dans l'image (taille +20 %, cache hit dégradé).
- `docker-compose.yml` référence `${OPENAI_API_KEY}`, `${PICARONES_PORT}`
  sans `.env.example` → les utilisateurs doivent deviner.

**Correctif** : 2 fichiers, 30 lignes chacun.

**Effort** : 0,1 PJ.

---

## 4. Problèmes mineurs — à intégrer en backlog

| # | Item | Fichier:ligne | Effort |
|---|------|---------------|--------|
| m-1 | Hardcoded FR `'Données d'ancrage non disponibles.'` bypass i18n | `_app.js:1087` | 0,1 PJ |
| m-2 | Hardcoded FR `'Données Gini non disponibles.'` (fallback) | `_app.js:1049` | 0,1 PJ |
| m-3 | Boutons « Réinitialiser » sans clé i18n | `_header.html:25` | 0,1 PJ |
| m-4 | Tableaux HTML sans `scope="col"` sur `<th>` | templates `view_*.html` | 0,3 PJ |
| m-5 | Palette heatmap non daltonien-friendly | `_styles.css` + `colors.py` | 0,5 PJ |
| m-6 | Nombres dans tableaux non localisés (1234567 vs 1 234 567) | `_app.js` (toLocaleString) | 0,3 PJ |
| m-7 | Pre-commit non rejoué en CI (bypassable via `--no-verify`) | `.github/workflows/ci.yml` | 0,1 PJ |
| m-8 | CI ne teste pas Python 3.13 (alors que `requires-python = ">=3.11"`) | `ci.yml:34` | 0,1 PJ |
| m-9 | API stability tests ne valident pas les `default values` des signatures | `tests/core/test_public_api.py` | 0,3 PJ |
| m-10 | Tests cloud OCR sans cas d'erreur HTTP (429, 401, 503) | `tests/engines/test_engines_cloud.py` | 0,5 PJ |
| m-11 | Versionnement des testdata absent (`tests/.testdata_versions.yaml`) | `tests/` | 0,2 PJ |
| m-12 | Numérotation sprint des fichiers de tests : trous (1, 37, 41, 43…) | `tests/` | 0,1 PJ (audit + nettoyage) |
| m-13 | `requirements.txt` racine partiellement divergent de `pyproject.toml` | `requirements.txt` | 0,1 PJ |
| m-14 | Pas de staleness check automatique sur `pricing.yaml` | générateur | 0,3 PJ |
| m-15 | `picarones.spec` (PyInstaller) avec `hiddenimports` manuels | `picarones.spec:45-98` | 0,5 PJ |
| m-16 | Aucun module `extras/historical/` ni `extras/importers/` séparé en package | `pyproject.toml:84-97` | 1 PJ (refactor planifié déjà documenté) |
| m-17 | `tests/measurements/test_sprint11_i18n_english.py` importe `report.generator` | `tests/measurements/` | 0,2 PJ (déplacer en `tests/integration/`) |

---

## 5. Points forts — à préserver et à valoriser dans la communication

Pour qu'un audit institutionnel soit *crédible*, il doit aussi nommer
explicitement ce qui marche. Les points suivants sont **au-dessus** de
ce qu'on observe dans 90 % des projets de recherche similaires :

1. **Architecture en 3 cercles tenue à 99 %.** Cercle 1 (`picarones/core/`)
   n'a aucune dépendance vers Cercles 2 ou 3. L'API publique
   (`picarones/__init__.py`) ré-exporte uniquement Cercle 1 — surface
   stable, contrat clair (`docs/api-stable.md`). Les 2 violations
   identifiées (B-1, B-2) sont **circonscrites et faciles à corriger**.

2. **Discipline de code rigoureuse.** Lint `ruff` 0 erreur. Logger
   nommé par module systématiquement. 0 `print()` en code métier.
   3 `TODO/FIXME` dans tout le repo (signe rare). 87 `except Exception`
   au total mais **84 sont annotés `# noqa: BLE001` avec contexte
   explicite**, seuls les 3 du B-3 sont des vraies violations.

3. **Sécurité de fond solide.**
   - XML défendu par `defusedxml` partout (XXE / Billion Laughs).
   - Zip-slip prévenu par `Path(member.filename).name` dans
     `web/corpus_utils.py:182-183`.
   - Toutes les requêtes SQLite paramétrées (le `f-string` de
     `jobs.py:235` est un faux positif — voir §6).
   - Aucun `pickle.load()` (vecteur de RCE classique).
   - `subprocess` utilisé une seule fois (`snapshot.py:186`,
     `git rev-parse HEAD` — args hardcodés, `timeout=2`,
     `stderr=DEVNULL`, gestion d'exception explicite).
   - Mode public (`PICARONES_PUBLIC_MODE`) avec gating des moteurs
     cloud, rate-limiting par IP, `Image.verify()` anti-bombe de
     décompression, en-têtes CSP / X-Content-Type-Options /
     X-Frame-Options.

4. **Couverture de tests volumineuse et structurée.** 3 359 tests
   collectés, organisés par cercle (`tests/core`, `tests/measurements`,
   `tests/engines`, `tests/web`, `tests/integration`, etc.). Tests
   d'API publique (`tests/core/test_public_api.py`) garantissant la
   stabilité du contrat externe. Pas de test fantôme `assert True`.

5. **Neutralité éditoriale exemplaire.** La règle « Picarones mesure et
   classe — il ne tranche pas le débat éditorial » est tenue jusque
   dans le moteur narratif (chaque nombre rendu est traçable au
   `payload` du `Fact` correspondant — anti-hallucination *prouvé*
   par tests). Les 5 « leviers d'amélioration » (Sprint 51) sont
   explicitement factuels, pas prescriptifs. Les profils diplomatique
   vs modernisant sont rapportés sans verdict.

6. **Reproductibilité partielle déjà en place.** Snapshot bit-à-bit
   identique sur même entrée (Sprint 27, vérifié par tests). Run
   save/load (Sprint 25). Comparaison de runs (Sprint 26). Manque
   uniquement la doc utilisateur (M-12) pour valoriser.

7. **Documentation interne (CLAUDE.md, CHANGELOG.md, SPECS.md)
   exceptionnellement détaillée.** Le journal des sprints permet à
   un nouveau contributeur ou à un auditeur de comprendre l'évolution
   de chaque décision.

8. **Politique de modules contribués (Sprint 97) déjà formalisée.**
   `core/module_policy.py` + `docs/developer/module-policy.md`. Picarones
   a anticipé le passage à un écosystème de plugins externes — rare
   pour un projet de cette taille.

---

## 6. Faux positifs identifiés et écartés

### F-1 — « SQL injection » dans `picarones/web/jobs.py:235`

L'agent code-quality a flagué cette ligne :

```python
c.execute(
    f"UPDATE jobs SET {', '.join(fields)} WHERE job_id = ?",
    values,
)
```

**Vérification manuelle** (lecture des lignes 210-238) : la liste
`fields` est construite *exclusivement* à partir de littéraux Python
hardcodés (`"progress = ?"`, `"current_engine = ?"`, etc.) selon des
branches `if X is not None`. À aucun moment un input utilisateur n'y
arrive. Tous les `values` correspondants sont bien paramétrés via `?`.

**Verdict** : pas une vulnérabilité d'injection SQL. Au pire, un *style
fragile* qui pourrait inviter à l'erreur lors d'un futur refactor. À
laisser tel quel ou à refactorer en `m-18` (mineur de polissage).

---

### F-2 — « Pas de `--cov-fail-under` » classé blocker par certains agents

L'agent docs et l'agent CI ont tous deux insisté. C'est bloquant **pour
l'institution** (B-8) mais pas pour la communauté open-source. Je l'ai
gardé en BLOCKER vu la cible BnF.

---

### F-3 — Allégations de couverture de test divergentes (1 072 vs 3 354)

`CLAUDE.md` cite « 1 072 passed » dans la section *État actuel
(Sprint 16)* puis « ~3 354 passed » plus loin (*Contexte développement*).
Le second chiffre est correct (3 359 tests collectés au 2 mai 2026).
La première mention est obsolète depuis le Sprint 16 — à mettre à jour.
Effort : 0,01 PJ (un edit).

---

## 7. Feuille de route synthétique (10 semaines, 1 ETP)

| Semaine | Sprint d'audit | Livrables |
|---------|----------------|-----------|
| 1 | **S-A1 Architecture** | B-1, B-2, B-3 (violations + importers). Tests verts. |
| 1-2 | **S-A2 Sécurité CI** | B-7 (scanners), B-8 (cov threshold), M-15 (timeout pytest). |
| 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). |
| 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). |
| 4-5 | **S-A5 Publication scientifique** | B-4 (CITATION + Zenodo), B-5 (refs primaires statistics), B-6 (normalization specs). |
| 5-6 | **S-A6 Distribution** | M-5 (PyPI release), M-6 (image ghcr.io), M-11 (CODEOWNERS + governance). |
| 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). |
| 7-8 | **S-A8 Robustesse runner+web** | M-13 (tests concurrence), M-14 (anti-régression CER), M-16 (lazy loading reports). |
| 8-9 | **S-A9 Type-checking** | M-4 (mypy strict sur core, gradient ailleurs). |
| 9-10 | **S-A10 Polissage final + audit externe** | Backlog mineur restant + audit externe RGAA + audit externe sécurité. |

En parallèle (n'occupe pas le ETP) : **rédaction du papier JOSS** par
le ou les auteurs académiques (8 à 12 semaines, dont 4 à 6 de revue
par les pairs). Recommandation : démarrer dès la semaine 1.

---

## 9. État de SPECS.md et README.md

> Section ajoutée après relecture ciblée des deux documents.
> **À elle seule, cette section identifie un BLOCKER supplémentaire
> (B-12) et plusieurs MAJORS / MINORS spécifiques à la documentation
> de premier contact.**

### 9.1 SPECS.md — promesses non tenues, sans deprecation

`SPECS.md` est daté « Version 1.0 — Mars 2025 » et n'a reçu qu'un
*addendum Sprints 16-30* (lignes 654-757). Les promesses initiales
suivantes **ne sont pas implémentées** et ne sont mentionnées nulle
part comme abandonnées ni reportées :

| Promesse SPECS | Section | Statut réel | Cohérence |
|---|---|---|---|
| Adapter Kraken (priorité v1.0) | §4.2 | Aucun fichier `picarones/engines/kraken.py`. L'extra `[kraken]` existe dans `pyproject.toml` mais ne pointe vers aucun adapter. | Promesse rompue, alors mentionnée v1.0 |
| Adapter AWS Textract (priorité v1.1) | §4.2 | Aucun fichier `picarones/engines/aws_textract.py`. `boto3` listé dans l'extra `[ocr-cloud]` et variables `AWS_*` documentées dans le README → **fausse piste pour l'utilisateur**. | Promesse rompue + README induit en erreur |
| Adapter Calamari (priorité v1.1) | §4.2 | Pas d'adapter, pas d'extra. | Promesse rompue |
| Adapter OCRopus4 (priorité v1.2) | §4.2 | Pas d'adapter, pas d'extra. | Promesse rompue |
| Moteur custom déclaré en YAML (`type: cli` / `type: api`) | §4.3 | Aucun loader `engine.yaml` dans `picarones/engines/`. Le YAML *pipeline* (Sprint 70) existe mais ne couvre pas la déclaration *d'engine* — c'est un autre périmètre. | Promesse rompue |
| Export PDF du rapport | §7.3 | Le rapport HTML n'a qu'un export CSV (cf. `_app.js:exportCSV`). Pas de génération PDF. | Promesse rompue |
| Export ALTO XML / PAGE XML / images annotées | §7.3 | Idem — non implémenté côté rapport HTML. | Promesse rompue (×3) |
| Commande `picarones estimate` (preview coût avant lancement) | §8.2 | N'existe pas. Le coût est calculé *post hoc* via la vue Pareto (Sprint 20). | Promesse rompue |
| Recommandation automatique « quel concurrent pour quel usage » | §7.1 | **Pivot philosophique opposé** : `CLAUDE.md` érige en règle « Picarones mesure et classe — il ne tranche pas ». Le moteur narratif (Sprint 19) interdit explicitement toute prescription. | **Contradiction directe** entre SPECS et règle propre du projet |
| Score de consensus / vote majoritaire / ensemble | §6.4 | Sprint 35 calcule `oracle_token_recall` et `complementarity_gap` — ce sont des bornes supérieures, pas un mécanisme de vote actif. Pas d'`EnsembleEngine`. | Promesse partielle, livrée comme observation factuelle |
| Clustering automatique des erreurs (k-means) | §6.4 | Pas de k-means dans le code. Sprint 75 (taxonomy_cooccurrence) couvre une partie via Jaccard. | Promesse partielle |
| Annotations inline du paléographe exportées en JSON | §7.2 | Pas trouvé. | Promesse rompue |
| Bibliothèque de prompts intégrée pour latin et documents mixtes | §5.4 | Le repo a 8 prompts FR + EN (médiéval, imprimé ancien) mais **pas de prompt latin** ni « documents mixtes ». | Promesse partielle |
| Badge SVG de qualité OCR pour CI | §8.3 | Pas trouvé. | Promesse rompue |
| Dataset de référence fourni avec Picarones (100 documents) | §3.3 | `picarones/fixtures.py` génère du synthétique ; pas de corpus réel embarqué. README admet : *« Picarones does not yet ship a curated library of standard datasets »*. | Promesse rompue (admise dans README) |

**À l'inverse**, ce qui a été *ajouté* depuis SPECS et n'y figure pas
(et donc n'est pas vendu à un primo-lecteur de SPECS) :
NER (Sprint 38-41), reading order F1 (Sprint 53), layout F1 (Sprint 54),
9 modules philologiques transversaux (Sprints 55-60), recherchabilité
fuzzy (Sprint 84), séquences numériques par catégorie (Sprint 85),
moteur narratif factuel anti-hallucination (Sprint 19), Friedman+Nemenyi+CDD
(Sprint 18), Pareto coût/vitesse/CO₂ (Sprint 20), glossaire contextuel
(Sprint 21), métriques inter-moteurs (Sprint 35-37, 89), absorption
d'erreur par jonction (Sprint 94), pipelines composables avec DAG
branchant (Sprints 63-66), CLI YAML pipeline (Sprint 70), interface
`BaseModule` générique (Sprint 33), registre typé de métriques
(Sprint 34), GT multi-niveaux (Sprint 32), audit de modules (Sprint 97),
comparaison de runs (Sprint 28), stratification (Sprint 45-46),
calibration ECE/MCE (Sprint 39-43), longitudinal régression+change-point
(Sprint 92), throughput effectif (Sprint 91), workflows pré-câblés
`diagnose` / `economics` / `edition` — pour ne citer que les plus
importants.

**SPECS ne reflète donc plus ni ce que le projet fait *moins* (les
9 promesses rompues), ni ce qu'il fait *bien plus*** (au moins 25
modules majeurs ajoutés sans entrer dans SPECS).

#### B-12 (BLOCKER) — SPECS.md à refondre intégralement

Pour une publication institutionnelle, SPECS.md est typiquement le
document que la direction d'une bibliothèque lit en premier. Le décalage
décrit ci-dessus disqualifie le document : il ment soit par excès
(promesses non tenues), soit par défaut (le quart de la valeur du projet
est invisible). **Effort** : 3 PJ. Réécrire SPECS.md comme un document
miroir du code réel, marquer explicitement « Reporté » ou « Abandonné
au profit de … » pour chaque item rompu.

---

### 9.2 README.md — désynchronisé d'environ 75 sprints

Le README est arrêté éditorialement à **Sprint 22** (vu le tableau
Roadmap qui s'arrête à Sprint 22 « Done » et le bloc « Known Issues &
Improvement Opportunities » daté « Sprint 22 audit »). Or `CLAUDE.md`
documente le travail jusqu'au **Sprint 97**. Concrètement :

#### B-13 (BLOCKER) — Markdown des taglines cassé (lignes 12-14)

```markdown
> **Heritage OCR / HTR / VLM and post-correction benchmarking

> **Banc d'essai d'OCR / HTR / VLM et de post-correction pour documents patrimoniaux
```

Les deux blockquotes ouvrent un `**` (gras) **jamais fermé**, et la
phrase est tronquée à mi-ligne (espace traînant, pas de point). Sur
GitHub, HuggingFace Space et tout viewer Markdown standard, la première
chose qu'un lecteur voit est *« un titre cassé »*. Pour la page de
visite-de-marque d'un projet visant la BnF, c'est rédhibitoire.

**Effort** : 0,1 PJ. Restaurer la phrase complète et fermer le `**`.

#### M-19 (MAJEUR) — Compteur de tests faux à 3 endroits

| Ligne | Affirmation | Réalité 2 mai 2026 |
|---|---|---|
| 583 | « 1242 tests (1 skipped: scipy optional) » | 3 359 collectés, 3 356 passed, 3 skipped |
| 623 | « 1242 passing, 1 skipped » | id. |
| 660 | « pytest tests/ -> 1242 passed, 1 skipped » | id. |

Un primo-lecteur conclut soit que le projet est plus petit qu'il ne
l'est, soit que le README ment. Les deux abîment la confiance.

#### M-20 (MAJEUR) — Roadmap arrêtée 75 sprints en arrière

Le tableau lignes 676-700 s'arrête à **Sprint 22 (« Case studies,
user/dev guides »)**. Tous les sprints suivants — moteur Friedman+Nemenyi
(18), Pareto (20), narrative (19), persistance jobs (26), snapshots
reproductibilité (27), neutralité éditoriale renforcée (29), refonte
Cercle 1/2/3 (32-34), métriques inter-moteurs (35-37), NER (38-41),
calibration (39-43), stratification (44-46), équivalences (47), coût
projeté (48), modernisation lexicale (49), robustesse projetée (50),
leviers (51), réadabilité (52), reading order F1 (53), layout F1 (54),
9 sprints philologiques (55-62), pipelines composables (63-66),
documentation user/dev sur l'axe B (67-69), CLI YAML pipeline (70),
rare-token (71), worst lines (72), historique baseline (73-74),
3 chantiers taxonomie (75-77), équivalences fines (78), projection coût
(79), modernisation lexicale (80), projection robustesse (81), leviers
(82), reliability+stabilité (83-84-85-86-87-88-89-90-91-92-93-94-95-96),
politique modules (97), et les chantiers post-Sprint 97 documentés dans
CHANGELOG — sont absents.

Un investisseur, un comité éditorial, un lecteur d'arXiv, ou même un
contributeur potentiel ne peut pas évaluer la valeur réelle du projet
depuis le README.

**Effort** : 1 PJ pour résumer en un tableau condensé. Idéalement, ne
pas dupliquer CHANGELOG : pointer vers lui pour le détail.

#### M-21 (MAJEUR) — Bloc « Known Issues » obsolète, plusieurs items déjà résolus

Lignes 703-772 décrivent l'audit Sprint 22 ; entre-temps :

| Issue listée | État réel |
|---|---|
| « `web/app.py` is 3072 lines » | **131 lignes** (refactoré Sprint 25 en 11 routers + utilities) |
| « `cli.py` is 971 lines » | **N'existe plus** : remplacé par le package `picarones/cli/` (374 lignes pour `__init__.py` + 6 sous-modules) |
| « `core/runner.py` is 847 lines » | **Le fichier n'existe plus à ce chemin** : déplacé en `picarones/measurements/runner.py` (1 019 lignes maintenant) |
| « `core/narrative/detectors.py` 680 lignes » | **Refactoré Sprint 19 en 6 fichiers de famille** (`measurements/narrative/detectors/{ranking,pareto,stratum,quality,history,ensemble}.py`) |
| « `picarones/i18n.py` shim 66 lignes » | À vérifier — pourrait être nettoyé |
| « `CHANGELOG.md` stops at Sprint 9 » | **Faux** : CHANGELOG va jusqu'à Sprint 97 + post-Sprint 97 (195 KB). |
| « pas de tests pour char_scores.py » | À vérifier — couverture probable |
| « pas de tests pour mistral_ocr.py / google_vision.py / azure_doc_intel.py » | **Faux** : Sprints 49, 50, 51 ajoutent des tests dédiés (`test_sprint49_mistral_confidences.py` etc.) |

Un audit interne qui pointe vers un état antérieur de 2 mois mine la
crédibilité. **Effort** : 0,5 PJ. Soit tout supprimer (le présent audit
le remplace), soit tout réécrire.

#### M-22 (MAJEUR) — Project Structure obsolète et trompeuse

Section « Project Structure » (lignes 471-588) décrit un repo
**d'avant Sprint 32-34** (la grande refonte Cercles 1/2/3) :

- Annonce 17 modules dans `picarones/core/` (corpus, metrics,
  normalization, statistics, runner, results, confusion, char_scores,
  taxonomy, structure, image_quality, difficulty, hallucination,
  line_metrics, history, robustness, pricing, narrative/) — **réalité**
  selon `CLAUDE.md` : `core/` ne contient plus que **7 fichiers**
  (modules.py, corpus.py, results.py, metric_registry.py,
  metric_hooks.py, pipeline.py, facts.py). Tout le reste a migré dans
  `measurements/` après le refactor.
- Annonce `picarones/importers/` — **réalité** : `picarones/extras/importers/`
- Annonce `picarones/web/app.py` (sans mention des 11 routers) — réalité :
  `picarones/web/routers/` (11 fichiers) + 6 utilities (security, jobs,
  state, models, etc.)
- Annonce `picarones/cli.py` — réalité : `picarones/cli/` (package).
- N'évoque ni `picarones/modules/` (BaseModule officiels — Sprint 33),
  ni `picarones/core/narrative/` qui a migré en `measurements/narrative/`.

Un développeur qui suit la structure README pour ajouter un module
ne trouvera **aucun** des fichiers qu'on lui annonce — ou pire,
créera son code au mauvais endroit.

**Effort** : 1 PJ. Régénérer la structure depuis le repo réel.

#### M-23 (MAJEUR) — Liste des moteurs OCR mensongère

| Moteur listé README | Statut réel |
|---|---|
| Tesseract 5 | ✓ implémenté |
| Pero OCR | ✓ implémenté |
| **Kraken** | ❌ **non implémenté** (pas d'adapter) |
| Mistral OCR | ✓ |
| Google Vision | ✓ |
| Azure Doc Intelligence | ✓ |
| **GPT-4o (VLM)** listed as "engine" | ✗ **n'est pas un OCR engine — c'est un LLM/VLM** utilisé via les pipelines |
| **Claude Sonnet (VLM)** listed as "engine" | id. |
| **Mistral Large (LLM)** listed as "engine" | id. |
| **Ollama** listed as "engine" | id. |
| Custom engine "YAML declaration, no code required" | ❌ **non implémenté** — le YAML pipeline existe mais ne couvre pas la déclaration *d'engine* CLI/REST |

Conséquence : un primo-utilisateur croit pouvoir installer Kraken
(`pip install -e ".[kraken]"` succède puisque l'extra existe…) puis
passer `--engines kraken` en ligne de commande — **et ça échoue**. La
documentation cause le bug.

**Effort** : 0,5 PJ. Soit implémenter Kraken (effort plus important),
soit retirer la ligne et documenter le statut « v1.x » dans la roadmap.

#### M-24 (MAJEUR) — Variables `AWS_*` documentées sans adapter

Lignes 604-606 du README :

```bash
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_DEFAULT_REGION="eu-west-1"
```

Aucun adapter AWS Textract n'existe. Si un usager les configure, il
ne se passe rien — mais il croit avoir armé une fonctionnalité.

**Effort** : 0,1 PJ. Supprimer ces 3 lignes (et l'`aws` dans l'extra
`ocr-cloud` ou y ajouter une note `# slot reserved for future Textract
adapter`).

#### M-25 (MAJEUR) — CLI sous-documentée

README lignes 327-339 liste **9 commandes** + `import iiif`. Réalité
(vérifié par `picarones --help`) : **15 commandes** :

| Manquantes du README | Apport |
|---|---|
| `picarones compare` | Compare deux runs JSON et signale les régressions (Sprint 28). Critique pour la CI/CD institutionnelle. |
| `picarones diagnose` | Workflow diagnostic complet (bench + leviers + recommandations factuelles) — Sprint post-97 |
| `picarones economics` | Workflow économique (bench + throughput effectif + cost projection) — Sprint 91/79 |
| `picarones edition` | Workflow édition critique (bench + métriques philologiques) — Sprint 55-60 |
| `picarones pipeline run` / `pipeline compare` | Banc d'essai de pipelines composées YAML (Sprint 70) |

Les trois workflows pré-câblés (`diagnose`, `economics`, `edition`)
sont précisément ce qu'un archiviste BnF non-Pythoniste cherche en
priorité — *« j'ai un objectif éditorial donné, donne-moi le workflow
correspondant »*. Les cacher dans une CLI non documentée est un gaspillage
de la pédagogie déjà en place dans le code.

**Effort** : 0,5 PJ.

#### M-26 (MAJEUR) — Endpoints API web sous-documentés

Lignes 374-386 listent **10 endpoints**. Réalité (audit web §A) :
**27+ endpoints**, dont au moins 13 absents du README :
`/api/benchmark/run` (le nouveau, pour pipelines composées),
`/api/benchmark/{job_id}/synthesis_preview`, `/api/config/{save,load}`,
`/api/history/regressions`, `/api/lang/{code}` (sélecteur langue),
`/api/corpus/{upload,uploads,image,uploads/{id}}`, `/api/htr-united/import`,
`/api/huggingface/import`, `/api/normalization/profiles`, `/api/reports`,
`/api/models/{provider}`.

Pour un intégrateur tiers, c'est inexploitable. La solution simple :
auto-générer la liste via FastAPI OpenAPI et l'embarquer en annexe
README ou la pointer (`/docs` Swagger UI).

**Effort** : 0,5 PJ.

#### M-27 (MAJEUR) — Métriques manquantes du README (vente sous-évaluée)

La section *Heritage-Specific Metrics* (lignes 158-175) liste **8
familles**. Le code en livre **au moins 28** depuis Sprint 22 :

NER (Sprint 38-41), reading order F1 (Sprint 53), layout F1 (Sprint 54),
delta Flesch (Sprint 52), recherchabilité fuzzy (Sprint 84),
séquences numériques par catégorie (Sprint 85), précision par bloc
Unicode (Sprint 55), abréviations médiévales (Sprint 56), couverture
MUFI (Sprint 57), typographie de l'imprimé ancien (Sprint 58),
marqueurs des archives modernes (Sprint 59), numéraux romains (Sprint 60),
stabilité multi-runs (Sprint 83), accord inter-annotateurs Cohen κ /
Krippendorff α (Sprint 83), divergence inter-moteurs (Sprint 35),
matrice de spécialisation (Sprint 89), absorption d'erreur (Sprint 94),
projection robustesse sur corpus réel (Sprint 81), prédictivité image
(Sprint 93), tendances longitudinales (Sprint 92), throughput effectif
(Sprint 91), coût marginal (Sprint 91), comparaison taxonomique
côte-à-côte (Sprint 77), co-occurrence taxonomique (Sprint 75),
heatmap intra-doc taxonomie (Sprint 76), modernisation lexicale (Sprint 80),
projection coût en volume cible (Sprint 79), équivalences diplomatiques
fines (Sprint 78).

Le projet vaut **3 à 4 fois ce que le README affiche**. C'est un
problème de communication, pas de code.

**Effort** : 1 PJ pour réorganiser la section en 3 sous-sections
(« Métriques classiques OCR/HTR », « Métriques philologiques », « Métriques
de comparaison et décision ») et renvoyer vers `docs/views.md` pour le
détail.

#### M-28 (MAJEUR) — Section « Interactive HTML Report » sous-vend de moitié

Lignes 198-219 listent ~15 features. Le code en livre **au moins
25 sections** dans le rapport HTML :

Tableau classement, narrative synthesis, CDD, Pareto, glossaire,
panneau avancé, galerie, vue document, vue caractères — déjà listés.
**Manquants** : tableau NER (Sprint 41), reliability diagrams calibration
(Sprint 43), section stratifiée par strate (Sprint 46), matrice
divergence inter-moteurs (Sprint 37), encart oracle complementarity
(Sprint 37), section leviers d'amélioration (Sprint 51-82), tableau
spécialisation (Sprint 89), tableau throughput (Sprint 91), tableau
longitudinal (Sprint 92), heatmap taxonomie intra-doc (Sprint 76),
tableau worst lines (Sprint 72), tableau modernisation lexicale (Sprint 80),
tableau séquences numériques (Sprint 86), tableau recherchabilité
(Sprint 86), tableau profil philologique (Sprint 62), boxplot
difficulté corpus (Sprint 74), DAG pipeline SVG (Sprint 95), tableau
absorption erreur (Sprint 94), comparaison incrémentale ANOVA-like
(Sprint 96), tableau audit modules (Sprint 97).

**Effort** : 1 PJ.

#### m-18 (MINEUR) — Liens et références menus

- Ligne 753 : « SPECS.md predates the narrative engine, Pareto view
  and glossary — worth a pass » — vrai mais auto-référentiel et
  insuffisant : SPECS prédate **75 sprints**, pas seulement 3.
- Ligne 786 : copyright « 2024 Picarones contributors » — le projet
  s'étend jusqu'en 2026 ; mettre `2024-2026`.
- Ligne 535 : prompts listés (8) — vérifier qu'aucun n'a été ajouté
  depuis ; en particulier, pas de prompt latin alors que SPECS le
  promettait (§5.4).

---

### 9.3 Cohérence transverse — quels chiffres faire foi

Trois documents donnent **trois chiffres différents** pour la suite de
tests :

| Document | Affirmation | Date implicite |
|---|---|---|
| README L583, L623, L660 | « 1 242 passed, 1 skipped » | Sprint 22 (~mars 2025) |
| CLAUDE.md « État actuel (Sprint 16) » | « 1 072 passed, 2 skipped » | Sprint 16 |
| CLAUDE.md « Contexte développement » | « ~3 354 passed, 2 skipped » | Sprint 97 |
| **Mesure réelle 2 mai 2026** | **3 356 passed, 3 skipped** | (vérifié) |

**Effort** : 0,1 PJ pour aligner les trois sources sur le chiffre vérifié
et automatiser la mise à jour (un test qui lit le baseline et le
compare à la doc).

---

### 9.4 Synthèse SPECS+README

| Item | Sévérité | Effort |
|---|---|---|
| B-12 SPECS à refondre intégralement | BLOCKER | 3 PJ |
| B-13 Markdown taglines README cassé | BLOCKER | 0,1 PJ |
| M-19 Compteur tests faux × 3 | MAJEUR | 0,1 PJ |
| M-20 Roadmap arrêtée Sprint 22 | MAJEUR | 1 PJ |
| M-21 Known Issues obsolète | MAJEUR | 0,5 PJ |
| M-22 Project Structure trompeuse | MAJEUR | 1 PJ |
| M-23 Kraken/customYAML annoncés sans implémentation | MAJEUR | 0,5 PJ |
| M-24 AWS env vars sans adapter | MAJEUR | 0,1 PJ |
| M-25 CLI sous-documentée (6/15) | MAJEUR | 0,5 PJ |
| M-26 API web sous-documentée (10/27) | MAJEUR | 0,5 PJ |
| M-27 Métriques sous-vendues (8/28) | MAJEUR | 1 PJ |
| M-28 Sections rapport sous-vendues (15/25) | MAJEUR | 1 PJ |
| m-18 Petits items (copyright, lien…) | MINEUR | 0,3 PJ |
| 9.3 Aligner les compteurs de tests entre 3 docs | MINEUR | 0,1 PJ |

**Total : ~9,8 PJ pour SPECS+README seuls**, soit ~2 semaines. À
prioriser **avant le travail de fond** sur les autres axes : le README
est la première impression, et la divergence actuelle disqualifie
toute communication scientifique ou institutionnelle qui s'appuierait
dessus.

**Recommandation procédurale** : ajouter dans la CI un job qui
*vérifie* qu'aucune assertion vérifiable du README ne diverge du repo
(compteur de tests, liste des moteurs présents dans `picarones/engines/`,
liste des commandes CLI exposées). Concrètement,
`tests/docs/test_readme_consistency.py` qui parse les tableaux et
échoue si un moteur listé n'a pas d'adapter.

---

## 8. Synthèse pour la direction

Picarones est un projet de recherche **techniquement solide, méthodologiquement
ambitieux, éditorialement neutre**. Il dispose déjà de la majorité des
briques d'une plateforme institutionnelle :
architecture cohérente, sécurité de fond, tests volumineux, snapshots
reproductibles, anti-hallucination prouvé.

Ce qui manque pour une adoption BnF / Bibliothèque nationale et pour
une citation académique se concentre sur **trois axes orthogonaux** au
code lui-même :

1. **Communication scientifique** (CITATION, JOSS, traçabilité des
   méthodes statistiques et des profils éditoriaux) — sans cela, le
   projet n'est pas citable et donc pas crédible pour un papier ou
   une thèse.
2. **Conformité opérationnelle** (CSRF, accessibilité WCAG niveau A,
   guides de déploiement, RGPD, gouvernance) — sans cela, aucune
   institution publique française ou européenne ne peut le mettre
   en production sur ses infrastructures.
3. **Hygiène CI/CD** (lock file, scanners, seuil de couverture,
   release PyPI, image immutable) — sans cela, la promesse de
   « plateforme reproductible et auditable » n'est pas tenue de bout
   en bout.

Avec 6 à 10 semaines d'investissement par un ingénieur senior + le
calendrier propre du papier JOSS, le projet peut atteindre un état
**publiable et adoptable institutionnellement**. Le code lui-même
nécessite peu de retouches profondes — l'essentiel du travail est
documentation, gouvernance, intégration continue et accessibilité.