From 27338a9b8526d1547c6117bc790cac8d342062ab Mon Sep 17 00:00:00 2001 From: "vladimir.buzalka" Date: Fri, 13 Mar 2026 14:37:00 +0100 Subject: [PATCH] Add MedicusWithClaude project - DB exploration scripts and notes Co-Authored-By: Claude Sonnet 4.6 --- MedicusWithClaude/CLAUDE_NOTES.md | 163 ++++++++++++++++++++++++++++ MedicusWithClaude/analyze_rtf.py | 34 ++++++ MedicusWithClaude/explore_db.py | 45 ++++++++ MedicusWithClaude/insert_test.py | 23 ++++ MedicusWithClaude/inspect_table.py | 28 +++++ MedicusWithClaude/sample_dekurs.py | 37 +++++++ MedicusWithClaude/sample_dekurs2.py | 25 +++++ MedicusWithClaude/sample_rtf.py | 35 ++++++ 8 files changed, 390 insertions(+) create mode 100644 MedicusWithClaude/CLAUDE_NOTES.md create mode 100644 MedicusWithClaude/analyze_rtf.py create mode 100644 MedicusWithClaude/explore_db.py create mode 100644 MedicusWithClaude/insert_test.py create mode 100644 MedicusWithClaude/inspect_table.py create mode 100644 MedicusWithClaude/sample_dekurs.py create mode 100644 MedicusWithClaude/sample_dekurs2.py create mode 100644 MedicusWithClaude/sample_rtf.py diff --git a/MedicusWithClaude/CLAUDE_NOTES.md b/MedicusWithClaude/CLAUDE_NOTES.md new file mode 100644 index 0000000..258d26e --- /dev/null +++ b/MedicusWithClaude/CLAUDE_NOTES.md @@ -0,0 +1,163 @@ +# 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 + +## 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) + +## 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 + +## Další postup (ná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 diff --git a/MedicusWithClaude/analyze_rtf.py b/MedicusWithClaude/analyze_rtf.py new file mode 100644 index 0000000..8da123a --- /dev/null +++ b/MedicusWithClaude/analyze_rtf.py @@ -0,0 +1,34 @@ +import fdb, re + +conn = fdb.connect( + dsn=r'localhost:c:\medicus 3\data\medicus.fdb', + user='SYSDBA', password='masterkey', charset='win1250' +) +cur = conn.cursor() +cur.execute('SELECT DEKURS FROM DEKURS WHERE ID=243082') +text = cur.fetchone()[0] +text = text.read() if hasattr(text, 'read') else text +text = text.decode('windows-1250', errors='replace') if isinstance(text, bytes) else text + +# colortbl +ct = re.search(r'\\colortbl[^}]+\}', text) +if ct: print('COLORTBL:', ct.group(0)) + +# stylesheet +ss = re.search(r'\\stylesheet\{.+?\}(?=\n)', text, re.DOTALL) +if ss: print('\nSTYLESHEET:', ss.group(0)[:600]) + +# Najdi různé styly použité v textu +print('\n--- Použité RTF tagy (unikátní) ---') +tags = re.findall(r'\\[a-z]+\d*', text) +from collections import Counter +for tag, count in Counter(tags).most_common(40): + print(f" {tag:<20} {count}x") + +# Ukázka tabulky +tbl_start = text.find(r'\trowd') +if tbl_start > 0: + print('\n--- Začátek tabulky ---') + print(text[tbl_start:tbl_start+300]) + +conn.close() diff --git a/MedicusWithClaude/explore_db.py b/MedicusWithClaude/explore_db.py new file mode 100644 index 0000000..c8e87e6 --- /dev/null +++ b/MedicusWithClaude/explore_db.py @@ -0,0 +1,45 @@ +""" +Průzkum Medicus databáze – výpis tabulek a jejich sloupců. +""" +import fdb + +conn = fdb.connect( + dsn=r'localhost:c:\medicus 3\data\medicus.fdb', + user='SYSDBA', + password='masterkey', + charset='win1250' +) + +cur = conn.cursor() + +# Všechny uživatelské tabulky +cur.execute(""" + SELECT rdb$relation_name + FROM rdb$relations + WHERE rdb$view_blr IS NULL + AND (rdb$system_flag IS NULL OR rdb$system_flag = 0) + ORDER BY rdb$relation_name +""") +tables = [row[0].strip() for row in cur.fetchall()] + +print(f"Počet tabulek: {len(tables)}\n") +print("=== SEZNAM TABULEK ===") +for t in tables: + print(f" {t}") + +print("\n=== SLOUPCE KAŽDÉ TABULKY ===") +for table in tables: + cur.execute(""" + SELECT rf.rdb$field_name, f.rdb$field_type, f.rdb$field_length + FROM rdb$relation_fields rf + JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name + WHERE rf.rdb$relation_name = ? + ORDER BY rf.rdb$field_position + """, (table,)) + cols = cur.fetchall() + col_names = [c[0].strip() for c in cols] + print(f"\n{table}") + print(" " + ", ".join(col_names)) + +cur.close() +conn.close() diff --git a/MedicusWithClaude/insert_test.py b/MedicusWithClaude/insert_test.py new file mode 100644 index 0000000..0a1ecc5 --- /dev/null +++ b/MedicusWithClaude/insert_test.py @@ -0,0 +1,23 @@ +import fdb +import datetime + +conn = fdb.connect( + dsn=r'localhost:c:\medicus 3\data\medicus.fdb', + user='SYSDBA', password='masterkey', charset='win1250' +) +cur = conn.cursor() + +cur.execute('SELECT MAX(ID) FROM DEKURS') +next_id = cur.fetchone()[0] + 1 +print('Next ID:', next_id) + +text = r'{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\fonttbl{\f0\fnil\fcharset238 Arial;}}{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}' + '\n' + r'\pard\plain\f0\fs20 Test Claude AI - automaticky vlo\u382en\'fd z\'e1znam 13.03.2026\par' + '\n}' + +cur.execute(""" + INSERT INTO DEKURS (ID, IDPAC, IDODD, IDPRAC, IDUZI, DATUM, CAS, DEKURS, DGN1) + VALUES (?, 9742, 2, 2, 6, ?, ?, ?, 'Z000 ') +""", (next_id, datetime.date(2026, 3, 13), datetime.time(12, 0, 0), text)) + +conn.commit() +print('Vloženo OK, ID:', next_id) +conn.close() diff --git a/MedicusWithClaude/inspect_table.py b/MedicusWithClaude/inspect_table.py new file mode 100644 index 0000000..5965aaf --- /dev/null +++ b/MedicusWithClaude/inspect_table.py @@ -0,0 +1,28 @@ +import fdb +import sys + +table = sys.argv[1] if len(sys.argv) > 1 else 'DEKURS' + +conn = fdb.connect( + dsn=r'localhost:c:\medicus 3\data\medicus.fdb', + user='SYSDBA', password='masterkey', charset='win1250' +) +cur = conn.cursor() + +cur.execute(""" + SELECT rf.rdb$field_name, f.rdb$field_type, f.rdb$field_length + FROM rdb$relation_fields rf + JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name + WHERE rf.rdb$relation_name = ? + ORDER BY rf.rdb$field_position +""", (table,)) +cols = cur.fetchall() +print(f"=== {table} – sloupce ===") +for c in cols: + print(f" {c[0].strip():<30} typ: {c[1]} délka: {c[2]}") + +cur.execute(f'SELECT COUNT(*) FROM {table}') +print(f"\nPočet záznamů: {cur.fetchone()[0]}") + +cur.close() +conn.close() diff --git a/MedicusWithClaude/sample_dekurs.py b/MedicusWithClaude/sample_dekurs.py new file mode 100644 index 0000000..58563f1 --- /dev/null +++ b/MedicusWithClaude/sample_dekurs.py @@ -0,0 +1,37 @@ +import fdb + +conn = fdb.connect( + dsn=r'localhost:c:\medicus 3\data\medicus.fdb', + user='SYSDBA', password='masterkey', charset='win1250' +) +cur = conn.cursor() + +print("=== NEJSTARŠÍ záznamy ===") +cur.execute(""" + SELECT FIRST 5 ID, DATUM, DEKURS + FROM DEKURS + WHERE DEKURS IS NOT NULL + ORDER BY DATUM ASC +""") +for row in cur.fetchall(): + text = row[2].read() if hasattr(row[2], 'read') else row[2] + if isinstance(text, bytes): + text = text.decode('windows-1250', errors='replace') + print(f"\n--- ID={row[0]} DATUM={row[1]} ---") + print(repr(text[:300])) + +print("\n\n=== NEJNOVĚJŠÍ záznamy ===") +cur.execute(""" + SELECT FIRST 5 ID, DATUM, DEKURS + FROM DEKURS + WHERE DEKURS IS NOT NULL + ORDER BY DATUM DESC +""") +for row in cur.fetchall(): + text = row[2].read() if hasattr(row[2], 'read') else row[2] + if isinstance(text, bytes): + text = text.decode('windows-1250', errors='replace') + print(f"\n--- ID={row[0]} DATUM={row[1]} ---") + print(repr(text[:300])) + +conn.close() diff --git a/MedicusWithClaude/sample_dekurs2.py b/MedicusWithClaude/sample_dekurs2.py new file mode 100644 index 0000000..4f5d98b --- /dev/null +++ b/MedicusWithClaude/sample_dekurs2.py @@ -0,0 +1,25 @@ +import fdb + +conn = fdb.connect( + dsn=r'localhost:c:\medicus 3\data\medicus.fdb', + user='SYSDBA', password='masterkey', charset='win1250' +) +cur = conn.cursor() + +cur.execute(""" + SELECT FIRST 1 ID, DATUM, DEKURS + FROM DEKURS + WHERE DEKURS IS NOT NULL + ORDER BY DATUM DESC +""") +row = cur.fetchone() +text = row[2].read() if hasattr(row[2], 'read') else row[2] +if isinstance(text, bytes): + text = text.decode('windows-1250', errors='replace') + +print(f"ID={row[0]} DATUM={row[1]}") +print(f"Délka: {len(text)} znaků") +print("\n--- CELÝ TEXT ---") +print(text) + +conn.close() diff --git a/MedicusWithClaude/sample_rtf.py b/MedicusWithClaude/sample_rtf.py new file mode 100644 index 0000000..5a18819 --- /dev/null +++ b/MedicusWithClaude/sample_rtf.py @@ -0,0 +1,35 @@ +import fdb + +conn = fdb.connect( + dsn=r'localhost:c:\medicus 3\data\medicus.fdb', + user='SYSDBA', password='masterkey', charset='win1250' +) +cur = conn.cursor() + +# Načti všechny záznamy pacienta a zjisti délku v Pythonu +cur.execute(""" + SELECT ID, DATUM, DEKURS + FROM DEKURS + WHERE IDPAC = 9742 AND DEKURS IS NOT NULL + ORDER BY DATUM DESC +""") +rows = cur.fetchall() + +def read_blob(val): + text = val.read() if hasattr(val, 'read') else val + if isinstance(text, bytes): + text = text.decode('windows-1250', errors='replace') + return text + +# Seřaď podle délky +data = [(r[0], r[1], read_blob(r[2])) for r in rows] +data.sort(key=lambda x: len(x[2]), reverse=True) + +print("Top 10 nejdelších záznamů:") +for d in data[:10]: + print(f" ID={d[0]} datum={d[1]} délka={len(d[2])}") + +print("\n--- Nejdelší záznam (celý RTF) ---") +print(data[0][2]) + +conn.close()