Picarones / CLAUDE.md
Claude
feat(sprint-S4-batch1+S5): coverage modules critiques + tests dégradation réseau
9e46e55 unverified
|
Raw
History Blame
14.2 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 + 22 renderers + 5 vues + templates Jinja2
│ ├── json/, csv/ exports tabulaires
│ ├── narrative/ moteur narratif (18 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/ 8 fichiers .txt FR+EN
├── data/ Tables indicatives (pricing.yaml)
└── i18n.py Helper i18n (multi-langue rapports)
```
---
## État des tests et bugs historiques
`pytest tests/` → **4320 passed, 12 skipped, 8 deselected, 0 failed**
(post-S59). 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 en prose est synchronisé automatiquement par
`scripts/gen_readme_tables.py` — toute modification manuelle sera
ré-écrasée au prochain `make lint`.
### 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
| Mode | Description |
|------|-------------|
| **zero_shot** | Le LLM reçoit l'image directement et transcrit sans OCR préalable (VLM) |
| **post_correction_texte** | OCR → texte brut → LLM corrige le texte (modèles texte seul) |
| **post_correction_image_texte** | OCR → LLM reçoit image + texte brut pour correction (VLM) |
`ministral-3b-latest` = modèle texte pur → utiliser mode `post_correction_texte` uniquement.
---
## 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.
**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 18 templates × 2 langues
└── detectors/ 18 détecteurs en 6 familles
├── ranking.py 5 (global_leader, statistical_tie, significant_gap,
│ speed_winner, median_mean_gap_warning)
├── pareto.py 2 (pareto_alternative, cost_outlier)
├── stratum.py 3 (stratum_winner, stratum_collapse,
│ stratification_recommended)
├── quality.py 4 (error_profile_outlier, llm_hallucination_flag,
│ robustness_fragile, confidence_warning)
├── history.py 3 (engine_off_baseline, engine_unstable,
│ regression_in_history)
└── ensemble.py 1 (ensemble_opportunity)
```
**Principe anti-hallucination** : chaque valeur numérique ou nom d'entité
dans le `payload` d'un `Fact` provient du JSON d'entrée. Le test
`test_sprint19_narrative_engine.py` parse la synthèse rendue et vérifie
la traçabilité.
**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** : `pytest tests/ -q` → 4320 passed, 9 skipped, 24
deselected, 0 failed (post-v2.0).
- **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 |