Picarones / CLAUDE.md
Claude
hardening(web): durcissement des defaults P0 issus de l'audit prod
53f4d56 unverified
|
Raw
History Blame
17.4 kB
# CLAUDE.md — Picarones
Plateforme de benchmark OCR/HTR pour documents patrimoniaux.
Repo : github.com/maribakulj/Picarones
HuggingFace Space : huggingface.co/spaces/Ma-Ri-Ba-Ku/Picarones (Docker, port 7860)
---
## Architecture — 8 couches concentriques
Voir le manifeste complet dans [`docs/explanation/architecture.md`](docs/explanation/architecture.md).
```
domain → formats → evaluation → pipeline → adapters → app → reports → interfaces
```
Règle d'import stricte : les dépendances vont uniquement de l'extérieur
vers l'intérieur. Vérifié par `tests/architecture/test_layer_dependencies.py`
+ `tests/architecture/test_no_legacy_imports_in_rewrite.py`
(`LEGACY_PACKAGES = ()` à v2.0 — plus aucun paquet legacy).
Tous les paquets legacy (`picarones/{core,measurements,engines,modules,
report,llm,pipelines,cli,web,extras}/` + `adapters/legacy_engines/` +
`adapters/legacy_pipelines/` + `interfaces/{cli,web}/_legacy/`) ont été
**supprimés** au cours des sprints A-H (mai 2026).
---
## Setup
```bash
pip install -e ".[dev,web]" # toujours inclure [web] pour les tests
pytest tests/ -q --tb=short # lancer les tests
picarones demo --output rapport.html # rapport démo sans moteur installé
picarones serve --port 8080 # interface web locale
```
---
## Structure
Architecture canonique en **8 couches concentriques** :
```
picarones/
├── domain/ Couche 1 — types purs (Pydantic + stdlib only)
│ ├── artifacts.py Artifact, ArtifactType (10 types)
│ ├── corpus.py CorpusSpec
│ ├── documents.py DocumentRef
│ ├── pipeline_spec.py PipelineSpec, PipelineStep
│ ├── module_protocol.py BaseModule
│ ├── facts.py Fact, FactType, FactImportance, DetectorRegistry
│ ├── evaluation_spec.py MetricSpec, EvaluationView, EvaluationSpec
│ ├── projection_spec.py ProjectionSpec
│ ├── provenance.py ProvenanceRecord
│ ├── run_manifest.py RunManifest
│ └── errors.py PicaronesError, AdapterStepError, ...
├── formats/ Couche 2 — parsing/sérialisation (lxml, defusedxml)
│ ├── alto/, pagexml/ ALTO 4, PAGE XML
│ ├── text/ normalisation, profils de comparaison
│ └── _xml_utils.py safe_parse_xml
├── evaluation/ Couche 3 — métriques et calcul
│ ├── metrics/ ~37 métriques (CER/WER, MUFI, philological, NER, …)
│ ├── statistics/ Wilcoxon, Friedman/Nemenyi, bootstrap, Pareto, CDD
│ ├── views/, projectors/ EvaluationView, AltoToText, PageToText, ...
│ ├── registry/ MetricRegistry (DI)
│ ├── corpus.py Document, Corpus, GTLevel + payloads
│ ├── benchmark_result.py BenchmarkResult, EngineReport, DocumentResult
│ ├── metric_registry.py MetricSpec, register_metric, compute_at_junction
│ ├── metric_hooks.py register_document_metric, register_corpus_aggregator
│ ├── metric_result.py MetricsResult, aggregate_metrics
│ ├── synthetic.py generate_sample_benchmark (pour `picarones demo`)
│ └── _diff_utils.py compute_word_diff, compute_char_diff, diff_stats
├── pipeline/ Couche 4 — orchestration
│ ├── executor.py PipelineExecutor (instance-based)
│ ├── planner.py ExecutionPlan, StepInputBinding
│ ├── protocols.py StepExecutor Protocol, ExecutionMode
│ ├── runner.py CorpusRunner (backpressure + timeout + cancel)
│ ├── types.py RunContext, StepResult, PipelineResult
│ ├── llm_pipeline_builder.py make_ocr_llm_pipeline_spec (3 modes)
│ └── llm_pipeline_config.py OCRLLMPipelineConfig (container OCR+LLM)
├── adapters/ Couche 5 — adapters externes (libs autorisées)
│ ├── ocr/ Tesseract, Pero, Mistral OCR, Google Vision, Azure DI, Precomputed + factory
│ ├── llm/ OpenAI, Anthropic, Mistral, Ollama (BaseLLMAdapter)
│ ├── vlm/ Adapters VLM (zero-shot)
│ ├── corpus/ IIIF, Gallica, HTR-United, HuggingFace, eScriptorium
│ └── storage/ ArtifactStore, JobStore
├── app/ Couche 6 — services applicatifs
│ └── services/ BenchmarkService, RunOrchestrator, JobRunner,
│ benchmark_runner (entry point CLI/web), partial_store
├── reports/ Couche 7 — rendu HTML / JSON / CSV
│ ├── html/ ReportGenerator + 28 renderers + 5 vues + templates Jinja2
│ ├── json/, csv/ exports tabulaires
│ ├── narrative/ moteur narratif (20 détecteurs)
│ ├── glossary/, i18n/ glossaire + i18n FR/EN
│ └── _helpers/ colors, render_helpers, assets
├── interfaces/ Couche 8 — entrées utilisateur
│ ├── cli/ Click CLI : run, diagnose, economics, edition,
│ │ compare, robustness, history, serve, metrics,
│ │ engines, info, demo, report, import (group)
│ └── web/ FastAPI : UI Jinja2 + SSE benchmark + ZIP upload
├── prompts/ 16 fichiers .txt (FR/EN/DE) — médiéval, imprimé ancien, presse XIXe
├── data/ Tables indicatives (pricing.yaml)
└── i18n.py Helper i18n (multi-langue rapports)
```
---
## État des tests et bugs historiques
`pytest tests/` → **5100 passed, 16 skipped, 8 deselected, 2 xfailed, 0 failed**
(post-audit code-quality, mai 2026). Les deselected sont les markers
`live` (5 tests d'intégration contre vraie API/binaire) + `network`
(3 tests qui hit le réseau réel), opt-in en local via `pytest -m live`
ou `pytest -m network`. Le compteur ``passed`` est synchronisé
automatiquement par `scripts/gen_readme_tables.py` (CI : job
``sync-counters`` ; local : `make sync-counters-check`). Le détail
``skipped``/``xfailed`` peut dériver de ±2 entre éditions et n'est
pas verrouillé en CI.
NB : utiliser ``python -m pytest tests/`` plutôt que ``pytest tests/``
directement — l'installation via ``uv tool install pytest`` masque
les deps Picarones et produit ~160 collection errors trompeurs.
### Bugs documentés antérieurement — tous résolus
| Bug | Statut | Sprint de résolution |
|-----|--------|---------------------|
| Pipeline OCR+LLM sortie vide (`tesseract → ministral-3b-latest`) | ✅ Résolu | Sprint 15 — adapter Mistral logue `finish_reason`, `completion_tokens`, normalise les ContentChunk |
| CI `python-multipart` manquant | ✅ Résolu | `pyproject.toml` expose `python-multipart>=0.0.9` dans les extras `dev` ET `web`; `ci.yml:71` installe `.[dev,web]` |
| Tests fixtures post-Sprint 10 (counts moteurs, flag `is_pipeline`) | ✅ Résolu | Fixtures mises à jour |
| Test Windows SQLite `test_history_empty_db` | ✅ Résolu | `try/except OSError` + `gc.collect()` avant `unlink` |
| Test HuggingFace `test_search_language_filter` | ✅ Résolu | Assertion corrigée |
En cas de régression sur un de ces bugs, chercher les fichiers de test
correspondants (`test_sprint15_llm_pipeline_bugs.py`, `test_sprint8_escriptorium_gallica.py`,
`test_sprint6_web_interface.py`) avant de ré-ouvrir une enquête.
---
## Règles importantes — ne pas toucher
- **Ne jamais retirer `python-multipart` des dépendances** : FastAPI vérifie sa présence à
l'import du module (décoration `@app.post` avec `UploadFile`), pas à l'exécution. Ça casse
tous les tests web au setup.
- **Ne jamais mettre `except Exception: pass`** : remplacer par
`logger.warning("[module] fonctionnalité dégradée : %s", e)`.
- **Toujours utiliser `logger.warning` avec message explicite** quand une fonctionnalité optionnelle
échoue (confusion, taxonomy, structure, image_quality, etc.).
- **Avant tout push, lancer `make lint`** (ou `ruff check picarones/ tests/`).
La config est centralisée dans `pyproject.toml` sous `[tool.ruff]`, donc
CI, Makefile et invocation directe produisent le même résultat. Le job
`lint` du CI est bloquant — un F401 (import inutilisé) ou un E741
(variable ambiguë) fait échouer la PR, par design.
- **Les profils de normalisation** sont dans `picarones/formats/text/normalization.py` — l'endpoint
`/api/normalization/profiles` doit les lire dynamiquement depuis ce fichier, pas depuis une
liste statique.
---
## Variables d'environnement
```bash
# Clés API LLM (configurées dans HuggingFace Space Settings → Variables and secrets)
MISTRAL_API_KEY=...
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
# OCR cloud (optionnel)
GOOGLE_APPLICATION_CREDENTIALS=/path/to/creds.json
AZURE_DOC_INTEL_ENDPOINT=https://...
AZURE_DOC_INTEL_KEY=...
```
---
## Pipelines OCR+LLM — modes
Trois modes canoniques (typés `Literal[…]` dans `PipelineConfig.pipeline_mode`
depuis Phase 2 du chantier post-rewrite — toute autre valeur est rejetée
en 422 par Pydantic, plus de fallback silencieux) :
| Mode (clé API) | Description |
|---|---|
| **`zero_shot`** | Le VLM reçoit l'image directement et transcrit sans OCR préalable (pas d'OCR amont). |
| **`text_only`** | OCR → texte brut → LLM corrige le texte sans voir l'image (modèles texte seul). |
| **`text_and_image`** | OCR → VLM reçoit image + texte brut pour correction multimodale. |
`ministral-3b-latest` = modèle texte pur → utiliser mode `text_only`
uniquement. L'UI envoie ces clés en anglais ; les libellés français
"Post-correction texte" / "Post-correction image+texte" sont des
labels d'affichage (i18n), pas des identifiants API.
---
## CI/CD
- **CI GitHub Actions** : `.github/workflows/ci.yml` — Python 3.11/3.12, Linux/macOS/Windows
- **Sync HuggingFace** : `.github/workflows/sync_to_huggingface.yml` — push auto sur main
(nécessite secret `HF_TOKEN` dans GitHub Settings → Secrets → Actions)
- **HuggingFace Space** : Docker sur port 7860
---
## Sprints réalisés
L'historique détaillé des **97+ sprints** du projet (de la fondation
S1 jusqu'au rewrite ciblé S27-S46, l'audit institutionnel S47-S59,
puis le retrait complet du legacy A-H jusqu'à v2.0) est dans le
CHANGELOG.md à la racine.
**v2.0 (mai 2026)** : la migration vers l'architecture 8 couches
canoniques est terminée. Plus aucun paquet legacy. Plus aucun shim.
**Chantier post-rewrite (mai 2026, branche `claude/fix-module-rewiring-MHssX`)** :
réconciliation des chemins UI/API/runner après audit révélant des
options ignorées, moteurs annoncés sans backend, surfaces filesystem
ouvertes et round-trip JSON appauvri (cf. CHANGELOG.md). Cinq phases
exécutées :
- **Phase 1 (sécurité P0)** : `output_dir` validé (importers HTR-United/HF),
`db_path` validé (`/api/history/regressions`), ZIP collision de
basename + validation image extraite.
- **Phase 2 (méthodologie P0)** : `pipeline_mode` strict Literal,
`BenchmarkResult.from_json_object` (round-trip JSON complet —
taxonomy/NER/calibration/philological/searchability/hallucination
préservés), `partial_store` fingerprint SHA-256 (engine_config +
normalization + char_exclude + corpus mtime/size + code version).
- **Phase 3 (moteurs fantômes)** : adapters `KrakenAdapter` et
`CalamariAdapter` implémentés (lazy imports, extras `[kraken]` /
`[calamari]`) + matrice CLI/Web/factory unifiée.
- **Phase 4 (code zombie)** : `upload_purge_task` (RGPD) branchée au
lifespan + payload corpus dans `JobStore.create_job`,
`/api/benchmark/start` unifié vers le worker v2,
`HTRUnitedCatalogue.from_remote` (avec fallback demo + champ
`is_demo` exposé), endpoints config save/load branchés à l'UI,
workflows CLI `diagnose`/`economics`/`edition` génèrent le HTML
automatiquement.
- **Phase 5 (naming)** : `CompetitorConfig``PipelineConfig`,
`ocr_engine``engine_name` (rupture API, le field accepte aussi
des VLMs et `corpus`).
**Règles d'architecture** :
- ``evaluation/`` whitelist externe : ``PIL, annotated_types,
jiwer, numpy, pydantic, rapidfuzz, scipy, spacy,
typing_extensions, yaml`` — **pas** ``pytesseract``,
``mistralai``, ``azure``, ``google``, ``pero_ocr``. Tout code
qui importe ces libs externes va dans ``adapters/``.
- ``evaluation/`` ne peut pas importer depuis ``pipeline/`` :
c'est le sens inverse de la dépendance. Un module qui pont
les deux contrats vit dans ``pipeline/``.
- ``reports/`` consomme uniquement ``evaluation/metrics/``.
- ``interfaces/`` (couche 8) consomme ``app.services`` (couche 6) :
``app.services.benchmark_runner`` est l'entry point unique pour
CLI et web.
- ``test_file_budgets`` : si un fichier dépasse 400 LOC, ajouter
une entrée avec budget = LOC actuel + ~15 %.
## Moteur narratif
Le modèle de données (`Fact`, `FactType`, `FactImportance`,
`DetectorRegistry`) vit en couche 1 (`domain`) dans
[`picarones/domain/facts.py`](picarones/domain/facts.py). Les détecteurs et
le rendu vivent en couche 7 (`reports`) :
```
picarones/reports/narrative/
├── __init__.py API publique + pipeline build_synthesis
├── arbiter.py Tri par importance, non-redondance, anti-contradiction
├── renderer.py Rendu templates YAML par str.format_map (déterministe)
├── registry.py Registre par défaut des détecteurs
├── templates/{fr,en}.yaml 20 templates × 2 langues
└── detectors/ 20 détecteurs en 6 familles
├── ranking.py 5 (global_leader, statistical_tie, significant_gap,
│ speed_winner, median_mean_gap_warning)
├── pareto.py 3 (pareto_alternative, cost_outlier,
│ pricing_staleness_warning)
├── stratum.py 3 (stratum_winner, stratum_collapse,
│ stratification_recommended)
├── quality.py 4 (error_profile_outlier, llm_hallucination_flag,
│ robustness_fragile, confidence_warning)
├── history.py 4 (engine_off_baseline, engine_unstable,
│ regression_in_history, importer_fallback_triggered)
└── ensemble.py 1 (ensemble_opportunity)
```
**Principe anti-hallucination** (formulation précise — audit F7) :
aucun LLM, aucune valeur aléatoire/fabriquée, rendu `str.format_map`
déterministe. Les **noms d'entités** sont repris *verbatim* du JSON
d'entrée ; les **nombres** sont soit verbatim, soit une **fonction
déterministe et auditable** de valeurs d'entrée (écart relatif,
accélération, largeur d'IC…). L'ancienne formulation « chaque nombre
provient du JSON » était trop forte (dérivations) et le test
historique `test_sprint19_narrative_engine.py` était *circulaire* (il
validait les nombres rendus contre le `payload` du `Fact`, lui-même
rempli par le détecteur). La traçabilité **à la source** est désormais
vérifiée par `tests/evaluation/test_scientific_audit_2026.py`
(`TestF7NarrativeTraceability`) : reconstruction depuis le
`BenchmarkResult` d'origine + déterminisme bit-à-bit.
**Règle anti-contradiction** (arbitre) : si `SIGNIFICANT_GAP` (Wilcoxon
non corrigé) et `STATISTICAL_TIE` (Nemenyi corrigé) concernent les mêmes
moteurs, Nemenyi l'emporte.
**Pipeline** : `build_synthesis(benchmark_data, lang, max_facts=5)`
détecte, arbitre, rend.
---
## Contexte développement
- **Environnement** : GitHub Codespaces, Python 3.11+
- **Tests** : voir « État des tests et bugs historiques » plus haut
(compteur synchronisé par ``scripts/gen_readme_tables.py``).
- **Manifeste architecture** : [`docs/explanation/architecture.md`](docs/explanation/architecture.md).
- **API publique stable** : [`docs/reference/api-stable.md`](docs/reference/api-stable.md).
### Statut v2.0 — mai 2026
L'architecture 8 couches est complète. Tous les paquets legacy
top-level (`core/`, `measurements/`, `engines/`, `modules/`,
`report/`, `llm/`, `pipelines/`, `cli/`, `web/`, `extras/`) ainsi
que les sous-paquets transitoires (`adapters/legacy_engines/`,
`adapters/legacy_pipelines/`, `interfaces/{cli,web}/_legacy/`) ont
été supprimés.
| Phase / Sprint | Statut | Détails |
|---|---|---|
| Foundation S1-S26 | ✅ | domain, formats, evaluation, narrative engine |
| Rewrite ciblé S27-S46 | ✅ | pipeline, app.services, adapters/ocr canonique, reports |
| Audit S47-S59 | ✅ | confidences, sécurité web, registry typé, baselines |
| Plan A-H (mai 2026) | ✅ | Retrait complet du legacy : core/measurements/engines/modules/report/llm/pipelines/cli/web/extras supprimés ; interfaces/{cli,web}/_legacy promus au niveau canonique ; v2.0 release |