18 KiB
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, ne spouštět přímo! |
07StahnoutVsechny.py |
Hlavní skript — načte pacienty z Medicusu, stáhne lékové záznamy, uloží XML i DB záznamy |
reimport_z_xml.py |
Reimport XML ze zálohy bez volání API — viz sekce níže |
recept/
├── setup.ps1 ← vytvoří .venv, nainstaluje závislosti, Playwright chromium
├── requirements.txt ← seznam Python závislostí
├── .venv/ ← virtuální prostředí (Python 3.x)
│
├── LékovýZáznamWithClaude/
│ ├── 05UlozitOdpoved.py
│ ├── 06UlozitDoMySQL.py
│ ├── 07StahnoutVsechny.py
│ ├── reimport_z_xml.py
│ ├── LEKOVY_ZAZNAM_DB.md ← tento soubor
│ ├── 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)
│
└── Dotazy/
├── prehled_pacienta.py ← konzolový přehled pacienta
├── prehled_pacienta_excel.py ← export přehledu pacienta do Excelu
└── DOTAZY.md ← dokumentace dotazovacích skriptů
⚠️ NIKDY nespouštět
06UlozitDoMySQL.pypřímo — zavolávytvor_schema(), která provedeDROP TABLEa smaže celou databázi. Pro import dat vždy použít07StahnoutVsechny.pyneboreimport_z_xml.py.
Nastavení prostředí (jednorázově)
# PowerShell — spustit jednou po naklonování projektu
cd U:\recept
.\setup.ps1
setup.ps1 provede:
- Vytvoří
.venvs Python interpretem zC:\Python\python.exe - Nainstaluje všechny závislosti z
requirements.txt - Nainstaluje Playwright Chromium (pro případné automatizace)
Po nastavení aktivace:
.venv\Scripts\Activate.ps1
requirements.txt
requests
requests-pkcs12
pymysql
fdb
zeep
mysql-connector-python
playwright
openpyxl
Typické spuštění
# 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
# Reimport ze zálohy XML (bez volání API) — viz níže
python reimport_z_xml.py
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 IGNOREnaid_lp_predpis/id_lp_vydejzabrá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:
- Zachytí chybu (HTTP 500 nebo SOAP Fault v těle odpovědi)
- Uloží text chyby do
pacient.poznamka - Pokračuje dalším pacientem
Při příštím úspěšném stažení se poznamka automaticky vymaže.
-- 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ě — poslední 3 číslice = kód odbornosti (001 = prakt. lékař, 272 = alergologie…) |
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 (
Slozkas množstvím, jednotkou, názvem suroviny). Kvalita dat závisí na lékárně.
Užitečné analytické dotazy
-- 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;
-- lékaři dle odbornosti — kolik předpisů pochází od které speciality
SELECT RIGHT(pr.icp, 3) AS odb_kod, COUNT(*) AS pocet_predpisu
FROM predpis p
JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho
WHERE pr.icp IS NOT NULL
GROUP BY RIGHT(pr.icp, 3)
ORDER BY pocet_predpisu DESC;
-- lékový záznam pacienta dle rodného čísla (přes Firebird → MySQL)
-- krok 1: z Medicusu zjistit příjmení a datum narozeni pro RC 7309208104
-- krok 2:
SELECT pac.prijmeni, pac.jmena, pac.datum_narozeni,
p.datum_vystaveni,
COALESCE(v.nazev, p.nazev) AS vydany_lek,
v.nazev IS NULL AS nevyzvednuto,
p.atc, p.navod,
pr.prijmeni AS lekar, RIGHT(pr.icp, 3) AS odb_kod
FROM pacient pac
JOIN zprava z ON z.pacient_id = pac.id
JOIN predpis p ON p.zprava_id = z.id
JOIN predepisujici pr ON pr.lekar_kod = p.kod_predepisujiciho
LEFT JOIN vydej v ON v.id_lp_predpis = p.id_lp_predpis
WHERE pac.prijmeni = 'Buzalka' AND pac.datum_narozeni = '1973-09-20'
ORDER BY p.datum_vystaveni DESC;
Reimport ze zálohy XML (reimport_z_xml.py)
Slouží k opětovnému naplnění MySQL databáze z lokálních XML souborů bez volání eRecept API.
Použití: obnova po neúmyslném smazání databáze, migrace na nový server, re-parsování při změně schématu.
Jak funguje
- Načte všechny registrované pacienty z Firebirdu (ICP
09305001, odbornost001) - Pro každý XML soubor v archivu:
- Naparsuje XML (volá
parsuj_xml()z06UlozitDoMySQL.py) - Dohledá pacienta v Firebirdu dle příjmení + data narození z XML
- Pokud je registrovaný →
upsertpacienta do MySQL (INSERT ON DUPLICATE KEY UPDATE) - Zavolá
uloz()— INSERT IGNORE, takže duplicity se ignorují
- Naparsuje XML (volá
- Výpis průběhu:
[ 1/1177] Buzalka_Vladimir_1973-09-20.xml OK 12p 18v
Spuštění
# Výchozí adresář: xml_archive/2026-04-11
python reimport_z_xml.py
# Konkrétní podadresář
python reimport_z_xml.py xml_archive/2026-04-11
# Celý archiv rekurzivně (všechna data)
python reimport_z_xml.py xml_archive
Konfigurace v souboru
XML_ADRESAR = Path(__file__).parent / "xml_archive" / "2026-04-11" # výchozí adresář
ICP = "09305001" # IČP ordinace pro filtr registrovaných pacientů
ODB = "001" # odbornost (001 = praktický lékař)
Poznámky
- Pacienti, kteří nejsou v Firebirdu registrováni pod daným ICP/ODB, se přeskočí (pokud ale existují v MySQL z předchozího importu, data se aktualizují)
- Firebird slouží jako autoritativní zdroj identit —
idpacz KAR se propíše do MySQLpacient.idpac INSERT IGNOREzajistí idempotentnost — opakované spuštění nepřidá duplikáty
Dotazovací skripty (Dotazy/)
Viz samostatnou dokumentaci: Dotazy/DOTAZY.md
Stručný přehled:
| Skript | Co dělá |
|---|---|
prehled_pacienta.py |
Konzolový výpis lékového záznamu pacienta (lékaři + předpisy) |
prehled_pacienta_excel.py |
Totéž, ale exportuje do formátovaného souboru Excel (.xlsx) |
Pacient se identifikuje rodným číslem (nastavení RODNE_CISLO v záhlaví skriptu).
Oba skripty zobrazují vydaný lék (ne předepsaný), odbornost lékaře a příznak *NV pro nevyzvednuto.
Závislosti (Python)
requests
requests-pkcs12
pymysql
fdb
zeep
mysql-connector-python
playwright
openpyxl
# Instalace (nebo použít setup.ps1)
pip install requests requests-pkcs12 pymysql fdb openpyxl
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 |