Files
medicus/MedicusWithClaude/CLAUDE_NOTES.md
T
michaela.buzalkova 598a376000 lenovo
2026-04-25 08:27:31 +02:00

20 KiB
Raw Blame History

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í.
  • 2026-04-01: Opravena chyba BlobReader invalid BLOB handle v dekurz_report.py viz níže.

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

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

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', '<encrypted>')

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í YYYYMMDBNAME = '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 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

Oprava BlobReader v dekurz_report.py (2026-04-01)

Chyba

Exception ignored while calling deallocator <function BlobReader.__del__>:
fdb.fbcore.DatabaseError: BlobReader.close/isc_close_blob: invalid BLOB handle (-901)

Skript fungoval správně (exit code 0, Excel uložen), ale garbage collector házel warningy.

Příčina

fdb vrací BLOB sloupce jako BlobReader objekty. Po conn.close() se při GC pokoušely zavřít handle, který již neexistoval.

Oprava

Explicitně zavřít BlobReader hned po .read(), ještě za živa spojení:

# PŘED (špatně):
rtf = dekurs_blob.read() if hasattr(dekurs_blob, 'read') else (dekurs_blob or '')

# PO (správně):
if hasattr(dekurs_blob, 'read'):
    rtf = dekurs_blob.read()
    dekurs_blob.close()
else:
    rtf = dekurs_blob or ''

Obecné pravidlo

Kdykoli čteme BLOB z fdb, vždy .close() po .read() jinak GC po conn.close() hlásí chybu.


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

KAR.POZNAMKA automatické tagy (2026-04-09)

Pole POZNAMKA v tabulce KAR je BLOB SUB_TYPE 1 (text blob, win1250) Medicus ho renderuje jako RTF stejně jako DEKURS. Lze psát i prostý text.

Konvence automatických tagů

Automaticky zapisované informace se ukládají na začátek POZNAMKA ve formátu:

[[klic:hodnota1, hodnota2]] #zapsáno DD-MM-YYYY HH:MM#
  • Oddělovače [[ a ]] se v lékařských poznámkách přirozeně nevyskytují
  • Tag lze programově najít a nahradit regexem
  • Ruční text lékaře/sestry pod tagem zůstává nedotčen

Implementovaný tag: prev_prohlidka

[[prev_prohlidka:15-05-2024 01022, 15-04-2026]] #zapsáno 09-04-2026 14:30#
  • 15-05-2024 = datum poslední PP
  • 01022 = kód výkonu (01022 = dospělí, 01021 = starší varianta)
  • 15-04-2026 = nejdřívější možný termín příští PP (23 měsíců od poslední)
  • Skript: MedicusWithClaudeTest/prev_prohlidka.py

Tabulka VZPARC výkonové dávky pojišťovnám (zjištěno 2026-04-09)

Zachyceno přes Firebird trace log při exportu dávek z Medicusu.

Struktura

  • 1954 záznamů, data od 2007 (s DAVKA blobem od 2013)
  • Klíčové sloupce: ID, ROK, DRUH, DATUMOD, DATUMDO, DAVKA (blob), ICP, ODB, IDICZ
  • Vazba na ICZ přes IDICZ

Typy dávek (DRUH)

DRUH Popis
98 Výkonová dávka obsahuje výkony vykázané pojišťovně (DP98 formát)
05 Doplňková dávka (DP05)
80 Registrační dávka přihlášení/odhlášení pacientů (DP80)
36 Jiný typ

DAVKA blob formát (CP852)

Stejný formát jako KDAVKA v PORTAL, dekódování identické:

text = raw.encode('cp1250', errors='replace').decode('cp852', errors='replace')

Struktura DP98 dávky:

DP98...   hlavička (IČP, rok, měsíc, disk, počet případů, částka)
A ...     ambulantní případ: RC pacienta na pozici 34, délka 10
V ...     výkon: V + DDMM + YYYY + KOD(5) + ...
Z ...     ZULP
L ...     lék

Parsování RC z A řádku

aktualni_rc = line[34:44].strip()   # pevná pozice, délka 10

Parsování výkonu z V řádku

dd   = int(line[1:3])
mm   = int(line[3:5])
yyyy = int(line[5:9])
kod  = line[9:14].strip()   # 5znakový kód výkonu

Poznámka: PORTAL vs VZPARC

  • PORTAL starší tabulka, KDAVKA blob, data jen do 2015
  • VZPARC správná tabulka pro výkonové dávky, data od 2013 do dnes
  • Pro hledání výkonů per pacient vždy používat VZPARC

Skript prev_prohlidka.py (MedicusWithClaudeTest/)

Účel: najde datum poslední preventivní prohlídky (PP) pro každého registrovaného pacienta a zapíše/aktualizuje tag v KAR.POZNAMKA.

Co dělá

  1. Načte registrované pacienty (přesný dotaz přes REGISTR + ICP, IČP=09305001)
  2. Projde všechny VZPARC DRUH=98 dávky, hledá výkony 01022 nebo 01021
  3. Pro každého pacienta zachová jen nejnovější datum PP
  4. Zapíše/přepíše tag [[prev_prohlidka:...]] na začátek KAR.POZNAMKA

Výsledek posledního spuštění (2026-04-09)

  • Registrovaných pacientů: 1621
  • Dávek VZPARC DRUH=98: 778
  • Pacientů s nalezenou PP: 1289
  • Zapsáno do KAR: 1120
  • Nenalezeno (odregistrovaní nebo bez PP): 169

Závislosti

pip install python-dateutil

(fdb již musí být nainstalováno)