Spaces:
Sleeping
Audit institutionnel BnF — état de Picarones au 2 mai 2026
Audit réalisé sur la branche
claude/audit-institutional-readiness-8Cw4wà partir du commit06aac6a(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). Lintruff 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
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
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 parlogger.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
Factdans 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 :
- Créer
CITATION.cff(5 min), avec auteurs ORCID et version. - Pousser une release GitHub taggée + obtenir un DOI Zenodo (intégration automatique : 1 h).
- 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 :
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
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.jspicarones/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 :
- un
<table>de données équivalentes, marquéaria-describedbydu canvas, masqué visuellement (visually-hidden) mais lu par les AT ; - un
aria-labeldescriptif sur le<canvas>; - 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> :
<a href="#main" class="skip-link">{{ i18n.skip_to_content }}</a>
- une classe CSS
.skip-linkqui reste cachée hors:focus; et ajouterid="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
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 depython:3.11-slimqui suit le stream). - Sprint A16 : ajout du digest sha256 (
@sha256:9bffe43…eec4) sur les deuxARG 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).
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é) :
snapshot.debian.org: rediriger les sourcesaptvers un snapshot Debian daté. Reproductible, mais casse la fenêtre des patches de sécurité Debian (fige les CVE non patchées au snapshot).Pinning explicite :
apt-get install pkg=versionpour chaque paquet. Maintenance lourde (≈ 13 paquets) ; fragile (toute MAJ Debian invalide le pin).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.ymldéclenché sur tagv*qui : build sdist+wheel → test surtestpypi→ publie sur PyPI viapypa/gh-action-pypi-publishavec 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.sqliteen 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
READMEet 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=32sous charge.picarones/web/jobs.py: pas de test pour SSELast-Event-IDreconnexion, écritures concurrentes SQLite (SQLITE_BUSY), basculePICARONES_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 :
[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.ymlré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 :
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.Discipline de code rigoureuse. Lint
ruff0 erreur. Logger nommé par module systématiquement. 0print()en code métier. 3TODO/FIXMEdans tout le repo (signe rare). 87except Exceptionau total mais 84 sont annotés# noqa: BLE001avec contexte explicite, seuls les 3 du B-3 sont des vraies violations.Sécurité de fond solide.
- XML défendu par
defusedxmlpartout (XXE / Billion Laughs). - Zip-slip prévenu par
Path(member.filename).namedansweb/corpus_utils.py:182-183. - Toutes les requêtes SQLite paramétrées (le
f-stringdejobs.py:235est un faux positif — voir §6). - Aucun
pickle.load()(vecteur de RCE classique). subprocessutilisé 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.
- XML défendu par
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ômeassert True.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
payloadduFactcorrespondant — 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.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.
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.
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 :
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)
> **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é selonCLAUDE.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é dansmeasurements/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), nipicarones/core/narrative/qui a migré enmeasurements/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 :
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 :
- 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.
- 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.
- 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é.