This commit is contained in:
2026-04-14 14:45:22 +02:00
parent cc6c80fca6
commit 9afbf79155
7 changed files with 232 additions and 454 deletions
@@ -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)
@@ -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í
# ─────────────────────────────────────────────────────────────────────────────
@@ -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.54.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 46s náhodné pauze
---