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