Spaces:
Sleeping
Sleeping
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é.
|