# NacistPredpis — Funkční SOAP klient pro IS eRecept SÚKL Pipeline pro stažení **detailu jednotlivých receptů** z eRecept SÚKL API. Doplňuje existující hromadný lékový záznam (`NacistLekovyZaznam`) o údaje, které hromadný dotaz nevrací. --- ## Soubory | Soubor | Co dělá | |--------|---------| | `NacistPredpis_FUNKCNI.py` | Stáhne detail **jednoho** receptu dle hardcoded ID_Dokladu (ruční test) | | `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 | | `12_DenníStaženíAZpracování.py` | **Denní kombinovaný skript** — stažení + parsování + emailový souhrn; spouští se každé 3 hodiny | ``` NačteníPředpisuWithClaude/ ├── NacistPredpis_FUNKCNI.py ← test jednoho receptu ├── 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 ├── 12_DenníStaženíAZpracování.py ← denní kombinovaný skript (stažení + parsování + email) ├── NacistPredpis_DOKUMENTACE.md ← tento soubor ├── xml_archive/ ← archiv XML odpovědí (YYYY-MM-DD/ERP_KOD.xml) ├── MedicusDebug/ ← zachycené SOAP požadavky z Medicusu └── Tests/ ← starší vývojové soubory ``` --- ## Co NacistPredpis vrací navíc oproti NacistLekovyZaznam Hromadný lékový záznam (`NacistLekovyZaznam`, endpoint `/cuer/Lekar2`) vrací seznam předpisů a výdejů za pacienta, ale **bez detailů o receptu jako celku**. `NacistPredpis` (endpoint `/cuer/Lekar`, namespace `201704`) vrací detail jednoho konkrétního receptu, včetně: ### Údaje o receptu (dokladu) | Pole | Popis | |------|-------| | `ID_Dokladu` | Alfanumerický kód receptu (např. `PPIBVF93285E`) | | `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 | | `Rodina` | „Pro potřebu rodiny" / ad usum proprium | | `Opakovani` | Počet výdejů u opakovacích receptů | | `DruhPojisteni` | VEREJNE / OSTATNI | | `ModryPruh` | Omamné/psychotropní látky | | `Pozn` | Poznámka na receptu (max 1000 znaků) | | `ZapocitatelnyDoplatekZbyvaDoLimitu` | Zbývá do limitu doplatků pacienta | | `Zmena` / `Zalozeni` | Datetime poslední změny / vytvoření | ### Údaje o úhradě léku (per PLP) | Pole | Popis | |------|-------| | `Uhrada` | ZAKLADNI / ZVYSENA / NEHRAZENY / PACIENT | | `Prekroceni` | Překročení limitu | ### Údaje o pacientovi | Pole | Popis | |------|-------| | `CP` | Číslo pojištěnce (rodné číslo) | | `ZP` | Zdravotní pojišťovna (kód + název) | | `Adresa` | Kompletní adresa pacienta | | `Pohlavi` | M / F (ne M/Z jak uvádí XSD — reálně posíláno M/F) | | `Telefon` | Telefonní číslo | | `Notifikace` | SMS / Email | ### Údaje o předepisujícím | 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 | ### Zkrácený výdej Odpověď obsahuje i sekci `Vydej[]` se zkrácenou informací o výdejích — název lékárny, jméno lékárníka (často „skryto"), datum vydeje, vydané léky. --- ## Porovnání operací | | NacistPredpis | NacistLekovyZaznam | |---|---|---| | **Namespace** | `201704` | `201912` | | **Endpoint** | `/cuer/Lekar` | `/cuer/Lekar2` | | **SOAPAction** | `NacistPredpis` | `NacistLekovyZaznam` | | **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 | --- ## Autentizace (stejná jako u všech operací eReceptu) | Parametr | Hodnota | |----------|---------| | Endpoint | `https://lekar-soap.erecept.sukl.cz/cuer/Lekar` | | mTLS certifikát | `AMBSUKL214235369G_31DEC2024.pfx` (platnost do 31. 12. 2026) | | HTTP Basic user | UUID lékaře `e08c89c6-2b1a-4eba-8ed9-4e3e63618379` | | SOAP operace | `NacistPredpis` | | 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) Alfanumerický kód receptu (ID_Dokladu) **není** v hromadném lékovém záznamu. Nachází se v tabulce **`RECEPT_EPODANI`** v Medicusu: ``` 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 SELECT DISTINCT ep.erp, r.datum, r.lek, r.dop, TRIM(kar.prijmeni) AS prijmeni, TRIM(kar.jmeno) AS jmeno FROM recept r JOIN recept_epodani ep ON r.id_epodani = ep.id JOIN kar ON r.idpac = kar.idpac 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 - **13 578** receptů celkem (7 bez ERP kódu — papírové/neodeslané) --- ## 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 / PRIPRAVOVANY / 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 / POJISTENI_EU | | `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 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 Stornované recepty jsou filtrovány přímo v dotazu do Firebirdu (`AND r.STORNO = 'F'`), takže se pro ně SOAP volání na SÚKL vůbec neprovádí. ### 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. Načte z MySQL `{id_dokladu: xml_soubor}` pro všechny již zpracované záznamy 2. Najde nejnovější XML pro každý ERP kód (nejvyšší datum v adresářové struktuře) 3. Přeskočí soubory, jejichž cesta se nezměnila oproti DB — zpracuje jen nově stažené 4. Naparsuje stav, platnost, pacient, předepisující, PLP položky 5. `recept_doklad`: INSERT ... ON DUPLICATE KEY UPDATE (stav se může změnit) 6. `recept_plp`: INSERT IGNORE (UUID je stabilní) ### Správné pořadí spuštění ``` 10_StahnoutXML.py → 11_ParseXML.py → 10_StahnoutXML.py → ... ``` `10` se spoléhá na `stav_terminal` v MySQL, který nastavuje `11`. Bez spuštění `11` budou terminální recepty znovu stahovány. --- ## 12_DenníStaženíAZpracování.py — denní kombinovaný skript ### Parametry (příkazová řádka) ``` python 12_DenníStaženíAZpracování.py python 12_DenníStaženíAZpracování.py --od 2025-01-01 python 12_DenníStaženíAZpracování.py --limit 50 ``` ### Co dělá 1. Načte terminální set z MySQL 2. Načte ERP kódy z Firebirdu (od `--od`, výchozí `2025-01-01`) 3. Stáhne XML pro neterminální recepty do `xml_archive/DNES/` (pauza 1–3 s mezi requesty) 4. Naparsuje XML z dnešního adresáře do MySQL 5. Odešle HTML emailový souhrn na `vladimir.buzalka@buzalka.cz` ### Chování při opakovaném spuštění (každé 3 hodiny) Stahování přeskakuje pouze terminální recepty — neterminální se stahují každý běh. Parsování přeskakuje soubory, jejichž cesta `xml_soubor` v DB se nezměnila: - **První běh dne** (po půlnoci): nová složka `xml_archive/YYYY-MM-DD/` → cesta nová → parsuje vše - **Další běhy téhož dne**: stejná složka, cesta beze změny → přeskočí → 0 zpracováno Email vždy obsahuje počet přeskočených, takže "0 zpracováno | 29 přeskočeno" je normální stav. ### Statistika (duben 2026) - Neterminálních receptů: ~29 (z 10 151 celkem) - Doba stahování: ~1 minuta --- ## Ověřeno (16. 4. 2026) - Hromadné stažení od 1. 1. 2025 dokončeno (`LIMIT = None`) - Staženo 9 616 receptů (po filtraci stornovaných z Firebirdu) - `11_ParseXML.py` přeskakuje nezměněné soubory — opakované spuštění zpracuje jen nově stažené --- ## XSD zdroje Schéma verze `202501A`, soubory v `Dokumentace/2025-04-24/WSDL_XSD/`: | Soubor | Obsah | |--------|-------| | `PRIORITNI_WEBOVE_SLUZBY/schema1.xsd` | `NacteniPredpisuDotaz`, `identifikace_dokladu_type`, `nacteni_predpisu_erp_odpoved_type` | | `NEPRIORITNI_WEBOVE_SLUZBY/CuerSchema.xsd` | `hvlp_type`, `zprava_type`, `jmeno_osoby_type` |