ea6ce9a96f
Přepracována na aktuální stav pipeline: - nové tabulky pacient, predepisujici, vydavajici - zprava rozšířena o pacient_id, xml_soubor - popis 07StahnoutVsechny.py (hromadný běh, přírůstkové stahování) - ošetření chyb API (SOAP Fault, pacient.poznamka) - XML archiv, logování do Logs/ - aktualizovány analytické dotazy (s JOIN na pacient) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
404 lines
13 KiB
Markdown
404 lines
13 KiB
Markdown
# Lékový záznam eRecept → MySQL
|
||
|
||
Pipeline pro hromadné stažení lékových záznamů všech registrovaných pacientů
|
||
z eRecept SÚKL API a jejich uložení do relační databáze MySQL.
|
||
|
||
---
|
||
|
||
## Soubory
|
||
|
||
| Soubor | Co dělá |
|
||
|--------|---------|
|
||
| `05UlozitOdpoved.py` | Stáhne XML pro **jednoho** pacienta (ruční test/ladění) |
|
||
| `06UlozitDoMySQL.py` | DDL schématu, parsování XML, import do MySQL — používá se jako knihovna i samostatně |
|
||
| `07StahnoutVsechny.py` | **Hlavní skript** — načte pacienty z Medicusu, stáhne lékové záznamy, uloží XML i DB záznamy |
|
||
|
||
```
|
||
LékovýZáznamWithClaude/
|
||
├── 05UlozitOdpoved.py
|
||
├── 06UlozitDoMySQL.py
|
||
├── 07StahnoutVsechny.py
|
||
├── LEKOVY_ZAZNAM_DB.md
|
||
├── Logs/ ← log každého běhu (UTF-8, YYYY-MM-DD_HH-MM-SS.log)
|
||
├── Tests/ ← starší vývojové skripty
|
||
└── xml_archive/ ← archiv XML odpovědí (YYYY-MM-DD/Prijmeni_Jmena_datnar.xml)
|
||
```
|
||
|
||
---
|
||
|
||
## Typické spuštění
|
||
|
||
```bash
|
||
# Čistý start — jednou (DROP + CREATE schéma, importuje odpoved_lekovy_zaznam.xml)
|
||
python 06UlozitDoMySQL.py
|
||
|
||
# Hromadné stažení všech registrovaných pacientů
|
||
python 07StahnoutVsechny.py
|
||
|
||
# Pouze vybraná příjmení (testování / rodina)
|
||
python 07StahnoutVsechny.py --prijmeni Buzalka,Buzalková,Kusinová
|
||
|
||
# Dávkování po částech
|
||
python 07StahnoutVsechny.py --offset 100 --limit 50
|
||
```
|
||
|
||
---
|
||
|
||
## Autentizace (eRecept SÚKL, ostrý provoz)
|
||
|
||
| Parametr | Hodnota |
|
||
|----------|---------|
|
||
| Endpoint | `https://lekar-soap.erecept.sukl.cz/cuer/Lekar2` |
|
||
| 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 | `NacistLekovyZaznam` |
|
||
| XML namespace | `http://www.sukl.cz/erp/201912` |
|
||
| Verze zprávy | `202501A` |
|
||
|
||
Certifikát = identifikace **ordinace**, UUID+heslo = identifikace **lékaře jako osoby**.
|
||
|
||
---
|
||
|
||
## Zdroj pacientů — Medicus (Firebird)
|
||
|
||
Pacienti se načítají přímo z `medicus.fdb` jako registrovaní pacienti ordinace:
|
||
|
||
```
|
||
DSN: localhost:c:\medicus 3\data\medicus.fdb
|
||
User: SYSDBA / masterkey
|
||
Charset: win1250
|
||
IČP: 09305001 (odbornost 001 — praktický lékař)
|
||
```
|
||
|
||
Podmínky registrace: `vyrazen = 'N'`, `registr.priznak IN ('V','D','A')`,
|
||
registrace platná k dnešnímu datu.
|
||
|
||
---
|
||
|
||
## Logika přírůstkového stahování
|
||
|
||
```
|
||
první stažení pacienta → PocetMesicu = 60 (maximum, 5 let)
|
||
opakované stažení → ceil(dny od posledního stažení / 30) + 1
|
||
```
|
||
|
||
- Překryv 1 měsíce zajistí, že nepřijdeme o nic na hranici období.
|
||
- `INSERT IGNORE` na `id_lp_predpis` / `id_lp_vydej` zabrání duplikátům.
|
||
- Pauza mezi voláními API: **náhodně 10–20 sekund**.
|
||
|
||
---
|
||
|
||
## Ošetření chyb API
|
||
|
||
Pacienti, kteří nejsou v eReceptu ztotožněni (nikdy nebyli v lékárně s e-receptem),
|
||
vrátí SOAP Fault `Z002`. Skript:
|
||
|
||
1. Zachytí chybu (HTTP 500 nebo SOAP Fault v těle odpovědi)
|
||
2. Uloží text chyby do `pacient.poznamka`
|
||
3. Pokračuje dalším pacientem
|
||
|
||
Při příštím úspěšném stažení se `poznamka` automaticky vymaže.
|
||
|
||
```sql
|
||
-- přehled pacientů s chybou
|
||
SELECT prijmeni, jmena, datum_narozeni, poznamka
|
||
FROM pacient
|
||
WHERE poznamka IS NOT NULL;
|
||
```
|
||
|
||
---
|
||
|
||
## XML archiv
|
||
|
||
Každá odpověď API se uloží jako soubor:
|
||
```
|
||
xml_archive/YYYY-MM-DD/Prijmeni_Jmena_YYYY-MM-DD.xml
|
||
```
|
||
|
||
Cesta je zároveň uložena v `zprava.xml_soubor`.
|
||
Účel: možnost re-parsování při budoucích změnách schématu bez nutnosti znovu volat API.
|
||
|
||
---
|
||
|
||
## Výstup do konzole a logů
|
||
|
||
Konzole zobrazuje jen jeden řádek na pacienta:
|
||
```
|
||
[ 1/1621] Abohamda Horia OK 168p 252v 247 KB
|
||
[ 5/1621] Alakbarov Farid CHYBA Z002 - Lekovy zaznam ne...
|
||
```
|
||
|
||
Kompletní detaily (počty nových záznamů, ID zprávy, doba čekání) jsou v:
|
||
```
|
||
Logs/YYYY-MM-DD_HH-MM-SS.log
|
||
```
|
||
|
||
---
|
||
|
||
## Databázové schéma — `medicus` (MySQL)
|
||
|
||
Všechny délky a datové typy jsou přesně dle XSD, **ne odhady**.
|
||
Lék je denormalizován přímo do řádku předpisu/výdeje.
|
||
|
||
### Relační diagram
|
||
|
||
```
|
||
pacient (1)
|
||
└── zprava (N) -- každé volání API = 1 zpráva
|
||
├── predpis (N)
|
||
│ └── predpis_slozka (N) -- složky IPLP z předpisu
|
||
└── vydej (N)
|
||
└── vydej_slozka (N) -- složky IPLP z výdeje
|
||
|
||
vydej.id_lp_predpis → predpis.id_lp_predpis (párování výdeje s předpisem)
|
||
predpis.kod_predepisujiciho → predepisujici.lekar_kod
|
||
vydej.kod_vydavajiciho → vydavajici.lekarnik_kod
|
||
```
|
||
|
||
### Tabulka `pacient`
|
||
|
||
Zrcadlo registrovaných pacientů z Medicusu. Aktualizuje se při každém běhu `07`.
|
||
|
||
| Sloupec | Typ | Poznámka |
|
||
|---------|-----|----------|
|
||
| `id` | INT PK | |
|
||
| `idpac` | INT UNIQUE | IDPAC z tabulky KAR v Medicusu |
|
||
| `prijmeni` | VARCHAR(35) | |
|
||
| `jmena` | VARCHAR(24) | |
|
||
| `datum_narozeni` | DATE | |
|
||
| `aktivni` | TINYINT(1) | 0 = přeskočit při hromadném běhu |
|
||
| `poznamka` | VARCHAR(500) | poslední chyba API; NULL = OK |
|
||
|
||
### Tabulka `zprava`
|
||
|
||
Jeden řádek = jedno volání API (jeden pacient, jeden čas).
|
||
|
||
| Sloupec | Typ | Poznámka |
|
||
|---------|-----|----------|
|
||
| `id_zpravy` | CHAR(36) UNIQUE | UUID z eReceptu |
|
||
| `pacient_id` | INT FK → pacient | |
|
||
| `verze` | VARCHAR(20) | verze zprávy (202501A) |
|
||
| `odeslano` | DATETIME | čas odeslání dotazu |
|
||
| `aplikace` | VARCHAR(512) | SW SÚKL serveru |
|
||
| `id_podani` | CHAR(36) | UUID podání |
|
||
| `prijato` | DATETIME | čas přijetí odpovědi |
|
||
| `pacient_prijmeni` | VARCHAR(35) | z XML odpovědi |
|
||
| `pacient_jmena` | VARCHAR(24) | z XML odpovědi |
|
||
| `pacient_datum_narozeni` | DATE | z XML odpovědi |
|
||
| `xml_soubor` | VARCHAR(255) | relativní cesta k archivu |
|
||
| `stazeno` | DATETIME | automaticky při INSERT |
|
||
|
||
### Tabulka `predpis`
|
||
|
||
Dle `lz_nacteni_predepsany_lp_erp_type`.
|
||
|
||
| Sloupec | Typ | NOT NULL | Poznámka |
|
||
|---------|-----|----------|----------|
|
||
| `id_lp_predpis` | CHAR(36) UNIQUE | ✓ | UUID z eReceptu |
|
||
| `zprava_id` | INT FK | ✓ | |
|
||
| `kod_predepisujiciho` | VARCHAR(36) | ✓ | UUID lékaře |
|
||
| `datum_vystaveni` | DATE | ✓ | |
|
||
| `mnozstvi` | SMALLINT | ✓ | 1–9999 |
|
||
| `navod` | VARCHAR(80) | ✓ | |
|
||
| `opakovani` | INT | | |
|
||
| `modry_pruh` | TINYINT(1) | | návykové látky |
|
||
| `typ_leku` | ENUM | | HVLPReg / HVLPNereg / IPLP / INN |
|
||
| `lek_kod` | CHAR(7) | | kód SÚKL (jen HVLP) |
|
||
| `atc` | VARCHAR(7) | | ATC kód |
|
||
| `nazev` | VARCHAR(200) | | |
|
||
| `forma` | VARCHAR(27) | | |
|
||
| `sila` | VARCHAR(24) | | |
|
||
| `cesta_podani` | VARCHAR(15) | | POR, INH, … |
|
||
| `baleni` | VARCHAR(22) | | string, např. "100 ks" |
|
||
| `postup_pripravy` | VARCHAR(4000) | | receptura IPLP |
|
||
|
||
### Tabulka `predpis_slozka`
|
||
|
||
Složky IPLP předpisů (lékař typicky nevyplňuje, kvalitní data spíše u výdeje).
|
||
|
||
| Sloupec | Typ | NOT NULL |
|
||
|---------|-----|----------|
|
||
| `predpis_id` | INT FK | ✓ |
|
||
| `mnozstvi` | DECIMAL(15,6) | ✓ |
|
||
| `jednotka` | ENUM('g','ks') | ✓ |
|
||
| `nazev` | VARCHAR(200) | ✓ |
|
||
| `surovina` | CHAR(7) | |
|
||
| `hvlp_reg` | CHAR(7) | |
|
||
|
||
### Tabulka `vydej`
|
||
|
||
Dle `lz_nacteni_vydany_lp_erp_type`.
|
||
|
||
| Sloupec | Typ | NOT NULL | Poznámka |
|
||
|---------|-----|----------|----------|
|
||
| `id_lp_vydej` | CHAR(36) UNIQUE | ✓ | UUID výdeje |
|
||
| `zprava_id` | INT FK | ✓ | |
|
||
| `id_lp_predpis` | CHAR(36) FK | | NULL = výdej bez e-předpisu |
|
||
| `kod_vydavajiciho` | VARCHAR(36) | ✓ | UUID lékárníka |
|
||
| `datum_vydeje` | DATE | ✓ | |
|
||
| `mnozstvi` | DECIMAL(6,2) | ✓ | desetinné — např. 0.5 balení |
|
||
| `navod` | VARCHAR(80) | ✓ | |
|
||
| `exspirace` | DATE | | exspirace šarže |
|
||
| `sarze` | VARCHAR(50) | ✓ | |
|
||
| `seriove_cislo` | VARCHAR(20) | | léky s el. sledováním |
|
||
| `pozn` | VARCHAR(1000) | | poznámka lékárníka |
|
||
| `typ_leku` | ENUM | | HVLPReg / HVLPNereg / IPLP |
|
||
| `lek_kod` | CHAR(7) | | kód SÚKL nebo KodVZP (IPLP) |
|
||
| `atc` | VARCHAR(7) | | jen HVLP |
|
||
| `nazev` | VARCHAR(146) | | |
|
||
| `forma` | VARCHAR(27) | | |
|
||
| `sila` | VARCHAR(24) | | |
|
||
| `cesta_podani` | VARCHAR(15) | | |
|
||
| `postup_pripravy` | VARCHAR(4000) | | receptura IPLP |
|
||
|
||
### Tabulka `vydej_slozka`
|
||
|
||
Jako `predpis_slozka`, navíc `hrazeno_zp`. Data lékáren — kvalita závisí na lékárně.
|
||
|
||
| Sloupec | Typ | NOT NULL | Poznámka |
|
||
|---------|-----|----------|----------|
|
||
| `vydej_id` | INT FK | ✓ | |
|
||
| `mnozstvi` | DECIMAL(15,6) | ✓ | |
|
||
| `jednotka` | ENUM('g','ks') | ✓ | |
|
||
| `nazev` | VARCHAR(200) | ✓ | |
|
||
| `hrazeno_zp` | DECIMAL(9,2) | | částka hrazená ZP |
|
||
| `surovina` | CHAR(7) | | |
|
||
| `hvlp_reg` | CHAR(7) | | |
|
||
|
||
### Tabulka `predepisujici`
|
||
|
||
Lékaři, kteří pacientovi předepisovali (ze všech ordinací).
|
||
|
||
| Sloupec | Typ | Poznámka |
|
||
|---------|-----|----------|
|
||
| `lekar_kod` | CHAR(36) UNIQUE | UUID lékaře = predpis.kod_predepisujiciho |
|
||
| `prijmeni` | VARCHAR(35) | |
|
||
| `jmena` | VARCHAR(24) | |
|
||
| `icz` | CHAR(8) | IČZ zdravotnického zařízení |
|
||
| `icp` | CHAR(8) | IČP pracoviště |
|
||
| `pzs_nazev` | VARCHAR(200) | název zdravotnického zařízení |
|
||
| `ulice` | VARCHAR(150) | |
|
||
| `mesto` | VARCHAR(100) | |
|
||
| `psc` | CHAR(5) | |
|
||
| `telefon` | VARCHAR(20) | |
|
||
|
||
### Tabulka `vydavajici`
|
||
|
||
Lékárníci / lékárny, kde byl výdej.
|
||
|
||
| Sloupec | Typ | Poznámka |
|
||
|---------|-----|----------|
|
||
| `lekarnik_kod` | CHAR(36) UNIQUE | UUID lékárníka = vydej.kod_vydavajiciho |
|
||
| `prijmeni` | VARCHAR(35) | |
|
||
| `jmena` | VARCHAR(24) | |
|
||
| `pzs_nazev` | VARCHAR(200) | název lékárny |
|
||
| `ulice` | VARCHAR(150) | |
|
||
| `mesto` | VARCHAR(100) | |
|
||
| `psc` | CHAR(5) | |
|
||
| `telefon` | VARCHAR(20) | |
|
||
|
||
---
|
||
|
||
## Typy léků v Predpis i Vydej
|
||
|
||
Každý předpis / výdej obsahuje právě **jeden** z těchto elementů:
|
||
|
||
| Typ | Popis | Klíčová pole |
|
||
|-----|-------|-------------|
|
||
| `HVLPReg` | Registrovaný hromadně vyráběný LP | Kod (SÚKL), ATC, Nazev, Forma, Sila, Baleni |
|
||
| `HVLPNereg` | Neregistrovaný HVLP | stejná struktura jako HVLPReg |
|
||
| `IPLP` | Individuálně připravovaný LP (magistraliter) | Nazev, PostupPripravy, Slozka[] |
|
||
| `INN` | Předpis účinnou látkou (genericky) | Nazev, Forma, Sila, Baleni |
|
||
|
||
#### IPLP — dvojí uložení receptury
|
||
|
||
- **Předpis**: lékař zadal recepturu jako volný text v `PostupPripravy`. Složky typicky nevyplněny.
|
||
- **Výdej**: lékárna zaznamenala strukturované složky (`Slozka` s množstvím, jednotkou, názvem suroviny). Kvalita dat závisí na lékárně.
|
||
|
||
---
|
||
|
||
## Užitečné analytické dotazy
|
||
|
||
```sql
|
||
-- nejčastěji předepisované ATC skupiny za posledních 12 měsíců
|
||
SELECT atc, nazev, COUNT(*) AS pocet, MAX(datum_vystaveni) AS naposledy
|
||
FROM predpis
|
||
WHERE datum_vystaveni >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
|
||
GROUP BY atc, nazev
|
||
ORDER BY pocet DESC;
|
||
|
||
-- co bylo předepsáno ale nevyzvednuto (non-compliance)
|
||
SELECT pac.prijmeni, pac.jmena, p.datum_vystaveni, p.nazev, p.atc, p.navod
|
||
FROM predpis p
|
||
JOIN zprava z ON z.id = p.zprava_id
|
||
JOIN pacient pac ON pac.id = z.pacient_id
|
||
LEFT JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis
|
||
WHERE v.id_lp_vydej IS NULL
|
||
ORDER BY p.datum_vystaveni DESC;
|
||
|
||
-- lékový záznam konkrétního pacienta (předpisy + výdeje)
|
||
SELECT p.datum_vystaveni, p.typ_leku, p.nazev, p.atc, p.navod,
|
||
v.datum_vydeje, v.mnozstvi AS vydano
|
||
FROM pacient pac
|
||
JOIN zprava z ON z.pacient_id = pac.id
|
||
JOIN predpis p ON p.zprava_id = z.id
|
||
LEFT JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis
|
||
WHERE pac.prijmeni = 'Buzalka' AND pac.jmena = 'Vladimír'
|
||
ORDER BY p.datum_vystaveni DESC;
|
||
|
||
-- IPLP magistraliter — kompletní receptury s frekvencí (napříč pacienty)
|
||
SELECT p.nazev, p.postup_pripravy, COUNT(*) AS pocet_predpisu
|
||
FROM predpis p
|
||
WHERE p.typ_leku = 'IPLP'
|
||
GROUP BY p.nazev, p.postup_pripravy
|
||
ORDER BY pocet_predpisu DESC;
|
||
|
||
-- nejčastěji používané suroviny v magistrech
|
||
SELECT nazev, jednotka, COUNT(*) AS pocet
|
||
FROM vydej_slozka
|
||
GROUP BY nazev, jednotka
|
||
ORDER BY pocet DESC;
|
||
|
||
-- generická záměna: co předepsal lékař vs. co lékárna vydala
|
||
SELECT pac.prijmeni, pac.jmena,
|
||
p.datum_vystaveni, p.nazev AS predepsano, p.atc,
|
||
v.nazev AS vydano, v.datum_vydeje
|
||
FROM pacient pac
|
||
JOIN zprava z ON z.pacient_id = pac.id
|
||
JOIN predpis p ON p.zprava_id = z.id
|
||
JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis
|
||
WHERE p.nazev <> v.nazev;
|
||
|
||
-- pacienti s chybou API (neztotožněni)
|
||
SELECT prijmeni, jmena, datum_narozeni, poznamka
|
||
FROM pacient
|
||
WHERE poznamka IS NOT NULL
|
||
ORDER BY prijmeni;
|
||
```
|
||
|
||
---
|
||
|
||
## Závislosti (Python)
|
||
|
||
```
|
||
requests
|
||
requests-pkcs12
|
||
pymysql
|
||
fdb
|
||
```
|
||
|
||
```bash
|
||
pip install requests requests-pkcs12 pymysql fdb
|
||
```
|
||
|
||
---
|
||
|
||
## XSD zdroje
|
||
|
||
Schéma verze `202501A`, soubory v `Dokumentace/2025-04-24/WSDL_XSD/NEPRIORITNI_WEBOVE_SLUZBY/`:
|
||
|
||
| Soubor | Obsah |
|
||
|--------|-------|
|
||
| `Cuer2Schema.xsd` | `NacistLekovyZaznamOdpoved`, `lz_nacteni_predepsany_lp_erp_type`, `lz_nacteni_vydany_lp_erp_type`, `slozka_iplp_*` |
|
||
| `CuerSchema.xsd` | `hvlp_type`, `zprava_odpoved_type`, `zprava_type`, `jmeno_osoby_type`, `jednotka` |
|