16 KiB
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.pyintegrována dos03soubory.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
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ý
pythonpří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ý textcs21= 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áznamusample_rtf.py– nejdelší záznamy pacienta (dle délky RTF)analyze_rtf.py– analýza RTF tagů v záznamuinsert_test.py– testovací INSERT do DEKURSCLAUDE_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:
- SELECT dat z FILES po 10 záznamech:
select first 10 ID, BODY, DATUM from FILES where ID > X ORDER BY ID ASC - Binární data (skeny) se zapíší do externího
.fdbsouboru - 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 - 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í
fileidstejně jako původní funkce
Co dělá:
- Z
soubordateodvodíYYYYMM→DBNAME = 'DB202603' - Načte PDF jako bytes
- 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
- Vygeneruje
UID = uuid.uuid4().hex(32 znaků) - Zapíše do DATA tabulky:
UID, DATA (blob), DATASIZE, CHUNK=0 - Sestaví 48bajtovou BODY referenci
- Získá
fileidz Gen_Files - Vloží do FILES s BODY = reference (ne blob)
Úprava s03soubory.py
- Přidán import
funkce_ext - Volání
funkce.zapis_file(...)nahrazenofunkce_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.pyvolázapis_file_extmísto původnífunkce.zapis_file- Celý import pipeline funguje end-to-end
Celý import pipeline (s03soubory.py)
- Scanuje složku
u:\NextcloudOrdinace\Dokumentace_ke_zpracování - Ověří formát názvu souboru:
RC YYYY-MM-DD Jmeno, Jmeno. [prvnizavorka] [druhazavorka].pdf - Ověří rodné číslo v KAR tabulce
- Seskupí soubory podle RC (jeden pacient = jeden dekurs)
- Pro každý PDF zavolá
funkce_ext.zapis_file_ext()→ zapíše do externí DB - Přesune soubor do
u:\NextcloudOrdinace\Dokumentace_zpracovaná - Sestaví RTF se záložkami (klikatelné odkazy na soubory v Medicusu)
- 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_DBtest_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
\cs22→\cs32v stylesheet i těle RTF- Header
Vložené přílohy:je na samostatném\pardřádku (RTF:Vlo\'9een\'e9 p\'f8\'edlohy:) - Všechny bookmarky mají stejný formát:
\pard\s10{\*\bkmkstart N}\plain\cs32\f0\ul\fs20\cf1 TEXT{\*\bkmkend N}\par - Přidáno
poradi += 1(bug: poradi se nikdy neinkrementoval) - 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)
- 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) - Poslední dekurs je z dnešního dne, ale sekci nemá
→ prepend nové sekce na začátek (
merge_rtf_prepend) - 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)
- Spočítat
"Files:"v{\info{\bookmarks}}= N → nový bkmkstart = N - Posunout všechny bkmkstart/bkmkend ≥ N o +1 (uvolnit index N)
- Vložit nový
\pardpřed\pard\s10\plain\cs15\f0\fs20 \par(konec sekce) - Vložit bookmark na pozici N do
{\info{\bookmarks}}
merge_rtf_prepend (prepend nové sekce)
- Posunout existující
\bkmkstart N/\bkmkend No počet nových souborů - Přidat naše bookmarky NA ZAČÁTEK
{\info{\bookmarks ...}} - Vložit naše
\pardtělo PŘED první\uc1\pardexistují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řílohytest_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)idpacse bere z prvního záznamu skupiny (čistěji než zrowpo skončení loopu)
Rozhodovací logika s03soubory.py – 3 případy
- Dnešní dekurs má sekci
Vložené přílohy→ soubory přidány dovnitř sekce (pridat_do_sekce_prilohy) - Dnešní dekurs nemá sekci příloh → nová sekce vložena na začátek (
merge_rtf_prepend) - Žá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.pyna 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