From a667fb8ba3cae35d668fb9e1cef86617544899f7 Mon Sep 17 00:00:00 2001 From: "vladimir.buzalka" Date: Mon, 13 Apr 2026 16:45:07 +0200 Subject: [PATCH] z230 --- Dotazy/DOTAZY.md | 250 +++++++++ Dotazy/LZ_Buzalka_Vladimír_1973-09-20.xlsx | Bin 0 -> 10181 bytes Dotazy/LZ_Havelka_Miroslav_1944-08-02.xlsx | Bin 0 -> 11419 bytes ...LZ_Havelka_Miroslav_1944-08-02_162618.xlsx | Bin 0 -> 15470 bytes Dotazy/LZ_Havelka_Miroslav_1944-08-02_v2.xlsx | Bin 0 -> 11517 bytes Dotazy/LZ_Havelka_Miroslav_1944-08-02_v3.xlsx | Bin 0 -> 12107 bytes Dotazy/prehled_pacienta.py | 233 +++++++- Dotazy/prehled_pacienta_excel.py | 515 ++++++++++++++++++ LékovýZáznamWithClaude/LEKOVY_ZAZNAM_DB.md | 168 +++++- LékovýZáznamWithClaude/reimport_z_xml.py | 194 +++++++ tmp_check_odb.py | 38 ++ tmp_out.txt | 38 ++ 12 files changed, 1410 insertions(+), 26 deletions(-) create mode 100644 Dotazy/DOTAZY.md create mode 100644 Dotazy/LZ_Buzalka_Vladimír_1973-09-20.xlsx create mode 100644 Dotazy/LZ_Havelka_Miroslav_1944-08-02.xlsx create mode 100644 Dotazy/LZ_Havelka_Miroslav_1944-08-02_162618.xlsx create mode 100644 Dotazy/LZ_Havelka_Miroslav_1944-08-02_v2.xlsx create mode 100644 Dotazy/LZ_Havelka_Miroslav_1944-08-02_v3.xlsx create mode 100644 Dotazy/prehled_pacienta_excel.py create mode 100644 LékovýZáznamWithClaude/reimport_z_xml.py create mode 100644 tmp_check_odb.py create mode 100644 tmp_out.txt diff --git a/Dotazy/DOTAZY.md b/Dotazy/DOTAZY.md new file mode 100644 index 0000000..696d1a5 --- /dev/null +++ b/Dotazy/DOTAZY.md @@ -0,0 +1,250 @@ +# Dotazy — přehled lékového záznamu pacienta + +Skripty pro zobrazení a export lékového záznamu konkrétního pacienta z MySQL databáze `medicus`. +Pacient se identifikuje **rodným číslem** — to se vyhledá v lokální Firebird databázi Medicusu, +odkud se získá příjmení a datum narození, a teprve těmito dvěma hodnotami se najde pacient v MySQL. + +--- + +## Soubory + +| Soubor | Co dělá | +|--------|---------| +| `prehled_pacienta.py` | Konzolový výpis — lékaři + předpisy pacienta | +| `prehled_pacienta_excel.py` | Export do formátovaného souboru Excel (.xlsx) | + +--- + +## Nastavení (obě skripty) + +Na začátku každého souboru jsou tři proměnné: + +```python +RODNE_CISLO = "440802/018" # rodné číslo — funguje s lomítkem i bez: "4408020183" +DATUM_OD = "01.01.2025" # předpisy od tohoto data; None = všechny předpisy +VYSTUP_DIR = None # pouze excel: složka výstupu; None = stejná jako skript +``` + +--- + +## Spuštění + +```bash +# Konzolový výpis +.venv\Scripts\python.exe Dotazy\prehled_pacienta.py + +# Export do Excelu +.venv\Scripts\python.exe Dotazy\prehled_pacienta_excel.py +``` + +--- + +## Zdroje dat + +### 1. Firebird — Medicus (`medicus.fdb`) + +Slouží výhradně k identifikaci pacienta podle rodného čísla. + +``` +DSN: localhost:c:\medicus 3\data\medicus.fdb +User: SYSDBA / masterkey +Charset: win1250 +Tabulka: KAR +``` + +Dotaz: +```sql +SELECT KAR.PRIJMENI, KAR.JMENO, KAR.DATNAR +FROM KAR WHERE KAR.RODCIS = ? +``` + +Rodné číslo se normalizuje před dotazem — odstraní se lomítko a mezery: +```python +rc = rc.replace("/", "").replace(" ", "").strip() +``` + +### 2. MySQL — databáze `medicus` + +Obsahuje lékové záznamy stažené z eReceptu SÚKL. + +``` +Host: 192.168.1.76 +User: root +DB: medicus +``` + +Pacient se vyhledá podle příjmení a data narození (získaných z Firebirdu): +```sql +SELECT id, prijmeni, jmena, datum_narozeni +FROM pacient +WHERE prijmeni = %s AND datum_narozeni = %s +``` + +--- + +## Co se zobrazuje + +### Část 1 — Předepisující lékaři + +Všichni lékaři, kteří pacientovi za celou dobu předepsali alespoň jeden lék, +seřazeni sestupně podle počtu předpisů. + +Sloupce: `#` | `Lékař` | `Odbornost` | `Pracoviště a adresa` | `Předpisů` + +```sql +SELECT pr.prijmeni, pr.jmena, + pr.icp, + CONCAT(pr.pzs_nazev, ', ', pr.ulice, ', ', pr.psc, ' ', pr.mesto) AS adresa, + COUNT(*) AS pocet_predpisu +FROM zprava z +JOIN predpis p ON p.zprava_id = z.id +JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho +WHERE z.pacient_id = %s +GROUP BY pr.lekar_kod, pr.prijmeni, pr.jmena, pr.icp, + pr.pzs_nazev, pr.ulice, pr.psc, pr.mesto +ORDER BY pocet_predpisu DESC +``` + +### Část 2 — Všechny předpisy + +Předpisy od `DATUM_OD`, seřazené sestupně dle data vystavení. + +Zobrazuje se **vydaný lék** (z tabulky `vydej`), nikoli předepsaný název. +Pokud lék nebyl vyzvednut, zobrazí se předepsaný název s příznakem `*NV`. + +Sloupce: `#` | `Datum` | `Vydaný lék` | `ATC` | `Návod` | `Lékař` | `Odbornost` | `Adresa` + +```sql +SELECT p.datum_vystaveni, + COALESCE(v.nazev, p.nazev) AS vydany_lek, + v.nazev IS NULL AS nevyzvednuto, + p.atc, + p.navod, + pr.prijmeni, + pr.jmena, + pr.icp, + CONCAT(pr.pzs_nazev, ', ', pr.ulice, ', ', pr.psc, ' ', pr.mesto) AS adresa +FROM zprava z +JOIN predpis p ON p.zprava_id = z.id +JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho +LEFT JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis +WHERE z.pacient_id = %s + AND p.datum_vystaveni >= %s -- pouze pokud DATUM_OD není None +ORDER BY p.datum_vystaveni DESC +``` + +Klíčový princip `COALESCE(v.nazev, p.nazev)`: +- `v.nazev` — název léku, který lékárna **skutečně vydala** (může být jiná značka než předepsaná) +- `p.nazev` — název léku, který lékař **předepsal** (zobrazí se jen pokud výdej neexistuje → `*NV`) + +--- + +## Odbornost lékaře + +Odbornost se odvozuje z posledních 3 číslic pole `predepisujici.icp` (IČP pracoviště). + +``` +ICP: 09305001 → kód odbornosti: 001 → Praktický lékař +ICP: 08006272 → kód odbornosti: 272 → Alergologie +ICP: 08075603 → kód odbornosti: 603 → Onkologie +``` + +Funkce: +```python +def odbornost_z_icp(icp): + if not icp or len(icp) < 3: + return "" + return ODBORNOST.get(icp[-3:], f"odb. {icp[-3:]}") +``` + +Pro neznámé kódy se zobrazí `odb. XXX` (XXX = třímístný číselný kód). + +### Slovník ODBORNOST — vybrané klíčové kódy + +| Kód | Odbornost | Kód | Odbornost | +|-----|-----------|-----|-----------| +| 001 | Praktický lékař | 101 | Vnitřní lékařství | +| 002 | Pediatr (prakt.) | 104 | Kardiologie | +| 003 | Chirurgie | 105 | Gastroenterologie | +| 004 | Ortopedie | 108 | Nefrologie | +| 005 | ORL | 110 | Diabetologie | +| 006 | Gynekologie | 121 | Endokrinologie | +| 007 | Urologie | 156 | Hematologie | +| 008 | Neurologie | 169 | Revmatologie | +| 009 | Psychiatrie | 263 | Urologie | +| 012 | Dermatovenerologie | 272 | Alergologie | +| 018 | Pneumologie | 283 | Dětská neurochir. | +| 021 | Radiodiagnostika | 302 | Radiodiagnostika | +| 024 | Klin. biochemie | 324 | Klin. onkologie | +| 060 | Dětská chirurgie | 590 | Lékárenství | +| 074 | Neurochirurgie | 603 | Onkologie | +| 091 | Gynekolog. onkologie | 704 | Kardiochirurgie | +| 096 | Léčebná rehabilitace | 801 | Fyzioterapie | + +Celý slovník obsahuje ~170 kódů (viz zdrojový kód skriptů). +Kompletní číselník VZP/SÚKL: (sekce číselníky). + +--- + +## Excel export (`prehled_pacienta_excel.py`) + +Soubor se ukládá do stejné složky jako skript (nebo do `VYSTUP_DIR`). + +### Pojmenování souborů + +``` +LZ_{Prijmeni}_{Jmeno}_{datum_narozeni}.xlsx ← základní +LZ_{Prijmeni}_{Jmeno}_{datum_narozeni}_v2.xlsx ← pokud základní existuje +LZ_{Prijmeni}_{Jmeno}_{datum_narozeni}_v3.xlsx ← atd. +``` + +Versioning zabrání přepsání dříve exportovaných souborů. + +### Vzhled a formátování + +| Prvek | Barva | Popis | +|-------|-------|-------| +| Záhlaví (jméno pacienta) | `#1F4E79` tmavě modrá | tučné, 14pt | +| Záhlaví tabulky | `#1F4E79` tmavě modrá | bílý text, 10pt | +| Nadpis sekce | `#2E75B6` střední modrá | bílý text, 11pt | +| Info o pacientovi | `#DEEAF1` světle modrá | datum narozeni, datum tisku, předpisy od | +| Sudé řádky | `#EBF3FB` velmi světle modrá | střídání řádků | +| Liché řádky | `#FFFFFF` bílá | | +| Nevyzvednuto | `#FCE4D6` lososová | zvýraznění celého řádku | +| Ohraničení | `#B8CCE4` světle modrá | tenká linka | + +- Font: **Arial** ve všech buňkách +- Automatická šířka sloupců a výška řádků (`autofit`) +- Zmrazení prvního řádku (`freeze_panes = "A2"`) +- 8 sloupců: `#` | `Lékař/Datum` | `Odbornost/Vydaný lék` | `Pracoviště/ATC` | … | `Předpisů/Pracoviště a adresa` + +### Tabulka lékařů (8 sloupců) + +`#` | `Lékař` | `Odbornost` | `Pracoviště` | `Ulice` | `PSČ` | `Město` | `Předpisů` + +### Tabulka předpisů (8 sloupců) + +`#` | `Datum` | `Vydaný lék` | `ATC` | `Návod` | `Lékař` | `Odbornost` | `Pracoviště a adresa` + +--- + +## Závislosti + +``` +pymysql ← MySQL klient +fdb ← Firebird klient +openpyxl ← Excel export (pouze prehled_pacienta_excel.py) +``` + +Všechny jsou součástí `requirements.txt` a nainstalují se přes `setup.ps1`. + +--- + +## Typické chybové situace + +| Chyba | Příčina | Řešení | +|-------|---------|--------| +| `Rodne cislo nenalezeno v Medicusu` | RC není v tabulce KAR | Zkontrolovat číslo, ověřit v Medicusu | +| `Pacient nema zaznam v MySQL` | Lékový záznam nebyl stažen | Spustit `07StahnoutVsechny.py` nebo `reimport_z_xml.py` | +| `PermissionError` při ukládání xlsx | Soubor je otevřen v Excelu | Zavřít Excel a spustit znovu — verzování uloží jako `_v2` | +| Odbornost zobrazena jako `odb. XXX` | Kód není ve slovníku | Informativní stav — kód je platný, jen není pojmenován | diff --git a/Dotazy/LZ_Buzalka_Vladimír_1973-09-20.xlsx b/Dotazy/LZ_Buzalka_Vladimír_1973-09-20.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0b1fca2dcbb51974ac76ccf8ea00accde48c9a19 GIT binary patch literal 10181 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 E0ioa@*8l(j literal 0 HcmV?d00001 diff --git a/Dotazy/LZ_Havelka_Miroslav_1944-08-02_v3.xlsx b/Dotazy/LZ_Havelka_Miroslav_1944-08-02_v3.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..88d05b31964a5c227c93a5abbbcfedb9941800bd GIT binary patch literal 12107 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 mWc}ZA{!XR;$|-01UlU7N{?!YP8U_aC8}") + print(f"{'#':<4} {'Lekar':<30} {'Odbornost':<25} {'Pracoviste a adresa':<50} {'Predpisu':>8}") print(SEP) for i, r in enumerate(rows, 1): - lekar = f"{r['prijmeni']} {r['jmena']}" - print(f"{i:<4} {lekar:<30} {r['adresa']:<55} {r['pocet_predpisu']:>8}") + lekar = f"{r['prijmeni']} {r['jmena']}" + odb = odbornost_z_icp(r['icp']) + print(f"{i:<4} {lekar:<30} {odb:<25} {r['adresa']:<50} {r['pocet_predpisu']:>8}") if not rows: print(" Zadne predpisy nenalezeny.") @@ -118,11 +322,13 @@ def tiskni_predpisy(cur, pacient_id, datum_od): cur.execute( f""" SELECT p.datum_vystaveni, - COALESCE(v.nazev, '(nevyzvednuto)') AS vydany_lek, + COALESCE(v.nazev, p.nazev) AS vydany_lek, + v.nazev IS NULL AS nevyzvednuto, p.atc, p.navod, pr.prijmeni, pr.jmena, + pr.icp, CONCAT(pr.pzs_nazev, ', ', pr.ulice, ', ', pr.psc, ' ', pr.mesto) AS adresa FROM zprava z JOIN predpis p ON p.zprava_id = z.id @@ -138,15 +344,18 @@ def tiskni_predpisy(cur, pacient_id, datum_od): od_text = f"od {datum_od.strftime('%d.%m.%Y')}" if datum_od else "vse" print(f"\nVSECHNY PREDPISY ({od_text}) — celkem {len(rows)}:") - print(f"{'#':<4} {'Datum':<12} {'Vydany lek':<30} {'ATC':<8} {'Navod':<25} {'Lekar':<25} Adresa") + print(f"{'#':<4} {'Datum':<12} {'Vydany lek':<30} {'ATC':<8} {'Navod':<20} {'Lekar':<25} {'Odbornost':<22} Adresa") print(SEP2) for i, r in enumerate(rows, 1): datum = r["datum_vystaveni"].strftime("%d.%m.%Y") lekar = f"{r['prijmeni']} {r['jmena']}" - lek = (r["vydany_lek"] or "")[:29] - navod = (r["navod"] or "")[:24] + lek = (r["vydany_lek"] or "")[:28] + if r["nevyzvednuto"]: + lek = f"{lek} *NV" + navod = (r["navod"] or "")[:19] atc = (r["atc"] or "") - print(f"{i:<4} {datum:<12} {lek:<30} {atc:<8} {navod:<25} {lekar:<25} {r['adresa']}") + odb = odbornost_z_icp(r["icp"])[:21] + print(f"{i:<4} {datum:<12} {lek:<30} {atc:<8} {navod:<20} {lekar:<25} {odb:<22} {r['adresa']}") if not rows: print(" Zadne predpisy nenalezeny.") print() diff --git a/Dotazy/prehled_pacienta_excel.py b/Dotazy/prehled_pacienta_excel.py new file mode 100644 index 0000000..d77ed23 --- /dev/null +++ b/Dotazy/prehled_pacienta_excel.py @@ -0,0 +1,515 @@ +""" +Export prehledu lekoveho zaznamu pacienta do Excelu. + +Nastaveni: + RODNE_CISLO ... rodne cislo pacienta (s lomitkem i bez) + DATUM_OD ... predpisy od tohoto data ve formatu DD.MM.RRRR (None = vsechny) + VYSTUP_DIR ... slozka kam se ulozi Excel (None = stejna slozka jako skript) +""" + +from datetime import datetime +from pathlib import Path +import sys +import fdb +import pymysql +import pymysql.cursors +from openpyxl import Workbook +from openpyxl.styles import (Font, PatternFill, Alignment, Border, Side, + GradientFill) +from openpyxl.utils import get_column_letter + +# Kody odbornosti dle SUKL / VZP (posledni 3 cislice ICP) +ODBORNOST = { + "001": "Praktický lékař", + "002": "Pediatr (prakt.)", + "003": "Chirurgie", + "004": "Ortopedie", + "005": "ORL", + "006": "Gynekologie", + "007": "Urologie", + "008": "Neurologie", + "009": "Psychiatrie", + "010": "Oftalmologie", + "011": "Zubní lékařství", + "012": "Dermatovenerologie", + "013": "Infekční lékařství", + "014": "Radiodiagnostika", + "015": "Stomatochirurgie", + "016": "Čelistní ortopedie", + "017": "Dětská psychiatrie", + "018": "Pneumologie", + "019": "Anesteziologie", + "020": "Rehabilitace", + "021": "Radiodiagnostika", + "022": "Radioterapie", + "023": "Nukleární medicína", + "024": "Klin. biochemie", + "025": "Alergologie/imunologie", + "026": "Hematologie", + "027": "Soudní lékařství", + "028": "Soudní psychiatrie", + "029": "Lékařská genetika", + "031": "Gastroenterologie", + "032": "Nefrologie", + "033": "Kardiologie", + "034": "Endokrinologie/diab.", + "035": "Revmatologie", + "040": "Vnitřní lékařství", + "041": "Geriatrie", + "042": "Klin. farmakologie", + "043": "Diabetologie", + "044": "Endokrinologie", + "045": "Hepatologie", + "052": "Dětská neurologie", + "060": "Dětská chirurgie", + "065": "Plastická chirurgie", + "066": "Cévní chirurgie", + "067": "Kardiochirurgie", + "072": "Foniatrie", + "074": "Neurochirurgie", + "077": "Maxilofaciální chir.", + "079": "Hrudní chirurgie", + "082": "Urologie", + "083": "Andrologie", + "085": "Proktologie", + "091": "Gynekolog. onkologie", + "092": "Reprodukční medicína", + "096": "Léčebná rehabilitace", + "097": "Fyzioterapie", + "101": "Vnitřní lékařství", + "102": "Kardiologie", + "104": "Kardiologie", + "105": "Gastroenterologie", + "106": "Hepatologie", + "107": "Nefrologie", + "108": "Nefrologie", + "110": "Diabetologie", + "111": "Endokrinologie", + "114": "Pneumologie", + "115": "Ftizeologie", + "121": "Endokrinologie", + "122": "Diabetologie", + "129": "Andrologie", + "143": "Psychiatrie", + "144": "Psychoterapie", + "145": "Adiktologie", + "148": "Dětská psychiatrie", + "155": "Oční onkologie", + "156": "Hematologie", + "157": "Hemostáza", + "160": "Neurologie", + "162": "Epileptologie", + "163": "Dětská neurologie", + "164": "Neurorehabilit.", + "168": "Klin. neurofyziologie", + "169": "Revmatologie", + "174": "Ortoped. protetika", + "181": "Infektologie", + "183": "Tropická medicína", + "185": "Mikrobiologie", + "188": "Virologie", + "200": "Stomatologie", + "201": "Stomatochirurgie", + "202": "Maxilofaciální chir.", + "203": "Parodontologie", + "204": "Ortodoncie", + "205": "Zubní protetika", + "206": "Dětská stomatologie", + "220": "Pediatrie", + "221": "Neonatologie", + "222": "Dětská endokrinol.", + "223": "Dětská gastroenterol.", + "234": "Dětská hematologie", + "239": "Dětská nefrologie", + "243": "Dětská pneumologie", + "245": "Dětská psychiatrie", + "246": "Dětská revmatologie", + "247": "Dětská kardiologie", + "250": "Dětská neurologie", + "251": "Dětská neurologie", + "258": "Dětská onkologie", + "261": "Dětská chirurgie", + "262": "Dětská ortopedie", + "263": "Urologie", + "264": "Dětská stomatologie", + "271": "Dětská klin. biochem.", + "272": "Alergologie", + "273": "Dětská alergologie", + "281": "Dětská dermatologie", + "282": "Dětská radiologie", + "283": "Dětská neurochir.", + "289": "Dětská kardiochir.", + "291": "Dětská onkol. chir.", + "294": "Dětská oftalmologie", + "295": "Dětská gynekologie", + "300": "Onkologie", + "301": "Klin. onkologie", + "302": "Radiodiagnostika", + "303": "Radioterapie", + "304": "Nukleární medicína", + "305": "Nukleární kardiologie", + "316": "Klin. genetika", + "319": "Soudní lékařství", + "321": "Cytologie", + "324": "Klin. onkologie", + "333": "Onkologie", + "501": "Zubní lékařství", + "502": "Čelistní ortopedie", + "503": "Stomatochirurgie", + "508": "Parodontologie", + "509": "Ortodoncie", + "510": "Dětská stomatologie", + "513": "Zubní protetika", + "535": "Orální medicína", + "555": "Stomatologie", + "558": "Zubní lékařství", + "559": "Stomatologie", + "560": "Stomatologie", + "562": "Stomatologie", + "571": "Stomatologie", + "574": "Stomatologie", + "580": "Stomatologie", + "581": "Stomatologie", + "582": "Stomatologie", + "584": "Stomatologie", + "590": "Lékárenství", + "600": "Onkologie", + "601": "Klin. onkologie", + "603": "Onkologie", + "606": "Radioterapie", + "607": "Nukleární medicína", + "615": "Onkologie", + "700": "Chirurgie", + "701": "Cévní chirurgie", + "702": "Hrudní chirurgie", + "704": "Kardiochirurgie", + "705": "Chirurgie", + "706": "Plastická chirurgie", + "719": "Dětská chirurgie", + "721": "Ortopedie", + "722": "Ortopedie", + "723": "Ortopedie", + "801": "Fyzioterapie", + "802": "Ergoterapie", + "852": "Fyzioterapie", + "853": "Fyzioterapie", + "858": "Fyzioterapie", + "860": "Fyzioterapie", + "862": "Fyzioterapie", + "873": "Fyzioterapie", + "880": "Rehabilitace", + "881": "Endokrinologie", + "885": "Rehabilitace", + "889": "Rehabilitace", + "890": "Rehabilitace", +} + + +def odbornost_z_icp(icp): + """Vrati nazev odbornosti z ICP kodu (posledni 3 cislice).""" + if not icp or len(icp) < 3: + return "" + return ODBORNOST.get(icp[-3:], f"odb. {icp[-3:]}") + + +# ── NASTAVENÍ ───────────────────────────────────────────────────────────────── +RODNE_CISLO = "440802/018" +DATUM_OD = "01.01.2025" # None = vsechny predpisy +VYSTUP_DIR = None # None = stejny adresar jako skript +# ───────────────────────────────────────────────────────────────────────────── + +FB = dict( + dsn = r"localhost:c:\medicus 3\data\medicus.fdb", + user = "SYSDBA", + password = "masterkey", + charset = "win1250", +) + +DB = dict( + host = "192.168.1.76", + user = "root", + password = "Vlado9674+", + database = "medicus", + charset = "utf8mb4", + cursorclass = pymysql.cursors.DictCursor, +) + +# ── Barvy ───────────────────────────────────────────────────────────────────── +C_HEADER_BG = "1F4E79" # tmave modra — hlavicka tabulky +C_HEADER_FG = "FFFFFF" # bila — text hlavicky +C_TITLE_BG = "2E75B6" # stredni modra — nadpis sekce +C_TITLE_FG = "FFFFFF" +C_INFO_BG = "DEEAF1" # svetle modra — info o pacientovi +C_ROW_ODD = "FFFFFF" # bila +C_ROW_EVEN = "EBF3FB" # velmi svetle modra — striped +C_NEVYZV_BG = "FCE4D6" # lososova — nevyzvednuto +C_BORDER = "B8CCE4" + + +def thin_border(): + s = Side(style="thin", color=C_BORDER) + return Border(left=s, right=s, top=s, bottom=s) + + +def header_fill(color): + return PatternFill("solid", fgColor=color) + + +def parse_datum(s, nazev): + try: + return datetime.strptime(s, "%d.%m.%Y").date() + except (ValueError, TypeError): + sys.exit(f"Spatny format data '{nazev}': '{s}'") + + +def najdi_v_firebirdu(rc): + rc = rc.replace("/", "").replace(" ", "") + conn = fdb.connect(**FB) + try: + cur = conn.cursor() + cur.execute("SELECT KAR.PRIJMENI, KAR.JMENO, KAR.DATNAR FROM KAR WHERE KAR.RODCIS = ?", (rc,)) + row = cur.fetchone() + if not row: + sys.exit(f"Rodne cislo '{rc}' nenalezeno v Medicusu.") + return {"prijmeni": row[0].strip(), "jmeno": row[1].strip(), "datnar": row[2]} + finally: + conn.close() + + +def nacti_data(prijmeni, datum_narozeni, datum_od): + conn = pymysql.connect(**DB) + try: + with conn.cursor() as cur: + cur.execute( + "SELECT id, prijmeni, jmena, datum_narozeni FROM pacient " + "WHERE prijmeni = %s AND datum_narozeni = %s", + (prijmeni, datum_narozeni) + ) + pac = cur.fetchone() + if not pac: + sys.exit(f"Pacient '{prijmeni}' nar. {datum_narozeni} nema zaznam v MySQL.") + + # Lekari + cur.execute(""" + SELECT pr.prijmeni, pr.jmena, + pr.icp, + pr.pzs_nazev, pr.ulice, pr.psc, pr.mesto, + COUNT(*) AS pocet + FROM zprava z + JOIN predpis p ON p.zprava_id = z.id + JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho + WHERE z.pacient_id = %s + GROUP BY pr.lekar_kod, pr.prijmeni, pr.jmena, pr.icp, + pr.pzs_nazev, pr.ulice, pr.psc, pr.mesto + ORDER BY pocet DESC + """, (pac["id"],)) + lekari = cur.fetchall() + + # Predpisy + podminka = "AND p.datum_vystaveni >= %s" if datum_od else "" + params = (pac["id"], datum_od) if datum_od else (pac["id"],) + cur.execute(f""" + SELECT p.datum_vystaveni, + COALESCE(v.nazev, p.nazev) AS vydany_lek, + v.nazev IS NULL AS nevyzvednuto, + p.atc, p.navod, + pr.prijmeni AS lek_prijmeni, pr.jmena AS lek_jmena, + pr.icp, + pr.pzs_nazev, pr.ulice, pr.psc, pr.mesto + FROM zprava z + JOIN predpis p ON p.zprava_id = z.id + JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho + LEFT JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis + WHERE z.pacient_id = %s {podminka} + ORDER BY p.datum_vystaveni DESC + """, params) + predpisy = cur.fetchall() + + return pac, lekari, predpisy + finally: + conn.close() + + +def nastav_sirky(ws, sirky): + for col, width in sirky.items(): + ws.column_dimensions[col].width = width + + +def autofit(ws, min_width=5, max_width=60, padding=2): + """Autofit sloupcu a radku podle obsahu.""" + col_widths = {} + for row in ws.iter_rows(): + for cell in row: + if cell.value is None: + continue + # Preskoc mergnuté bunky — jejich sirka se pocita ze zakladni bunky + if isinstance(cell, type(cell)) and hasattr(cell, 'column'): + text = str(cell.value) + # Tučný text je trochu širší + factor = 1.15 if (cell.font and cell.font.bold) else 1.0 + width = len(text) * factor + padding + col = get_column_letter(cell.column) + col_widths[col] = max(col_widths.get(col, min_width), width) + + for col, width in col_widths.items(): + ws.column_dimensions[col].width = min(max(width, min_width), max_width) + + # Autofit výšky řádků (wrap_text obsah) + for row in ws.iter_rows(): + max_lines = 1 + for cell in row: + if cell.value and cell.alignment and cell.alignment.wrap_text: + col_w = ws.column_dimensions[get_column_letter(cell.column)].width or 10 + lines = max(1, int(len(str(cell.value)) / max(col_w, 1)) + 1) + max_lines = max(max_lines, lines) + row_num = row[0].row + if max_lines > 1: + ws.row_dimensions[row_num].height = max(ws.row_dimensions[row_num].height or 15, + max_lines * 14) + + +def zapis_nadpis_sekce(ws, row, text, n_cols): + ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=n_cols) + cell = ws.cell(row=row, column=1, value=text) + cell.font = Font(name="Arial", bold=True, size=11, color=C_TITLE_FG) + cell.fill = header_fill(C_TITLE_BG) + cell.alignment = Alignment(horizontal="left", vertical="center", indent=1) + ws.row_dimensions[row].height = 20 + return row + 1 + + +def zapis_hlavicku(ws, row, hlavicka, n_cols=None): + for col, text in enumerate(hlavicka, 1): + cell = ws.cell(row=row, column=col, value=text) + cell.font = Font(name="Arial", bold=True, size=10, color=C_HEADER_FG) + cell.fill = header_fill(C_HEADER_BG) + cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) + cell.border = thin_border() + ws.row_dimensions[row].height = 28 + return row + 1 + + +def zapis_radek(ws, row, hodnoty, highlight=False): + bg = C_NEVYZV_BG if highlight else (C_ROW_EVEN if row % 2 == 0 else C_ROW_ODD) + fill = header_fill(bg) + for col, val in enumerate(hodnoty, 1): + cell = ws.cell(row=row, column=col, value=val) + cell.font = Font(name="Arial", size=10) + cell.fill = fill + cell.alignment = Alignment(vertical="center", wrap_text=True) + cell.border = thin_border() + ws.row_dimensions[row].height = 18 + return row + 1 + + +def vytvor_excel(pac, lekari, predpisy, datum_od, fb_pac): + wb = Workbook() + ws = wb.active + ws.title = "Lekovy zaznam" + + # ── Záhlaví — info o pacientovi ────────────────────────────────────────── + n_cols = 8 + ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=n_cols) + title_cell = ws.cell(row=1, column=1, + value=f"LÉKOVÝ ZÁZNAM — {pac['prijmeni'].upper()} {fb_pac['jmeno'].upper()}") + title_cell.font = Font(name="Arial", bold=True, size=14, color=C_HEADER_FG) + title_cell.fill = header_fill(C_HEADER_BG) + title_cell.alignment = Alignment(horizontal="left", vertical="center", indent=1) + ws.row_dimensions[1].height = 32 + + info = [ + ("Datum narození:", pac["datum_narozeni"].strftime("%d.%m.%Y")), + ("Datum tisku:", datetime.today().strftime("%d.%m.%Y")), + ("Předpisy od:", datum_od.strftime("%d.%m.%Y") if datum_od else "vše"), + ] + for i, (label, val) in enumerate(info, 2): + ws.merge_cells(start_row=i, start_column=1, end_row=i, end_column=2) + ws.merge_cells(start_row=i, start_column=3, end_row=i, end_column=n_cols) + lbl = ws.cell(row=i, column=1, value=label) + lbl.font = Font(name="Arial", bold=True, size=10) + lbl.fill = header_fill(C_INFO_BG) + lbl.alignment = Alignment(vertical="center", indent=1) + val_cell = ws.cell(row=i, column=3, value=val) + val_cell.font = Font(name="Arial", size=10) + val_cell.fill = header_fill(C_INFO_BG) + val_cell.alignment = Alignment(vertical="center") + ws.row_dimensions[i].height = 16 + + row = len(info) + 3 # prázdný řádek + + # ── Tabulka lékařů ─────────────────────────────────────────────────────── + row = zapis_nadpis_sekce(ws, row, "PŘEDEPISUJÍCÍ LÉKAŘI", n_cols) + row = zapis_hlavicku(ws, row, ["#", "Lékař", "Odbornost", "Pracoviště", "Ulice", "PSČ", "Město", "Předpisů"]) + for i, r in enumerate(lekari, 1): + adresa_ulice = r.get("ulice") or "" + row = zapis_radek(ws, row, [ + i, + f"{r['prijmeni']} {r['jmena']}", + odbornost_z_icp(r.get("icp")), + r.get("pzs_nazev") or "", + adresa_ulice, + r.get("psc") or "", + r.get("mesto") or "", + r["pocet"], + ]) + + row += 1 # prázdný řádek + + # ── Tabulka předpisů ───────────────────────────────────────────────────── + od_text = datum_od.strftime("%d.%m.%Y") if datum_od else "vše" + row = zapis_nadpis_sekce(ws, row, f"VŠECHNY PŘEDPISY (od {od_text}) — celkem {len(predpisy)}", n_cols) + row = zapis_hlavicku(ws, row, ["#", "Datum", "Vydaný lék", "ATC", "Návod", "Lékař", "Odbornost", "Pracoviště a adresa"]) + for i, r in enumerate(predpisy, 1): + nevyzv = bool(r["nevyzvednuto"]) + adresa = (f"{r.get('pzs_nazev') or ''}, {r.get('ulice') or ''}, " + f"{r.get('psc') or ''} {r.get('mesto') or ''}").strip(", ") + row = zapis_radek(ws, row, [ + i, + r["datum_vystaveni"].strftime("%d.%m.%Y") if r["datum_vystaveni"] else "", + r["vydany_lek"], + r.get("atc") or "", + r.get("navod") or "", + f"{r['lek_prijmeni']} {r['lek_jmena']}", + odbornost_z_icp(r.get("icp")), + adresa, + ], highlight=nevyzv) + + # ── Autofit sloupců a řádků ─────────────────────────────────────────────── + autofit(ws, min_width=5, max_width=60) + + # Zmraz záhlaví + ws.freeze_panes = "A2" + + return wb + + +def main(): + datum_od = parse_datum(DATUM_OD, "DATUM_OD") if DATUM_OD else None + + fb_pac = najdi_v_firebirdu(RODNE_CISLO) + prijmeni = fb_pac["prijmeni"] + datum_narozeni = fb_pac["datnar"] + + print(f"Nacitam data: {prijmeni} {fb_pac['jmeno']} nar. {datum_narozeni} ...") + pac, lekari, predpisy = nacti_data(prijmeni, datum_narozeni, datum_od) + print(f" {len(lekari)} lekaru, {len(predpisy)} predpisu") + + wb = vytvor_excel(pac, lekari, predpisy, datum_od, fb_pac) + + vyst = Path(VYSTUP_DIR) if VYSTUP_DIR else Path(__file__).parent + zaklad = vyst / f"LZ_{prijmeni}_{fb_pac['jmeno']}_{datum_narozeni}.xlsx" + if not zaklad.exists(): + soubor = zaklad + else: + i = 2 + while True: + soubor = vyst / f"LZ_{prijmeni}_{fb_pac['jmeno']}_{datum_narozeni}_v{i}.xlsx" + if not soubor.exists(): + break + i += 1 + wb.save(soubor) + print(f"Ulozeno: {soubor}") + + +if __name__ == "__main__": + main() diff --git a/LékovýZáznamWithClaude/LEKOVY_ZAZNAM_DB.md b/LékovýZáznamWithClaude/LEKOVY_ZAZNAM_DB.md index 9627bbc..6cfdc57 100644 --- a/LékovýZáznamWithClaude/LEKOVY_ZAZNAM_DB.md +++ b/LékovýZáznamWithClaude/LEKOVY_ZAZNAM_DB.md @@ -10,18 +10,67 @@ z eRecept SÚKL API a jejich uložení do relační databáze MySQL. | Soubor | Co dělá | |--------|---------| | `05UlozitOdpoved.py` | Stáhne XML pro **jednoho** pacienta (ruční test/ladění) | -| `06UlozitDoMySQL.py` | DDL schématu, parsování XML, import do MySQL — používá se jako knihovna i samostatně | +| `06UlozitDoMySQL.py` | DDL schématu, parsování XML, import do MySQL — používá se jako **knihovna**, ne spouštět přímo! | | `07StahnoutVsechny.py` | **Hlavní skript** — načte pacienty z Medicusu, stáhne lékové záznamy, uloží XML i DB záznamy | +| `reimport_z_xml.py` | Reimport XML ze zálohy bez volání API — viz sekce níže | ``` -LékovýZáznamWithClaude/ -├── 05UlozitOdpoved.py -├── 06UlozitDoMySQL.py -├── 07StahnoutVsechny.py -├── LEKOVY_ZAZNAM_DB.md -├── Logs/ ← log každého běhu (UTF-8, YYYY-MM-DD_HH-MM-SS.log) -├── Tests/ ← starší vývojové skripty -└── xml_archive/ ← archiv XML odpovědí (YYYY-MM-DD/Prijmeni_Jmena_datnar.xml) +recept/ +├── setup.ps1 ← vytvoří .venv, nainstaluje závislosti, Playwright chromium +├── requirements.txt ← seznam Python závislostí +├── .venv/ ← virtuální prostředí (Python 3.x) +│ +├── LékovýZáznamWithClaude/ +│ ├── 05UlozitOdpoved.py +│ ├── 06UlozitDoMySQL.py +│ ├── 07StahnoutVsechny.py +│ ├── reimport_z_xml.py +│ ├── LEKOVY_ZAZNAM_DB.md ← tento soubor +│ ├── Logs/ ← log každého běhu (UTF-8, YYYY-MM-DD_HH-MM-SS.log) +│ ├── Tests/ ← starší vývojové skripty +│ └── xml_archive/ ← archiv XML odpovědí (YYYY-MM-DD/Prijmeni_Jmena_datnar.xml) +│ +└── Dotazy/ + ├── prehled_pacienta.py ← konzolový přehled pacienta + ├── prehled_pacienta_excel.py ← export přehledu pacienta do Excelu + └── DOTAZY.md ← dokumentace dotazovacích skriptů +``` + +> **⚠️ NIKDY nespouštět `06UlozitDoMySQL.py` přímo** — zavolá `vytvor_schema()`, +> která provede `DROP TABLE` a smaže celou databázi. +> Pro import dat vždy použít `07StahnoutVsechny.py` nebo `reimport_z_xml.py`. + +--- + +## Nastavení prostředí (jednorázově) + +```powershell +# PowerShell — spustit jednou po naklonování projektu +cd U:\recept +.\setup.ps1 +``` + +`setup.ps1` provede: +1. Vytvoří `.venv` s Python interpretem z `C:\Python\python.exe` +2. Nainstaluje všechny závislosti z `requirements.txt` +3. Nainstaluje Playwright Chromium (pro případné automatizace) + +Po nastavení aktivace: +```powershell +.venv\Scripts\Activate.ps1 +``` + +### requirements.txt + +``` +requests +requests-pkcs12 +pymysql +fdb +zeep +mysql-connector-python +playwright +openpyxl ``` --- @@ -29,9 +78,6 @@ LékovýZáznamWithClaude/ ## Typické spuštění ```bash -# Čistý start — jednou (DROP + CREATE schéma, importuje odpoved_lekovy_zaznam.xml) -python 06UlozitDoMySQL.py - # Hromadné stažení všech registrovaných pacientů python 07StahnoutVsechny.py @@ -40,6 +86,9 @@ python 07StahnoutVsechny.py --prijmeni Buzalka,Buzalková,Kusinová # Dávkování po částech python 07StahnoutVsechny.py --offset 100 --limit 50 + +# Reimport ze zálohy XML (bez volání API) — viz níže +python reimport_z_xml.py ``` --- @@ -275,7 +324,7 @@ Lékaři, kteří pacientovi předepisovali (ze všech ordinací). | `prijmeni` | VARCHAR(35) | | | `jmena` | VARCHAR(24) | | | `icz` | CHAR(8) | IČZ zdravotnického zařízení | -| `icp` | CHAR(8) | IČP pracoviště | +| `icp` | CHAR(8) | IČP pracoviště — **poslední 3 číslice = kód odbornosti** (001 = prakt. lékař, 272 = alergologie…) | | `pzs_nazev` | VARCHAR(200) | název zdravotnického zařízení | | `ulice` | VARCHAR(150) | | | `mesto` | VARCHAR(100) | | @@ -374,10 +423,96 @@ SELECT prijmeni, jmena, datum_narozeni, poznamka FROM pacient WHERE poznamka IS NOT NULL ORDER BY prijmeni; + +-- lékaři dle odbornosti — kolik předpisů pochází od které speciality +SELECT RIGHT(pr.icp, 3) AS odb_kod, COUNT(*) AS pocet_predpisu +FROM predpis p +JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho +WHERE pr.icp IS NOT NULL +GROUP BY RIGHT(pr.icp, 3) +ORDER BY pocet_predpisu DESC; + +-- lékový záznam pacienta dle rodného čísla (přes Firebird → MySQL) +-- krok 1: z Medicusu zjistit příjmení a datum narozeni pro RC 7309208104 +-- krok 2: +SELECT pac.prijmeni, pac.jmena, pac.datum_narozeni, + p.datum_vystaveni, + COALESCE(v.nazev, p.nazev) AS vydany_lek, + v.nazev IS NULL AS nevyzvednuto, + p.atc, p.navod, + pr.prijmeni AS lekar, RIGHT(pr.icp, 3) AS odb_kod +FROM pacient pac +JOIN zprava z ON z.pacient_id = pac.id +JOIN predpis p ON p.zprava_id = z.id +JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho +LEFT JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis +WHERE pac.prijmeni = 'Buzalka' AND pac.datum_narozeni = '1973-09-20' +ORDER BY p.datum_vystaveni DESC; ``` --- +## Reimport ze zálohy XML (`reimport_z_xml.py`) + +Slouží k opětovnému naplnění MySQL databáze z lokálních XML souborů **bez volání eRecept API**. +Použití: obnova po neúmyslném smazání databáze, migrace na nový server, re-parsování při změně schématu. + +### Jak funguje + +1. Načte všechny registrované pacienty z Firebirdu (ICP `09305001`, odbornost `001`) +2. Pro každý XML soubor v archivu: + - Naparsuje XML (volá `parsuj_xml()` z `06UlozitDoMySQL.py`) + - Dohledá pacienta v Firebirdu dle příjmení + data narození z XML + - Pokud je registrovaný → `upsert` pacienta do MySQL (INSERT ON DUPLICATE KEY UPDATE) + - Zavolá `uloz()` — INSERT IGNORE, takže duplicity se ignorují +3. Výpis průběhu: `[ 1/1177] Buzalka_Vladimir_1973-09-20.xml OK 12p 18v` + +### Spuštění + +```bash +# Výchozí adresář: xml_archive/2026-04-11 +python reimport_z_xml.py + +# Konkrétní podadresář +python reimport_z_xml.py xml_archive/2026-04-11 + +# Celý archiv rekurzivně (všechna data) +python reimport_z_xml.py xml_archive +``` + +### Konfigurace v souboru + +```python +XML_ADRESAR = Path(__file__).parent / "xml_archive" / "2026-04-11" # výchozí adresář +ICP = "09305001" # IČP ordinace pro filtr registrovaných pacientů +ODB = "001" # odbornost (001 = praktický lékař) +``` + +### Poznámky + +- Pacienti, kteří nejsou v Firebirdu registrováni pod daným ICP/ODB, se přeskočí + (pokud ale existují v MySQL z předchozího importu, data se aktualizují) +- Firebird slouží jako autoritativní zdroj identit — `idpac` z KAR se propíše do MySQL `pacient.idpac` +- `INSERT IGNORE` zajistí idempotentnost — opakované spuštění nepřidá duplikáty + +--- + +## Dotazovací skripty (`Dotazy/`) + +Viz samostatnou dokumentaci: [`Dotazy/DOTAZY.md`](../Dotazy/DOTAZY.md) + +Stručný přehled: + +| Skript | Co dělá | +|--------|---------| +| `prehled_pacienta.py` | Konzolový výpis lékového záznamu pacienta (lékaři + předpisy) | +| `prehled_pacienta_excel.py` | Totéž, ale exportuje do formátovaného souboru Excel (.xlsx) | + +Pacient se identifikuje **rodným číslem** (nastavení `RODNE_CISLO` v záhlaví skriptu). +Oba skripty zobrazují **vydaný lék** (ne předepsaný), **odbornost lékaře** a příznak `*NV` pro nevyzvednuto. + +--- + ## Závislosti (Python) ``` @@ -385,10 +520,15 @@ requests requests-pkcs12 pymysql fdb +zeep +mysql-connector-python +playwright +openpyxl ``` ```bash -pip install requests requests-pkcs12 pymysql fdb +# Instalace (nebo použít setup.ps1) +pip install requests requests-pkcs12 pymysql fdb openpyxl ``` --- diff --git a/LékovýZáznamWithClaude/reimport_z_xml.py b/LékovýZáznamWithClaude/reimport_z_xml.py new file mode 100644 index 0000000..82168ad --- /dev/null +++ b/LékovýZáznamWithClaude/reimport_z_xml.py @@ -0,0 +1,194 @@ +""" +Reimport vsech XML souboru z xml_archive do MySQL — bez volani API. + +Pouziti: + python reimport_z_xml.py # vsechna XML z 2026-04-11 + python reimport_z_xml.py xml_archive/2026-04-11 # konkretni adresar + python reimport_z_xml.py xml_archive # vsechny podadresare rekurzivne +""" + +import sys +import importlib.util +from pathlib import Path +from datetime import date +import fdb +import pymysql +import pymysql.cursors + +# Windows konzole +if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(errors="replace") + +# ── Konfigurace ─────────────────────────────────────────────────────────────── +XML_ADRESAR = Path(__file__).parent / "xml_archive" / "2026-04-11" + +FB = dict( + dsn = r"localhost:c:\medicus 3\data\medicus.fdb", + user = "SYSDBA", + password = "masterkey", + charset = "win1250", +) + +DB = dict( + host = "192.168.1.76", + user = "root", + password = "Vlado9674+", + database = "medicus", + charset = "utf8mb4", + cursorclass = pymysql.cursors.DictCursor, +) + +ICP = "09305001" +ODB = "001" +# ───────────────────────────────────────────────────────────────────────────── + +# Nacteni parsovaci logiky z 06UlozitDoMySQL.py +_spec = importlib.util.spec_from_file_location( + "m06", Path(__file__).parent / "06UlozitDoMySQL.py" +) +_m06 = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(_m06) + +parsuj_xml = _m06.parsuj_xml +uloz = _m06.uloz +inicializuj_schema = _m06.inicializuj_schema + + +def nacti_pacienty_z_fb(): + """Vrati slovnik {(prijmeni_upper, datnar): idpac} ze vsech pacientu v Medicusu.""" + conn = fdb.connect(**FB) + try: + cur = conn.cursor() + dnes = date.today().isoformat() + cur.execute(""" + SELECT KAR.IDPAC, KAR.PRIJMENI, KAR.JMENO, KAR.DATNAR + FROM KAR + WHERE KAR.vyrazen = 'N' + AND EXISTS ( + SELECT 1 FROM registr r + JOIN icp i ON r.idicp = i.idicp + WHERE r.idpac = kar.idpac + AND r.datum <= ? + AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= ?) + AND r.priznak IN ('V','D','A') + AND i.icp = ? + AND i.odb = ? + ) + """, (dnes, dnes, ICP, ODB)) + result = {} + for row in cur.fetchall(): + idpac, prijmeni, jmeno, datnar = row + klic = (prijmeni.strip().upper(), datnar) + result[klic] = {"idpac": idpac, "prijmeni": prijmeni.strip(), "jmeno": jmeno.strip(), "datnar": datnar} + print(f"Firebird: nacteno {len(result)} registrovanych pacientu") + return result + finally: + conn.close() + + +def upsert_pacient(cur, pac): + cur.execute(""" + INSERT INTO pacient (idpac, prijmeni, jmena, datum_narozeni) + VALUES (%s, %s, %s, %s) + ON DUPLICATE KEY UPDATE + prijmeni = VALUES(prijmeni), + jmena = VALUES(jmena) + """, (pac["idpac"], pac["prijmeni"], pac["jmeno"], pac["datnar"])) + cur.execute("SELECT id FROM pacient WHERE idpac = %s", (pac["idpac"],)) + return cur.fetchone()["id"] + + +def main(): + # Adresar z argumentu nebo default + adresar = Path(sys.argv[1]) if len(sys.argv) > 1 else XML_ADRESAR + if not adresar.is_dir(): + sys.exit(f"Adresar neexistuje: {adresar}") + + # Najdi vsechna XML rekurzivne + xml_soubory = sorted(adresar.rglob("*.xml")) + if not xml_soubory: + sys.exit(f"Zadne XML soubory nalezeny v: {adresar}") + print(f"Nalezeno {len(xml_soubory)} XML souboru v: {adresar}") + + # Nacti pacienty z Firebirdu + fb_pacienti = nacti_pacienty_z_fb() + + # Pripoj se k MySQL a inicializuj schema + conn = pymysql.connect(**DB) + try: + inicializuj_schema(conn) + + ok = chyba = preskoceno = 0 + p_celkem = v_celkem = 0 + + for i, xml_path in enumerate(xml_soubory, 1): + rel = xml_path.relative_to(Path(__file__).parent) + try: + zprava, predpisy, vydeji, predepisujici, vydavajici = parsuj_xml(xml_path) + except Exception as e: + print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} CHYBA parsovani: {e}") + chyba += 1 + continue + + # Zjisti prijmeni a datum narozeni z XML odpovedi + pac_prijmeni = (zprava.get("pacient_prijmeni") or "").upper() + pac_datnar = zprava.get("pacient_datum_narozeni") # string YYYY-MM-DD nebo None + + # Prevod na date objekt pro porovnani s Firebirdem + if pac_datnar and isinstance(pac_datnar, str): + try: + from datetime import datetime + pac_datnar_d = datetime.strptime(pac_datnar[:10], "%Y-%m-%d").date() + except ValueError: + pac_datnar_d = None + elif hasattr(pac_datnar, "year"): + pac_datnar_d = pac_datnar + else: + pac_datnar_d = None + + klic = (pac_prijmeni, pac_datnar_d) + fb_pac = fb_pacienti.get(klic) + + if not fb_pac: + # Pacient neni registrovan — uloz bez idpac (bude ignorovan pri hromadnem behu) + # Zkus najit v MySQL podle jmena a data + with conn.cursor() as cur: + cur.execute( + "SELECT id FROM pacient WHERE prijmeni = %s AND datum_narozeni = %s", + (zprava.get("pacient_prijmeni"), pac_datnar) + ) + row = cur.fetchone() + if row: + pacient_id = row["id"] + else: + preskoceno += 1 + print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} PRESKOCENO (neni v registru)") + continue + else: + with conn.cursor() as cur: + pacient_id = upsert_pacient(cur, fb_pac) + conn.commit() + + try: + stats = uloz(conn, zprava, predpisy, vydeji, predepisujici, vydavajici, + pacient_id=pacient_id, xml_soubor=str(rel)) + conn.commit() + p_celkem += stats["predpisy_novych"] + v_celkem += stats["vydeji_novych"] + print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} OK " + f"{stats['predpisy_novych']:3}p {stats['vydeji_novych']:3}v") + ok += 1 + except Exception as e: + conn.rollback() + print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} CHYBA ukladani: {e}") + chyba += 1 + + print() + print(f"Hotovo: {ok} OK, {chyba} chyb, {preskoceno} preskoceno") + print(f"Celkem vlozeno: {p_celkem} predpisu, {v_celkem} vydejuu") + finally: + conn.close() + + +if __name__ == "__main__": + main() diff --git a/tmp_check_odb.py b/tmp_check_odb.py new file mode 100644 index 0000000..756ae9f --- /dev/null +++ b/tmp_check_odb.py @@ -0,0 +1,38 @@ +import fdb, sys + +conn = fdb.connect( + dsn=r"localhost:c:\medicus 3\data\medicus.fdb", + user="SYSDBA", password="masterkey", charset="win1250" +) +cur = conn.cursor() + +# Try CSSZ_ODB +try: + cur.execute("SELECT FIRST 30 * FROM CSSZ_ODB") + cols = [d[0] for d in cur.description] + print("CSSZ_ODB cols:", cols) + for r in cur.fetchall(): + print(r) +except Exception as e: + print("CSSZ_ODB:", e) + +# Try ODB table +try: + cur.execute("SELECT FIRST 30 * FROM ODB") + cols = [d[0] for d in cur.description] + print("ODB cols:", cols) + for r in cur.fetchall(): + print(r) +except Exception as e: + print("ODB:", e) + +# List all tables with ODB in name +try: + cur.execute("SELECT RDB$RELATION_NAME FROM RDB$RELATIONS WHERE RDB$RELATION_NAME LIKE '%ODB%' AND RDB$SYSTEM_FLAG = 0") + for r in cur.fetchall(): + print("Table:", r[0].strip()) +except Exception as e: + print("table list:", e) + +conn.close() +print("DONE") diff --git a/tmp_out.txt b/tmp_out.txt new file mode 100644 index 0000000..0371114 --- /dev/null +++ b/tmp_out.txt @@ -0,0 +1,38 @@ +CSSZ_ODB cols: ['ID', 'KOD', 'NAZEV', 'PLATNOST_OD', 'PLATNOST_DO', 'POZNAMKA'] +(104, 'L01', 'alergologie a klinickß imunologie', datetime.date(2019, 9, 1), None, None) +(105, 'L02', 'anesteziologie a intenzivnÝ medicÝna', datetime.date(2019, 9, 1), None, None) +(106, 'L04', 'cÚvnÝ chirurgie', datetime.date(2019, 9, 1), None, None) +(107, 'L05', 'dermatovenerologie', datetime.date(2019, 9, 1), None, None) +(108, 'L07', 'dýtskß chirurgie', datetime.date(2019, 9, 1), None, None) +(109, 'L08', 'dýtskÚ lÚka°stvÝ', datetime.date(2019, 9, 1), None, None) +(110, 'L09', 'endokrinologie a diabetologie', datetime.date(2019, 9, 1), None, None) +(111, 'L10', 'gastroenterologie', datetime.date(2019, 9, 1), None, None) +(112, 'L11', 'geriatrie', datetime.date(2019, 9, 1), None, None) +(113, 'L12', 'gynekologie a porodnictvÝ', datetime.date(2019, 9, 1), None, None) +(114, 'L13', 'hematologie a transf˙znÝ lÚka°stvÝ', datetime.date(2019, 9, 1), None, None) +(115, 'L14', 'hygiena a epidemiologie', datetime.date(2019, 9, 1), None, None) +(116, 'L15', 'chirurgie', datetime.date(2019, 9, 1), None, None) +(117, 'L16', 'infekŔnÝ lÚka°stvÝ', datetime.date(2019, 9, 1), None, None) +(118, 'L17', 'kardiochirurgie', datetime.date(2019, 9, 1), None, None) +(119, 'L18', 'kardiologie', datetime.date(2019, 9, 1), None, None) +(120, 'L19', 'klinickß biochemie', datetime.date(2019, 9, 1), None, None) +(121, 'L20', 'klinickß onkologie', datetime.date(2019, 9, 1), None, None) +(122, 'L21', 'lÚka°skß genetika', datetime.date(2019, 9, 1), None, None) +(123, 'L22', 'lÚka°skß mikrobiologie', datetime.date(2019, 9, 1), None, None) +(124, 'L23', 'nefrologie', datetime.date(2019, 9, 1), None, None) +(125, 'L24', 'neurochirurgie', datetime.date(2019, 9, 1), None, None) +(126, 'L25', 'neurologie', datetime.date(2019, 9, 1), None, None) +(127, 'L26', 'nukleßrnÝ medicÝna', datetime.date(2019, 9, 1), None, None) +(128, 'L27', 'oftalmologie', datetime.date(2019, 9, 1), None, None) +(129, 'L28', 'ortopedie a traumatologie pohybovÚho ˙strojÝ', datetime.date(2019, 9, 1), None, None) +(130, 'L29', 'otorinolaryngologie a chirurgie hlavy a krku', datetime.date(2019, 9, 1), None, None) +(131, 'L30', 'patologie', datetime.date(2019, 9, 1), None, None) +(132, 'L31', 'plastickß chirurgie', datetime.date(2019, 9, 1), None, None) +(133, 'L32', 'pneumologie a ftizeologie', datetime.date(2019, 9, 1), None, None) +ODB: ('Error while preparing SQL statement:\n- SQLCODE: -204\n- Dynamic SQL Error\n- SQL error code = -204\n- Table unknown\n- ODB\n- At line 1, column 24', -204, 335544569) +Table: HODBOD +Table: ODBORN +Table: ODBORN_MASK +Table: ODBORN_SKUP +Table: CSSZ_ODB +DONE