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>
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>
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>
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>
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>
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>
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>
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>