This commit is contained in:
2026-05-20 08:45:15 +02:00
parent 2dae83b224
commit ccf84aa282
4 changed files with 127 additions and 49 deletions
@@ -1378,5 +1378,49 @@
{
"original": "split_013.pdf",
"corrected": "1006055083 2026-05-18 Šmíd, Jiří [EKG] [bez hodnocení].pdf"
},
{
"original": "5955100272 2022-06-29 Čulíková, Hana [LZ endokrinologie] [autoimunitní thyreoiditida ve fázi hypotyreózy, autoprotilátky pozit., substituce, arteriální hypertenze, dyslipidémie, zvýšená glykémie nalačno].pdf",
"corrected": "5955100272 2022-06-29 Čulíková, Hana [LZ endokrinologie] [autoimunitní thyreoiditida ve fázi hypotyreózy, autoprotilátky pozit., substituce, arteriální hypertenze, dyslipidémie, zvýšená glykémie nalačno, ko +3m].pdf"
},
{
"original": "5955100272 2024-04-10 Čulíková, Hana [LZ endokrinologie] [seropozitivní AI thyreoiditida ve fázi hypotyreózy, substituce, arteriální hypertenze, dyslipidémie, zvýšená glykémie nalačno, osteopénie femur/krček/předloktí].pdf",
"corrected": "5955100272 2024-04-10 Čulíková, Hana [LZ endokrinologie] [seropozitivní AI thyreoiditida ve fázi hypotyreózy, substituce, arteriální hypertenze, dyslipidémie, zvýšená glykémie nalačno, osteopénie femurkrčekpředloktí].pdf"
},
{
"original": "5955100272 Čulíková, Hana split_009.pdf",
"corrected": "5955100272 2026-05-19 Čulíková, Hana [EKG] [bez hodnocení].pdf"
},
{
"original": "6804160990 2025-10-02 Pokorný, Bohumil [LZ chirurgie] [st.p. kýla klasicky 2008, TAPP 2018, tlaky a bolest L tříslo, recidivu nehmatám, sono + CT třísla, ko urol/ortop/koloskopie].pdf",
"corrected": "6804160990 2025-10-02 Pokorný, Bohumil [LZ chirurgie] [st.p. kýla klasicky 2008, TAPP 2018, tlaky a bolest L tříslo, recidivu nehmatám, sono + CT třísla, ko urolortopkoloskopie].pdf"
},
{
"original": "7261142658 2025-06-18 Šindelářová, Věra [LZ imunologie] [časté resp. infekce po COVID 11/2021, prick testy neg., spirometrie v normě, FeNO 19 PPB, ANA neg., IgE 19.8].pdf",
"corrected": "7261142658 2025-06-18 Šindelářová, Věra [LZ imunologie] [časté resp. infekce po COVID 112021, prick testy neg., spirometrie v normě, FeNO 19 PPB, ANA neg., IgE 19.8].pdf"
},
{
"original": "315620122 2025-12-19 Tesaříková, Květuše [LZ endokrinologie] [hypotyreóza, TSH 2.514, fT4 17.84, Euthyrox 50ug, ekzém DKK, DEXA 5/2026].pdf",
"corrected": "315620122 2025-12-19 Tesaříková, Květuše [LZ endokrinologie] [hypotyreóza, TSH 2.514, fT4 17.84, Euthyrox 50ug, ekzém DKK, DEXA 52026].pdf"
},
{
"original": "315620122 2026-01-14 Tesaříková, Květuše [LZ kardiologie] [EF 65%, lehká porucha diastol. funkce, mitrální regurgitace 3/4, hraniční PK].pdf",
"corrected": "315620122 2026-01-14 Tesaříková, Květuše [LZ kardiologie] [EF 65%, lehká porucha diastol. funkce, mitrální regurgitace 34, hraniční PK, ko JUN2026].pdf"
},
{
"original": "515803110 2025-12-16 Rygerová, Lenka [Laboratoř] [dg. J069 - CKD-EPI CHRIG5, leukocyty 13.1, MCH 27.4, trombocyty 521, RF 24, ESR 78].pdf",
"corrected": "515803110 2025-12-16 Rygerová, Lenka [Laboratoř] [dg. J069 - CKD-EPI CHRIG2, leukocyty 13.1, MCH 27.4, trombocyty 521, RF 24, ESR 78].pdf"
},
{
"original": "6860090446 2025-12-15 Diepoldová, Kateřina [Laboratoř] [dg. K29 - CKD-EPI CHRIG3b, MCV 80.0 (↓), MCH 27.8 (↓)].pdf",
"corrected": "6860090446 2025-12-15 Diepoldová, Kateřina [Laboratoř] [dg. K29 - CKD-EPI CHRIG2, MCV 80.0 (↓), MCH 27.8 (↓)].pdf"
},
{
"original": "7153180551 2025-12-15 Šamšová, Irena [Laboratoř] [dg. J069 - Anti HAV total 25.22 IU/L (↑), pacient s ochrannou hladinou protilátek].pdf",
"corrected": "7153180551 2025-12-15 Šamšová, Irena [Laboratoř] [dg. J069 - Anti HAV total 25.22 IUL (↑), pacient s ochrannou hladinou protilátek].pdf"
},
{
"original": "9959130423 2025-12-17 Sládková, Aneta [Laboratoř] [dg. N309 - E. coli 10E6 CFU/ml, citlivá na ampicilin, cefuroxim, cotrimoxazol, nitrofurantoin, fosfomycin].pdf",
"corrected": "9959130423 2025-12-17 Sládková, Aneta [Laboratoř] [dg. N309 - E. coli 10E6 CFUml, citlivá na ampicilin, cefuroxim, cotrimoxazol, nitrofurantoin, fosfomycin].pdf"
}
]
+7 -1
View File
@@ -13,7 +13,13 @@ Tato pravidla platí vždy při generování polí `poznamka` a `nazev_souboru`.
- Pokud datum hospitalizace nelze určit, druhou závorku napiš bez datumu.
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: eGFR ≥ 90 → CHRIG1, 6089 → CHRIG2, 4559 → CHRIG3a, 3044 → CHRIG3b, 1529 → CHRIG4, < 15 → CHRIG5. Klasifikaci uváděj pouze pokud je CHRIG2 nebo horší (tj. eGFR < 90) — CHRIG1 je v normě, nezmiňuj ho.
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:
- 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, 6089 → CHRIG2, 4559 → CHRIG3a, 3044 → CHRIG3b, 1529 → CHRIG4, < 15 → CHRIG5.
- 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.
- 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.
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`.
+1
View File
@@ -29,6 +29,7 @@ Pro vývoj: `TESTOVANI = True` + `PATH_TO_TESTFILE` na začátku skriptu.
| `/` | otočit CCW (counterclockwise) |
| `*` | otočit CW (clockwise) |
| `Del` / `.` | smaž stránku (vynech z exportu) |
| `0` | znovu spustí OCR na aktuální stránce (smaže cache pro tuto stránku) |
| `Enter` | exportuj všechny skupiny |
| `Esc` | konec |
+75 -48
View File
@@ -12,6 +12,7 @@ Numerická klávesnice:
5 / Space přepni hranici pacienta před touto stránkou
8 / Up přesuň stránku doleva (swap)
2 / Down přesuň stránku doprava (swap)
0 znovu spustí OCR na aktuální stránce (smaže cache pro tuto stránku)
- výběr pacienta ručně z Medicusu
Enter exportuj všechny skupiny do Split/
Esc konec
@@ -271,63 +272,76 @@ class OcrWorker:
def stop(self):
self._stop.set()
def _run(self):
def _ocr_page(self, i: int):
"""Spustí OCR pipeline pro stránku i a uloží výsledek do self.results."""
import pytesseract
pytesseract.pytesseract.tesseract_cmd = TESSERACT_PATH
n = len(self.doc)
for i in range(n):
page = self.doc[i]
mat = fitz.Matrix(2.0, 2.0) # 144 DPI — dostatečné pro OCR
pix = page.get_pixmap(matrix=mat, colorspace=fitz.csRGB)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
# 1. Tesseract
rc = None
tess_text = None
try:
tess_text = pytesseract.image_to_string(img, lang="ces")
rc = _extract_rc(tess_text)
except Exception as e:
print(f"[OCR str.{i+1}] Tesseract: {e}")
# 2. Medicus — první pokus
medicus = _verify_medicus(rc) if rc else None
# 3. Claude Vision — když Tesseract nenašel RČ, nebo našel ale Medicus nezná
claude_raw = None
claude_usage = None
if not rc or (medicus and medicus.get("status") == "not_found"):
try:
rc_claude, claude_raw, claude_usage = self._claude_rc(img)
if rc_claude:
medicus_claude = _verify_medicus(rc_claude)
if medicus_claude.get("status") in ("ok", "fuzzy"):
print(f"[OCR str.{i+1}] Claude opravil RČ: {rc}{rc_claude}")
rc = rc_claude
medicus = medicus_claude
elif not rc:
rc = rc_claude
medicus = medicus_claude
except Exception as e:
print(f"[OCR str.{i+1}] Claude: {e}")
result = {
"rc": rc,
"medicus": medicus,
"tesseract_text": tess_text,
"claude_raw": claude_raw,
"claude_usage": claude_usage,
}
with self._lock:
self.results[i] = result
def _run(self):
for i in range(len(self.doc)):
if self._stop.is_set():
break
if i in self.results:
continue # cache hit
page = self.doc[i]
mat = fitz.Matrix(2.0, 2.0) # 144 DPI — dostatečné pro OCR
pix = page.get_pixmap(matrix=mat, colorspace=fitz.csRGB)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
# 1. Tesseract
rc = None
tess_text = None
try:
tess_text = pytesseract.image_to_string(img, lang="ces")
rc = _extract_rc(tess_text)
except Exception as e:
print(f"[OCR str.{i+1}] Tesseract: {e}")
# 2. Medicus — první pokus
medicus = _verify_medicus(rc) if rc else None
# 3. Claude Vision — když Tesseract nenašel RČ, nebo našel ale Medicus nezná
claude_raw = None
claude_usage = None
if not rc or (medicus and medicus.get("status") == "not_found"):
try:
rc_claude, claude_raw, claude_usage = self._claude_rc(img)
if rc_claude:
medicus_claude = _verify_medicus(rc_claude)
if medicus_claude.get("status") in ("ok", "fuzzy"):
print(f"[OCR str.{i+1}] Claude opravil RČ: {rc}{rc_claude}")
rc = rc_claude
medicus = medicus_claude
elif not rc:
rc = rc_claude
medicus = medicus_claude
except Exception as e:
print(f"[OCR str.{i+1}] Claude: {e}")
result = {
"rc": rc,
"medicus": medicus,
"tesseract_text": tess_text,
"claude_raw": claude_raw,
"claude_usage": claude_usage,
}
self.results[i] = result
self._ocr_page(i)
self._save_cache()
self.on_page_done(i)
def rerun_page(self, page_idx: int, on_done):
"""Znovu spustí OCR pro jednu stránku (ignoruje cache). Volá on_done(page_idx) po dokončení."""
def _worker():
with self._lock:
self.results.pop(page_idx, None)
self._ocr_page(page_idx)
self._save_cache()
on_done(page_idx)
threading.Thread(target=_worker, daemon=True).start()
def _claude_rc(self, img: Image.Image) -> tuple[Optional[str], Optional[str], Optional[dict]]:
import anthropic, base64
@@ -633,7 +647,7 @@ class SplitterUI:
"1/3: přesuň stránku "
"/: otočit ↺CCW *: otočit ↻CW "
"Del/.: smaž stránku "
"-: vyber pacienta ručně "
"0: znovu OCR -: vyber pacienta ručně "
"Enter: exportuj Esc: konec"
)
self.bot_label = tk.Label(
@@ -738,6 +752,7 @@ class SplitterUI:
103: "num7", 105: "num9",
97: "num1", 99: "num3", 110: "numdot",
111: "numslash", 106: "numstar", 109: "numminus",
96: "num0",
}
action = numpad.get(kc) or {
"Left": "num4", "Right": "num6",
@@ -748,6 +763,7 @@ class SplitterUI:
"KP_Divide": "numslash", "KP_Multiply": "numstar",
"slash": "numslash", "asterisk": "numstar",
"KP_Subtract": "numminus", "minus": "numminus",
"Insert": "num0", "KP_Insert": "num0",
}.get(ks)
if action == "num4":
@@ -772,6 +788,8 @@ class SplitterUI:
self._delete_page()
elif action == "numminus":
self._open_patient_picker()
elif action == "num0":
self._rerun_ocr_current()
elif ks in ("Return", "KP_Enter"):
self._export()
elif ks == "Escape":
@@ -843,6 +861,15 @@ class SplitterUI:
PatientPickerDialog(self.root, on_select)
def _rerun_ocr_current(self):
page_idx = self.page_order[self.cursor]
self.ocr_results.pop(page_idx, None)
self._redraw()
self.ocr_worker.rerun_page(
page_idx,
on_done=lambda idx: self.root.after(0, self._on_ocr_done, idx),
)
def _update_boundaries_around(self, pos: int):
"""Přidá/odstraní hranice kolem pozice pos podle potvrzených pacientů."""
def confirmed_rc(p: int) -> Optional[str]: