Commit Graph

18 Commits

Author SHA1 Message Date
Dom
e55daf275e fix(ui): shim de compatibilité pour streamlit-drawable-canvas 0.9.3
streamlit-drawable-canvas 0.9.3 (dernière version disponible sur PyPI)
utilise l'API privée `streamlit.elements.image.image_to_url` qui a été
retirée à partir de Streamlit ≈ 1.49. Sur Streamlit 1.56 (installé ici),
le canvas plante à l'ouverture du mode "🔧 Calibration zones" :

  AttributeError: module 'streamlit.elements.image' has no attribute 'image_to_url'

Plutôt que de downgrader Streamlit globalement (impact sur les autres
features de l'overlay), on injecte une implémentation locale de
`image_to_url` au tout début de pipeline/ui_overlay.py si elle est absente.
L'implémentation produit un data URI base64 que le canvas consomme
directement côté navigateur, sans toucher au système de fichiers media.

À retirer dès qu'une version > 0.9.3 de streamlit-drawable-canvas
publiera un correctif officiel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 09:52:19 +02:00
Dom
3a87751444 test: couvrir les modules purs du pipeline (96 nouveaux tests)
Suite de tests unitaires pour tous les modules pipeline qui ne dépendent
pas du VLM — utiles pour garantir la non-régression après refactor et
servir de spec vivante de chaque fonction.

Fichiers :
- tests/test_json_utils.py   (20 tests) : parse_json_output + toutes les
  stratégies de récupération (fences, virgules manquantes, boucles vides,
  fermeture JSON, fallback _raw/_parse_error)
- tests/test_deskew.py       (11 tests) : détection Hough + correction,
  image synthétique + fixtures cache réel
- tests/test_checkboxes.py   (17 tests) : parse_ghs_injustifie,
  dark_ratio, inner_frac, et ground truth visuel sur 17 dossiers
  (mapping hash→OGC résolu au runtime pour éviter les constantes fragiles)
- tests/test_validation.py   (18 tests) : _check_cim10/ccam/ghm/ghs,
  cross-checks GHM↔GHS, annotate sur JSON vide et complet,
  preservation de l'input (copie défensive)
- tests/test_schema.py       (8 tests)  : clean_dossier retire les champs
  debug, préserve les champs métier, compacte la validation, ne modifie
  pas l'input
- tests/test_zones_config.py (8 tests)  : load/save round-trip, merge
  avec defaults, résilience JSON corrompu, get_zone

Total : 107 tests, 5.1 s d'exécution, tous passent. Aucune dépendance
GPU, s'exécutent en CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:29:23 +02:00
Dom
d326524e49 refactor(extract): décomposer en étages testables (json_utils + recueil)
extract.py contenait 4 responsabilités mélangées (320 lignes) :
parsing JSON tolérant, résolution de zones, crop Recodage avec classification
métier, orchestration. Séparation en modules cohérents :

- pipeline/json_utils.py : parsing tolérant réutilisable (strip fences,
  virgules manquantes, troncature des boucles d'objets vides, fermeture
  des structures JSON ouvertes). N'a aucune connaissance métier OGC.

- pipeline/recueil.py    : toute la logique spécifique à la page recueil —
  résolution de zones configurables, filter_cim10_codes, classification
  DP/DR/DAS par règle métier, run_recodage_crop_pass, merge_codage_reco,
  enrich_recueil (orchestration des trois : checkboxes + ghs_injustifie
  + crop Recodage). Chaque fonction est testable indépendamment du VLM.

- pipeline/extract.py    : réduit à l'orchestration pure — ingest, routing,
  boucle page par page, délégation à recueil.enrich_recueil, validation
  ATIH finale. Plus aucune logique métier enfouie.

La fonction extract_dossier garde exactement la même signature et produit
le même JSON en sortie : aucun breaking change externe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:29:03 +02:00
Dom
1255468676 feat(ui): calibration visuelle des zones via dessin à la souris
Nouveau module pipeline/zones_config.py : charge les zones d'extraction
depuis un fichier zones_config.json (coordonnées relatives 0-1), avec
fallback sur les constantes Python. Config partagée entre :
- pipeline/extract.py (crop colonne Recodage)
- pipeline/checkboxes.py (cases Accord/Désaccord)

Zones configurables aujourd'hui (page recueil) :
- codage_reco (crop zonal pour le second passage VLM)
- accord_checkbox / desaccord_checkbox (densité de pixels)

Mode "🔧 Calibration zones" ajouté dans pipeline/ui_overlay.py :
- Sélection d'un PDF de référence (idéalement bien cadré)
- Canvas interactif (streamlit-drawable-canvas) avec les zones
  existantes pré-dessinées en rouge
- Dessin/déplacement/redimensionnement à la souris
- Saisie d'un nom et description par zone
- Sauvegarde en JSON (ou OGC_ZONES_CONFIG si défini)

Permet au métier (Khalid) de recalibrer les zones sans toucher au code,
par exemple si le formulaire ATIH évolue ou si les scans sont d'un autre
établissement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:07:59 +02:00
Dom
c0b0cd9b87 perf(ocr_qwen): support CPU + bfloat16 AVX-512 + threads explicites
Trois ajouts pour rendre le pipeline utilisable sur CPU quand la VRAM
est saturée par d'autres process :

1. Variable QWEN_DEVICE=cpu pour forcer le device CPU. Le défaut "auto"
   choisit CUDA si dispo, fallback CPU sinon.

2. Sur CPU, détection automatique du support AVX-512 BF16 via /proc/cpuinfo
   (Zen 4/5, Intel Sapphire Rapids+). Si présent, bfloat16 au lieu de
   float32 — divise par 2 la RAM et ~2x plus rapide sur matmul.

3. Appel explicite de torch.set_num_threads(N) et set_num_interop_threads(N)
   (OMP_NUM_THREADS seul ne suffit pas). Configurable via TORCH_NUM_THREADS,
   défaut = os.cpu_count().

Mesure sur Ryzen 9 9950X (Zen 5, 16c/32t, AVX-512 BF16 natif) :
- AVANT : 645% CPU (~6.5 cores), 15 Go RAM (float32)
- APRÈS : 2433% CPU (~24 cores), 8 Go RAM (bfloat16)

Appel `torch.cuda.empty_cache()` en fin d'inférence pour réduire la
fragmentation VRAM quand d'autres process GPU tournent en parallèle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:07:45 +02:00
Dom
6c8184cc03 feat(deskew): correction automatique du skew au chargement des PDFs
Nouveau module pipeline/deskew.py basé sur cv2.HoughLinesP :
- détecte les lignes quasi-horizontales (±15° de l'horizontale)
- prend la médiane de leurs angles (robuste aux outliers)
- seuils : |angle|>0.3° pour corriger, |angle|>10° = suspect (on ne corrige pas)
- PIL.rotate() avec BICUBIC + fillcolor blanc, sans expand

Intégré dans pipeline/ingest.py (paramètre `deskew=True` par défaut).
L'angle appliqué est tracé dans un fichier `page_XX.skew` à côté de
l'image, pour audit.

Mesuré sur les 18 dossiers de l'échantillon 2018 CARC : seule OGC 1 a
un skew au-dessus du seuil (+0.91°), les 17 autres sont déjà droits.
Le deskew corrige OGC 1 en 0.00° résiduel (vérif visuelle en-tête OK).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:07:29 +02:00
Dom
b47f5c47e0 feat(schema): module de nettoyage des JSONs pour consommation aval
Le pipeline produit un JSON riche pendant l'exécution (ratios
checkbox, OCR raw, flags _parse_error/_truncated_loop/_crop_recodage,
_source, _elapsed_s…). Utile en audit, mais pollue quand on veut
exposer le résultat à un consommateur aval (Excel, dashboard, API).

pipeline/schema.py :
- SCHEMA_VERSION "2.0"
- clean_dossier(raw) : retourne une copie propre avec structure stable
  (en-tête → codage → GHM/GHS → décisions) et validation ATIH en
  format compact (summary + cross_checks + flags par champ).
- CLEAN_FIELDS_RECUEIL / CLEAN_FIELDS_CONCERTATION_{1,2} / CLEAN_FIELDS_PREUVES
  documentent les champs stables par type de page.
- CLI : `python -m pipeline.schema` → nettoie `output/v2/*.json` vers
  `output/v2_clean/`.

Séparation claire : `output/v2/` reste le JSON raw (audit), `output/v2_clean/`
est la sortie propre et stable pour livrables.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:54:50 +02:00
Dom
3f2e2ee9f4 feat(extract): second passage VLM sur crop colonne Recodage (P0)
Qwen ne lit systématiquement que la colonne de gauche du tableau
Codage quand on lui donne la page recueil entière : la colonne droite
(Recodage) a 27% de couverture en V2.0 avec 100% de validité — une
régression majeure puisque c'est le cœur métier du contrôle T2A.

Solution : après le passage principal, refaire une extraction dédiée
sur un crop zonal de la seule colonne Recodage (y=0.330→0.490 pour
exclure le bloc Actes adjacent). Prompt strict anti-hallucination
("beaucoup de lignes sont vides, n'invente rien"). Le résultat écrase
partiellement `codage_reco` (DP/DR/DAS) dans le JSON principal.

Classification Python par règle métier :
- 1er code sans position  → DP
- 2e code sans position   → DR (ignoré si == DP : Qwen duplique parfois)
- codes avec position     → DAS

Filtre CIM-10 par regex en Python pour retirer les codes CCAM (actes)
qui pourraient rester si le crop déborde.

Ajout d'une env var `QWEN_MAX_PIXELS` (défaut 800) pour ajuster la
consommation VRAM sur machines avec GPU partagé (test sur RTX 5070
avec rpa_vision_v3 en parallèle).

Ajout de `torch.cuda.empty_cache()` après chaque inférence pour
réduire la fragmentation VRAM sur exécutions longues.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:54:35 +02:00
Dom
7d45018139 feat(extract): normaliser ghs_injustifie en 0/1 (P2)
Qwen renvoie typiquement le libellé complet `0 SE 1 2 3 4 ATU FFM FSD`
dans le champ ghs_injustifie alors qu'une seule valeur 0/1 est attendue.
Ajout de `pipeline.checkboxes.parse_ghs_injustifie` qui extrait le
premier chiffre 0/1 via regex, ou "" si illisible.

Post-traitement appliqué à chaque extraction recueil et aux 18 JSONs
V2 existants (10 fichiers corrigés en place — les 8 autres avaient
déjà ghs_injustifie absent ou vide).

Note sur les 7 cases SE1-4/ATU/FFM/FSD : zones trop petites pour être
calibrées à l'œil et aucun cas positif (`ghs_injustifie=1`) dans
l'échantillon 2018 pour valider visuellement. La détection est en
placeholder, à recalibrer sur un cas positif réel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:54:16 +02:00
Dom
7dc3eba1fc fix(persist): corriger tag ocr_model et pipeline_version dans _meta
Auparavant le JSON de sortie étiquetait systématiquement
`ocr_model: "zai-org/GLM-OCR"` et `pipeline_version: "v1"` alors que le
pipeline avait été basculé sur Qwen2.5-VL-3B en V2.

`_meta` lit désormais `MODEL_PATH` depuis `pipeline.ocr_qwen` pour
garantir la cohérence entre le modèle effectivement utilisé et la
trace dans le fichier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:54:01 +02:00
Dom
2ceb3c4916 docs: README avec installation Linux/macOS et référence des répertoires
Guide de démarrage pour un nouveau collaborateur :
- Prérequis système (Python 3.10+, poppler, GPU ≥ 8 Go VRAM)
- Installation (Debian/Ubuntu et macOS) et venv Python
- Commandes principales : pipeline.cli, ui_overlay Streamlit,
  annotate_validation, tests, reconstruction ATIH
- Structure des répertoires (ce qui est dans git vs ignoré)
- Schéma d'architecture et format du JSON produit
- État actuel chiffré + limites connues + pistes suite

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:11:49 +02:00
Dom
71f91d9c31 chore(scratch): archives des scripts exploratoires de choix d'OCR
Conservés comme trace de recherche — non documentés, non factorisés,
ne pas dépendre de ce dossier depuis le code de production.

- test_glm_ocr.py          : benchmark GLM-OCR 0.9B (écarté pour
                             faiblesse sur dp_libelle, praticien et
                             colonne Recodage).
- test_got_ocr.py          : tests GOT-OCR2.0 (échec sur tableaux
                             denses à en-têtes verticaux).
- test_paddle.py           : tentative PaddleOCR (incompatible avec
                             paddlepaddle installé).
- test_surya.py            : tentative Surya (incompatible
                             transformers 5.6).
- test_qwen_vl.py          : Qwen2.5-VL-7B (excellent mais 220s/page,
                             écarté faute de VRAM et vitesse).
- test_qwen_vl_3b.py       : Qwen2.5-VL-3B (retenu, 3s/page, qualité
                             > GLM-OCR sur les champs critiques).
- test_prompt_ab.py        : A/B test prompts Accord/Désaccord.
- test_prompt_crop*.py     : prompts + crop ciblé checkboxes (échec
                             → module pipeline/checkboxes.py).
- test_prompt_recueil_*.py : prompts page recueil (consignes verbeuses
                             dégradent la sortie, cf. discussion).
- README.md                : index du dossier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:06:44 +02:00
Dom
b6dd9ff1df chore(bench): résultats V2 et rapports de benchmarking
Snapshot des 18 JSONs produits par le pipeline V2 (Qwen2.5-VL-3B +
checkboxes densité + validation ATIH), utiles au collaborateur comme
référence de ce que la chaîne actuelle produit.

Rapports :
- bench_v2_report.md       : comparaison V2 vs legacy docTR+VLM
                             (couverture, divergences, régressions
                             notables sur codage_reco et praticien).
- validation_report.md     : résumé de la validation ATIH sur les 18
                             JSONs (131/149 → 140/149 codes valides
                             après fix suffixes `*` et `+N`, 0
                             incohérence GHM↔GHS, 8 suggestions de
                             correction OCR).

Script de comparaison :
- bench_v11_vs_legacy.py   : tableau d'accord champ par champ entre
                             un run du pipeline (output/v2/) et les
                             JSONs legacy (output/).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:06:30 +02:00
Dom
1f75670770 feat(ui): interface Streamlit de review & annotation
Ajoute pipeline/ui_overlay.py : interface web pour inspecter les
extractions et construire un gold set annoté manuellement.

Fonctionnalités :
- Un onglet par type de page détectée dans le dossier (recueil,
  concertation 1/2, concertation 2/2, preuves…).
- Image PDF à gauche + champs éditables à droite, spécifiques au type
  de page (codes CIM/CCAM pour recueil, GHS + décision pour
  concertation 2, argumentaire pour concertation 1…).
- Badges de validation ATIH à côté de chaque code :
    🟢 valide (libellé officiel au survol)
    🟡 invalide, suggestion Levenshtein≤1 disponible
    🔴 invalide, pas de suggestion
- Comparateur au gold set : ✓/✗/∅/— selon divergence.
- Sidebar : sélecteur dossier, métriques ATIH, cohérence GHM↔GHS.
- Expanders JSON pipeline / JSON gold / OCR raw pour debug.

Sauvegarde des annotations dans gold/<nom>.json au même format que
les JSONs pipeline, ce qui permettra de mesurer objectivement la
qualité de futures versions du pipeline (champ par champ vs gold).

Lancement : `streamlit run pipeline/ui_overlay.py` depuis la racine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:06:18 +02:00
Dom
6df590ae95 feat(referentials): validation ATIH 2018 des codes médicaux
Ajoute une couche de validation post-extraction contre les référentiels
officiels de l'ATIH (Agence Technique de l'Information sur
l'Hospitalisation) pour 2018. Zéro tolérance sur les codes T2A : un
code invalide est flaggé, et une correction par plus proche voisin
(Levenshtein ≤ 1) est proposée.

Contenu :
- pipeline/referentials.py : API publique is_valid_{cim10,ccam,ghm,ghs},
  get_cim10_libelle, nearest_cim10, ghm_to_ghs. CLI --build/--test/--stats.
- pipeline/validation.py    : annote un JSON d'extraction avec un bloc
  `_validation` par page (codes valides/invalides + suggestions + cross-
  checks GHM↔GHS).
- referentials/sources/     : données brutes ATIH publiques (CIM-10 ClaML
  2019 substitut, CCAM v5 2018, GHM v2018, tarifs fév. 2018).
- referentials/atih_2018.sqlite : base SQLite prête à l'emploi
  (11 623 CIM-10 · 8 147 CCAM · 2 593 GHM · 5 329 couples GHM→GHS).
- tests/test_referentials.py : 11 tests unitaires (11/11 passent).
- annotate_validation.py    : script qui annote tous les JSONs V2 en
  place et produit validation_report.md.

Note CIM-10 : la version 2018 ATIH n'est publiée qu'en PDF, ClaML 2019
est utilisée en substitut (écart connu ≈ 60 codes / 11 600).

Gestion des suffixes PMSI : `*` (CMA exclue par le DP) et `+N`
(extension PMSI) sont strippés avant validation, le code racine seul
est comparé au référentiel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:06:01 +02:00
Dom
ed4d9bd765 feat(pipeline): extraction OGC via Qwen2.5-VL-3B
Pipeline modulaire remplaçant le monolithe extract_ogc.py (conservé
en legacy pour comparaison).

Modules :
- ingest.py      : PDF → PNG 300dpi avec cache par SHA256
- ocr_qwen.py    : wrapper singleton Qwen2.5-VL-3B (bfloat16, ~7 Go VRAM)
- ocr_glm.py     : wrapper GLM-OCR 0.9B (alternatif, conservé)
- classify.py    : détection type de page + routing par index standard
                   (ordre des 6 pages OGC → -50% d'appels OCR)
- prompts.py     : JSON schemas par type (recueil, concertation 1/2/2/2,
                   preuves) + mots-clés de classification
- checkboxes.py  : détection Accord/Désaccord par densité de pixels
                   (inner-frac 0.35, 17/17 corrects sur échantillon vérifié ;
                   GLM-OCR et Qwen échouent sur les checkboxes, cf.
                   scratch/test_prompt_crop_v2.py)
- extract.py     : orchestration 1 dossier (ingest → classify → OCR →
                   parse JSON tolérant aux boucles + validation ATIH)
- persist.py     : sauvegarde JSON + metadata (pipeline_version,
                   ocr_model, timestamp)
- cli.py         : `python -m pipeline.cli <pdf|dir>`

Temps mesuré : ~35s/dossier (6 pages) sur RTX 5070.

Qwen2.5-VL-3B retenu après comparaison avec GLM-OCR 0.9B, GOT-OCR2.0,
Surya, PaddleOCR (cf. scratch/). Il extrait correctement dp_libelle,
praticien_conseil et les 4 GHM/GHS là où les autres échouent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:05:40 +02:00
Dom
ddebd8dfbf chore(gitignore): exclure venvs de test, cache images et artefacts
- .venv_glm/, .venv_got/ (venvs créés pendant le choix d'OCR)
- .cache/ (images PDF→PNG intermédiaires, reconstructibles)
- test_*_out/ (résultats bruts des tests exploratoires)
- test_glm_*.md, test_got_*.md (stdout redirigés)
- .DS_Store, Thumbs.db

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:05:20 +02:00
Dom
0c0f62fbf1 feat: extraction OGC et génération de PDFs propres
Pipeline complet pour extraire les données structurées des fiches OGC
scannées (recueil praticien conseil + concertation) et générer des PDFs
propres et lisibles à partir des JSON extraits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:12:21 +01:00