"""A/B V1 vs V2 — schéma COMPLET comme base (ancrage maximal).""" import json import time from pathlib import Path from pipeline.ocr_glm import GLMOCR from pipeline.ingest import pdf_to_images from pipeline.extract import parse_json_output from pipeline.prompts import SCHEMA_RECUEIL as PROMPT_V1_CURRENT PROMPT_V2 = """Lis cette fiche médicale OGC (contrôle T2A Assurance Maladie) et renvoie STRICTEMENT le JSON ci-dessous, sans commentaire ni markdown. CONSIGNES IMPORTANTES : - Le tableau "Codage de l'Établissement / Recodage" a DEUX colonnes distinctes : les codes "Recodage" sont dans la colonne la plus à DROITE, visuellement séparés des codes "Établissement" (à gauche). Ne recopie JAMAIS les codes Établissement dans Recodage. Si la colonne Recodage est vide, laisse vide. - "dp_libelle" = texte descriptif majuscules qui suit le code DP sur la même ligne (ex: "HEMORR. ET HEMATOME COMPLIQ. UN ACTE, NCA"). - Les 4 valeurs GHM/GHS sont sur UNE SEULE LIGNE en bas, lisibles dans cet ordre : "GHM établissement : XXX GHS établissement : YYY GHM après recodage : ZZZ GHS après recodage : WWW". Extrais les 4 séparément. - "praticien_conseil" = nom manuscrit (forme "DR + NOM") tout en bas de page, sous "Nom du praticien conseil responsable du codage". - Les codes CIM-10 commencent TOUJOURS par une LETTRE majuscule (A-Z) suivie de chiffres. JAMAIS par un chiffre. Ex : "I652", "K650", "T814" — jamais "1652". - Les codes GHM : 2 chiffres + lettre + 3 chiffres (ex: "11M122", "06M033"). - Les codes GHS : nombre à 3-5 chiffres (ex: "4323", "863"). - Si un champ est illisible ou absent, 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": "" }""" CASES = [("2018 CARC/OGC 7.pdf",1), ("2018 CARC/OGC 27.pdf",1), ("2018 CARC/OGC 55.pdf",1), ("2018 CARC/OGC 86.pdf",1)] TARGETS = ["codage_etab.dp", "codage_etab.dp_libelle", "codage_reco.dp", "ghm_etab", "ghs_etab", "ghm_reco", "ghs_reco", "praticien_conseil"] def get(d, path): for k in path.split("."): d = d.get(k, "") if isinstance(d, dict) else "" return str(d).strip() def run_prompt(label, prompt, ocr): print(f"\n### {label}") scores = {f: 0 for f in TARGETS} totals = {f: 0 for f in TARGETS} for pdf, page in CASES: name = Path(pdf).stem img = pdf_to_images(pdf)[page-1] with open(f"output/{name}.json") as f: legacy = json.load(f)["recueil"]["parsed"] t0 = time.time() res = ocr.run(img, prompt, max_new_tokens=4096) parsed = parse_json_output(res["text"]) or {} print(f" {name} ({time.time()-t0:.1f}s)") for tf in TARGETS: v_ext = get(parsed, tf) v_leg = get(legacy, tf) # Tolérance dp_libelle : accepter inclusion if tf == "codage_etab.dp_libelle": match = v_leg in v_ext if (v_ext and v_leg) else (v_ext == v_leg) else: match = v_ext == v_leg if v_leg: # ne compter que les champs où legacy a une valeur totals[tf] += 1 if match: scores[tf] += 1 mark = "✓" if match else ("∅" if not v_ext and not v_leg else "✗") if tf in ("codage_reco.dp", "ghs_reco", "praticien_conseil", "codage_etab.dp_libelle"): print(f" {mark} {tf:26s} ext={v_ext!r:40s} leg={v_leg!r}") print(f" --- Score par champ (vs legacy si renseigné) ---") for tf in TARGETS: print(f" {tf:26s}: {scores[tf]}/{totals[tf]}") return scores, totals def main(): ocr = GLMOCR() print(f"VRAM = {ocr.vram_gb:.2f} Go") s1, t1 = run_prompt("V1 (schéma actuel)", PROMPT_V1_CURRENT, ocr) s2, t2 = run_prompt("V2 (consignes précises)", PROMPT_V2, ocr) print("\n=========== DELTA V2 - V1 ===========") for tf in TARGETS: d = s2[tf] - s1[tf] mark = "+" if d > 0 else ("-" if d < 0 else "=") print(f" {mark} {tf:26s} V1={s1[tf]}/{t1[tf]} → V2={s2[tf]}/{t2[tf]}") if __name__ == "__main__": main()