# MedicusWithClaude – poznámky pro Clauda ## Stav projektu - Zahájeno: 2026-03-13 - Cíl: průzkum Firebird DB Medicus → analýzy a reporty - Zatím: úspěšně čteme i zapisujeme do DB, rozumíme RTF formátu - **2026-03-17**: Obnovena session – Claude si přečetl poznámky, připraven pokračovat - **2026-03-18**: Obnovena session – Claude si přečetl poznámky, připraven pokračovat. Průběžně zapisuje do tohoto souboru. - **2026-03-20**: Obnovena session – merge logika z `test_import_single.py` integrována do `s03soubory.py`. Import pipeline kompletní. ## Bezpečnost - Pracujeme na **místní kopii** – poškození DB nevadí, obnova = 5 minut - Lze bez obav experimentovat, zapisovat testovací data atd. - Testovací záznamy nemusíme mazat ## Připojení k DB ```python import fdb conn = fdb.connect( dsn=r'localhost:c:\medicus 3\data\medicus.fdb', user='SYSDBA', password='masterkey', charset='win1250' ) ``` - fdb verze 2.0.4, Python na PATH (prostý `python` příkaz) - Firebird instalace: `C:\Program Files\Firebird\Firebird_2_5_CGM\bin\` - Jedná se o **HQbird** (IBSurgeon enhanced Firebird) – port **3070** (ne standardní 3050) - fbtracemgr trace nefungoval spolehlivě – doporučeno použít **FBScanner** (stejný výrobce) ## O Medicusu - Lékařský software pro praktického lékaře - Vyvíjen od ~roku 2000, organicky rostl přidáváním funkcí - Firebird 2.5 – nikdy nepřejdou na nový (desetitisíce instalací, 993 tabulek) - Žádné zabezpečení DB – vše čitelné přes SYSDBA/masterkey - Charset: win1250 ## DB fakta - 993 tabulek celkem - Charset: win1250 ## Klíčové tabulky ### KAR – Kartotéka (základní kámen Medicusu) - 6319 pacientů – všichni kdo kdy prošli ordinací - Primární klíč: **IDPAC** (interní ID, používá se jako FK v ostatních tabulkách) - Identifikace pacienta v ČR/pojišťovna: **RODCIS** (rodné číslo, bez lomítka, např. 7309208104) - **PRIJMENI**, **JMENO**, TITUL, TITULZA, DATNAR, POHLAVI - **POJ** – pojišťovna (3 znaky, např. 111 = VZP) - Adresy: TRV* (trvalé), PRE* (přechodné) - **VYRAZEN** – příznak vyřazeného pacienta - HLAVDGN – hlavní diagnóza ### DEKURS – záznamy z návštěv - 172 068 záznamů - **IDPAC** → vazba na KAR - **DATUM** (date), **CAS** (time) – kdy - **DEKURS** – text záznamu (BLOB, RTF nebo holý text) - **DGN1** – hlavní diagnóza (5 znaků, např. 'Z000 '), VDGN1-4 – vedlejší - **IDPRAC** – který pracovník - IDODD, IDUZI – oddělení/uživatel (u Buzalky = 2, 2, 6) - IDSKUPINA, IDTYP – volitelné (NULL OK) ## Základní schéma ``` KAR (kartotéka) ←IDPAC→ DEKURS (záznamy z návštěv) ``` ## Testovací pacient (uživatel) - **IDPAC = 9742** – Buzalka Vladimír, RC 7309208104, nar. 20.9.1973 - IDODD=2, IDPRAC=2, IDUZI=6 (použít při INSERT do DEKURS) ## RTF formát v DEKURS ### Základní struktura ``` {\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029 {\info{\bookmarks "popis","Typ:ID",číslo}} ← metadata Medicusu {\fonttbl{\f0\fnil\fcharset238 Arial;}{\f2...Courier New;}{\f5...Symbol;}} {\colortbl ;\red0\green0\blue255;\red255\green255\blue0;\red0\green128\blue0;\red192\green192\blue192;} {\stylesheet{styly...}} \uc1\pard\s10\plain... ← tělo záznamu } ``` ### Barvy (\cfX) - `\cf1` = modrá (odkazy) - `\cf2` = žlutá (highlight) - `\cf3` = zelená - `\cf4` = šedá (pozadí buněk tabulky `\clcbpat4`) ### Velikost písma (\fsXX = XX/2 bodů) - `\fs18` = 9pt (tabulky) - `\fs20` = 10pt (normální) - `\fs24` = 12pt atd. ### Řezy a formátování - `\b` = tučné, `\i` = kurzíva, `\ul` = podtržené - `\ql` = vlevo, `\qr` = vpravo, `\qc` = střed ### Styly (stylesheet) - `cs15/cs16` = Normální - `cs20` = Vkládaný text - `cs21` = Záhlaví (italic) - `cs22/cs23` = Odkaz (underline, modrá) - `s8/s10` = Vlevo (paragraph styl) ### Záložky (bookmarks v {\info}) - Klikatelné odkazy v Medicusu - Formát: `"popis","Typ:ID",číslo` - Typy: `Files:ID`, `MEDLAB:ID`, `Lab:ID`, `Ock:ID`, `VykA:ID` ### České znaky - RTF hex escape v win1250: `\'e1`=á, `\'9e`=ž, `\'fd`=ý atd. ### Tabulky - Plně funkční RTF tabulky (`\trowd`, `\cell`, `\cellx...`) - Používají se např. pro laboratorní výsledky (viz ID=243082, 50KB) ### Holý text (starší záznamy) - Některé starší záznamy jsou prostý text bez RTF obálky - Nutno detekovat: začíná na `{\\rtf` = RTF, jinak plain text ### Příklad minimálního RTF záznamu ``` {\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029 {\fonttbl{\f0\fnil\fcharset238 Arial;}} {\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;} \pard\plain\f0\fs20 Text záznamu zde\par } ``` ## INSERT do DEKURS – ověřený postup ```python import fdb, datetime conn = fdb.connect(...) cur = conn.cursor() cur.execute('SELECT MAX(ID) FROM DEKURS') next_id = cur.fetchone()[0] + 1 text = r'{\rtf1\ansi\ansicpg1250...\par' + '\n}' # raw string pro RTF! cur.execute(""" INSERT INTO DEKURS (ID, IDPAC, IDODD, IDPRAC, IDUZI, DATUM, CAS, DEKURS, DGN1) VALUES (?, ?, 2, 2, 6, ?, ?, ?, ?) """, (next_id, idpac, datum, cas, text, dgn1)) conn.commit() ``` - RTF string musí být **raw string** (r'...') kvůli zpětným lomítkům - DGN1 = 5 znaků s mezerou: `'Z000 '` - DATUM = datetime.date(...), CAS = datetime.time(...) ## Soubory v projektu - `explore_db.py` – výpis všech tabulek a jejich sloupců - `inspect_table.py` – detailní info o konkrétní tabulce (arg: název tabulky) - `sample_dekurs.py` – ukázky nejstarších a nejnovějších záznamů - `sample_dekurs2.py` – celý text jednoho záznamu - `sample_rtf.py` – nejdelší záznamy pacienta (dle délky RTF) - `analyze_rtf.py` – analýza RTF tagů v záznamu - `insert_test.py` – testovací INSERT do DEKURS - `CLAUDE_NOTES.md` – tento soubor ## Export FILES do externích DB (zjištěno 2026-03-17 přes FBScanner) ### Mechanismus přesunu Medicus vytváří **měsíční** externí Firebird databáze ve formátu `MEDICUS_FILES_YYYYMM.fdb`. **Postup exportu:** 1. SELECT dat z FILES po 10 záznamech: `select first 10 ID, BODY, DATUM from FILES where ID > X ORDER BY ID ASC` 2. Binární data (skeny) se zapíší do externího `.fdb` souboru 3. V hlavní DB se BODY přepíše referenčním číslem (ID v externím souboru): `update FILES set BODY = 379 where ID = 10009` 4. Do EXTERNI_DB se zapíše lokace: `insert into EXTERNI_DB (DBNAME, SERVER, PATH, HESLO) values ('DB202501', 'localhost/3053', 'u:\MEDICUS_FILES_202501.fdb', '')` ### Výsledek - Hlavní DB: FILES.BODY obsahuje jen malé číslo (referenci) místo MB blobu - Skenované soubory jsou fyzicky v `u:\MEDICUS_FILES_YYYYMM.fdb` - Medicus načítá soubory přes EXTERNI_DB tabulku (SERVER + PATH + HESLO) - Hesla v EXTERNI_DB jsou šifrovaná (base64 AES) ### FBScanner - Nainstalován, funguje jako proxy: Medicus → [3050] FBScanner → [3053] Firebird - Community Edition 2.5 – stačí pro monitoring - Export proveden na záloze (ne produkční DB) ## Přímý zápis do externí DB – dokončeno (2026-03-17) ### Nový soubor: `funkce_ext.py` Náhrada za `funkce.zapis_file()`. Ukládá PDF přímo do měsíční externí FDB místo do hlavní medicus.fdb. **Funkce:** `zapis_file_ext(vstupconnection, idpac, cesta, souborname, prvnizavorka, soubordate, souborfiledate, poznamka, ext_base_path=r'u:\\')` - Parametry shodné s `funkce.zapis_file()` → jen prohodit import - Vrací `fileid` stejně jako původní funkce **Co dělá:** 1. Z `soubordate` odvodí `YYYYMM` → `DBNAME = 'DB202603'` 2. Načte PDF jako bytes 3. Vyhledá EXTERNI_DB pro daný měsíc: - Nalezeno → připojí se k existující FDB (masterkey) - Nenalezeno → vytvoří novou `MEDICUS_FILES_YYYYMM.fdb`, zaregistruje do EXTERNI_DB 4. Vygeneruje `UID = uuid.uuid4().hex` (32 znaků) 5. Zapíše do DATA tabulky: `UID, DATA (blob), DATASIZE, CHUNK=0` 6. Sestaví 48bajtovou BODY referenci 7. Získá `fileid` z Gen_Files 8. Vloží do FILES s BODY = reference (ne blob) ### Úprava `s03soubory.py` - Přidán import `funkce_ext` - Volání `funkce.zapis_file(...)` nahrazeno `funkce_ext.zapis_file_ext(...)` ### BODY reference – binární formát (48 B) ``` magic 4 B = b'\xee\xbb\xaa\x0b' uid 32 B = UUID4 hex (ASCII, 32 znaků) dblen 4 B = len(DBNAME) jako little-endian uint32 dbname N B = DBNAME ASCII (typicky 'DB202603', 8 B) ``` Celkem typicky: 4 + 32 + 4 + 8 = **48 bajtů** ### Struktura DATA tabulky (externe DB) ``` UID VARCHAR(32) NOT NULL DATA BLOB SUB_TYPE 0 DATASIZE INTEGER CHUNK INTEGER ``` Generator: `GEN_UID` (Medicus ho asi nepoužívá přímo, UID je UUID string) ### EXTERNI_DB tabulka (hlavní DB) ``` DBNAME např. 'DB202603' SERVER např. 'localhost/3053' (FBScanner proxy) PATH např. 'u:\MEDICUS_FILES_202603.fdb' HESLO šifrované heslo (Medicus AES) – zkopírujeme z existující položky ``` Při vytváření nové DB: HESLO zkopírujeme z libovolné existující EXTERNI_DB položky (všechny používají masterkey, jen jinak zašifrované – ale stejný klíč). ### Poznámka k HESLO šifrování Hesla v EXTERNI_DB jsou šifrovaná Medicusem. Při vytváření nové DB proto zkopírujeme HESLO z existující položky (stejné heslo = stejný šifrovaný text). Medicus pak dokáže novou DB otevřít standardně. Pokud by to nefungovalo, lze heslo zadat manuálně přes správce Medicusu. ## Stav 2026-03-18 – nastudováno po obnově session ### Co bylo dokončeno (včera večer) - `funkce_ext.zapis_file_ext()` je **otestována a funkční** - `s03soubory.py` volá `zapis_file_ext` místo původní `funkce.zapis_file` - Celý import pipeline funguje end-to-end ### Celý import pipeline (s03soubory.py) 1. Scanuje složku `u:\NextcloudOrdinace\Dokumentace_ke_zpracování` 2. Ověří formát názvu souboru: `RC YYYY-MM-DD Jmeno, Jmeno. [prvnizavorka] [druhazavorka].pdf` 3. Ověří rodné číslo v KAR tabulce 4. Seskupí soubory podle RC (jeden pacient = jeden dekurs) 5. Pro každý PDF zavolá `funkce_ext.zapis_file_ext()` → zapíše do externí DB 6. Přesune soubor do `u:\NextcloudOrdinace\Dokumentace_zpracovaná` 7. Sestaví RTF se záložkami (klikatelné odkazy na soubory v Medicusu) 8. Zapíše dekurs do DEKURS tabulky ### Klíčové soubory - `funkce_ext.py` – zápis PDF do měsíční externí FDB (náhrada za funkce.zapis_file) - `funkce.py` – původní funkce (zapis_file, zapis_dekurs, get_files_id, get_idpac, convert_to1250) - `s03soubory.py` – hlavní import script (spouštět na Windows) - `s04_presun_externi_db.py` – utility: přesun ext DB souborů do u:\externi\ a update PATH v EXTERNI_DB - `test_ext_db4.py` – ověření BODY bajty z FILES tabulky (hex dump) ### Formát názvu souboru pro import ``` RC YYYY-MM-DD Prijmeni, Jmeno. [prvnizavorka] [druhazavorka].pdf např: 7309208104 2026-03-17 Buzalka, Vladimír. [EKG] [normální nález].pdf ``` ## Oprava RTF klikacích odkazů (2026-03-18) ### Problém `s03soubory.py` generoval RTF s `\cs22` pro styl odkazu, ale Medicus vyžaduje `\cs32`. Druhý+ bookmark neměl link styling vůbec. `poradi` se nikdy neinkrementoval. ### Zjištěno analýzou Medicusem vytvořeného DEKURS (ID=263480, AhojClaude.pdf) Správný RTF formát klikacího odkazu: ``` {\*\cs32\f0\ul\fs20\cf1 Odkaz;} ← stylesheet: cs32, NE cs22 \uc1\pard\s10\plain\cs20\f0\i\fs20 Přílohy: {\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 TEXT{\*\bkmkend 0}\par ``` - "Přílohy:" je inline s prvním odkazem na stejném `\pard` řádku - Každý další odkaz: `\pard\s10{\*\bkmkstart N}\plain\cs32\f0\ul\fs20\cf1 TEXT{\*\bkmkend N}\par` ### Opraveno v s03soubory.py + test_import_3files.py 1. `\cs22` → `\cs32` v stylesheet i těle RTF 2. Header `Vložené přílohy:` je na **samostatném** `\pard` řádku (RTF: `Vlo\'9een\'e9 p\'f8\'edlohy:`) 3. Všechny bookmarky mají stejný formát: `\pard\s10{\*\bkmkstart N}\plain\cs32\f0\ul\fs20\cf1 TEXT{\*\bkmkend N}\par` 4. Přidáno `poradi += 1` (bug: poradi se nikdy neinkrementoval) 5. Odstraněna proměnná `prvnibookmark` – již nepotřebná ### RTF kódování českých znaků (win1250) - ž = `\'9e`, é = `\'e9`, ř = `\'f8`, í = `\'ed`, á = `\'e1` - "Vložené přílohy:" → `Vlo\'9een\'e9 p\'f8\'edlohy:` ## Merge do existujícího dekurzu (2026-03-18) ### Rozhodovací logika (3 případy) 1. Poslední dekurs pacienta je z dnešního dne A má `Vložené přílohy:` → **přidat soubor DO existující sekce** (`pridat_do_sekce_prilohy`) 2. Poslední dekurs je z dnešního dne, ale sekci nemá → **prepend nové sekce na začátek** (`merge_rtf_prepend`) 3. Poslední dekurs je z jiného dne / neexistuje → **nový dekurs pro dnešek** - Funkce: `najdi_posledni_dekurs_dnes(conn, idpac, datum_vlozeni)` ### pridat_do_sekce_prilohy (přidání do existující sekce) 1. Spočítat `"Files:"` v `{\info{\bookmarks}}` = N → nový bkmkstart = N 2. Posunout všechny bkmkstart/bkmkend ≥ N o +1 (uvolnit index N) 3. Vložit nový `\pard` před `\pard\s10\plain\cs15\f0\fs20 \par` (konec sekce) 4. Vložit bookmark na pozici N do `{\info{\bookmarks}}` ### merge_rtf_prepend (prepend nové sekce) 1. Posunout existující `\bkmkstart N` / `\bkmkend N` o počet nových souborů 2. Přidat naše bookmarky NA ZAČÁTEK `{\info{\bookmarks ...}}` 3. Vložit naše `\pard` tělo PŘED první `\uc1\pard` existujícího těla ### Detekce sekce - Přítomnost: `PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"` – hledáme v RTF - Konec sekce: `PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'` ### Skripty - `test_import_merge.py` – 3 soubory → merge do dnešního dekurzu (bez sekce přílohy) - `test_import_single.py` – 1 soubor → merge DO existující sekce přílohy - `test_import_3files.py` – 3 soubory → vždy nový dekurs (bez merge logiky, starší) ### Stav testování (2026-03-18) - `test_import_3files.py` ✅ ověřeno – klikací odkazy fungují, "Vložené přílohy:" správně - `test_import_merge.py` ✅ připraveno – merge 3 souborů do dnešního dekurzu (bez sekce) - `test_import_single.py` ✅ připraveno – přidání 1 souboru DO existující sekce přílohy ### Stav s03soubory.py (2026-03-20) – KOMPLETNÍ ✅ - Merge logika plně integrována z `test_import_single.py` - Přidány konstanty `PRILOHY_HEADER`, `PRILOHY_CLOSING` - Přidány funkce: `najdi_posledni_dekurs_dnes()`, `ma_sekci_prilohy()`, `pridat_do_sekce_prilohy()`, `merge_rtf_prepend()` - RTF šablona přesunuta do konstanty `RTF_TEMPLATE` - Odstraněn dead code: `prvnibookmark`, staré šablony, nepoužívané `convert_to1250` - `pridat_do_sekce_prilohy()` podporuje **více souborů najednou** (oproti test_import_single.py, kde byl jen 1) - `idpac` se bere z prvního záznamu skupiny (čistěji než z `row` po skončení loopu) ### Rozhodovací logika s03soubory.py – 3 případy 1. Dnešní dekurs **má** sekci `Vložené přílohy` → soubory přidány **dovnitř** sekce (`pridat_do_sekce_prilohy`) 2. Dnešní dekurs **nemá** sekci příloh → nová sekce vložena **na začátek** (`merge_rtf_prepend`) 3. Žádný dnešní dekurs → **nový** záznam (INSERT) ### Stav testovacích skriptů - `test_import_3files.py` ✅ ověřeno – klikací odkazy fungují - `test_import_merge.py` ✅ otestováno – merge 3 souborů (jen 2 případy, bez detekce sekce) - `test_import_single.py` ✅ otestováno – všechny 3 případy, základ pro s03soubory.py ## Další postup (nápady) - Otestovat `s03soubory.py` na Windows se skutečnými soubory (všechny 3 případy) - Napsat `rtf_to_text()` pro extrakci čistého textu z dekurzů - Prozkoumat tabulky: LECH/LECD (léky?), POU (poukazy?), AMBULEKY (výkony?) - První report – domluvit s uživatelem co chce vidět