diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f521b32..ddcfe59 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -7,7 +7,8 @@ "Read(//c/xampp/mysql/**)", "Bash(find /c -name \"mysql.exe\")", "Bash(python3 -c ':*)", - "Bash(python -c ':*)" + "Bash(python -c ':*)", + "Bash(.venv/Scripts/python.exe -c ':*)" ] } } diff --git a/Dotazy/DOTAZY.md b/Dotazy/DOTAZY.md index 696d1a5..76865c7 100644 --- a/Dotazy/DOTAZY.md +++ b/Dotazy/DOTAZY.md @@ -144,9 +144,9 @@ Klíčový princip `COALESCE(v.nazev, p.nazev)`: 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 +ICP: 09305001 → kód odbornosti: 001 → všeobecné praktické lékařství +ICP: 08006272 → kód odbornosti: 272 → alergologie +ICP: 08075603 → kód odbornosti: 603 → onkologie ``` Funkce: @@ -157,32 +157,38 @@ def odbornost_z_icp(icp): 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). +Pro neznámé kódy se zobrazí `odb. XXX` (XXX = třímístný kód). -### Slovník ODBORNOST — vybrané klíčové kódy +### Zdroj dat — tabulky `vzp_pracoviste` + `odbornost` (MySQL) -| 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 | +Slovník `ODBORNOST` se **načítá dynamicky při startu skriptu** z MySQL: -Celý slovník obsahuje ~170 kódů (viz zdrojový kód skriptů). -Kompletní číselník VZP/SÚKL: (sekce číselníky). +```python +def _nacti_odbornosti(): + conn = pymysql.connect(**DB) + try: + with conn.cursor(pymysql.cursors.Cursor) as cur: + cur.execute(""" + SELECT vp.icp, o.nazev + FROM vzp_pracoviste vp + JOIN odbornost o ON o.kod = vp.odbornost + WHERE CURDATE() BETWEEN vp.platnost_od AND vp.platnost_do + ORDER BY vp.platnost_od DESC + """) + result = {} + for icp, nazev in cur.fetchall(): + result.setdefault(icp, nazev) + return result + finally: + conn.close() + +ODBORNOST = _nacti_odbornosti() +``` + +- `vzp_pracoviste` — oficiální číselník VZP (stahován týdně z VZP Point přes `import_vzp_pracoviste.py`), obsahuje ~52 000 záznamů s přímou vazbou ICP → kód odbornosti +- `odbornost` — číselník názvů odborností importovaný z Firebird tabulky `odborn` (360 aktuálně platných kódů) +- Vyhledávání probíhá podle **plného 8-znakového ICP** — spolehlivé i pro pracoviště, která změnila odbornost +- Slovník obsahuje ~43 000 aktuálně platných ICP kódů --- diff --git a/Dotazy/prehled_pacienta.py b/Dotazy/prehled_pacienta.py index 1ca7a5b..681f24d 100644 --- a/Dotazy/prehled_pacienta.py +++ b/Dotazy/prehled_pacienta.py @@ -16,207 +16,13 @@ import fdb import pymysql import pymysql.cursors -# Kody odbornosti dle SUKL / VZP (posledni 3 cislice ICP) -ODBORNOST = { - # Základní ambulantní odbornosti - "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", - # Interní a specializované odbornosti - "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", - # Chirurgické a dětské odbornosti - "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", - # Onkologie a zobrazovací metody - "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", - # Stomatologie (500-599) - "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í", - # Onkologie (600-699) - "600": "Onkologie", - "601": "Klin. onkologie", - "603": "Onkologie", - "606": "Radioterapie", - "607": "Nukleární medicína", - "615": "Onkologie", - # Kardiochirurgie, ostatní (700+) - "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", - # Fyzioterapie, rehabilitace (800+) - "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: + """Vrati nazev odbornosti podle ICP (plny 8-znakovy kod) z tabulky vzp_pracoviste.""" + if not icp: return "" - kod = icp[-3:] - return ODBORNOST.get(kod, f"odb. {kod}") + return ODBORNOST.get(icp, f"odb. {icp[-3:]}") # ── NASTAVENÍ ───────────────────────────────────────────────────────────────── RODNE_CISLO = "440802/018" # funguje s lomitkem i bez: 7309208104 nebo 730920/8104 @@ -230,6 +36,8 @@ FB = dict( charset = "win1250", ) + + DB = dict( host = "192.168.1.76", user = "root", @@ -238,6 +46,28 @@ DB = dict( charset = "utf8mb4", cursorclass = pymysql.cursors.DictCursor, ) +def _nacti_odbornosti(): + """Nacteni odbornosti z MySQL: vzp_pracoviste JOIN odbornost (aktualne platne ICP).""" + conn = pymysql.connect(**DB) + try: + with conn.cursor(pymysql.cursors.Cursor) as cur: + cur.execute(""" + SELECT vp.icp, o.nazev + FROM vzp_pracoviste vp + JOIN odbornost o ON o.kod = vp.odbornost + WHERE CURDATE() BETWEEN vp.platnost_od AND vp.platnost_do + ORDER BY vp.platnost_od DESC + """) + result = {} + for icp, nazev in cur.fetchall(): + result.setdefault(icp, nazev) + return result + finally: + conn.close() + +ODBORNOST = _nacti_odbornosti() + + SEP = "-" * 110 SEP2 = "-" * 165 diff --git a/Dotazy/prehled_pacienta_excel.py b/Dotazy/prehled_pacienta_excel.py index d77ed23..9a5bf3e 100644 --- a/Dotazy/prehled_pacienta_excel.py +++ b/Dotazy/prehled_pacienta_excel.py @@ -18,198 +18,13 @@ 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: + """Vrati nazev odbornosti podle ICP (plny 8-znakovy kod) z tabulky vzp_pracoviste.""" + if not icp: return "" - return ODBORNOST.get(icp[-3:], f"odb. {icp[-3:]}") + return ODBORNOST.get(icp, f"odb. {icp[-3:]}") # ── NASTAVENÍ ───────────────────────────────────────────────────────────────── @@ -225,6 +40,8 @@ FB = dict( charset = "win1250", ) + + DB = dict( host = "192.168.1.76", user = "root", @@ -233,6 +50,28 @@ DB = dict( charset = "utf8mb4", cursorclass = pymysql.cursors.DictCursor, ) +def _nacti_odbornosti(): + """Nacteni odbornosti z MySQL: vzp_pracoviste JOIN odbornost (aktualne platne ICP).""" + conn = pymysql.connect(**DB) + try: + with conn.cursor(pymysql.cursors.Cursor) as cur: + cur.execute(""" + SELECT vp.icp, o.nazev + FROM vzp_pracoviste vp + JOIN odbornost o ON o.kod = vp.odbornost + WHERE CURDATE() BETWEEN vp.platnost_od AND vp.platnost_do + ORDER BY vp.platnost_od DESC + """) + result = {} + for icp, nazev in cur.fetchall(): + result.setdefault(icp, nazev) + return result + finally: + conn.close() + +ODBORNOST = _nacti_odbornosti() + + # ── Barvy ───────────────────────────────────────────────────────────────────── C_HEADER_BG = "1F4E79" # tmave modra — hlavicka tabulky diff --git a/NačteníPředpisuWithClaude/09_VytvorTabulky.py b/NačteníPředpisuWithClaude/09_VytvorTabulky.py index f828368..9e61df1 100644 --- a/NačteníPředpisuWithClaude/09_VytvorTabulky.py +++ b/NačteníPředpisuWithClaude/09_VytvorTabulky.py @@ -103,7 +103,7 @@ DDL = [ COMMENT 'UUID PLP = predpis.id_lp_predpis', id_dokladu VARCHAR(20) NOT NULL, - uhrada ENUM('ZAKLADNI','ZVYSENA','NEHRAZENY'), + uhrada VARCHAR(20), prekroceni TINYINT(1), FOREIGN KEY (id_dokladu) REFERENCES recept_doklad (id_dokladu) diff --git a/NačteníPředpisuWithClaude/10_StahnoutXML.py b/NačteníPředpisuWithClaude/10_StahnoutXML.py index b91d0cb..5714a68 100644 --- a/NačteníPředpisuWithClaude/10_StahnoutXML.py +++ b/NačteníPředpisuWithClaude/10_StahnoutXML.py @@ -31,7 +31,7 @@ if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(errors="replace") # ── Konfigurace eRecept ────────────────────────────────────────────────────── -PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx" +PFX_FILE = Path(__file__).parent.parent / "AMBSUKL214235369G_31DEC2024.pfx" PFX_PASS = "Vlado7309208104++" API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379" API_PASS = "Buzalka@Vladimir2025" @@ -64,7 +64,7 @@ XML_DIR = Path(__file__).parent / "xml_archive" # ── Parametry spuštění (uprav zde) ─────────────────────────────────────────── DATUM_OD = "2025-01-01" # recepty od tohoto data -LIMIT = 10 # max počet receptů ke stažení; None = bez omezení +LIMIT = None # max počet receptů ke stažení; None = bez omezení # ───────────────────────────────────────────────────────────────────────────── diff --git a/NačteníPředpisuWithClaude/NacistPredpis_DOKUMENTACE.md b/NačteníPředpisuWithClaude/NacistPredpis_DOKUMENTACE.md index a8dd612..32a946f 100644 --- a/NačteníPředpisuWithClaude/NacistPredpis_DOKUMENTACE.md +++ b/NačteníPředpisuWithClaude/NacistPredpis_DOKUMENTACE.md @@ -11,12 +11,18 @@ které hromadný dotaz nevrací. | Soubor | Co dělá | |--------|---------| | `NacistPredpis_FUNKCNI.py` | Stáhne detail **jednoho** receptu dle hardcoded ID_Dokladu (ruční test) | -| `08StahnoutPredpisy.py` | **Hlavní skript** — načte ERP kódy z Medicusu, stáhne detaily, uloží XML | +| `08StahnoutPredpisy.py` | Starší skript bez DB integrace — nahrazen `10_StahnoutXML.py` | +| `09_VytvorTabulky.py` | Vytvoří tabulky `recept_doklad` a `recept_plp` v MySQL | +| `10_StahnoutXML.py` | **Stahování** — načte ERP kódy z Medicusu, přeskočí terminální, uloží XML | +| `11_ParseXML.py` | **Parsování** — naparsuje XML archiv a uloží data do MySQL | ``` NačteníPředpisuWithClaude/ ├── NacistPredpis_FUNKCNI.py ← test jednoho receptu -├── 08StahnoutPredpisy.py ← hromadné stahování +├── 08StahnoutPredpisy.py ← starší skript (bez DB) +├── 09_VytvorTabulky.py ← DDL MySQL tabulek +├── 10_StahnoutXML.py ← hromadné stahování s přeskakováním +├── 11_ParseXML.py ← parsování XML do MySQL ├── NacistPredpis_DOKUMENTACE.md ← tento soubor ├── xml_archive/ ← archiv XML odpovědí (YYYY-MM-DD/ERP_KOD.xml) ├── MedicusDebug/ ← zachycené SOAP požadavky z Medicusu @@ -38,7 +44,7 @@ jednoho konkrétního receptu, včetně: | Pole | Popis | |------|-------| | `ID_Dokladu` | Alfanumerický kód receptu (např. `PPIBVF93285E`) | -| `Stav` | Stav receptu: PREDEPSANY, CASTECNE_VYDANY, PLNE_VYDANY, ZRUSENY… | +| `Stav` | Stav receptu: PREDEPSANY, CASTECNE_VYDANY, PLNE_VYDANY, ZRUSENY | | `PlatnostDo` | Datum konce platnosti receptu | | `VypisDo` | Prodloužení platnosti výpisem | | `Akutni` | Příznak akutní péče | @@ -54,7 +60,7 @@ jednoho konkrétního receptu, včetně: | Pole | Popis | |------|-------| -| `Uhrada` | ZAKLADNI / ZVYSENA / NEHRAZENY | +| `Uhrada` | ZAKLADNI / ZVYSENA / NEHRAZENY / PACIENT | | `Prekroceni` | Překročení limitu | ### Údaje o pacientovi @@ -64,7 +70,7 @@ jednoho konkrétního receptu, včetně: | `CP` | Číslo pojištěnce (rodné číslo) | | `ZP` | Zdravotní pojišťovna (kód + název) | | `Adresa` | Kompletní adresa pacienta | -| `Pohlavi` | M / Z | +| `Pohlavi` | M / F (ne M/Z jak uvádí XSD — reálně posíláno M/F) | | `Telefon` | Telefonní číslo | | `Notifikace` | SMS / Email | @@ -72,6 +78,7 @@ jednoho konkrétního receptu, včetně: | Pole | Popis | |------|-------| +| `Lekar.Kod` | UUID lékaře — nebo `"skryto"` (ukládáme jako NULL) | | `Odbornost` | Kód + název (např. 001 — všeobecné praktické lékařství) | | `Email` | Email lékaře | @@ -92,6 +99,7 @@ jméno lékárníka (často „skryto"), datum vydeje, vydané léky. | **Identifikace** | ID_Dokladu (alfanumerický kód receptu) | jméno + datum narození pacienta | | **Výsledek** | detail jednoho receptu | celý lékový záznam pacienta (roky) | | **Velikost odpovědi** | ~3.5–4.5 KB | ~227 KB | +| **Pokrytí** | pouze naše ordinace (ERP kód z Medicusu) | všichni lékaři pacienta | --- @@ -106,6 +114,8 @@ jméno lékárníka (často „skryto"), datum vydeje, vydané léky. | XML namespace | `http://www.sukl.cz/erp/201704` | | Verze zprávy | `202501A` | +Certifikát se hledá relativně ke skriptu: `../../AMBSUKL214235369G_31DEC2024.pfx` + --- ## Zdroj ID_Dokladu — Medicus (Firebird) @@ -118,6 +128,9 @@ RECEPT.id_epodani → RECEPT_EPODANI.id RECEPT_EPODANI.erp = ID_Dokladu (např. "PPIBVF93285E") ``` +> **Důsledek:** Detaily receptů lze stáhnout **pouze pro naši ordinaci**. +> O předpisech cizích lékařů víme jen to, co vrací lékový záznam. + ### SQL dotaz ```sql @@ -130,6 +143,9 @@ WHERE r.datum >= '2025-01-01' AND ep.erp IS NOT NULL ORDER BY r.datum DESC ``` +> Pozor: `LIMIT` v `10_StahnoutXML.py` omezuje počet řádků z Firebirdu před deduplikací. +> Po deduplikaci (jeden recept = více léků = více řádků) může být výsledný počet receptů nižší. + ### Statistika (duben 2026) - **13 571** receptů s ERP kódem od 1. 1. 2025 @@ -137,58 +153,144 @@ ORDER BY r.datum DESC --- -## 08StahnoutPredpisy.py — hlavní skript +## Databázové schéma — MySQL + +### Relační diagram + +``` +recept_doklad (1) ────────────────── (N) recept_plp + id_dokladu PK id_lp PK ──────► predpis.id_lp_predpis + id_dokladu FK +``` + +Tabulka `recept_plp.id_lp` = `predpis.id_lp_predpis` — přímý JOIN s lékovým záznamem. + +### Tabulka `recept_doklad` + +Jeden řádek na celý recept (ID_Dokladu). + +| Sloupec | Typ | Poznámka | +|---------|-----|----------| +| `id_dokladu` | VARCHAR(20) PK | ERP kód | +| `stav` | ENUM | PREDEPSANY / CASTECNE_VYDANY / PLNE_VYDANY / ZRUSENY | +| `stav_terminal` | TINYINT(1) | 1 = nepotřebuje další stahování | +| `datum_vystaveni` | DATE | | +| `platnost_do` | DATE | | +| `vypis_do` | DATE | prodloužení výpisem | +| `akutni` | TINYINT(1) | | +| `rodina` | TINYINT(1) | ad usum proprium | +| `opakovani` | INT | NULL = není opakovací | +| `druh_pojisteni` | ENUM | VEREJNE / OSTATNI | +| `modry_pruh` | TINYINT(1) | | +| `pozn` | VARCHAR(1000) | | +| `zap_doplatek` | DECIMAL(10,2) | ZapocitatelnyDoplatekZbyvaDoLimitu | +| `zalozeni` / `zmena` | DATETIME | z eReceptu | +| `lekar_kod` | CHAR(36) | UUID lékaře; NULL pokud "skryto" | +| `odbornost_kod` / `odbornost_nazev` | VARCHAR | | +| `lekar_email` | VARCHAR(100) | | +| `cp` | VARCHAR(10) | číslo pojištěnce | +| `zp_kod` / `zp_nazev` | VARCHAR | pojišťovna při předpisu (snapshot) | +| `pac_telefon` | VARCHAR(20) | | +| `pac_notifikace` | ENUM | SMS / EMAIL | +| `pac_pohlavi` | VARCHAR(5) | M / F | +| `xml_soubor` | VARCHAR(255) | cesta k poslednímu XML | +| `stazeno` | DATETIME | poslední aktualizace | + +### Tabulka `recept_plp` + +Jeden řádek na PLP položku (lék na receptu). + +| Sloupec | Typ | Poznámka | +|---------|-----|----------| +| `id_lp` | CHAR(36) PK | UUID = `predpis.id_lp_predpis` | +| `id_dokladu` | VARCHAR(20) FK | | +| `uhrada` | VARCHAR(20) | ZAKLADNI / ZVYSENA / NEHRAZENY / PACIENT | +| `prekroceni` | TINYINT(1) | | + +### JOIN lékový záznam + detail receptu + +```sql +SELECT p.datum_vystaveni, p.nazev, p.atc, p.navod, + rd.stav, rd.platnost_do, rd.zp_nazev, + rp.uhrada, + v.datum_vydeje +FROM predpis p +LEFT JOIN recept_plp rp ON rp.id_lp = p.id_lp_predpis +LEFT JOIN recept_doklad rd ON rd.id_dokladu = rp.id_dokladu +LEFT JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis +WHERE p.atc LIKE 'C09%' +ORDER BY p.datum_vystaveni DESC; +``` + +> LEFT JOIN — pro cizí lékaře `rd` a `rp` budou NULL (nemáme jejich ERP kód). + +--- + +## 10_StahnoutXML.py — stahování ### Parametry (editovat přímo v souboru) ```python -LIMIT = 100 # max počet receptů ke stažení -DATUM_OD = "2025-01-01" # recepty od tohoto data -PRIJMENI = ["Buzalka"] # filtr příjmení (list), nebo None = všichni +DATUM_OD = "2025-01-01" # recepty od tohoto data +LIMIT = None # max počet receptů; None = bez omezení +``` + +### Logika přeskakování + +Na začátku načte z MySQL jeden dotaz: +```sql +SELECT id_dokladu FROM recept_doklad WHERE stav_terminal = 1 +``` +Výsledek = Python set. Pro každý ERP kód z Medicusu: +- je v setu → přeskočit (vydaný / zrušený / expirovaný) +- není v setu → stáhnout + +### Co je terminální (`stav_terminal = 1`) + +- `stav IN ('PLNE_VYDANY', 'ZRUSENY')` +- nebo `platnost_do < dnes` (expirovaný bez vyzvednutí) + +### Ošetření chyb + +| Kód | Popis | Chování | +|-----|-------|---------| +| **D003** | Předpis zrušen lékařem | Uloží `_CHYBA.xml`, pokračuje | +| HTTP 500 | SOAP Fault | Uloží `_CHYBA.xml`, pokračuje | +| Exception | Síťová chyba | Vypíše EXCEPTION, pokračuje | + +--- + +## 11_ParseXML.py — parsování do MySQL + +### Parametry (editovat přímo v souboru) + +```python +DATUM_FILTR = None # např. "2026-04-14", nebo None = celý archiv ``` ### Co dělá -1. Připojí se k Firebirdu, načte unikátní ERP kódy (deduplikované — jeden recept může mít více léků) -2. Pro každý ERP kód zavolá `NacistPredpis` přes SOAP API -3. Uloží XML odpověď do `xml_archive/YYYY-MM-DD/{ERP_KOD}.xml` -4. Chybové odpovědi uloží jako `{ERP_KOD}_CHYBA.xml` -5. Pauza 5 sekund mezi voláními +1. Najde nejnovější XML pro každý ERP kód (nejvyšší datum v adresářové struktuře) +2. Naparsuje stav, platnost, pacient, předepisující, PLP položky +3. `recept_doklad`: INSERT ... ON DUPLICATE KEY UPDATE (stav se může změnit) +4. `recept_plp`: INSERT IGNORE (UUID je stabilní) -### Výstup v konzoli +### Správné pořadí spuštění ``` -[ 1/55] Buzalka Vladimír PPM5HM49EBF9 OK 3.6 KB EZETIMIB/ATORVASTATIN STADA 10MG/20MG TB -[ 25/55] Buzalka Vladimír POMOTIAJ77PI CHYBA HTTP 500 D003 - Předpis zrušen... +10_StahnoutXML.py → 11_ParseXML.py → 10_StahnoutXML.py → ... ``` -### Ošetření chyb - -| Kód | Popis | Chování skriptu | -|-----|-------|-----------------| -| **D003** | Předpis byl zrušen lékařem | Uloží `_CHYBA.xml`, pokračuje dál | -| HTTP 500 | SOAP Fault (obecný) | Uloží `_CHYBA.xml`, pokračuje dál | -| Timeout / Exception | Síťová chyba | Vypíše EXCEPTION, pokračuje dál | +`10` se spoléhá na `stav_terminal` v MySQL, který nastavuje `11`. +Bez spuštění `11` budou terminální recepty znovu stahovány. --- ## Ověřeno (14. 4. 2026) -Testovací běh na receptech pacienta Buzalka od 1. 1. 2025: - -- **55** unikátních ERP kódů nalezeno v Medicusu -- **51** úspěšně staženo (OK) -- **4** chyby D003 (zrušené recepty) -- XML uloženy do `xml_archive/2026-04-14/` - ---- - -## Další kroky (plán) - -1. **MySQL tabulka `recept`** — uložit detail receptu (stav, platnost, úhrada, pojišťovna…) -2. **Parsování XML** — extrakce dat z odpovědí do MySQL -3. **Inkrementální stahování** — procházet jen nové recepty od posledního běhu -4. **Stažení všech 13 571 receptů** od 1. 1. 2025 (odhad: ~19 hodin při 5s pauze) +- Hromadné stažení od 1. 1. 2025 spuštěno (`LIMIT = None`) +- Průběžně zpracováno 2 167 / 9 595 receptů bez chyb +- Odhad celkového běhu: ~16 hodin při 4–6s náhodné pauze ---