Přidán podprojekt Recepty (eRecept SÚKL)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
# 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 |
|
||||
|
||||
```
|
||||
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
|
||||
├── 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.
|
||||
|
||||
---
|
||||
|
||||
## 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` |
|
||||
Reference in New Issue
Block a user