Files
Vladimir Buzalka adb84523cd Přidán podprojekt Recepty (eRecept SÚKL)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 07:06:17 +02:00

18 KiB
Raw Permalink Blame History

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.py přímo — zavolá vytvor_schema(), která provede DROP TABLE a smaže celou databázi. Pro import dat vždy použít 07StahnoutVsechny.py nebo reimport_z_xml.py.


Nastavení prostředí (jednorázově)

# PowerShell — spustit jednou po naklonování projektu
cd U:\recept
.\setup.ps1

setup.ps1 provede:

  1. Vytvoří .venv s Python interpretem z C:\Python\python.exe
  2. Nainstaluje všechny závislosti z requirements.txt
  3. 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 IGNORE na id_lp_predpis / id_lp_vydej zabrání duplikátům.
  • Pauza mezi voláními API: náhodně 1020 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.

-- 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 19999
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 (Slozka s 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

  1. Načte všechny registrované pacienty z Firebirdu (ICP 09305001, odbornost 001)
  2. Pro každý XML soubor v archivu:
    • Naparsuje XML (volá parsuj_xml() z 06UlozitDoMySQL.py)
    • Dohledá pacienta v Firebirdu dle příjmení + data narození z XML
    • Pokud je registrovaný → upsert pacienta do MySQL (INSERT ON DUPLICATE KEY UPDATE)
    • Zavolá uloz() — INSERT IGNORE, takže duplicity se ignorují
  3. 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 — idpac z KAR se propíše do MySQL pacient.idpac
  • INSERT IGNORE zajistí 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