Files
medicus/MedicusWithClaude/s03soubory_01_FINAL_notes.md
T
2026-04-04 08:57:00 +02:00

9.4 KiB
Raw Blame History

s03soubory_01_FINAL.py dokumentace

Finální verze importního skriptu pro vkládání PDF dokumentů do dekurzů Medicusu.

Datum finalizace: 2026-04-04 Autor: Vladimír Buzalka + Claude (Anthropic)


Co skript dělá

Zpracuje PDF soubory v určené složce (cesta) a pro každý soubor:

  1. Ověří správnost názvu souboru (formát, RC, datum)
  2. Zkontroluje, zda cílový dekurz není zamčený v Medicusu
  3. Zapíše soubor do externí databáze souborů (tabulka FILES)
  4. Přesune soubor do složky zpracovaných
  5. Vloží odkaz (bookmark) do dekurzu pacienta jako RTF záznam

Adresáře

Proměnná Cesta (testovací) Popis
cesta u:\testimport Vstupní složka sem patří soubory ke zpracování
cestazpracovana u:\testimportzpracovana Cílová složka sem se přesouvají zpracované soubory

V produkci tyto cesty nahradit skutečnými složkami (Nextcloud/Dropbox).


Formát názvu souboru

Každý PDF soubor musí mít název ve tvaru:

RC YYYY-MM-DD Příjmení, Jméno [typ dokumentu] [poznámka].pdf

Příklad:

7309208104 2020-10-16 Buzalka, Vladimír [LZ ortopedie] [VAS LS páteře, obstřik].pdf
Část Popis
RC Rodné číslo pacienta (9 nebo 10 číslic) musí existovat v tabulce KAR
YYYY-MM-DD Datum dokumentu
Příjmení, Jméno Jméno pacienta (jen pro čitelnost, nepoužívá se k vyhledání)
[typ dokumentu] První závorka druh nálezu (LZ ortopedie, EKG, Lab. nález…)
[poznámka] Druhá závorka krátký popis obsahu (může být prázdná [])

Chybný soubor (špatný název, RC nenalezeno v DB) je přejmenován přidáním prefixu :

♥chybny soubor.pdf

Skript ho přeskočí a nechá v složce pro ruční opravu.


Databázové tabulky

Tabulka DB Popis
KAR Medicus (medicus.fdb) Kartotéka pacientů lookup RC → IDPAC
DEKURS Medicus (medicus.fdb) Dekurzy čtení a zápis RTF záznamu
FILES Externí DB (MEDICUS_FILES_YYYYMM.fdb) Binární uložení PDF souborů

Klíčová novinka oproti s03soubory.py ochrana před zamčeným dekurzem

Problém

Medicus drží exkluzivní zámek (Firebird row lock) na záznamu tabulky DEKURS po celou dobu, kdy má lékařka pacienta otevřeného. Kdyby skript provedl UPDATE DEKURS do zamčeného záznamu, přepsal by lékařčiny neuložené změny.

Řešení detekce zámku pomocí vlákna s timeoutem

Firebird neumí NOWAIT nastavit per-statement v SQL (syntaxe FOR UPDATE WITH LOCK NOWAIT není platná). Nastavení NOWAIT je vlastnost transakce, nikoliv dotazu. Knihovna fdb navíc toto nastavení spolehlivě nepodporuje.

Zvolené řešení: spuštění pokusu o zámek ve vedlejším vlákně s timeoutem 2 sekundy.

hlavní vlákno                    vedlejší vlákno (_pokus_o_zamek)
─────────────────                ─────────────────────────────────
t.start()              ──────►   fdb.connect() [nové spojení]
t.join(timeout=2s)               SELECT ... FOR UPDATE WITH LOCK
                                   ├── záznam volný → fetchone() → rollback() → konec
                                   └── záznam zamčený → čeká (blokuje)...
─────────────────
po 2 sekundách:
  t.is_alive()?
    ANO → záznam zamčený → RuntimeError → přeskoč skupinu
    NE  → záznam volný → pokračuj se zápisem

Důležitý detail pořadí operací

Kontrola zámku probíhá PŘED zápisem do FILES a PŘED přesunem souboru. Kdyby se pořadí obrátilo, mohlo by dojít k situaci:

  • soubor zapsán do FILES ✓
  • soubor přesunut do zpracovaných ✓
  • dekurz zamčen → UPDATE selže
  • soubor je pryč ze vstupní složky, ale odkaz v dekurzu chybí

Správné pořadí:

1. Zkontroluj zámek dekurzu (NOWAIT)
   └── zamčeno → přeskoč (soubory zůstanou v cesta)
2. Zapiš soubory do ext. DB (FILES)
3. Přesuň soubory do zpracovaných
4. Sestav RTF
5. UPDATE nebo INSERT do DEKURS

Logika vkládání do dekurzu 3 případy

Po úspěšné kontrole zámku skript rozhodne, co s dekurzem udělat:

Existuje dnešní dekurz pro pacienta?
      │
      ├── ANO → obsahuje sekci "Vložené přílohy"?
      │         ├── ANO  → Případ 1: přidá nové soubory DOVNITŘ sekce
      │         └── NE   → Případ 2: vloží celou sekci na ZAČÁTEK dekurzu (prepend)
      │
      └── NE  → Případ 3: vytvoří NOVÝ dekurz ze šablony RTF_TEMPLATE

