364 lines
16 KiB
Markdown
364 lines
16 KiB
Markdown
# 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', '<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í `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
|