This commit is contained in:
2026-05-12 16:15:15 +02:00
parent b693987a5c
commit 7ac050b466
3 changed files with 335 additions and 27 deletions
@@ -1062,5 +1062,9 @@
{
"original": "3568c410-090f-49d3-b898-8fef12f05397.pdf",
"corrected": "8459699809 2026-04-20 Bysmak, Mariia [sono žil DK] [žíly volně průchodné, varikózní dilatace VSM bilat se známkami chlopenní insuficience].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"
}
]
@@ -197,6 +197,119 @@ def check_duplicates(rc: str, datum: str) -> list[str]:
return [name for name in _dokumentace_index if name.startswith(prefix)]
# ─── EKG zpracování ──────────────────────────────────────────────────────────
_EKG_FLAG = "rotated-by-script"
def _is_ekg(pdf_path: Path) -> bool:
"""Detekuje EKG PDF podle metadat — PDFCreator 2.4.x je specifický pro EKG přístroj."""
if pdf_path.suffix.lower() != ".pdf":
return False
try:
import fitz
doc = fitz.open(str(pdf_path))
meta = doc.metadata
doc.close()
haystack = " ".join(filter(None, [
meta.get("creator", ""), meta.get("producer", "")
])).lower()
return "pdfcreator" in haystack
except Exception:
return False
def _ekg_rotate_if_needed(pdf_path: Path):
"""Otočí první stránku o 90° CW a odstraní případnou druhou stránku."""
import fitz
doc = fitz.open(str(pdf_path))
meta = doc.metadata
keywords = meta.get("keywords", "") or ""
if _EKG_FLAG in keywords:
doc.close()
return
page = doc[0]
page.set_rotation((page.rotation + 90) % 360)
if doc.page_count > 1:
doc.delete_page(1)
meta["keywords"] = (keywords + " " + _EKG_FLAG).strip()
doc.set_metadata(meta)
tmp = pdf_path.with_suffix(".tmp.pdf")
doc.save(tmp, deflate=True)
doc.close()
os.replace(tmp, pdf_path)
print(" [EKG] Stránka otočena o 90°.")
def _ekg_ocr(pdf_path: Path) -> str:
import fitz
import pytesseract
from PIL import Image as _PILImage
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
doc = fitz.open(str(pdf_path))
pix = doc[0].get_pixmap(dpi=300)
img = _PILImage.frombytes("RGB", [pix.width, pix.height], pix.samples)
doc.close()
return pytesseract.image_to_string(img, lang="ces", config="--psm 6")
def _ekg_extract_rc(text: str) -> str | None:
m = re.search(r"(\d{6})\s*/?\s*(\d{3,4})", text)
if not m:
return None
return m.group(1) + m.group(2).zfill(4)
def _ekg_extract_date(text: str) -> str | None:
"""Vrátí datum ve formátu YYYY-MM-DD nebo None."""
m = re.search(r"(\d{1,2})[\.,]\s*(\d{1,2})[\.,]\s*(\d{4})", text)
if m:
d, mo, y = m.groups()
return f"{y}-{mo.zfill(2)}-{d.zfill(2)}"
for pat in [r"\b(\d{2})(\d{2})(\d{4})\b", r"\b(\d{2})(\d{1})(\d{4})\b"]:
for m in re.finditer(pat, text):
d, mo, y = m.groups()
if 1 <= int(d) <= 31 and 1 <= int(mo) <= 12 and 1900 <= int(y) <= 2100:
return f"{y}-{mo.zfill(2)}-{d.zfill(2)}"
return None
def extract_info_ekg(pdf_path: Path) -> dict:
"""EKG větev: rotace in-place, Tesseract OCR, Medicus ověření."""
_ekg_rotate_if_needed(pdf_path)
print(" [EKG] OCR přes Tesseract...")
raw_text = _ekg_ocr(pdf_path)
print(f"\n--- EKG OCR TEXT ---\n{raw_text}\n--- KONEC ---\n")
rc_ocr = _ekg_extract_rc(raw_text)
date_iso = _ekg_extract_date(raw_text)
print(f" [EKG] RČ: {rc_ocr or 'NENALEZENO'} | Datum: {date_iso or 'NENALEZENO'}")
print(f" [EKG] Ověřuji v Medicus (RČ: {rc_ocr or '?'})...")
verif = verify_patient(rc_ocr or "")
rc_final = rc_ocr
if verif["status"] == "fuzzy" and verif.get("rc_corrected"):
rc_final = verif["rc_corrected"]
print(f" [EKG] RČ opraveno: {rc_ocr}{rc_final}")
patient = verif.get("patient")
name_part = f"{patient['prijmeni']}, {patient['jmeno']}" if patient else ""
if rc_final and date_iso:
nazev = f"{rc_final} {date_iso}{(' ' + name_part) if name_part else ''} [EKG] [bez hodnocení].pdf"
else:
nazev = None
return {
"rodne_cislo": rc_final,
"datum_zpravy": date_iso,
"nazev_souboru": nazev,
"_verif": verif,
"_rc_ocr": rc_ocr or "",
}
# ─── Korekce (few-shot příklady) ─────────────────────────────────────────────
def load_corrections() -> list[dict]:
@@ -414,50 +527,64 @@ def _parse_split_filename(name: str) -> tuple[str, str] | None:
def process_file(pdf_path: Path):
print(f"\nSoubor: {pdf_path.name}")
# Spusť načítání indexu dokumentace na pozadí — hotovo za dobu volání Claude
# Spusť načítání indexu dokumentace na pozadí — hotovo za dobu volání Claude/OCR
start_dokumentace_index()
# 1. Otevři preview originálu
is_ekg = _is_ekg(pdf_path)
split = None
if is_ekg:
# EKG větev: rotace in-place PŘED preview, pak Tesseract OCR + Medicus
print(" [EKG] Detekován EKG soubor (PDFCreator).")
info = extract_info_ekg(pdf_path)
nazev = info.get("nazev_souboru") or pdf_path.name
rc_from_scan = re.sub(r"\D", "", info.get("rodne_cislo") or "")
verif = info["_verif"]
rc_ocr = info["_rc_ocr"]
# 1. Otevři preview (pro EKG: soubor je již otočen)
preview, geom_file = open_preview(pdf_path)
below_y = read_preview_bottom(geom_file)
# 2. Zjisti RČ a jméno — buď z názvu (split soubor) nebo přes Claude Vision API
split = _parse_split_filename(pdf_path.name)
if split:
rc_from_scan, name_from_filename = split
print(f" Split soubor — RČ z názvu: {rc_from_scan}, jméno: {name_from_filename}")
# Claude stále voláme, ale předáme mu identitu pacienta — ať se soustředí na obsah
info = extract_info(pdf_path, known_patient=name_from_filename, known_rc=rc_from_scan)
# RC a jméno bereme z názvu souboru, ne z Claudovy odpovědi
nazev = info.get("nazev_souboru") or pdf_path.name
nazev = re.sub(r"^\d{9,10}\s+", f"{rc_from_scan} ", nazev) # přepiš RC v názvu naším
else:
info = extract_info(pdf_path)
nazev = info.get("nazev_souboru") or pdf_path.name
rc_from_scan = re.sub(r"\D", "", info.get("rodne_cislo") or "")
if not is_ekg:
# 2. Zjisti RČ a jméno — buď z názvu (split soubor) nebo přes Claude Vision API
split = _parse_split_filename(pdf_path.name)
if split:
rc_from_scan, name_from_filename = split
print(f" Split soubor — RČ z názvu: {rc_from_scan}, jméno: {name_from_filename}")
info = extract_info(pdf_path, known_patient=name_from_filename, known_rc=rc_from_scan)
nazev = info.get("nazev_souboru") or pdf_path.name
nazev = re.sub(r"^\d{9,10}\s+", f"{rc_from_scan} ", nazev)
else:
info = extract_info(pdf_path)
nazev = info.get("nazev_souboru") or pdf_path.name
rc_from_scan = re.sub(r"\D", "", info.get("rodne_cislo") or "")
# 3. Medicus ověření + fuzzy matching RČ
print(f" Ověřuji v Medicus (RČ: {rc_from_scan})...")
verif = verify_patient(rc_from_scan)
# 3. Medicus ověření + fuzzy matching RČ
print(f" Ověřuji v Medicus (RČ: {rc_from_scan})...")
verif = verify_patient(rc_from_scan)
rc_ocr = rc_from_scan
# Oprava RČ při fuzzy matchi (jen pro nesplit soubory — u split máme RC spolehlivé)
if not split and verif["status"] == "fuzzy" and verif.get("rc_corrected") and nazev:
nazev = nazev.replace(rc_from_scan, verif["rc_corrected"], 1)
print(f" → RČ opraveno: {rc_from_scan}{verif['rc_corrected']}")
# Oprava RČ při fuzzy matchi (jen pro nesplit soubory — u split máme RC spolehlivé)
if not split and verif["status"] == "fuzzy" and verif.get("rc_corrected") and nazev:
nazev = nazev.replace(rc_from_scan, verif["rc_corrected"], 1)
print(f" → RČ opraveno: {rc_from_scan}{verif['rc_corrected']}")
# Info řádky pro dialog
status = verif["status"]
patient = verif.get("patient")
info_lines = []
if split:
if is_ekg:
info_lines.append("⚡ EKG soubor — Tesseract OCR")
elif split:
info_lines.append(f"⚡ Split soubor — identita z názvu: {name_from_filename} | RČ {rc_from_scan}")
if status == "ok":
info_lines.append(f"✓ Medicus: {patient['prijmeni']} {patient['jmeno']} | RČ {patient['rodcis']}")
elif status == "fuzzy":
info_lines.append(f"⚠ RČ ze skenu '{rc_from_scan}' → 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']}")
elif status == "not_found":
info_lines.append(f"✗ RČ '{rc_from_scan}' nenalezeno v Medicus")
info_lines.append(f"✗ RČ '{rc_ocr}' nenalezeno v Medicus")
else:
info_lines.append("— Medicus nedostupný (offline)")
@@ -468,7 +595,7 @@ def process_file(pdf_path: Path):
info_lines.append(f"⚠ DUPLICITA: {', '.join(duplicity)}")
if not info_lines:
info_lines = ["[Claude nevrátil název — uprav ručně]"]
info_lines = ["[uprav ručně]"]
print(" Otevírám dialog pro schválení názvu...")
final_name = run_rename_dialog(nazev, info_lines, below_y=below_y)