This commit is contained in:
2026-06-16 17:56:47 +02:00
parent 9b6f89f437
commit 9edfddae95
3 changed files with 192 additions and 6 deletions
@@ -909,6 +909,97 @@ def build_corrections_prompt() -> str:
return "\n".join(lines) + "\n\n" 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 02.3, v ml/min 0140 — 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 ──────────────────────────────────────────────────────── # ─── Claude Vision API ────────────────────────────────────────────────────────
def extract_info(pdf_path: Path, known_patient: str | None = None, known_rc: str | None = None) -> dict: 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" "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" 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 " "- \"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." "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: if not is_ekg and nazev:
varianty = generate_name_variants(info, 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 { return {
"path": pdf_path, "path": pdf_path,
"is_ekg": is_ekg, "is_ekg": is_ekg,
@@ -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", "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" "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á] [26APR202624MAY2026, st.p. fract. femoris+humeri l.dx., vertebrogenní sy, DM2, polyneuropatie DKK].pdf",
"corrected": "425915482 2026-05-24 Lebedová, Zdenka [PZ lázně] [26APR202624MAY2026, 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] [27APR04MAY2026, Prestance 5/5mg ráno, Agen 100mg večer].pdf",
"corrected": "425915482 2026-05-04 Lebedová, Zdenka [domácí měření TK] [27APR04MAY2026, 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] [18MAY15JUN2026, průměr 13980, hypertenze 11d, zvýšený TK 8d].pdf",
"corrected": "891209 2026-06-15 [Holter TK] [18MAY15JUN2026, 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"
} }
] ]
+7 -5
View File
@@ -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. 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í CHRIG1CHRIG5. 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í CHRIG1CHRIG5.
- **Jednotka:** Nejprve zkontroluj jednotku uvedenou v laboratoři: - **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:
- 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. - **ml/s** (resp. ml/sec, ml/s/1.73m²) — typicky malá čísla cca 0.22.3 (např. 0.8, **1.27**, 1.5). Tuto hodnotu **přenásob ×60**, abys dostal ml/min.
- Pokud je hodnota v **ml/min** nebo **ml/min/1.73m²** (typicky velká čísla jako 55, 68, 90…), použij přímo. - **ml/min** (resp. ml/min/1.73m²) — typicky velká čísla 5140 (např. 55, 68, 90). Použij přímo.
- **Klasifikace** (v ml/min/1.73m²): ≥ 90 → CHRIG1, 6089 → CHRIG2, 4559 → CHRIG3a, 3044 → CHRIG3b, 1529 → CHRIG4, < 15 → CHRIG5. - **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.
- Prahové hodnoty pro orientaci při jednotce ml/s: ≥ 1.50 → G1, 1.001.49 → G2, 0.750.99 → G3a, 0.500.74 → G3b, 0.250.49 → G4, < 0.25 → G5. - **Klasifikace** (vždy až v ml/min/1.73m²): ≥ 90 → CHRIG1, 6089 → CHRIG2, 4559 → CHRIG3a, 3044 → CHRIG3b, 1529 → CHRIG4, < 15 → CHRIG5.
- Prahové hodnoty pro orientaci přímo při jednotce ml/s: ≥ 1.50 → G1, 1.001.49 → **G2**, 0.750.99 → G3a, 0.500.74 → G3b, 0.250.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. - 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]`. 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í]`. 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`. 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`.