This commit is contained in:
2026-05-13 11:40:58 +02:00
parent 71b8ed676a
commit fe698a4232
3 changed files with 141 additions and 0 deletions
+1
View File
@@ -12,6 +12,7 @@ Spuštění: `python extract_patient_info.py` (bez argumentů = celá složka To
2. Claude Vision API (sonnet-4-6) extrahuje: jméno, RČ, datum, typ dokumentu, poznámku, navržený název, rotaci 2. Claude Vision API (sonnet-4-6) extrahuje: jméno, RČ, datum, typ dokumentu, poznámku, navržený název, rotaci
3. Ověří pacienta v Medicus Firebird (tabulka KAR, pole RODCIS/PRIJMENI/JMENO) 3. Ověří pacienta v Medicus Firebird (tabulka KAR, pole RODCIS/PRIJMENI/JMENO)
4. Fuzzy matching RČ při nenalezení: vynechání cifry + záměna podobných (0↔8, 1↔7, 5↔6, 3↔8) + checksum /11 4. Fuzzy matching RČ při nenalezení: vynechání cifry + záměna podobných (0↔8, 1↔7, 5↔6, 3↔8) + checksum /11
- Fallback: pokud RČ stále nenalezeno, vyhledá dle příjmení+jméno (z Claude) — status `by_name` / `by_name_multi`
5. Upozorní na duplicitu v `U:\Dropbox\Ordinace\Dokumentace_zpracovaná\` 5. Upozorní na duplicitu v `U:\Dropbox\Ordinace\Dokumentace_zpracovaná\`
6. Interaktivní schválení / oprava názvu 6. Interaktivní schválení / oprava názvu
7. JPG/PNG → skutečné PDF (správná orientace, DPI=150, quality=80) 7. JPG/PNG → skutečné PDF (správná orientace, DPI=150, quality=80)
@@ -1066,5 +1066,69 @@
{ {
"original": "6604011073 2025-12-08 Kramule, Petr [Laboratoř] [stěr/výtěr krk, fyziologická mikrobiota HCD].pdf", "original": "6604011073 2025-12-08 Kramule, Petr [Laboratoř] [stěr/výtěr krk, fyziologická mikrobiota HCD].pdf",
"corrected": "6604011073 2025-12-08 Kramule, Petr [Laboratoř] [stěrvýtěr krk, fyziologická mikrobiota HCD].pdf" "corrected": "6604011073 2025-12-08 Kramule, Petr [Laboratoř] [stěrvýtěr krk, fyziologická mikrobiota HCD].pdf"
},
{
"original": "8504043768 2026-05-12 Pospíchal, David [LZ praktický lékař] [přeregistrace, výpis z dokumentace, poslední návštěva 226 resp. infekt, elevace CRP].pdf",
"corrected": "8504043768 2026-05-12 Pospíchal, David [LZ praktický lékař] [přeregistrace, výpis z dokumentace, poslední návštěva FEB2026 resp. infekt, elevace CRP].pdf"
},
{
"original": "496203179 2026-05-11 Netřebská, Blanka [LZ rehabilitace] [vyrozumění o pokračování v řízení, LRP KLP, koxartroza, gonartroza].pdf",
"corrected": "496203179 2026-05-11 Netřebská, Blanka [Schválení lázně OZP] [schválení 21 dní, VII_7, koxartroza, gonartroza].pdf"
},
{
"original": "460610084 2024-07-30 Baladrán, Antonín [LZ oční] [myopie levis o.u. cum astigmat., presbyopie, cat sen CN o.u., op. kataraktu výhledově].pdf",
"corrected": "460610084 2024-07-30 Baladrán, Antonín [LZ oční] [může řídit s brýlemi, myopie levis o.u. cum astigmat., presbyopie, cat sen CN o.u., op. kataraktu výhledově].pdf"
},
{
"original": "460610084 2026-01-20 Baladrán, Antonín [LZ urologie] [papilární karcinom L ledviny pT1aN1M0, st.p. NE l.sin. 2022, progrese paraaort. LU vlevo, CT 52026].pdf",
"corrected": "460610084 2026-01-20 Baladrán, Antonín [LZ urologie] [papilární karcinom L ledviny pT1aN1M0, st.p. NE l.sin. 2022, progrese paraaort. LU vlevo, CT MAY2026].pdf"
},
{
"original": "505805215 2026-04-30 Sedláčková, Vlasta [LZ kardiologie] [st.p. náhradě mitrální chlopně SJM Epic No29, EF LK 55% sy ND NYHA I-II, lehká sekund. trikusp. regurg., PASP 32-37mmHg, paFiS dg. 52023, rivaroxaban].pdf",
"corrected": "505805215 2026-04-30 Sedláčková, Vlasta [LZ kardiologie] [st.p. náhradě mitrální chlopně SJM Epic No29, EF LK 55% sy ND NYHA I-II, lehká sekund. trikusp. regurg., PASP 32-37mmHg, paFiS dg. 52023, rivaroxaban, kontrola +6m].pdf"
},
{
"original": "510802325 2026-05-11 Simion, Vladimír [LZ oční] [keratokonj. l.utr., vlevo defekt epitelu, susp. expoziční keratopatie, laxita víček].pdf",
"corrected": "510802325 2026-05-11 Simion, Vladimír [LZ oční] [zánět spojivek l.utr., vlevo defekt epitelu, susp. expoziční keratopatie, laxita víček].pdf"
},
{
"original": "5409194296 2026-05-05 Sklenář, Vladimír [LZ interna] [kontrola, obezita BMI 38.22, DM2, HTN, paFiS, NT-proBNP 551.70, bez efektu Mysimby].pdf",
"corrected": "5409194296 2026-05-05 Sklenář, Vladimír [LZ interna] [kontrola, obezita BMI 38.22, DM2, HTN, paFiS, NT-proBNP 551.70, bez efektu Mysimby, kontrola NOV2026].pdf"
},
{
"original": "5761140275 2019-09-11 Vrňáková, Jaroslava [LZ kardiologie] [EKG: bez projevů myokard. ischemie či hypertrofie, bez závažných poruch rytmu].pdf",
"corrected": "5761140275 2019-09-11 Vrňáková, Jaroslava [EKG] [EKG bez projevů myokard. ischemie či hypertrofie, bez závažných poruch rytmu].pdf"
},
{
"original": "5761140275 2016-11-19 Vrňáková, Jaroslava [EKG] [EKG bez přesvědčivých patomorfol. změn, poruchy rytmu ani převodu].pdf",
"corrected": "5761140275 2016-10-19 Vrňáková, Jaroslava [EKG] [EKG bez přesvědčivých patomorfol. změn, poruchy rytmu ani převodu].pdf"
},
{
"original": "5955100272 2025-04-08 Čulíková, Hana [LZ endokrinologie] [Autoimunitní thyreoiditida, hypotyr., substituce, DXA osteopénie femur/krček/předloktí, dyslipidémie, HTN].pdf",
"corrected": "5955100272 2025-04-08 Čulíková, Hana [LZ endokrinologie] [Autoimunitní thyreoiditida, hypotyr., substituce, DXA osteopénie femurkrčekpředloktí, dyslipidémie, HTN].pdf"
},
{
"original": "8806100413 2025-12-31 Zbranek, Adam [LZ ortopedie] [st.p. fraktuře diafýz tibie a fibuly vpravo, výkon 23.12., rány klidné].pdf",
"corrected": "8806100413 2025-12-31 Zbranek, Adam [LZ ortopedie] [kontrola, st.p. fraktuře diafýz tibie a fibuly vpravo, výkon 23.12., rány klidné].pdf"
},
{
"original": "9901040160 2026-05-12 Tvrz, Matěj [Holter TK] [HTN, avg SYS 134/139 den/120 noc, dipper 14%, SYS>140 42% den, 58% noc].pdf",
"corrected": "9901040160 2026-05-12 Tvrz, Matěj [Holter TK] [HTN, avg SYS 134139 den120 noc, dipper 14%, SYS140 42% den, 58% noc].pdf"
},
{
"original": "460610084 2026-05-12 Baladrán, Antonín [Lékařský posudek řidič] [zdravotně způsobilý s podmínkou sk. B brýle, platnost do 12.05.2028].pdf",
"corrected": "460610084 2026-05-12 Baladrán, Antonín [Posudek ŘP] [schopen B s brýlemi].pdf"
},
{
"original": "460610084 2026-05-12 Baladrán, Antonín [Posudek ŘP] [schopen B s brýlemi].pdf",
"corrected": "460610084 2026-05-12 Baladrán, Antonín [Prohlášení ŘP] [cítí se na to].pdf"
},
{
"original": "485507406 2026-05-12 Jourová, Eva [Posudek ŘP] [schopna B].pdf",
"corrected": "485507406 2026-05-12 Jourová, Eva [Posudek ŘP] [schopen bez omezení B].pdf"
},
{
"original": "485507406 2026-05-12 Jourová, Eva [Prohlášení ŘP] [cítí se zdráva, sk. B, SOS léky po štípnutí hmyzem].pdf",
"corrected": "485507406 2026-05-12 Jourová, Eva [Prohlášení ŘP] [cítí se na to].pdf"
} }
] ]
@@ -165,6 +165,59 @@ def _rc_checksum_ok(rc: str) -> bool:
return int(digits) % 11 == 0 return int(digits) % 11 == 0
return True return True
def _parse_jmeno_prijmeni(name_str: str) -> tuple[str, str] | None:
"""Parsuje 'Příjmení, Jméno' nebo 'Příjmení Jméno' -> (prijmeni, jmeno)."""
name_str = name_str.strip()
if "," in name_str:
parts = [p.strip() for p in name_str.split(",", 1)]
if len(parts) == 2 and parts[0] and parts[1]:
return parts[0], parts[1]
parts = name_str.split()
if len(parts) >= 2:
return parts[0], " ".join(parts[1:])
return None
def _lookup_by_name(cur, prijmeni: str, jmeno: str) -> list[dict]:
"""Vyhledá pacienty v KAR podle příjmení a prvního slova jména."""
jmeno_first = jmeno.split()[0] if jmeno.split() else jmeno
cur.execute(
"SELECT IDPAC, PRIJMENI, JMENO, RODCIS FROM KAR "
"WHERE UPPER(PRIJMENI) = UPPER(?)",
(prijmeni,)
)
rows = cur.fetchall()
result = []
for row in rows:
db_jmeno = (row[2] or "").strip().upper()
if db_jmeno.startswith(jmeno_first.upper()):
result.append({
"idpac": row[0],
"prijmeni": row[1].strip(),
"jmeno": row[2].strip(),
"rodcis": row[3].strip(),
})
return result
def verify_patient_by_name(name_str: str) -> dict:
"""Vyhledá pacienta v Medicus podle jména — fallback když RČ chybí."""
parsed = _parse_jmeno_prijmeni(name_str)
if not parsed:
return {"status": "not_found", "patient": None, "rc_corrected": None}
prijmeni, jmeno = parsed
con = _medicus_connect()
if con is None:
return {"status": "offline", "patient": None, "rc_corrected": None}
try:
cur = con.cursor()
matches = _lookup_by_name(cur, prijmeni, jmeno)
if not matches:
return {"status": "not_found", "patient": None, "rc_corrected": None}
if len(matches) == 1:
return {"status": "by_name", "patient": matches[0], "rc_corrected": None}
return {"status": "by_name_multi", "patient": matches[0], "rc_corrected": None, "all_matches": matches}
finally:
con.close()
def verify_patient(rc_raw: str) -> dict: def verify_patient(rc_raw: str) -> dict:
rc = re.sub(r"\D", "", rc_raw or "") rc = re.sub(r"\D", "", rc_raw or "")
if not rc: if not rc:
@@ -570,6 +623,24 @@ def process_file(pdf_path: Path):
nazev = nazev.replace(rc_from_scan, verif["rc_corrected"], 1) nazev = nazev.replace(rc_from_scan, verif["rc_corrected"], 1)
print(f" → RČ opraveno: {rc_from_scan}{verif['rc_corrected']}") print(f" → RČ opraveno: {rc_from_scan}{verif['rc_corrected']}")
# Fallback: RČ nenalezeno → zkus vyhledat podle jména z Claude
if verif["status"] == "not_found" and info.get("jmeno"):
jmeno_z_claude = info["jmeno"]
print(f" RČ nenalezeno, zkouším vyhledat dle jména: {jmeno_z_claude}")
verif_name = verify_patient_by_name(jmeno_z_claude)
if verif_name["status"] in ("by_name", "by_name_multi"):
verif = verif_name
rc_z_medicus = re.sub(r"\D", "", verif["patient"]["rodcis"])
p = verif["patient"]
print(f" → Nalezeno dle jména: {p['prijmeni']} {p['jmeno']} | RČ {p['rodcis']}")
if verif_name["status"] == "by_name_multi":
print(f" ⚠ Více shod ({len(verif_name['all_matches'])}) — přijat první výsledek")
# Aktualizuj RČ v navrženém názvu
if nazev and rc_z_medicus:
nazev = re.sub(r"^null\s*", rc_z_medicus + " ", nazev)
if not nazev.startswith(rc_z_medicus):
nazev = re.sub(r"^\S+\s*", rc_z_medicus + " ", nazev)
# Info řádky pro dialog # Info řádky pro dialog
status = verif["status"] status = verif["status"]
patient = verif.get("patient") patient = verif.get("patient")
@@ -583,6 +654,11 @@ def process_file(pdf_path: Path):
elif status == "fuzzy": elif status == "fuzzy":
info_lines.append(f"⚠ RČ ze skenu '{rc_ocr}' → opraveno na {verif['rc_corrected']}") info_lines.append(f"⚠ RČ ze skenu '{rc_ocr}' → opraveno na {verif['rc_corrected']}")
info_lines.append(f" Pacient: {patient['prijmeni']} {patient['jmeno']} | RČ {patient['rodcis']}") info_lines.append(f" Pacient: {patient['prijmeni']} {patient['jmeno']} | RČ {patient['rodcis']}")
elif status == "by_name":
info_lines.append(f"✓ Nalezeno dle jména: {patient['prijmeni']} {patient['jmeno']} | RČ {patient['rodcis']}")
elif status == "by_name_multi":
count = len(verif.get("all_matches", []))
info_lines.append(f"⚠ Nalezeno dle jména ({count} shod, 1. výsledek): {patient['prijmeni']} {patient['jmeno']} | RČ {patient['rodcis']}")
elif status == "not_found": elif status == "not_found":
info_lines.append(f"✗ RČ '{rc_ocr}' nenalezeno v Medicus") info_lines.append(f"✗ RČ '{rc_ocr}' nenalezeno v Medicus")
else: else: