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>
This commit is contained in:
108
pipeline/prompts.py
Normal file
108
pipeline/prompts.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Prompts GLM-OCR par type de page OGC.
|
||||
|
||||
Pour chaque type structuré, on demande un JSON strict. Pour les pages libres
|
||||
(concertation 2, éléments de preuve…) on sort du texte brut.
|
||||
"""
|
||||
|
||||
# Prompt court pour détecter le type de page depuis l'en-tête.
|
||||
PROMPT_HEADER = "Text Recognition:"
|
||||
|
||||
# --- Page 1 : Fiche médicale de recueil du praticien conseil ---
|
||||
# Note : accord_desaccord retiré du prompt — géré par pipeline.checkboxes
|
||||
# (densité de pixels) car GLM-OCR ne sait pas lire les checkboxes
|
||||
# (cf. test_prompt_crop_v2.py).
|
||||
#
|
||||
SCHEMA_RECUEIL = """Lis la fiche médicale OGC et renvoie STRICTEMENT le JSON suivant, sans commentaire ni markdown.
|
||||
Les codes CIM-10 sont au format lettre + 2 à 4 chiffres (ex: K650, T814).
|
||||
Les codes CCAM sont au format 4 lettres + 3 chiffres (ex: EBFA012).
|
||||
Les codes GHM sont au format 2 chiffres + lettre + 3 chiffres (ex: 11M122).
|
||||
Les codes GHS sont des nombres à 3-5 chiffres (ex: 4323).
|
||||
Si un champ est illisible, laisse une chaîne vide. Ne devine pas.
|
||||
|
||||
{
|
||||
"etablissement": "",
|
||||
"finess": "",
|
||||
"date_debut_controle": "",
|
||||
"n_ogc": "",
|
||||
"n_champ": "",
|
||||
"dates_sejour": "",
|
||||
"sejour_etab": {
|
||||
"age": "", "sexe": "", "duree_sejour": "",
|
||||
"mode_entree": "", "provenance": "",
|
||||
"mode_sortie": "", "destination": ""
|
||||
},
|
||||
"sejour_reco": {
|
||||
"age": "", "sexe": "", "duree_sejour": "",
|
||||
"mode_entree": "", "provenance": "",
|
||||
"mode_sortie": "", "destination": ""
|
||||
},
|
||||
"rum_etab": {"um": "", "igs": "", "duree": "", "dates": ""},
|
||||
"codage_etab": {
|
||||
"dp": "", "dp_libelle": "", "dr": "",
|
||||
"das": [{"code": "", "position": "", "libelle": ""}]
|
||||
},
|
||||
"codage_reco": {
|
||||
"dp": "", "dr": "",
|
||||
"das": [{"code": "", "position": ""}]
|
||||
},
|
||||
"actes_etab": [{"code": "", "position": "", "libelle": ""}],
|
||||
"actes_reco": [{"code": "", "position": ""}],
|
||||
"ghm_etab": "", "ghs_etab": "",
|
||||
"ghm_reco": "", "ghs_reco": "",
|
||||
"recodage_impactant": "",
|
||||
"ghs_injustifie": "",
|
||||
"praticien_conseil": ""
|
||||
}"""
|
||||
|
||||
# --- Page 5 : Fiche administrative de concertation 2/2 (décision finale) ---
|
||||
SCHEMA_CONCERTATION_2 = """Lis la fiche de concertation et renvoie STRICTEMENT le JSON suivant, sans commentaire ni markdown.
|
||||
Si un champ est illisible, laisse une chaîne vide.
|
||||
|
||||
{
|
||||
"ghs_initial": "",
|
||||
"ghs_avant_concertation": "",
|
||||
"ghs_final": "",
|
||||
"decision": "",
|
||||
"date_concertation": "",
|
||||
"praticien_controleur": "",
|
||||
"medecin_dim": ""
|
||||
}
|
||||
|
||||
Pour "decision", choisis UNIQUEMENT une de ces valeurs selon la case cochée :
|
||||
- "maintien_avis_controleur" si "Maintien de l'avis initial" est coché
|
||||
- "retour_groupage_dim" si "Retour groupage initial DIM" est coché
|
||||
- "autre_groupage" si "Autre groupage" est coché
|
||||
- "" si rien n'est coché"""
|
||||
|
||||
# --- Page 6 : Fiche administrative de concertation 1/2 (argumentaire) ---
|
||||
SCHEMA_CONCERTATION_1 = """Lis la fiche d'argumentaire du médecin contrôleur et renvoie STRICTEMENT le JSON suivant, sans commentaire ni markdown.
|
||||
|
||||
{
|
||||
"date_concertation": "",
|
||||
"argumentaire": ""
|
||||
}
|
||||
|
||||
Pour "argumentaire", transcris TOUT le paragraphe sous "ARGUMENTAIRE DU MEDECIN CONTROLEUR" tel quel, sans reformuler."""
|
||||
|
||||
# --- Page 4 : Éléments de preuve ---
|
||||
SCHEMA_PREUVES = """Lis le tableau des éléments de preuve et renvoie STRICTEMENT le JSON suivant, sans commentaire ni markdown.
|
||||
Pour chaque ligne, indique si la case "Présent" ou "Photocopié" est cochée (true/false).
|
||||
|
||||
{
|
||||
"date": "",
|
||||
"praticien_controleur": "",
|
||||
"medecin_dim": "",
|
||||
"pieces": [
|
||||
{"intitule": "", "present": false, "photocopie": false, "absent_date": "", "date_obtention": ""}
|
||||
]
|
||||
}"""
|
||||
|
||||
# Types de page reconnus
|
||||
PAGE_TYPES = {
|
||||
"recueil": {"keywords": ["RECUEIL DU PRATICIEN"], "prompt": SCHEMA_RECUEIL},
|
||||
"concertation_2": {"keywords": ["CONCERTATION 2/2"], "prompt": SCHEMA_CONCERTATION_2},
|
||||
"concertation_1": {"keywords": ["CONCERTATION 1/2"], "prompt": SCHEMA_CONCERTATION_1},
|
||||
"preuves": {"keywords": ["ELEMENTS DE PREUVE", "PREUVE"], "prompt": SCHEMA_PREUVES},
|
||||
"concertation_med": {"keywords": ["FICHE MEDICALE DE CONCERTATION"], "prompt": PROMPT_HEADER},
|
||||
"hospitalisation":{"keywords": ["SEJOUR D'HOSPITALISATION", "HOSPITALISATION COMPLETE"], "prompt": PROMPT_HEADER},
|
||||
}
|
||||
Reference in New Issue
Block a user