Případ 1 pridat_do_sekce_prilohy()

Dnešní dekurz existuje a má sekci „Vložené přílohy".

  • Spočítá kolik odkazů (Files:) už sekce obsahuje → nové indexy bookmarků navazují
  • Nové \pard řádky vloží před uzavírací prázdný řádek sekce (PRILOHY_CLOSING)
  • Nové bookmarky přidá na konec {\info{\bookmarks ...}}
  • Provede UPDATE DEKURS SET DEKURS = ? WHERE ID = ?

Případ 2 merge_rtf_prepend()

Dnešní dekurz existuje, ale nemá sekci příloh (lékařka do něj napsala text).

  • Přečísluje existující bookmarky (posunutí o počet nových souborů)
  • Novou sekci „Vložené přílohy" vloží na začátek těla RTF (před \uc1\pard)
  • Nové bookmarky předřadí před existující v {\info{\bookmarks ...}}
  • Lékařčin text zůstane zachován, jen se posune níž
  • Provede UPDATE DEKURS SET DEKURS = ? WHERE ID = ?

Případ 3 nový INSERT

Pro dnešní datum neexistuje žádný dekurz.

  • Vyplní RTF_TEMPLATE (bookmarky + tělo sekce příloh)
  • Provede INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)
  • iduzi=6 (Vladimír Buzalka), idprac=2, idodd=2

RTF formát dekurzu

Struktura bookmarku

Každý přiložený soubor je reprezentován jako:

  1. Bookmark entry v {\info{\bookmarks ...}}:

    "2020-10-16 LZ ortopedie: VAS LS páteře, obstřik","Files:21923",9
    
    • "popis" zobrazený text odkazu
    • "Files:ID" odkaz na záznam v tabulce FILES (slouží Medicusu k načtení souboru)
    • 9 číslo fontu/stylu (od 9, každý další +7)
  2. Vizuální řádek v těle RTF:

    \pard\s10{\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 2020-10-16 LZ ortopedie: VAS LS páteře, obstřik{\*\bkmkend 0}\par
    
    • \bkmkstart N / \bkmkend N index bookmarku (0, 1, 2…)
    • \cs32\ul\cf1 styl „Odkaz" (modrý podtržený text)

Konstanty pro detekci sekce příloh (win1250 RTF escape)

PRILOHY_HEADER  = r"Vlo\'9een\'e9 p\'f8\'edlohy:"   # "Vložené přílohy:"
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'  # uzavírací prázdný řádek

Funkce přehled

Funkce Popis
restore_files_for_import(retezec) Debug utilita vrátí soubory z Nextcloudu zpět do Dropboxu. Nepoužívá se v produkci.
kontrola_rc(rc, connection) Ověří zda RC existuje v KAR, vrátí IDPAC nebo False.
kontrola_struktury(souborname, connection) Ověří formát názvu souboru a existenci RC v DB.
vrat_info_o_souboru(souborname, connection) Parsuje název souboru, dohledá IDPAC, vrátí tuple s metadaty.
prejmenuj_chybny_soubor(souborname, cesta) Přidá prefix k chybnému souboru.
_pokus_o_zamek(dekurs_id, vysledek) Interní běží ve vlákně, zkouší zamknout dekurz.
zkus_zamknout_dnesni_dekurs(conn, idpac, datum_vlozeni, timeout_sec=2) Zjistí zda dnešní dekurz existuje a není zamčený. Vyhodí RuntimeError pokud je zamčený.
ma_sekci_prilohy(rtf) Vrátí True pokud RTF obsahuje sekci „Vložené přílohy".
pridat_do_sekce_prilohy(rtf, bookmark_list, filenameforbookmark_list) Případ 1 přidá soubory do existující sekce příloh.
merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new) Případ 2 vloží sekci příloh na začátek existujícího dekurzu.

Ošetření chyb

Situace Chování
Soubor má chybný název Přejmenován na ♥soubor.pdf, přeskočen
RC nenalezeno v KAR Přejmenován na ♥soubor.pdf, přeskočen
Dekurz zamčený (timeout vlákna) Skupina přeskočena, soubory zůstanou v cesta
DB konflikt při zamykání (-913 deadlock) Skupina přeskočena, soubory zůstanou v cesta
Přesun souboru selže 3 pokusy s 5s pauzou, poté varování
Jiná DB chyba Výjimka se propaguje, skript havaruje

Vývoj a testování

Verze Soubor Co přibilo
Prototyp test_import_FINAL.py Ruční zadání IDPAC a DATUM, ověření RTF logiky
v1 s03soubory.py Automatický parsing RC z názvu, dávkování po skupinách
v1 FINAL s03soubory_01_FINAL.py Ochrana před zamčeným dekurzem (threading + timeout)

Jak byl objeven problém se zámky

Experimentem bylo ověřeno, že Medicus drží Firebird row lock na záznamu DEKURS po celou dobu, kdy má lékařka pacienta otevřeného (SELECT FIRST 1 ... FOR UPDATE WITH LOCK z Pythonu čekalo dokud lékařka neuložila). NOWAIT nelze nastavit přes SQL syntaxi ani spolehlivě přes fdb TPB bajty, proto bylo zvoleno řešení přes vlákno s timeoutem.