diff --git a/Medevio/60 ScansProcessing/Extract_pacient_info_v1.0.py b/Medevio/60 ScansProcessing/Extract_pacient_info_v1.0.py index d6eb804..784bd16 100644 --- a/Medevio/60 ScansProcessing/Extract_pacient_info_v1.0.py +++ b/Medevio/60 ScansProcessing/Extract_pacient_info_v1.0.py @@ -909,6 +909,97 @@ def build_corrections_prompt() -> str: return "\n".join(lines) + "\n\n" +# ─── CHRI / CKD klasifikace (deterministická pojistka) ─────────────────────── +# Claude opakovaně plete jednotky eGFR: hodnotu v ml/s (např. 1.27) klasifikuje, +# jako by byla v ml/min (→ CHRIG5), místo přepočtu ×60 (1.27 ml/s = 76 ml/min → CHRIG2). +# Proto stadium počítáme v Pythonu z hodnoty + jednotky, kterou Claude vrátí v poli +# "egfr", a opravíme jím navržený název i varianty. + +def _parse_egfr(egfr) -> tuple[float | None, str | None]: + """Z pole "egfr" (dict / číslo / text) vytáhne (hodnota, jednotka).""" + if egfr is None: + return None, None + if isinstance(egfr, dict): + h = egfr.get("hodnota", egfr.get("value")) + j = egfr.get("jednotka", egfr.get("unit")) + try: + h = float(str(h).replace(",", ".")) if h is not None else None + except (TypeError, ValueError): + h = None + return h, (str(j) if j else None) + if isinstance(egfr, (int, float)): + return float(egfr), None + if isinstance(egfr, str): + m = re.search(r"\d+[.,]?\d*", egfr) + h = float(m.group(0).replace(",", ".")) if m else None + jl = egfr.lower() + if "ml/s" in jl or "ml/sec" in jl or "ml/sek" in jl: + j = "ml/s" + elif "ml/min" in jl: + j = "ml/min" + else: + j = None + return h, j + return None, None + + +def klasifikuj_chri(hodnota: float | None, jednotka: str | None = None) -> str | None: + """Vrátí stadium CHRI ('G1'..'G5', vč. 'G3a'/'G3b') z eGFR hodnoty. + + Jednotku použije z parametru, jinak heuristicky podle velikosti: eGFR v ml/s má + fyziologicky hodnoty zhruba 0–2.3, v ml/min 0–140 — proto hodnotu < 3 bereme jako + ml/s a násobíme ×60. Prahy (v ml/min/1.73m²): ≥90 G1, ≥60 G2, ≥45 G3a, ≥30 G3b, + ≥15 G4, <15 G5. + """ + if hodnota is None: + return None + j = (jednotka or "").lower() + if "min" in j: + egfr = hodnota # ml/min explicitně + elif "ml/s" in j or "ml/sec" in j or "ml/sek" in j: + egfr = hodnota * 60.0 # ml/s explicitně → ml/min + else: + egfr = hodnota * 60.0 if hodnota < 3.0 else hodnota # heuristika dle velikosti + if egfr >= 90: + return "G1" + if egfr >= 60: + return "G2" + if egfr >= 45: + return "G3a" + if egfr >= 30: + return "G3b" + if egfr >= 15: + return "G4" + return "G5" + + +# Najde zmínku CHRI/CKD klasifikace: prefix (+ volitelná hodnota/jednotka) + 'G<číslo>'. +_CHRI_RE = re.compile( + r"(CHRI|CKD(?:[\s-]?EPI)?|CK[\s-]?EPI)" # prefix + r"(\s*(?:[\d.,]+\s*(?:ml\s*/?\s*s(?:ec|ek)?|ml\s*/?\s*min)?\s*)?)" # volit. hodnota+jednotka + r"G\s*[1-5](?:\s*[ab])?", # staré stadium + re.IGNORECASE, +) + + +def oprav_chri_klasifikaci(text: str, stupen: str | None) -> str: + """Opraví číslo CHRI/CKD stadia v textu na spočtené `stupen` ('G2', 'G3a'…). + + Zachová prefix (CHRI vs CKD) i případnou číselnou hodnotu, mění jen stadium. + Pro G1 (norma) zmínku odstraní i s hodnotou a uklidí okolní oddělovače. + """ + if not text or not stupen: + return text + if stupen == "G1": + out = _CHRI_RE.sub("", text) + out = re.sub(r",\s*,", ",", out) # dvojitá čárka + out = re.sub(r"\[\s*,\s*", "[", out) # čárka hned za [ + out = re.sub(r"\s*,\s*\]", "]", out) # čárka hned před ] + out = re.sub(r"\s{2,}", " ", out).replace("[ ", "[").replace(" ]", "]") + return out.strip() + return _CHRI_RE.sub(lambda m: f"{m.group(1)}{m.group(2)}{stupen}", text) + + # ─── Claude Vision API ──────────────────────────────────────────────────────── def extract_info(pdf_path: Path, known_patient: str | None = None, known_rc: str | None = None) -> dict: @@ -967,7 +1058,11 @@ def extract_info(pdf_path: Path, known_patient: str | None = None, known_rc: str "Teprve pokud závěr chybí, shrň obsah z celé zprávy.\n" f"- \"nazev_souboru\": název souboru ve formátu {nazev_format}\n" "- \"rotace\": o kolik stupňů CCW je třeba otočit obrázek aby byl text čitelně na výšku nebo šířku " - "(hodnoty: 0, 90, 180, 270). Pokud je text již správně orientovaný, vrať 0.\n\n" + "(hodnoty: 0, 90, 180, 270). Pokud je text již správně orientovaný, vrať 0.\n" + "- \"egfr\": pokud zpráva obsahuje glomerulární filtraci (eGFR, CKD-EPI, CK-EPI), vrať objekt " + "{\"hodnota\": <číslo přesně jak je na zprávě>, \"jednotka\": \"ml/s\" nebo \"ml/min\" dle zprávy}. " + "Hodnotu i jednotku jen opiš, NEKLASIFIKUJ stadium — slouží jen pro kontrolní přepočet CHRI v Pythonu. " + "Pokud filtrace ve zprávě není, vrať null.\n\n" "Pokud pole nenajdeš, použij null. Nepiš nic jiného než JSON." ) @@ -1260,6 +1355,19 @@ def _analyze_file(pdf_path: Path) -> dict: if not is_ekg and nazev: varianty = generate_name_variants(info, nazev) + # Deterministická oprava CHRI/CKD stadia — Claude plete ml/s vs ml/min. + hodnota_egfr, jednotka_egfr = _parse_egfr(info.get("egfr")) + chri_stupen = klasifikuj_chri(hodnota_egfr, jednotka_egfr) + if chri_stupen: + nazev_pred = nazev + nazev = oprav_chri_klasifikaci(nazev, chri_stupen) + varianty = [oprav_chri_klasifikaci(v, chri_stupen) for v in varianty] + if nazev != nazev_pred: + jed = f" {jednotka_egfr}" if jednotka_egfr else "" + info_lines.append(f"✓ CHRI přepočteno: {hodnota_egfr}{jed} → CHRI{chri_stupen}") + print(f" ✓ CHRI klasifikace opravena → CHRI{chri_stupen} " + f"(eGFR {hodnota_egfr}{jed})") + return { "path": pdf_path, "is_ekg": is_ekg, diff --git a/Medevio/60 ScansProcessing/corrections.json b/Medevio/60 ScansProcessing/corrections.json index 8f7112c..2b64775 100644 --- a/Medevio/60 ScansProcessing/corrections.json +++ b/Medevio/60 ScansProcessing/corrections.json @@ -2042,5 +2042,81 @@ { "original": "460509135 2026-04-29 Novotný, Miroslav [domácí péče] [6 do 28JUL2026, 06315 1xd3xt, 06329 1xd3xt, 06137 ad hoc].pdf", "corrected": "460509135 2026-04-29 Novotný, Miroslav [domácí péče] [6 do 30JUN2026 06315 1xd3xt, 06329 1xd3xt, 06137 ad hoc].pdf" + }, + { + "original": "380314026 2026-06-05 Chomát, Jiří [Laboratoř] [dg. M5449, S_Urea 9.32↑, CHRIG5, S_ALP 2.17↑].pdf", + "corrected": "380314026 2026-06-05 Chomát, Jiří [Laboratoř] [dg. M5449, S_Urea 9.32↑, CHRIG2, S_ALP 2.17↑].pdf" + }, + { + "original": "391111080 2026-06-03 Veltruský, Jaroslav [Laboratoř] [dg. I10, Urea 18.15↑, Krea 122↑, CHRIG3b, GGT 3.61↑, ALP 2.32↑, VitB12 710↑, NT-proBNP 615↑, Hb 124↓, Trombo 144↓].pdf", + "corrected": "391111080 2026-06-03 Veltruský, Jaroslav [Laboratoř] [dg. I10, Urea 18.15↑, Krea 122↑, CHRIG3a, GGT 3.61↑, ALP 2.32↑, VitB12 710↑, NT-proBNP 615↑, Hb 124↓, Trombo 144↓].pdf" + }, + { + "original": "401120069 2026-05-28 Císař, Petr [LZ hematologie] [kontrola, CLL z B-lymfocytů, B-CLL/SLL 28% malých monoklon. B lymfocytů, del 13q14].pdf", + "corrected": "401120069 2026-05-28 Císař, Petr [LZ hematologie] [kontrola, CLL z B-lymfocytů, B-CLLSLL 28% malých monoklon. B lymfocytů, del 13q14].pdf" + }, + { + "original": "425915482 2026-05-24 Lebedová, Zdenka [PZ lázeňská] [26APR2026–24MAY2026, st.p. fract. femoris+humeri l.dx., vertebrogenní sy, DM2, polyneuropatie DKK].pdf", + "corrected": "425915482 2026-05-24 Lebedová, Zdenka [PZ lázně] [26APR2026–24MAY2026, st.p. fract. femoris+humeri l.dx., vertebrogenní sy, DM2, polyneuropatie DKK].pdf" + }, + { + "original": "476014105 2026-03-24 Šmídová, Zdeňka [předoperační příprava] [TEP kolenního kloubu, nástup 22.06.2026, výkon 23.06.2026, albumin mimo normu].pdf", + "corrected": "476014105 2026-03-24 Šmídová, Zdeňka [žádost o předoperační vyšetření] [TEP kolenního kloubu, nástup 22.06.2026, výkon 23.06.2026, albumin mimo normu].pdf" + }, + { + "original": "476014105 2026-05-25 Šmídová, Zdeňka [LZ gynekologie] [osteopenie, mírný sestup přední stěny poševní, ko 10/26].pdf", + "corrected": "476014105 2026-05-25 Šmídová, Zdeňka [LZ gynekologie] [osteopenie, mírný sestup přední stěny poševní, ko 1026].pdf" + }, + { + "original": "5458071212 2026-06-05 Zívrová, Helena [LZ gastroenterologie] [kontrola, CN extenzivní postižení ilea, switch na ustekinumab 3/2025].pdf", + "corrected": "5458071212 2026-06-05 Zívrová, Helena [LZ gastroenterologie] [kontrola, CN extenzivní postižení ilea, switch na ustekinumab 32025].pdf" + }, + { + "original": "5853126928 2026-06-09 Fialová, Marta [Laboratoř] [dg. E78, C_CKD-EPI 1.45 ml/s → CHRIG2, S_Na 141↑].pdf", + "corrected": "5853126928 2026-06-09 Fialová, Marta [Laboratoř] [dg. E78, C_CKD-EPI 1.45 mls → CHRIG2, S_Na 141↑].pdf" + }, + { + "original": "7356020441 2026-06-09 Billouz, Hana [Laboratoř] [Stěr/Výtěr nos – primokultivace: Negativní].pdf", + "corrected": "7356020441 2026-06-09 Billouz, Hana [Laboratoř] [StěrVýtěr nos – primokultivace Negativní].pdf" + }, + { + "original": "8001030422 2026-05-15 Kalous, Petr [Laboratoř] [dg. M790, S_Anti-CCP IgG <1.0 negativní].pdf", + "corrected": "8001030422 2026-05-15 Kalous, Petr [Laboratoř] [dg. M790, S_Anti-CCP IgG 1.0 negativní].pdf" + }, + { + "original": "425915482 2026-05-04 Lebedová, Zdenka [deník krevního tlaku] [27APR–04MAY2026, Prestance 5/5mg ráno, Agen 100mg večer].pdf", + "corrected": "425915482 2026-05-04 Lebedová, Zdenka [domácí měření TK] [27APR–04MAY2026, Prestance 55mg ráno, Agen 100mg večer].pdf" + }, + { + "original": "536117166 2026-06-15 Jiráková, Božena [EKG] [bez hodnocení].pdf", + "corrected": "536117166 2026-06-15 Jiráková, Božena [EKG] [bez hodnocení].pdf" + }, + { + "original": "7355180789 2026-06-15 Švecová, Jitka [EKG] [bez hodnocení].pdf", + "corrected": "7355180789 2026-06-15 Švecová, Jitka [EKG] [bez hodnocení].pdf" + }, + { + "original": "7857173940 2026-06-15 Bytsiv, Lyubov [EKG] [bez hodnocení].pdf", + "corrected": "7857173940 2026-06-15 Bytsiv, Lyubov [EKG] [bez hodnocení].pdf" + }, + { + "original": "0562280048 2026-06-16 [EKG] [bez hodnocení].pdf", + "corrected": "0562280048 2026-06-16 [EKG] [bez hodnocení].pdf" + }, + { + "original": "7751120333 2026-06-10 Šmídová, Šárka [Laboratoř] [B_MPV 11 (↑), S_anti-HBs >1000 arbj (↑), eGFR , vit.D 43.8 nmol/l].pdf", + "corrected": "7751120333 2026-06-10 Šmídová, Šárka [Laboratoř] [B_MPV 11 (↑), S_anti-HBs 1000 arbj (↑), eGFR , vit.D 43.8 nmoll].pdf" + }, + { + "original": "891209 2026-06-15 [domácí měření TK] [18MAY–15JUN2026, průměr 13980, hypertenze 11d, zvýšený TK 8d].pdf", + "corrected": "891209 2026-06-15 [Holter TK] [18MAY–15JUN2026, průměr 139_80, hypertenze 11d, zvýšený TK 8d].pdf" + }, + { + "original": "7952090443 Kalousová, Eva split_011.pdf", + "corrected": "7952090443 2026-06-09 Kalousová, Eva [LZ urologie] [recidivující IMC].pdf" + }, + { + "original": "7952090443 Kalousová, Eva split_012.pdf", + "corrected": "7952090443 2026-06-02 Kalousová, Eva [kultivace moč] [negativní].pdf" } ] \ No newline at end of file diff --git a/Medevio/60 ScansProcessing/naming_rules.md b/Medevio/60 ScansProcessing/naming_rules.md index ff08974..e8d8dde 100644 --- a/Medevio/60 ScansProcessing/naming_rules.md +++ b/Medevio/60 ScansProcessing/naming_rules.md @@ -14,12 +14,14 @@ Tato pravidla platí vždy při generování polí `poznamka` a `nazev_souboru`. 3. Když je dokument typ "Laboratoř", do `poznamka` uváděj POUZE hodnoty mimo normu (patologické nálezy) — hodnoty v normě vynech. **Osmolalitu séra (Osmolalita, Osm, osmolality) NIKDY nezmiňuj — ani když je mimo normu, ani v jakékoli zkratce.** Toto je absolutní výjimka: osmolalita se do názvu souboru ani do poznámky nepíše nikdy za žádných okolností. Chybně: `C_Osmolalita 293 (↑)` — správně: tuto hodnotu zcela vynech. 4. Pokud laboratorní výsledky obsahují glomerulární filtraci — bývá označena jako eGFR, CKD-EPI nebo CK-EPI — do `poznamka` nikdy nepiš číselnou hodnotu eGFR. Místo toho uveď pouze klasifikaci dle stadií CHRIG1–CHRIG5. - - **Jednotka:** Nejprve zkontroluj jednotku uvedenou v laboratoři: - - Pokud je hodnota v **ml/s** nebo **ml/sec** (typicky malá čísla jako 0.8, 1.14, 1.5…), přenásob ×60 pro převod na ml/min. - - Pokud je hodnota v **ml/min** nebo **ml/min/1.73m²** (typicky velká čísla jako 55, 68, 90…), použij přímo. - - **Klasifikace** (v ml/min/1.73m²): ≥ 90 → CHRIG1, 60–89 → CHRIG2, 45–59 → CHRIG3a, 30–44 → CHRIG3b, 15–29 → CHRIG4, < 15 → CHRIG5. - - Prahové hodnoty pro orientaci při jednotce ml/s: ≥ 1.50 → G1, 1.00–1.49 → G2, 0.75–0.99 → G3a, 0.50–0.74 → G3b, 0.25–0.49 → G4, < 0.25 → G5. + - **NEJDŮLEŽITĚJŠÍ — jednotka:** Hodnota glomerulární filtrace bývá v ČR uvedena ve **dvou různých jednotkách** a klasifikace stadia se MUSÍ dělat až po převodu na ml/min: + - **ml/s** (resp. ml/sec, ml/s/1.73m²) — typicky malá čísla cca 0.2–2.3 (např. 0.8, **1.27**, 1.5). Tuto hodnotu **přenásob ×60**, abys dostal ml/min. + - **ml/min** (resp. ml/min/1.73m²) — typicky velká čísla 5–140 (např. 55, 68, 90). Použij přímo. + - **POZOR na typickou chybu:** malé číslo jako `1.27` je v **ml/s**, tj. `1.27 × 60 = 76 ml/min → CHRIG2`. NIKDY ho neklasifikuj jako by bylo v ml/min (76 by jinak vyšlo špatně jako CHRIG5). Pokud je hodnota menší než ~3, je téměř jistě v ml/s a patří přenásobit ×60. + - **Klasifikace** (vždy až v ml/min/1.73m²): ≥ 90 → CHRIG1, 60–89 → CHRIG2, 45–59 → CHRIG3a, 30–44 → CHRIG3b, 15–29 → CHRIG4, < 15 → CHRIG5. + - Prahové hodnoty pro orientaci přímo při jednotce ml/s: ≥ 1.50 → G1, 1.00–1.49 → **G2**, 0.75–0.99 → G3a, 0.50–0.74 → G3b, 0.25–0.49 → G4, < 0.25 → G5. - Klasifikaci uváděj pouze pokud je CHRIG2 nebo horší (tj. eGFR < 90 ml/min nebo < 1.50 ml/s) — CHRIG1 je v normě, nezmiňuj ho. + - Příklady: `1.27 ml/s → CHRIG2`, `0.92 ml/s → CHRIG3a`, `0.55 ml/s → CHRIG3b`, `68 ml/min → CHRIG2`, `38 ml/min → CHRIG3b`. 5. Když je dokument typ "Laboratoř" a zpráva obsahuje diagnózu (dg., dg:, diagnóza), umísti ji do `nazev_souboru` jako první část druhé závorky, tedy: `[Laboratoř] [dg. XY00 - stručná poznamka]`. 6. Zkratky a pojmenování: slovo „sono" (sonografie/ultrazvuk) piš vždy malými písmeny — `sono břicha`, `sono ŠŽ`, nikoli `SONO`. Štítnou žlázu označuj vždy zkratkou `ŠŽ`. Sonografii prsu/prsů (sono mamm., sono mamografie, sono mamma apod.) piš vždy jako `sono prsů`. Denzitometrii (DEXA, DXA, denzitometrie) piš vždy pouze jako `[DXA]` — bez prefixu LZ. Algologii piš vždy jako `[LZ léčba bolesti]`. Dermatovenerologii (dermatologie, dermatovenerologie, kožní) piš vždy jako `[LZ kožní]`. Angiologii piš vždy jako `[LZ cévní]`. 7. V číselných hodnotách VŽDY používej desetinnou tečku, nikoli desetinnou čárku. Toto pravidlo platí absolutně pro všechna čísla v `poznamka` i `nazev_souboru` — např. `TG 4.73`, nikoli `TG 4,73`.