notebook vb
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
# 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:
|
||||
```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)
|
||||
```python
|
||||
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.
|
||||
Reference in New Issue
Block a user