Files
recept/NačteníPředpisuWithClaude/NacistPredpis_DOKUMENTACE.md
2026-04-16 14:20:38 +02:00

310 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.54.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` |