Přidán podprojekt Recepty (eRecept SÚKL)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from requests import Session
|
||||
from requests_pkcs12 import Pkcs12Adapter
|
||||
|
||||
# --- Konfigurace ---
|
||||
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
|
||||
PFX_PASSWORD = "Vlado7309208104++"
|
||||
|
||||
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
|
||||
API_PASS = "Buzalka@Vladimir2025"
|
||||
|
||||
UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
|
||||
PRACOVISTE = "00214235367"
|
||||
|
||||
ENDPOINT = "https://lekar-soap.erecept.sukl.cz/cuer/Lekar2"
|
||||
|
||||
PRIJMENI = "Buzalka"
|
||||
JMENA = "Vladimír"
|
||||
DATUM_NAROZENI = "1973-09-20"
|
||||
|
||||
POCET_ZNAKU_ATC = 7
|
||||
POCET_MESICU =60
|
||||
|
||||
VYSTUP = Path(__file__).parent / "odpoved_lekovy_zaznam.xml"
|
||||
|
||||
|
||||
def nacist_a_ulozit():
|
||||
sess = Session()
|
||||
sess.mount("https://", Pkcs12Adapter(
|
||||
pkcs12_filename=PFX_FILE,
|
||||
pkcs12_password=PFX_PASSWORD
|
||||
))
|
||||
sess.auth = (API_USER, API_PASS)
|
||||
|
||||
id_zpravy = str(uuid.uuid4())
|
||||
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
||||
|
||||
soap_body = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
|
||||
'<soapenv:Body>'
|
||||
f'<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">'
|
||||
f'<Doklad>'
|
||||
f'<Pristupujici><Uzivatel>{UZIVATEL}</Uzivatel><Pracoviste>{PRACOVISTE}</Pracoviste></Pristupujici>'
|
||||
f'<PocetZnakuATC>{POCET_ZNAKU_ATC}</PocetZnakuATC>'
|
||||
f'<PocetMesicu>{POCET_MESICU}</PocetMesicu>'
|
||||
f'<Pacient><Totoznost><Jmeno><Prijmeni>{PRIJMENI}</Prijmeni><Jmena>{JMENA}</Jmena></Jmeno>'
|
||||
f'<DatumNarozeni>{DATUM_NAROZENI}</DatumNarozeni></Totoznost></Pacient>'
|
||||
f'</Doklad>'
|
||||
f'<Zprava><ID_Zpravy>{id_zpravy}</ID_Zpravy><Verze>202501A</Verze>'
|
||||
f'<Odeslano>{odeslano}</Odeslano><SW_Klienta>MEDICUS_____</SW_Klienta></Zprava>'
|
||||
f'</NacistLekovyZaznamLekarDotaz>'
|
||||
'</soapenv:Body>'
|
||||
'</soapenv:Envelope>'
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Type": 'text/xml; charset="UTF-8"',
|
||||
"SOAPAction": '"NacistLekovyZaznam"',
|
||||
"User-Agent": "Medicus"
|
||||
}
|
||||
|
||||
print(f"POST {ENDPOINT} ...")
|
||||
resp = sess.post(ENDPOINT, data=soap_body.encode("utf-8"), headers=headers, timeout=30)
|
||||
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů")
|
||||
|
||||
if resp.status_code == 200:
|
||||
VYSTUP.write_text(resp.text, encoding="utf-8")
|
||||
print(f"Uloženo: {VYSTUP}")
|
||||
else:
|
||||
print("CHYBA — odpověď neuložena")
|
||||
print(resp.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
nacist_a_ulozit()
|
||||
@@ -0,0 +1,765 @@
|
||||
"""
|
||||
Nacte odpoved lekoveho zaznamu (XML) a ulozi ji do MySQL.
|
||||
Schema: pacient / zprava / predpis / predpis_slozka / vydej / vydej_slozka / predepisujici / vydavajici
|
||||
Typy a delky presne dle XSD (Cuer2Schema.xsd + CuerSchema.xsd, verze 202501A)
|
||||
|
||||
Spusteni (jednorazova inicializace + import jednoho XML):
|
||||
python 06UlozitDoMySQL.py
|
||||
nebo:
|
||||
python 06UlozitDoMySQL.py cesta/k/odpoved.xml
|
||||
|
||||
Pro hromadne stazeni vsech pacientu pouzij 07StahnoutVsechny.py.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import pymysql
|
||||
import pymysql.cursors
|
||||
|
||||
# ── konfigurace ───────────────────────────────────────────────────────────────
|
||||
XML_SOUBOR = Path(__file__).parent / "odpoved_lekovy_zaznam.xml"
|
||||
|
||||
DB = dict(
|
||||
host = "192.168.1.76",
|
||||
user = "root",
|
||||
password = "Vlado9674+",
|
||||
database = "medicus",
|
||||
charset = "utf8mb4",
|
||||
cursorclass = pymysql.cursors.DictCursor,
|
||||
)
|
||||
|
||||
NS = "http://www.sukl.cz/erp/201912"
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def t(el, tag):
|
||||
"""Prvni potomek s danym tagem → text nebo None."""
|
||||
found = el.find(f"{{{NS}}}{tag}")
|
||||
return found.text.strip() if found is not None and found.text else None
|
||||
|
||||
|
||||
def datum(s):
|
||||
return s[:10] if s else None
|
||||
|
||||
|
||||
def ts(s):
|
||||
return s[:19].replace("T", " ") if s else None
|
||||
|
||||
|
||||
# ── DDL ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Pouziva se ve vytvor_schema() (DROP + CREATE) pro ciste spusteni
|
||||
DDL_DROP = [
|
||||
"DROP TABLE IF EXISTS predpis_slozka",
|
||||
"DROP TABLE IF EXISTS vydej_slozka",
|
||||
"DROP TABLE IF EXISTS vydej",
|
||||
"DROP TABLE IF EXISTS predpis",
|
||||
"DROP TABLE IF EXISTS zprava",
|
||||
"DROP TABLE IF EXISTS pacient",
|
||||
"DROP TABLE IF EXISTS predepisujici",
|
||||
"DROP TABLE IF EXISTS vydavajici",
|
||||
]
|
||||
|
||||
DDL_CREATE = [
|
||||
# ── pacient ───────────────────────────────────────────────────────────────
|
||||
# Zrcadlo registrovanych pacientu z Medicusu (Firebird).
|
||||
# idpac = IDPAC z KAR tabulky Medicusu.
|
||||
# poznamka: posledni chyba API (napr. "neztotozneny pacient"); NULL = OK
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS pacient (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
idpac INT NOT NULL UNIQUE,
|
||||
prijmeni VARCHAR(35) NOT NULL,
|
||||
jmena VARCHAR(24),
|
||||
datum_narozeni DATE NOT NULL,
|
||||
aktivni TINYINT(1) NOT NULL DEFAULT 1,
|
||||
poznamka VARCHAR(500),
|
||||
INDEX idx_prijmeni (prijmeni)
|
||||
) ENGINE=InnoDB
|
||||
""",
|
||||
|
||||
# ── zprava ────────────────────────────────────────────────────────────────
|
||||
# zprava_odpoved_type + zprava_type:
|
||||
# ID_Zpravy CHAR(36), Verze, Odeslano dateTime
|
||||
# Aplikace(512), ID_Podani CHAR(36), Prijato dateTime
|
||||
# jmeno_osoby_type: Prijmeni(35), Jmena(24)
|
||||
# pacient_id: FK na pacient.id (NULL pokud volano z 06 primo)
|
||||
# xml_soubor: relativni cesta k ulozene XML odpovedi
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS zprava (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
id_zpravy CHAR(36) NOT NULL UNIQUE,
|
||||
pacient_id INT,
|
||||
verze VARCHAR(20),
|
||||
odeslano DATETIME,
|
||||
aplikace VARCHAR(512),
|
||||
id_podani CHAR(36),
|
||||
prijato DATETIME,
|
||||
pacient_prijmeni VARCHAR(35),
|
||||
pacient_jmena VARCHAR(24),
|
||||
pacient_datum_narozeni DATE,
|
||||
xml_soubor VARCHAR(255),
|
||||
stazeno DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (pacient_id) REFERENCES pacient(id) ON DELETE SET NULL,
|
||||
INDEX idx_pacient_id (pacient_id),
|
||||
INDEX idx_pacient (pacient_prijmeni, pacient_datum_narozeni)
|
||||
) ENGINE=InnoDB
|
||||
""",
|
||||
|
||||
# ── predpis ───────────────────────────────────────────────────────────────
|
||||
# lz_nacteni_predepsany_lp_erp_type:
|
||||
# ID_LP_Predpis CHAR(36) NOT NULL, KodPredepisujiciho(36) NOT NULL,
|
||||
# DatumVystaveni date NOT NULL, Mnozstvi int(1-9999) NOT NULL,
|
||||
# Navod(80) NOT NULL, Opakovani int?, ModryPruh boolean?
|
||||
# lek — vzdy jen jeden z: HVLPReg / HVLPNereg / IPLP / INN
|
||||
# hvlp_type: Kod CHAR(7)?, ATC(7)?, Nazev(146)!, Forma(27)?,
|
||||
# Sila(24)?, CestaPodani(15)?, Baleni VARCHAR(22)?
|
||||
# inn_predpis_type: Nazev(200) — nejdelsi nazev mezi typy
|
||||
# iplp_predpis_type: PostupPripravy(4000), Nazev(146), CestaPodani(15), Forma(27)
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS predpis (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
zprava_id INT NOT NULL,
|
||||
id_lp_predpis CHAR(36) NOT NULL UNIQUE,
|
||||
kod_predepisujiciho VARCHAR(36) NOT NULL,
|
||||
datum_vystaveni DATE NOT NULL,
|
||||
mnozstvi SMALLINT NOT NULL,
|
||||
navod VARCHAR(80) NOT NULL,
|
||||
opakovani INT,
|
||||
modry_pruh TINYINT(1),
|
||||
typ_leku ENUM('HVLPReg','HVLPNereg','IPLP','INN'),
|
||||
lek_kod CHAR(7),
|
||||
atc VARCHAR(7),
|
||||
nazev VARCHAR(200),
|
||||
forma VARCHAR(27),
|
||||
sila VARCHAR(24),
|
||||
cesta_podani VARCHAR(15),
|
||||
baleni VARCHAR(22),
|
||||
postup_pripravy VARCHAR(4000),
|
||||
FOREIGN KEY (zprava_id) REFERENCES zprava(id) ON DELETE CASCADE,
|
||||
INDEX idx_atc (atc),
|
||||
INDEX idx_datum (datum_vystaveni)
|
||||
) ENGINE=InnoDB
|
||||
""",
|
||||
|
||||
# ── predpis_slozka ────────────────────────────────────────────────────────
|
||||
# slozka_iplp_predpis_type:
|
||||
# Mnozstvi DECIMAL(15,6) NOT NULL, Jednotka ENUM('g','ks') NOT NULL,
|
||||
# Nazev(200) NOT NULL, Surovina CHAR(7)?, HVLPReg CHAR(7)?
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS predpis_slozka (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
predpis_id INT NOT NULL,
|
||||
mnozstvi DECIMAL(15,6) NOT NULL,
|
||||
jednotka ENUM('g','ks') NOT NULL,
|
||||
nazev VARCHAR(200) NOT NULL,
|
||||
surovina CHAR(7),
|
||||
hvlp_reg CHAR(7),
|
||||
FOREIGN KEY (predpis_id) REFERENCES predpis(id) ON DELETE CASCADE,
|
||||
INDEX idx_nazev (nazev)
|
||||
) ENGINE=InnoDB
|
||||
""",
|
||||
|
||||
# ── vydej ─────────────────────────────────────────────────────────────────
|
||||
# lz_nacteni_vydany_lp_erp_type:
|
||||
# Mnozstvi DECIMAL(6,2) NOT NULL, Navod(80) NOT NULL,
|
||||
# Sarze(50) NOT NULL, SerioveCislo(20)?, Pozn(1000)?
|
||||
# lek — vzdy jen jeden z: HVLPReg / HVLPNereg / IPLP
|
||||
# iplp_type (vydej): KodVZP CHAR(7)?, PostupPripravy(4000), Nazev(146), CestaPodani(15)
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS vydej (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
zprava_id INT NOT NULL,
|
||||
id_lp_vydej CHAR(36) NOT NULL UNIQUE,
|
||||
id_lp_predpis CHAR(36),
|
||||
kod_vydavajiciho VARCHAR(36) NOT NULL,
|
||||
datum_vydeje DATE NOT NULL,
|
||||
mnozstvi DECIMAL(6,2) NOT NULL,
|
||||
navod VARCHAR(80) NOT NULL,
|
||||
exspirace DATE,
|
||||
sarze VARCHAR(50) NOT NULL,
|
||||
seriove_cislo VARCHAR(20),
|
||||
pozn VARCHAR(1000),
|
||||
typ_leku ENUM('HVLPReg','HVLPNereg','IPLP'),
|
||||
lek_kod CHAR(7),
|
||||
atc VARCHAR(7),
|
||||
nazev VARCHAR(146),
|
||||
forma VARCHAR(27),
|
||||
sila VARCHAR(24),
|
||||
cesta_podani VARCHAR(15),
|
||||
postup_pripravy VARCHAR(4000),
|
||||
FOREIGN KEY (zprava_id) REFERENCES zprava(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (id_lp_predpis) REFERENCES predpis(id_lp_predpis) ON DELETE SET NULL,
|
||||
INDEX idx_predpis (id_lp_predpis),
|
||||
INDEX idx_atc (atc),
|
||||
INDEX idx_datum (datum_vydeje)
|
||||
) ENGINE=InnoDB
|
||||
""",
|
||||
|
||||
# ── vydej_slozka ──────────────────────────────────────────────────────────
|
||||
# slozka_iplp_type:
|
||||
# Mnozstvi DECIMAL(15,6) NOT NULL, Jednotka ENUM('g','ks') NOT NULL,
|
||||
# Nazev(200) NOT NULL, HrazenoZP DECIMAL(9,2)?,
|
||||
# Surovina CHAR(7)?, HVLPReg CHAR(7)?
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS vydej_slozka (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
vydej_id INT NOT NULL,
|
||||
mnozstvi DECIMAL(15,6) NOT NULL,
|
||||
jednotka ENUM('g','ks') NOT NULL,
|
||||
nazev VARCHAR(200) NOT NULL,
|
||||
hrazeno_zp DECIMAL(9,2),
|
||||
surovina CHAR(7),
|
||||
hvlp_reg CHAR(7),
|
||||
FOREIGN KEY (vydej_id) REFERENCES vydej(id) ON DELETE CASCADE,
|
||||
INDEX idx_nazev (nazev)
|
||||
) ENGINE=InnoDB
|
||||
""",
|
||||
|
||||
# ── predepisujici ─────────────────────────────────────────────────────────
|
||||
# PredepisujiciSeznam > Predepisujici:
|
||||
# Lekar: Kod CHAR(36), Jmeno: Prijmeni(35), Jmena(24)
|
||||
# ICZ CHAR(8)?, ICP CHAR(8)?
|
||||
# PZS: Nazev(200), Adresa: NazevUlice, CisloPopisne, CisloOrientacni, NazevObce, PSC
|
||||
# Telefon(20)?
|
||||
# lekar_kod = predpis.kod_predepisujiciho
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS predepisujici (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
lekar_kod CHAR(36) NOT NULL UNIQUE,
|
||||
prijmeni VARCHAR(35),
|
||||
jmena VARCHAR(24),
|
||||
icz CHAR(8),
|
||||
icp CHAR(8),
|
||||
pzs_nazev VARCHAR(200),
|
||||
ulice VARCHAR(150),
|
||||
mesto VARCHAR(100),
|
||||
psc CHAR(5),
|
||||
telefon VARCHAR(20),
|
||||
INDEX idx_icp (icp),
|
||||
INDEX idx_icz (icz)
|
||||
) ENGINE=InnoDB
|
||||
""",
|
||||
|
||||
# ── vydavajici ────────────────────────────────────────────────────────────
|
||||
# VydavajiciSeznam > Vydavajici:
|
||||
# Lekarnik: Kod CHAR(36), Jmeno: Prijmeni(35), Jmena(24)
|
||||
# PZS: Nazev(200), Telefon(20)?, Adresa: NazevUlice, CisloPopisne, CisloOrientacni, NazevObce, PSC
|
||||
# lekarnik_kod = vydej.kod_vydavajiciho
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS vydavajici (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
lekarnik_kod CHAR(36) NOT NULL UNIQUE,
|
||||
prijmeni VARCHAR(35),
|
||||
jmena VARCHAR(24),
|
||||
pzs_nazev VARCHAR(200),
|
||||
ulice VARCHAR(150),
|
||||
mesto VARCHAR(100),
|
||||
psc CHAR(5),
|
||||
telefon VARCHAR(20)
|
||||
) ENGINE=InnoDB
|
||||
""",
|
||||
]
|
||||
|
||||
|
||||
def vytvor_schema(conn):
|
||||
"""DROP + CREATE vsech tabulek. Pouzij pro ciste spusteni / reset dat."""
|
||||
with conn.cursor() as cur:
|
||||
for stmt in DDL_DROP:
|
||||
cur.execute(stmt)
|
||||
for stmt in DDL_CREATE:
|
||||
stmt = stmt.strip()
|
||||
if stmt:
|
||||
cur.execute(stmt)
|
||||
conn.commit()
|
||||
print("Schema OK — tabulky smazany a vytvoreny znovu")
|
||||
|
||||
|
||||
def inicializuj_schema(conn):
|
||||
"""CREATE TABLE IF NOT EXISTS — bezpecne pro opakowane spusteni (neznici data)."""
|
||||
with conn.cursor() as cur:
|
||||
for stmt in DDL_CREATE:
|
||||
stmt = stmt.strip()
|
||||
if stmt:
|
||||
cur.execute(stmt)
|
||||
# Zpetna kompatibilita: pridat sloupec poznamka pokud jeste neexistuje
|
||||
cur.execute("""
|
||||
SELECT COUNT(*) AS cnt
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'pacient'
|
||||
AND COLUMN_NAME = 'poznamka'
|
||||
""")
|
||||
if cur.fetchone()["cnt"] == 0:
|
||||
cur.execute(
|
||||
"ALTER TABLE pacient ADD COLUMN poznamka VARCHAR(500) NULL DEFAULT NULL"
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
# ── parsovani predepisujicich a vydavajicich ─────────────────────────────────
|
||||
|
||||
def _parsuj_adresu(pzs_el):
|
||||
"""Ze elementu PZS vraci (ulice, mesto, psc)."""
|
||||
adr = pzs_el.find(f"{{{NS}}}Adresa") if pzs_el is not None else None
|
||||
if adr is None:
|
||||
return None, None, None
|
||||
ulice_parts = [
|
||||
t(adr, "NazevUlice") or "",
|
||||
t(adr, "CisloPopisne") or "",
|
||||
t(adr, "CisloOrientacni") or "",
|
||||
]
|
||||
ulice = " ".join(p for p in ulice_parts if p).strip() or None
|
||||
psc = t(adr, "PSC")
|
||||
if psc and len(psc) > 5:
|
||||
psc = psc[:5]
|
||||
return ulice, t(adr, "NazevObce"), psc
|
||||
|
||||
|
||||
def parsuj_predepisujici(doklad):
|
||||
"""Vraci seznam slovniku pro tabulku predepisujici."""
|
||||
seznam = []
|
||||
sez_el = doklad.find(f"{{{NS}}}PredepisujiciSeznam")
|
||||
if sez_el is None:
|
||||
return seznam
|
||||
for el in sez_el.findall(f"{{{NS}}}Predepisujici"):
|
||||
lekar = el.find(f"{{{NS}}}Lekar")
|
||||
if lekar is None:
|
||||
continue
|
||||
jmeno = lekar.find(f"{{{NS}}}Jmeno")
|
||||
pzs = el.find(f"{{{NS}}}PZS")
|
||||
ulice, mesto, psc = _parsuj_adresu(pzs)
|
||||
seznam.append(dict(
|
||||
lekar_kod = t(lekar, "Kod"),
|
||||
prijmeni = t(jmeno, "Prijmeni") if jmeno is not None else None,
|
||||
jmena = t(jmeno, "Jmena") if jmeno is not None else None,
|
||||
icz = t(el, "ICZ"),
|
||||
icp = t(el, "ICP"),
|
||||
pzs_nazev = t(pzs, "Nazev") if pzs is not None else None,
|
||||
ulice = ulice,
|
||||
mesto = mesto,
|
||||
psc = psc,
|
||||
telefon = t(el, "Telefon"),
|
||||
))
|
||||
return seznam
|
||||
|
||||
|
||||
def parsuj_vydavajici(doklad):
|
||||
"""Vraci seznam slovniku pro tabulku vydavajici."""
|
||||
seznam = []
|
||||
sez_el = doklad.find(f"{{{NS}}}VydavajiciSeznam")
|
||||
if sez_el is None:
|
||||
return seznam
|
||||
for el in sez_el.findall(f"{{{NS}}}Vydavajici"):
|
||||
lekarnik = el.find(f"{{{NS}}}Lekarnik")
|
||||
if lekarnik is None:
|
||||
continue
|
||||
jmeno = lekarnik.find(f"{{{NS}}}Jmeno")
|
||||
pzs = el.find(f"{{{NS}}}PZS")
|
||||
ulice, mesto, psc = _parsuj_adresu(pzs)
|
||||
seznam.append(dict(
|
||||
lekarnik_kod = t(lekarnik, "Kod"),
|
||||
prijmeni = t(jmeno, "Prijmeni") if jmeno is not None else None,
|
||||
jmena = t(jmeno, "Jmena") if jmeno is not None else None,
|
||||
pzs_nazev = t(pzs, "Nazev") if pzs is not None else None,
|
||||
ulice = ulice,
|
||||
mesto = mesto,
|
||||
psc = psc,
|
||||
telefon = t(pzs, "Telefon") if pzs is not None else None,
|
||||
))
|
||||
return seznam
|
||||
|
||||
|
||||
# ── parsovani leku ────────────────────────────────────────────────────────────
|
||||
|
||||
def parsuj_slozky_predpis(lek_el):
|
||||
"""Ze IPLP elementu vraci seznam slovniku pro predpis_slozka."""
|
||||
slozky = []
|
||||
for s in lek_el.findall(f"{{{NS}}}Slozka"):
|
||||
slozky.append(dict(
|
||||
mnozstvi = t(s, "Mnozstvi"),
|
||||
jednotka = t(s, "Jednotka"),
|
||||
nazev = t(s, "Nazev"),
|
||||
surovina = t(s, "Surovina"),
|
||||
hvlp_reg = t(s, "HVLPReg"),
|
||||
))
|
||||
return slozky
|
||||
|
||||
|
||||
def parsuj_slozky_vydej(lek_el):
|
||||
"""Ze IPLP elementu vraci seznam slovniku pro vydej_slozka."""
|
||||
slozky = []
|
||||
for s in lek_el.findall(f"{{{NS}}}Slozka"):
|
||||
slozky.append(dict(
|
||||
mnozstvi = t(s, "Mnozstvi"),
|
||||
jednotka = t(s, "Jednotka"),
|
||||
nazev = t(s, "Nazev"),
|
||||
hrazeno_zp = t(s, "HrazenoZP"),
|
||||
surovina = t(s, "Surovina"),
|
||||
hvlp_reg = t(s, "HVLPReg"),
|
||||
))
|
||||
return slozky
|
||||
|
||||
|
||||
def parsuj_lek_predpis(p):
|
||||
"""Vraci (lek_fields dict, slozky list).
|
||||
Typ leku: HVLPReg / HVLPNereg / IPLP / INN — vzdy jen jeden.
|
||||
"""
|
||||
for typ in ("HVLPReg", "HVLPNereg"):
|
||||
lek = p.find(f"{{{NS}}}{typ}")
|
||||
if lek is not None:
|
||||
return dict(
|
||||
typ_leku = typ,
|
||||
lek_kod = t(lek, "Kod"),
|
||||
atc = t(lek, "ATC"),
|
||||
nazev = t(lek, "Nazev"),
|
||||
forma = t(lek, "Forma"),
|
||||
sila = t(lek, "Sila"),
|
||||
cesta_podani = t(lek, "CestaPodani"),
|
||||
baleni = t(lek, "Baleni"),
|
||||
postup_pripravy = None,
|
||||
), []
|
||||
|
||||
lek = p.find(f"{{{NS}}}IPLP")
|
||||
if lek is not None:
|
||||
return dict(
|
||||
typ_leku = "IPLP",
|
||||
lek_kod = None,
|
||||
atc = None,
|
||||
nazev = t(lek, "Nazev"),
|
||||
forma = t(lek, "Forma"),
|
||||
sila = None,
|
||||
cesta_podani = t(lek, "CestaPodani"),
|
||||
baleni = None,
|
||||
postup_pripravy = t(lek, "PostupPripravy"),
|
||||
), parsuj_slozky_predpis(lek)
|
||||
|
||||
lek = p.find(f"{{{NS}}}INN")
|
||||
if lek is not None:
|
||||
return dict(
|
||||
typ_leku = "INN",
|
||||
lek_kod = None,
|
||||
atc = None,
|
||||
nazev = t(lek, "Nazev"),
|
||||
forma = t(lek, "Forma"),
|
||||
sila = t(lek, "Sila"),
|
||||
cesta_podani = t(lek, "CestaPodani"),
|
||||
baleni = t(lek, "Baleni"),
|
||||
postup_pripravy = None,
|
||||
), []
|
||||
|
||||
return dict(typ_leku=None, lek_kod=None, atc=None, nazev=None,
|
||||
forma=None, sila=None, cesta_podani=None,
|
||||
baleni=None, postup_pripravy=None), []
|
||||
|
||||
|
||||
def parsuj_lek_vydej(v):
|
||||
"""Vraci (lek_fields dict, slozky list).
|
||||
Typ leku: HVLPReg / HVLPNereg / IPLP — vzdy jen jeden.
|
||||
"""
|
||||
for typ in ("HVLPReg", "HVLPNereg"):
|
||||
lek = v.find(f"{{{NS}}}{typ}")
|
||||
if lek is not None:
|
||||
return dict(
|
||||
typ_leku = typ,
|
||||
lek_kod = t(lek, "Kod"),
|
||||
atc = t(lek, "ATC"),
|
||||
nazev = t(lek, "Nazev"),
|
||||
forma = t(lek, "Forma"),
|
||||
sila = t(lek, "Sila"),
|
||||
cesta_podani = t(lek, "CestaPodani"),
|
||||
postup_pripravy = None,
|
||||
), []
|
||||
|
||||
lek = v.find(f"{{{NS}}}IPLP")
|
||||
if lek is not None:
|
||||
return dict(
|
||||
typ_leku = "IPLP",
|
||||
lek_kod = t(lek, "KodVZP"),
|
||||
atc = None,
|
||||
nazev = t(lek, "Nazev"),
|
||||
forma = None,
|
||||
sila = None,
|
||||
cesta_podani = t(lek, "CestaPodani"),
|
||||
postup_pripravy = t(lek, "PostupPripravy"),
|
||||
), parsuj_slozky_vydej(lek)
|
||||
|
||||
return dict(typ_leku=None, lek_kod=None, atc=None, nazev=None,
|
||||
forma=None, sila=None, cesta_podani=None,
|
||||
postup_pripravy=None), []
|
||||
|
||||
|
||||
# ── parsovani XML ─────────────────────────────────────────────────────────────
|
||||
|
||||
def parsuj_xml(xml_soubor):
|
||||
tree = ET.parse(xml_soubor)
|
||||
root = tree.getroot()
|
||||
odpoved = root[0][0] # Envelope > Body > NacistLekovyZaznamOdpoved
|
||||
|
||||
# Doklad prvni, Zprava druha — presne dle XSD sequence
|
||||
doklad = odpoved.find(f"{{{NS}}}Doklad")
|
||||
zpr = odpoved.find(f"{{{NS}}}Zprava")
|
||||
|
||||
# ── Zprava ───────────────────────────────────────────────────────────────
|
||||
zprava = dict(
|
||||
id_zpravy = t(zpr, "ID_Zpravy"),
|
||||
verze = t(zpr, "Verze"),
|
||||
odeslano = ts(t(zpr, "Odeslano")),
|
||||
aplikace = t(zpr, "Aplikace"),
|
||||
id_podani = t(zpr, "ID_Podani"),
|
||||
prijato = ts(t(zpr, "Prijato")),
|
||||
)
|
||||
|
||||
# ── Pacient ───────────────────────────────────────────────────────────────
|
||||
pac = doklad.find(f"{{{NS}}}Pacient")
|
||||
jmeno = pac.find(f"{{{NS}}}Jmeno")
|
||||
zprava["pacient_prijmeni"] = t(jmeno, "Prijmeni") if jmeno is not None else None
|
||||
zprava["pacient_jmena"] = t(jmeno, "Jmena") if jmeno is not None else None
|
||||
zprava["pacient_datum_narozeni"] = datum(t(pac, "DatumNarozeni"))
|
||||
|
||||
# ── Predpisy ──────────────────────────────────────────────────────────────
|
||||
predpisy = [] # kazda polozka: (row_dict, slozky_list)
|
||||
predpis_seznam = doklad.find(f"{{{NS}}}PredpisSeznam")
|
||||
if predpis_seznam is not None:
|
||||
for p in predpis_seznam.findall(f"{{{NS}}}Predpis"):
|
||||
row = dict(
|
||||
id_lp_predpis = t(p, "ID_LP_Predpis"),
|
||||
kod_predepisujiciho = t(p, "KodPredepisujiciho"),
|
||||
datum_vystaveni = datum(t(p, "DatumVystaveni")),
|
||||
mnozstvi = t(p, "Mnozstvi"),
|
||||
navod = t(p, "Navod"),
|
||||
opakovani = t(p, "Opakovani"),
|
||||
modry_pruh = t(p, "ModryPruh"),
|
||||
)
|
||||
lek_fields, slozky = parsuj_lek_predpis(p)
|
||||
row.update(lek_fields)
|
||||
predpisy.append((row, slozky))
|
||||
|
||||
# ── Vydeji ────────────────────────────────────────────────────────────────
|
||||
vydeji = [] # kazda polozka: (row_dict, slozky_list)
|
||||
vydej_seznam = doklad.find(f"{{{NS}}}VydejSeznam")
|
||||
if vydej_seznam is not None:
|
||||
for v in vydej_seznam.findall(f"{{{NS}}}Vydej"):
|
||||
row = dict(
|
||||
id_lp_vydej = t(v, "ID_LP_Vydej"),
|
||||
id_lp_predpis = t(v, "ID_LP_Predpis"),
|
||||
kod_vydavajiciho = t(v, "KodVydavajiciho"),
|
||||
datum_vydeje = datum(t(v, "DatumVydeje")),
|
||||
mnozstvi = t(v, "Mnozstvi"),
|
||||
navod = t(v, "Navod"),
|
||||
exspirace = datum(t(v, "Exspirace")),
|
||||
sarze = t(v, "Sarze"),
|
||||
seriove_cislo = t(v, "SerioveCislo"),
|
||||
pozn = t(v, "Pozn"),
|
||||
)
|
||||
lek_fields, slozky = parsuj_lek_vydej(v)
|
||||
row.update(lek_fields)
|
||||
vydeji.append((row, slozky))
|
||||
|
||||
predepisujici = parsuj_predepisujici(doklad)
|
||||
vydavajici = parsuj_vydavajici(doklad)
|
||||
|
||||
return zprava, predpisy, vydeji, predepisujici, vydavajici
|
||||
|
||||
|
||||
# ── ulozeni do DB ─────────────────────────────────────────────────────────────
|
||||
|
||||
def _najdi_id(cur, tabulka, sloupec, hodnota):
|
||||
"""Pomocna funkce — vrati id radku dle unikatniho sloupce."""
|
||||
cur.execute(f"SELECT id FROM {tabulka} WHERE {sloupec} = %s", (hodnota,))
|
||||
row = cur.fetchone()
|
||||
return row["id"] if row else None
|
||||
|
||||
|
||||
def uloz(conn, zprava, predpisy, vydeji, predepisujici, vydavajici,
|
||||
pacient_id=None, xml_soubor=None):
|
||||
"""
|
||||
Ulozi parsovana data do MySQL.
|
||||
pacient_id — FK na tabulku pacient (None pokud volano primo z 06)
|
||||
xml_soubor — relativni cesta k archivnimu XML souboru (None pokud neni archivovano)
|
||||
|
||||
Vraci dict se statistikami:
|
||||
predpisy_novych, predpisy_celkem,
|
||||
vydeji_novych, vydeji_celkem,
|
||||
predpis_slozka, vydej_slozka
|
||||
"""
|
||||
iplp_predpisu = 0
|
||||
iplp_vydejuu = 0
|
||||
|
||||
with conn.cursor() as cur:
|
||||
|
||||
# ── zprava ────────────────────────────────────────────────────────────
|
||||
zprava_row = dict(zprava)
|
||||
zprava_row["pacient_id"] = pacient_id
|
||||
zprava_row["xml_soubor"] = xml_soubor
|
||||
cur.execute("""
|
||||
INSERT INTO zprava
|
||||
(id_zpravy, pacient_id, verze, odeslano, aplikace, id_podani, prijato,
|
||||
pacient_prijmeni, pacient_jmena, pacient_datum_narozeni, xml_soubor)
|
||||
VALUES
|
||||
(%(id_zpravy)s, %(pacient_id)s, %(verze)s, %(odeslano)s, %(aplikace)s,
|
||||
%(id_podani)s, %(prijato)s,
|
||||
%(pacient_prijmeni)s, %(pacient_jmena)s, %(pacient_datum_narozeni)s,
|
||||
%(xml_soubor)s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
prijato = VALUES(prijato),
|
||||
xml_soubor = COALESCE(VALUES(xml_soubor), xml_soubor),
|
||||
stazeno = CURRENT_TIMESTAMP
|
||||
""", zprava_row)
|
||||
zprava_id = _najdi_id(cur, "zprava", "id_zpravy", zprava["id_zpravy"])
|
||||
|
||||
# ── predpisy + jejich slozky ───────────────────────────────────────────
|
||||
vlozeno_p = 0
|
||||
vlozeno_ps = 0
|
||||
for row, slozky in predpisy:
|
||||
row["zprava_id"] = zprava_id
|
||||
cur.execute("""
|
||||
INSERT IGNORE INTO predpis
|
||||
(zprava_id, id_lp_predpis, kod_predepisujiciho,
|
||||
datum_vystaveni, mnozstvi, navod, opakovani, modry_pruh,
|
||||
typ_leku, lek_kod, atc, nazev, forma, sila,
|
||||
cesta_podani, baleni, postup_pripravy)
|
||||
VALUES
|
||||
(%(zprava_id)s, %(id_lp_predpis)s, %(kod_predepisujiciho)s,
|
||||
%(datum_vystaveni)s, %(mnozstvi)s, %(navod)s,
|
||||
%(opakovani)s, %(modry_pruh)s,
|
||||
%(typ_leku)s, %(lek_kod)s, %(atc)s, %(nazev)s, %(forma)s,
|
||||
%(sila)s, %(cesta_podani)s, %(baleni)s, %(postup_pripravy)s)
|
||||
""", row)
|
||||
vlozeno_p += cur.rowcount
|
||||
|
||||
if slozky:
|
||||
iplp_predpisu += 1
|
||||
predpis_db_id = cur.lastrowid or _najdi_id(cur, "predpis", "id_lp_predpis", row["id_lp_predpis"])
|
||||
for s in slozky:
|
||||
s["predpis_id"] = predpis_db_id
|
||||
cur.execute("""
|
||||
INSERT INTO predpis_slozka
|
||||
(predpis_id, mnozstvi, jednotka, nazev, surovina, hvlp_reg)
|
||||
VALUES
|
||||
(%(predpis_id)s, %(mnozstvi)s, %(jednotka)s,
|
||||
%(nazev)s, %(surovina)s, %(hvlp_reg)s)
|
||||
""", s)
|
||||
vlozeno_ps += 1
|
||||
|
||||
# ── vydeji + jejich slozky ────────────────────────────────────────────
|
||||
vlozeno_v = 0
|
||||
vlozeno_vs = 0
|
||||
for row, slozky in vydeji:
|
||||
row["zprava_id"] = zprava_id
|
||||
cur.execute("""
|
||||
INSERT IGNORE INTO vydej
|
||||
(zprava_id, id_lp_vydej, id_lp_predpis, kod_vydavajiciho,
|
||||
datum_vydeje, mnozstvi, navod, exspirace, sarze,
|
||||
seriove_cislo, pozn,
|
||||
typ_leku, lek_kod, atc, nazev, forma, sila,
|
||||
cesta_podani, postup_pripravy)
|
||||
VALUES
|
||||
(%(zprava_id)s, %(id_lp_vydej)s, %(id_lp_predpis)s, %(kod_vydavajiciho)s,
|
||||
%(datum_vydeje)s, %(mnozstvi)s, %(navod)s, %(exspirace)s, %(sarze)s,
|
||||
%(seriove_cislo)s, %(pozn)s,
|
||||
%(typ_leku)s, %(lek_kod)s, %(atc)s, %(nazev)s, %(forma)s,
|
||||
%(sila)s, %(cesta_podani)s, %(postup_pripravy)s)
|
||||
""", row)
|
||||
vlozeno_v += cur.rowcount
|
||||
|
||||
if slozky:
|
||||
iplp_vydejuu += 1
|
||||
vydej_db_id = cur.lastrowid or _najdi_id(cur, "vydej", "id_lp_vydej", row["id_lp_vydej"])
|
||||
for s in slozky:
|
||||
s["vydej_id"] = vydej_db_id
|
||||
cur.execute("""
|
||||
INSERT INTO vydej_slozka
|
||||
(vydej_id, mnozstvi, jednotka, nazev,
|
||||
hrazeno_zp, surovina, hvlp_reg)
|
||||
VALUES
|
||||
(%(vydej_id)s, %(mnozstvi)s, %(jednotka)s, %(nazev)s,
|
||||
%(hrazeno_zp)s, %(surovina)s, %(hvlp_reg)s)
|
||||
""", s)
|
||||
vlozeno_vs += 1
|
||||
|
||||
# ── predepisujici ─────────────────────────────────────────────────────
|
||||
for row in predepisujici:
|
||||
if not row.get("lekar_kod"):
|
||||
continue
|
||||
cur.execute("""
|
||||
INSERT INTO predepisujici
|
||||
(lekar_kod, prijmeni, jmena, icz, icp,
|
||||
pzs_nazev, ulice, mesto, psc, telefon)
|
||||
VALUES
|
||||
(%(lekar_kod)s, %(prijmeni)s, %(jmena)s, %(icz)s, %(icp)s,
|
||||
%(pzs_nazev)s, %(ulice)s, %(mesto)s, %(psc)s, %(telefon)s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
prijmeni = VALUES(prijmeni),
|
||||
jmena = VALUES(jmena),
|
||||
icz = VALUES(icz),
|
||||
icp = VALUES(icp),
|
||||
pzs_nazev = VALUES(pzs_nazev),
|
||||
ulice = VALUES(ulice),
|
||||
mesto = VALUES(mesto),
|
||||
psc = VALUES(psc),
|
||||
telefon = VALUES(telefon)
|
||||
""", row)
|
||||
|
||||
# ── vydavajici ────────────────────────────────────────────────────────
|
||||
for row in vydavajici:
|
||||
if not row.get("lekarnik_kod"):
|
||||
continue
|
||||
cur.execute("""
|
||||
INSERT INTO vydavajici
|
||||
(lekarnik_kod, prijmeni, jmena,
|
||||
pzs_nazev, ulice, mesto, psc, telefon)
|
||||
VALUES
|
||||
(%(lekarnik_kod)s, %(prijmeni)s, %(jmena)s,
|
||||
%(pzs_nazev)s, %(ulice)s, %(mesto)s, %(psc)s, %(telefon)s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
prijmeni = VALUES(prijmeni),
|
||||
jmena = VALUES(jmena),
|
||||
pzs_nazev = VALUES(pzs_nazev),
|
||||
ulice = VALUES(ulice),
|
||||
mesto = VALUES(mesto),
|
||||
psc = VALUES(psc),
|
||||
telefon = VALUES(telefon)
|
||||
""", row)
|
||||
|
||||
conn.commit()
|
||||
|
||||
return dict(
|
||||
predpisy_novych = vlozeno_p,
|
||||
predpisy_celkem = len(predpisy),
|
||||
vydeji_novych = vlozeno_v,
|
||||
vydeji_celkem = len(vydeji),
|
||||
predpis_slozka = vlozeno_ps,
|
||||
vydej_slozka = vlozeno_vs,
|
||||
)
|
||||
|
||||
|
||||
# ── main ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
xml = Path(sys.argv[1]) if len(sys.argv) > 1 else XML_SOUBOR
|
||||
print(f"XML: {xml} ({xml.stat().st_size // 1024} KB)")
|
||||
|
||||
print("Parsovani XML ...")
|
||||
zprava, predpisy, vydeji, predepisujici, vydavajici = parsuj_xml(xml)
|
||||
print(f" -> {len(predpisy)} predpisu, {len(vydeji)} vydejuu, "
|
||||
f"{len(predepisujici)} predepisujicich, {len(vydavajici)} vydavajicich")
|
||||
|
||||
print("Pripojeni k MySQL ...")
|
||||
conn = pymysql.connect(**DB)
|
||||
try:
|
||||
vytvor_schema(conn)
|
||||
print("Ukladani ...")
|
||||
stats = uloz(conn, zprava, predpisy, vydeji, predepisujici, vydavajici)
|
||||
print(f" predpisy: {stats['predpisy_novych']} novych (celkem {stats['predpisy_celkem']})")
|
||||
print(f" vydeji: {stats['vydeji_novych']} novych (celkem {stats['vydeji_celkem']})")
|
||||
print(f" slozky: {stats['predpis_slozka']} predpis / {stats['vydej_slozka']} vydej")
|
||||
print("Hotovo OK")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,427 @@
|
||||
"""
|
||||
Hromadne stazeni lekovych zaznamu z eReceptu pro registrovane pacienty Medicusu.
|
||||
|
||||
Spusteni:
|
||||
# pouze rodina (testovaci run)
|
||||
python 07StahnoutVsechny.py --prijmeni Buzalka,Buzalkova,Kusinova
|
||||
|
||||
# vsichni registrovani pacienti
|
||||
python 07StahnoutVsechny.py
|
||||
|
||||
# davkovani po castech
|
||||
python 07StahnoutVsechny.py --offset 100 --limit 50
|
||||
|
||||
Vystup:
|
||||
Konzole — jeden stručny radek na pacienta
|
||||
Logs/ — kompletni log se vsemi detaily (UTF-8)
|
||||
|
||||
Logika poctu mesicu:
|
||||
- prvni stazeni pacienta → 60 mesicu (maximum)
|
||||
- opakovane stazeni → ceil(pocet_dni_od_posledniho / 30) + 1
|
||||
(prekryv 1 mesic pro jistotu, INSERT IGNORE zajisti bez duplikatu)
|
||||
|
||||
XML archiv:
|
||||
xml_archive/YYYY-MM-DD/{Prijmeni}_{Jmena}_{datnar}.xml
|
||||
Cesta ulozena take v zprava.xml_soubor pro snadne dohledani.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import logging
|
||||
import math
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime, timezone, date
|
||||
from pathlib import Path
|
||||
from xml.sax.saxutils import escape as xml_escape
|
||||
|
||||
import fdb
|
||||
import pymysql
|
||||
import pymysql.cursors
|
||||
from requests import Session
|
||||
from requests_pkcs12 import Pkcs12Adapter
|
||||
|
||||
# Windows konzole — nahrad neunikatni znaky misto padu
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(errors="replace")
|
||||
|
||||
# ── Import parsovaci logiky z 06 ──────────────────────────────────────────────
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
"m06", Path(__file__).parent / "06UlozitDoMySQL.py"
|
||||
)
|
||||
_m06 = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(_m06)
|
||||
|
||||
parsuj_xml = _m06.parsuj_xml
|
||||
uloz = _m06.uloz
|
||||
inicializuj_schema = _m06.inicializuj_schema
|
||||
|
||||
# ── Konfigurace eRecept ───────────────────────────────────────────────────────
|
||||
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
|
||||
PFX_PASS = "Vlado7309208104++"
|
||||
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
|
||||
API_PASS = "Buzalka@Vladimir2025"
|
||||
UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
|
||||
PRACOVISTE = "00214235367"
|
||||
ENDPOINT = "https://lekar-soap.erecept.sukl.cz/cuer/Lekar2"
|
||||
|
||||
POCET_ZNAKU_ATC = 7
|
||||
POCET_MESICU_MAX = 60
|
||||
PAUZA_MIN = 10 # sekund
|
||||
PAUZA_MAX = 20 # sekund
|
||||
|
||||
# ── Konfigurace Firebird ──────────────────────────────────────────────────────
|
||||
FB_DSN = r'localhost:c:\medicus 3\data\medicus.fdb'
|
||||
FB_USER = 'SYSDBA'
|
||||
FB_PASS = 'masterkey'
|
||||
FB_CHARSET = 'win1250'
|
||||
ICP = '09305001'
|
||||
ODB = '001'
|
||||
|
||||
# ── Konfigurace MySQL ─────────────────────────────────────────────────────────
|
||||
DB = dict(
|
||||
host = "192.168.1.76",
|
||||
user = "root",
|
||||
password = "Vlado9674+",
|
||||
database = "medicus",
|
||||
charset = "utf8mb4",
|
||||
cursorclass = pymysql.cursors.DictCursor,
|
||||
)
|
||||
|
||||
# ── Adresare ──────────────────────────────────────────────────────────────────
|
||||
XML_DIR = Path(__file__).parent / "xml_archive"
|
||||
LOGS_DIR = Path(__file__).parent / "Logs"
|
||||
|
||||
|
||||
# ── Logging ───────────────────────────────────────────────────────────────────
|
||||
|
||||
def setup_logging(dnes_str, cas_str):
|
||||
"""
|
||||
Dva handlery:
|
||||
- soubor (DEBUG) → Logs/YYYY-MM-DD_HH-MM-SS.log — vse vcetne detailu
|
||||
- konzole (INFO) → stdout — jen souhrnne radky
|
||||
"""
|
||||
LOGS_DIR.mkdir(exist_ok=True)
|
||||
log_soubor = LOGS_DIR / f"{dnes_str}_{cas_str}.log"
|
||||
|
||||
log = logging.getLogger("lz")
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
fh = logging.FileHandler(log_soubor, encoding="utf-8")
|
||||
fh.setLevel(logging.DEBUG)
|
||||
fh.setFormatter(logging.Formatter("%(asctime)s %(message)s", datefmt="%H:%M:%S"))
|
||||
|
||||
ch = logging.StreamHandler(sys.stdout)
|
||||
ch.setLevel(logging.INFO)
|
||||
ch.setFormatter(logging.Formatter("%(message)s"))
|
||||
|
||||
log.addHandler(fh)
|
||||
log.addHandler(ch)
|
||||
return log, log_soubor
|
||||
|
||||
|
||||
# ── Firebird: nacteni registrovanych pacientu ─────────────────────────────────
|
||||
|
||||
_SQL_VSICHNI = """
|
||||
SELECT
|
||||
KAR.IDPAC,
|
||||
KAR.PRIJMENI,
|
||||
KAR.JMENO,
|
||||
KAR.DATNAR
|
||||
FROM KAR
|
||||
WHERE (vyrazen = 'N')
|
||||
AND EXISTS (
|
||||
SELECT id FROM registr r
|
||||
JOIN icp i ON r.idicp = i.idicp
|
||||
WHERE r.idpac = kar.idpac
|
||||
AND (r.datum <= ?)
|
||||
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= ?)
|
||||
AND (r.priznak IN ('V','D','A'))
|
||||
AND (i.icp = ?)
|
||||
AND (i.odb = ?)
|
||||
)
|
||||
ORDER BY KAR.PRIJMENI_UP, KAR.RODCIS
|
||||
"""
|
||||
|
||||
_SQL_FILTR = """
|
||||
SELECT
|
||||
KAR.IDPAC,
|
||||
KAR.PRIJMENI,
|
||||
KAR.JMENO,
|
||||
KAR.DATNAR
|
||||
FROM KAR
|
||||
WHERE (vyrazen = 'N')
|
||||
AND KAR.PRIJMENI IN ({ph})
|
||||
ORDER BY KAR.PRIJMENI_UP, KAR.RODCIS
|
||||
"""
|
||||
|
||||
|
||||
def nacti_pacienty(prijmeni_filtr=None):
|
||||
conn = fdb.connect(dsn=FB_DSN, user=FB_USER, password=FB_PASS, charset=FB_CHARSET)
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
if prijmeni_filtr:
|
||||
ph = ",".join("?" * len(prijmeni_filtr))
|
||||
cur.execute(_SQL_FILTR.format(ph=ph), prijmeni_filtr)
|
||||
else:
|
||||
dnes = date.today().isoformat()
|
||||
cur.execute(_SQL_VSICHNI, (dnes, dnes, ICP, ODB))
|
||||
cols = [d[0].lower() for d in cur.description]
|
||||
return [dict(zip(cols, row)) for row in cur.fetchall()]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ── MySQL: pacient UPSERT ─────────────────────────────────────────────────────
|
||||
|
||||
def upsert_pacient(cur, pac):
|
||||
cur.execute("""
|
||||
INSERT INTO pacient (idpac, prijmeni, jmena, datum_narozeni)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
prijmeni = VALUES(prijmeni),
|
||||
jmena = VALUES(jmena)
|
||||
""", (pac["idpac"], pac["prijmeni"], pac["jmeno"], pac["datnar"]))
|
||||
cur.execute("SELECT id FROM pacient WHERE idpac = %s", (pac["idpac"],))
|
||||
return cur.fetchone()["id"]
|
||||
|
||||
|
||||
def posledni_stazeni(cur, pacient_id):
|
||||
cur.execute(
|
||||
"SELECT MAX(stazeno) AS posledni FROM zprava WHERE pacient_id = %s",
|
||||
(pacient_id,)
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return row["posledni"] if row and row["posledni"] else None
|
||||
|
||||
|
||||
def vypocti_pocet_mesicu(posledni):
|
||||
if posledni is None:
|
||||
return POCET_MESICU_MAX
|
||||
delta_dni = (datetime.now() - posledni).days
|
||||
return min(math.ceil(delta_dni / 30) + 1, POCET_MESICU_MAX)
|
||||
|
||||
|
||||
def uloz_poznamku(conn, pacient_id, poznamka):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"UPDATE pacient SET poznamka = %s WHERE id = %s",
|
||||
(poznamka, pacient_id)
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
# ── SOAP volani ───────────────────────────────────────────────────────────────
|
||||
|
||||
def extrahuj_soap_fault(xml_text):
|
||||
"""Vraci text chyby pokud odpoved obsahuje SOAP Fault, jinak None."""
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
NS_SOAP = "http://schemas.xmlsoap.org/soap/envelope/"
|
||||
NS_SUKL = "http://www.sukl.cz/erp/201912"
|
||||
root = ET.fromstring(xml_text)
|
||||
body = root.find(f"{{{NS_SOAP}}}Body")
|
||||
if body is None:
|
||||
return "Chybejici SOAP Body"
|
||||
fault = body.find(f"{{{NS_SOAP}}}Fault") or body.find("Fault")
|
||||
if fault is not None:
|
||||
faultstring = (fault.findtext("faultstring")
|
||||
or fault.findtext("faultcode")
|
||||
or "Nezname SOAP Fault")
|
||||
detail = fault.find("detail")
|
||||
if detail is not None and detail.text:
|
||||
faultstring = f"{faultstring}: {detail.text.strip()[:200]}"
|
||||
return faultstring
|
||||
if body.find(f"{{{NS_SUKL}}}NacistLekovyZaznamOdpoved") is None:
|
||||
first = list(body)
|
||||
tag = first[0].tag if first else "prazdne Body"
|
||||
return f"Neocekavana odpoved: {tag}"
|
||||
return None
|
||||
except Exception as e:
|
||||
return f"Chyba pri parsovani odpovedi: {e}"
|
||||
|
||||
|
||||
def nacti_lekovy_zaznam(sess, prijmeni, jmena, datum_narozeni, pocet_mesicu):
|
||||
id_zpravy = str(uuid.uuid4())
|
||||
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
||||
soap = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
|
||||
'<soapenv:Body>'
|
||||
f'<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">'
|
||||
f'<Doklad>'
|
||||
f'<Pristupujici>'
|
||||
f'<Uzivatel>{UZIVATEL}</Uzivatel>'
|
||||
f'<Pracoviste>{PRACOVISTE}</Pracoviste>'
|
||||
f'</Pristupujici>'
|
||||
f'<PocetZnakuATC>{POCET_ZNAKU_ATC}</PocetZnakuATC>'
|
||||
f'<PocetMesicu>{pocet_mesicu}</PocetMesicu>'
|
||||
f'<Pacient><Totoznost><Jmeno>'
|
||||
f'<Prijmeni>{xml_escape(prijmeni)}</Prijmeni>'
|
||||
f'<Jmena>{xml_escape(jmena)}</Jmena>'
|
||||
f'</Jmeno><DatumNarozeni>{datum_narozeni}</DatumNarozeni>'
|
||||
f'</Totoznost></Pacient>'
|
||||
f'</Doklad>'
|
||||
f'<Zprava>'
|
||||
f'<ID_Zpravy>{id_zpravy}</ID_Zpravy>'
|
||||
f'<Verze>202501A</Verze>'
|
||||
f'<Odeslano>{odeslano}</Odeslano>'
|
||||
f'<SW_Klienta>MEDICUS_____</SW_Klienta>'
|
||||
f'</Zprava>'
|
||||
f'</NacistLekovyZaznamLekarDotaz>'
|
||||
'</soapenv:Body>'
|
||||
'</soapenv:Envelope>'
|
||||
)
|
||||
headers = {
|
||||
"Content-Type": 'text/xml; charset="UTF-8"',
|
||||
"SOAPAction": '"NacistLekovyZaznam"',
|
||||
"User-Agent": "Medicus",
|
||||
}
|
||||
resp = sess.post(ENDPOINT, data=soap.encode("utf-8"), headers=headers, timeout=60)
|
||||
if resp.status_code != 200:
|
||||
raise RuntimeError(f"HTTP {resp.status_code}: {resp.text[:300]}")
|
||||
return resp.text
|
||||
|
||||
|
||||
# ── XML archiv ────────────────────────────────────────────────────────────────
|
||||
|
||||
def uloz_xml_na_disk(xml_text, prijmeni, jmena, datnar_str, dnes_str):
|
||||
adr = XML_DIR / dnes_str
|
||||
adr.mkdir(parents=True, exist_ok=True)
|
||||
nazev = f"{prijmeni}_{jmena}_{datnar_str}.xml".replace(" ", "_")
|
||||
soubor = adr / nazev
|
||||
soubor.write_text(xml_text, encoding="utf-8")
|
||||
return str(soubor.relative_to(Path(__file__).parent))
|
||||
|
||||
|
||||
# ── Hlavni smycka ─────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="Hromadne stazeni lekovych zaznamu z eReceptu")
|
||||
ap.add_argument("--prijmeni", default=None,
|
||||
help="Filtr prijmeni oddelena carkou, napr: Buzalka,Buzalkova,Kusinova")
|
||||
ap.add_argument("--limit", type=int, default=None, help="Zpracuj pouze N pacientu")
|
||||
ap.add_argument("--offset", type=int, default=0,
|
||||
help="Preskoc prvnich N pacientu (pro postupne davkovani)")
|
||||
args = ap.parse_args()
|
||||
|
||||
dnes_str = date.today().isoformat()
|
||||
cas_str = datetime.now().strftime("%H-%M-%S")
|
||||
log, log_soubor = setup_logging(dnes_str, cas_str)
|
||||
|
||||
prijmeni_filtr = None
|
||||
if args.prijmeni:
|
||||
prijmeni_filtr = [p.strip() for p in args.prijmeni.split(",")]
|
||||
log.info(f"Filtr prijmeni: {prijmeni_filtr}")
|
||||
|
||||
log.info("Nacitam pacienty z Medicusu...")
|
||||
pacienti = nacti_pacienty(prijmeni_filtr)
|
||||
log.debug(f"Celkem registrovanych: {len(pacienti)}")
|
||||
|
||||
if args.offset:
|
||||
pacienti = pacienti[args.offset:]
|
||||
log.debug(f"Preskoceno: {args.offset}")
|
||||
if args.limit:
|
||||
pacienti = pacienti[:args.limit]
|
||||
log.debug(f"Omezeno na: {len(pacienti)}")
|
||||
|
||||
celkem = len(pacienti)
|
||||
log.info(f"Pacientu ke zpracovani: {celkem} | log: {log_soubor.name}")
|
||||
|
||||
if not celkem:
|
||||
log.info("Zadni pacienti — konec.")
|
||||
return
|
||||
|
||||
conn = pymysql.connect(**DB)
|
||||
inicializuj_schema(conn)
|
||||
log.debug("MySQL schema OK")
|
||||
|
||||
sess = Session()
|
||||
sess.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_FILE, pkcs12_password=PFX_PASS))
|
||||
sess.auth = (API_USER, API_PASS)
|
||||
|
||||
ok = 0
|
||||
chyby = 0
|
||||
|
||||
try:
|
||||
for i, pac in enumerate(pacienti, 1):
|
||||
prijmeni = pac["prijmeni"]
|
||||
jmena = pac["jmeno"]
|
||||
datnar = pac["datnar"]
|
||||
datnar_str = datnar.isoformat() if hasattr(datnar, "isoformat") else str(datnar)
|
||||
jmeno_str = f"{prijmeni} {jmena}"
|
||||
|
||||
log.debug(f"[{i:4}/{celkem}] {jmeno_str} (*{datnar_str})")
|
||||
|
||||
with conn.cursor() as cur:
|
||||
pacient_id = upsert_pacient(cur, pac)
|
||||
posledni = posledni_stazeni(cur, pacient_id)
|
||||
conn.commit()
|
||||
|
||||
pocet_mesicu = vypocti_pocet_mesicu(posledni)
|
||||
log.debug(f" stahuju {pocet_mesicu}m "
|
||||
f"(posledni: {posledni.strftime('%Y-%m-%d') if posledni else 'nikdy'})")
|
||||
|
||||
# Zavolej API
|
||||
try:
|
||||
xml_text = nacti_lekovy_zaznam(sess, prijmeni, jmena, datnar_str, pocet_mesicu)
|
||||
except Exception as e:
|
||||
zprava_chyby = str(e)[:400]
|
||||
log.debug(f" CHYBA API: {zprava_chyby}")
|
||||
log.info(f"[{i:4}/{celkem}] {jmeno_str:<30} CHYBA {zprava_chyby[:60]}")
|
||||
uloz_poznamku(conn, pacient_id, zprava_chyby)
|
||||
chyby += 1
|
||||
continue
|
||||
|
||||
# Detekuj SOAP Fault
|
||||
soap_fault = extrahuj_soap_fault(xml_text)
|
||||
if soap_fault:
|
||||
log.debug(f" SOAP FAULT: {soap_fault}")
|
||||
log.info(f"[{i:4}/{celkem}] {jmeno_str:<30} CHYBA {soap_fault[:60]}")
|
||||
uloz_poznamku(conn, pacient_id, soap_fault[:400])
|
||||
chyby += 1
|
||||
continue
|
||||
|
||||
# Uloz XML
|
||||
xml_soubor = uloz_xml_na_disk(xml_text, prijmeni, jmena, datnar_str, dnes_str)
|
||||
xml_path = Path(__file__).parent / xml_soubor
|
||||
kb = xml_path.stat().st_size // 1024
|
||||
log.debug(f" XML: {xml_soubor} ({kb} KB)")
|
||||
|
||||
# Parsuj + uloz do MySQL
|
||||
try:
|
||||
zprava_d, predpisy, vydeji, predepisujici, vydavajici = parsuj_xml(xml_path)
|
||||
stats = uloz(conn, zprava_d, predpisy, vydeji, predepisujici, vydavajici,
|
||||
pacient_id=pacient_id, xml_soubor=xml_soubor)
|
||||
uloz_poznamku(conn, pacient_id, None)
|
||||
log.debug(f" predpisy: {stats['predpisy_novych']}n/{stats['predpisy_celkem']} "
|
||||
f"vydeji: {stats['vydeji_novych']}n/{stats['vydeji_celkem']} "
|
||||
f"slozky: {stats['predpis_slozka']}p/{stats['vydej_slozka']}v")
|
||||
log.info(f"[{i:4}/{celkem}] {jmeno_str:<30} OK "
|
||||
f"{stats['predpisy_celkem']:4}p {stats['vydeji_celkem']:4}v {kb:4} KB")
|
||||
ok += 1
|
||||
except Exception as e:
|
||||
zprava_chyby = str(e)[:400]
|
||||
log.debug(f" CHYBA parsovani/ulozeni: {zprava_chyby}")
|
||||
log.info(f"[{i:4}/{celkem}] {jmeno_str:<30} CHYBA {zprava_chyby[:60]}")
|
||||
uloz_poznamku(conn, pacient_id, zprava_chyby)
|
||||
chyby += 1
|
||||
|
||||
if i < celkem:
|
||||
pauza = random.randint(PAUZA_MIN, PAUZA_MAX)
|
||||
log.debug(f" cekam {pauza}s ...")
|
||||
time.sleep(pauza)
|
||||
|
||||
finally:
|
||||
conn.close()
|
||||
sess.close()
|
||||
|
||||
zhrnutí = f"Hotovo: {ok} OK | {chyby} chyb | celkem {celkem} pacientu"
|
||||
log.info("=" * 55)
|
||||
log.info(zhrnutí)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,543 @@
|
||||
# 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
|
||||
# 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:
|
||||
```powershell
|
||||
.venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
### requirements.txt
|
||||
|
||||
```
|
||||
requests
|
||||
requests-pkcs12
|
||||
pymysql
|
||||
fdb
|
||||
zeep
|
||||
mysql-connector-python
|
||||
playwright
|
||||
openpyxl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typické spuštění
|
||||
|
||||
```bash
|
||||
# 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ě 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ě — **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
|
||||
|
||||
```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;
|
||||
|
||||
-- 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í
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```python
|
||||
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`](../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
|
||||
```
|
||||
|
||||
```bash
|
||||
# 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` |
|
||||
@@ -0,0 +1,77 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from requests import Session
|
||||
from requests_pkcs12 import Pkcs12Adapter
|
||||
|
||||
# --- Konfigurace ---
|
||||
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
|
||||
PFX_PASSWORD = "Vlado7309208104++"
|
||||
|
||||
# HTTP Basic Auth - UUID lékaře (jednoznačný v ČR) + osobní heslo
|
||||
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
|
||||
API_PASS = "Buzalka@Vladimir2025"
|
||||
|
||||
UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
|
||||
PRACOVISTE = "00214235367"
|
||||
|
||||
ENDPOINTS = [
|
||||
"https://lekar-soap.erecept.sukl.cz/cuer/Lekar2",
|
||||
]
|
||||
|
||||
# --- Pacient ---
|
||||
PRIJMENI = "Buzalka"
|
||||
JMENA = "Vladimír"
|
||||
DATUM_NAROZENI = "1973-09-20"
|
||||
|
||||
POCET_ZNAKU_ATC = 7
|
||||
POCET_MESICU = 60
|
||||
|
||||
|
||||
def nacist_lekovy_zaznam():
|
||||
sess = Session()
|
||||
sess.mount("https://", Pkcs12Adapter(
|
||||
pkcs12_filename=PFX_FILE,
|
||||
pkcs12_password=PFX_PASSWORD
|
||||
))
|
||||
sess.auth = (API_USER, API_PASS)
|
||||
|
||||
id_zpravy = str(uuid.uuid4())
|
||||
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
||||
|
||||
soap_body = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
|
||||
'<soapenv:Body>'
|
||||
f'<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">'
|
||||
f'<Doklad>'
|
||||
f'<Pristupujici><Uzivatel>{UZIVATEL}</Uzivatel><Pracoviste>{PRACOVISTE}</Pracoviste></Pristupujici>'
|
||||
f'<PocetZnakuATC>{POCET_ZNAKU_ATC}</PocetZnakuATC>'
|
||||
f'<PocetMesicu>{POCET_MESICU}</PocetMesicu>'
|
||||
f'<Pacient><Totoznost><Jmeno><Prijmeni>{PRIJMENI}</Prijmeni><Jmena>{JMENA}</Jmena></Jmeno>'
|
||||
f'<DatumNarozeni>{DATUM_NAROZENI}</DatumNarozeni></Totoznost></Pacient>'
|
||||
f'</Doklad>'
|
||||
f'<Zprava><ID_Zpravy>{id_zpravy}</ID_Zpravy><Verze>202501A</Verze>'
|
||||
f'<Odeslano>{odeslano}</Odeslano><SW_Klienta>MEDICUS_____</SW_Klienta></Zprava>'
|
||||
f'</NacistLekovyZaznamLekarDotaz>'
|
||||
'</soapenv:Body>'
|
||||
'</soapenv:Envelope>'
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Type": 'text/xml; charset="UTF-8"',
|
||||
"SOAPAction": '"NacistLekovyZaznam"',
|
||||
"User-Agent": "Medicus"
|
||||
}
|
||||
|
||||
for url in ENDPOINTS:
|
||||
print(f"\n--- POST: {url} ---")
|
||||
try:
|
||||
resp = sess.post(url, data=soap_body.encode("utf-8"), headers=headers, timeout=15)
|
||||
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů")
|
||||
print(resp.text)
|
||||
except Exception as e:
|
||||
print(f"CHYBA: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
nacist_lekovy_zaznam()
|
||||
@@ -0,0 +1,99 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from requests import Session
|
||||
from requests_pkcs12 import Pkcs12Adapter
|
||||
|
||||
# --- Konfigurace ---
|
||||
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
|
||||
PFX_PASSWORD = "Vlado7309208104++"
|
||||
|
||||
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
|
||||
API_PASS = "Buzalka@Vladimir2025"
|
||||
|
||||
UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
|
||||
PRACOVISTE = "00214235367"
|
||||
|
||||
ENDPOINT = "https://cuer-soap.erecept.sukl.cz/"
|
||||
|
||||
# --- Filtr ---
|
||||
CP_PACIENTA = "7309208104" # rodné číslo pacienta (bez lomítka)
|
||||
DATUM_OD = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d")
|
||||
DATUM_DO = datetime.now().strftime("%Y-%m-%d")
|
||||
LIMIT = 100
|
||||
|
||||
|
||||
def seznam_predpisu():
|
||||
sess = Session()
|
||||
sess.mount("https://", Pkcs12Adapter(
|
||||
pkcs12_filename=PFX_FILE,
|
||||
pkcs12_password=PFX_PASSWORD
|
||||
))
|
||||
sess.auth = (API_USER, API_PASS)
|
||||
|
||||
id_zpravy = str(uuid.uuid4())
|
||||
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
||||
|
||||
filtr_pacient = ""
|
||||
if CP_PACIENTA:
|
||||
filtr_pacient = f"<CP_Pacienta>{CP_PACIENTA}</CP_Pacienta>"
|
||||
|
||||
soap_body = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
|
||||
'<soapenv:Body>'
|
||||
'<SeznamPredpisuDotaz xmlns="http://www.sukl.cz/erp/201704">'
|
||||
'<Doklad>'
|
||||
f'<Pristupujici><Uzivatel>{UZIVATEL}</Uzivatel><Pracoviste>{PRACOVISTE}</Pracoviste></Pristupujici>'
|
||||
f'<Filtr>'
|
||||
f'<DatumOd>{DATUM_OD}</DatumOd>'
|
||||
f'<DatumDo>{DATUM_DO}</DatumDo>'
|
||||
f'<Limit>{LIMIT}</Limit>'
|
||||
f'{filtr_pacient}'
|
||||
f'</Filtr>'
|
||||
'</Doklad>'
|
||||
f'<Zprava>'
|
||||
f'<ID_Zpravy>{id_zpravy}</ID_Zpravy>'
|
||||
f'<Verze>202501A</Verze>'
|
||||
f'<Odeslano>{odeslano}</Odeslano>'
|
||||
f'<SW_Klienta>MEDICUS_____</SW_Klienta>'
|
||||
f'</Zprava>'
|
||||
'</SeznamPredpisuDotaz>'
|
||||
'</soapenv:Body>'
|
||||
'</soapenv:Envelope>'
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Type": 'text/xml; charset="UTF-8"',
|
||||
"SOAPAction": '"SeznamPredpisu"',
|
||||
"User-Agent": "Medicus"
|
||||
}
|
||||
|
||||
print(f"=== SOAP REQUEST ===\n{soap_body}\n===================\n")
|
||||
print(f"Datum od: {DATUM_OD}, do: {DATUM_DO}, limit: {LIMIT}")
|
||||
if CP_PACIENTA:
|
||||
print(f"Filtr pacienta (RC): {CP_PACIENTA}")
|
||||
print(f"ID zprávy: {id_zpravy}")
|
||||
print(f"Endpoint: {ENDPOINT}\n")
|
||||
|
||||
resp = sess.post(ENDPOINT, data=soap_body.encode("utf-8"), headers=headers, timeout=30)
|
||||
|
||||
print(f"HTTP status: {resp.status_code}")
|
||||
|
||||
output_file = f"seznam_predpisu_{id_zpravy}.xml"
|
||||
with open(output_file, "wb") as f:
|
||||
f.write(resp.content)
|
||||
print(f"Odpověď uložena do: {output_file}")
|
||||
|
||||
text = resp.text
|
||||
if "Fault" in text or "fault" in text:
|
||||
print("\n!!! SOAP Fault v odpovědi !!!")
|
||||
print(text[:2000])
|
||||
else:
|
||||
print("Dotaz proběhl úspěšně.")
|
||||
print(f"Velikost odpovědi: {len(resp.content):,} bytů")
|
||||
print("\n--- Odpověď (prvních 3000 znaků) ---")
|
||||
print(text[:3000])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
seznam_predpisu()
|
||||
@@ -0,0 +1,66 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from requests import Session
|
||||
from requests_pkcs12 import Pkcs12Adapter
|
||||
|
||||
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
|
||||
PFX_PASSWORD = "Vlado7309208104++"
|
||||
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
|
||||
API_PASS = "Buzalka@Vladimir2025"
|
||||
|
||||
sess = Session()
|
||||
sess.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_FILE, pkcs12_password=PFX_PASSWORD))
|
||||
sess.auth = (API_USER, API_PASS)
|
||||
|
||||
id_zpravy = str(uuid.uuid4())
|
||||
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+01:00")
|
||||
|
||||
# Přesně tělo z Medicusu
|
||||
soap_body = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
|
||||
'<soapenv:Body>'
|
||||
'<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">'
|
||||
'<Doklad><Pristupujici>'
|
||||
'<Uzivatel>E08C89C6-2B1A-4EBA-8ED9-4E3E63618379</Uzivatel>'
|
||||
'<Pracoviste>00214235367</Pracoviste>'
|
||||
'</Pristupujici>'
|
||||
'<PocetZnakuATC>7</PocetZnakuATC>'
|
||||
'<PocetMesicu>60</PocetMesicu>'
|
||||
'<Pacient><Totoznost><Jmeno>'
|
||||
'<Prijmeni>Buzalka</Prijmeni>'
|
||||
'<Jmena>Vladim\u00edr</Jmena>'
|
||||
'</Jmeno>'
|
||||
'<DatumNarozeni>1973-09-20</DatumNarozeni>'
|
||||
'</Totoznost></Pacient></Doklad>'
|
||||
f'<Zprava><ID_Zpravy>{id_zpravy}</ID_Zpravy>'
|
||||
'<Verze>202501A</Verze>'
|
||||
f'<Odeslano>{odeslano}</Odeslano>'
|
||||
'<SW_Klienta>MEDICUS_____</SW_Klienta></Zprava>'
|
||||
'</NacistLekovyZaznamLekarDotaz>'
|
||||
'</soapenv:Body>'
|
||||
'</soapenv:Envelope>'
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Type": 'text/xml; charset="UTF-8"',
|
||||
"SOAPAction": '"NacistLekovyZaznam"',
|
||||
"User-Agent": "Medicus"
|
||||
}
|
||||
|
||||
endpoints = [
|
||||
"https://cuer-soap.erecept.sukl.cz/",
|
||||
"https://rlpo-soap.erecept.sukl.cz/",
|
||||
"https://common-soap.erecept.sukl.cz/",
|
||||
"https://lz-soap.erecept.sukl.cz/",
|
||||
"https://lekar-soap.erecept.sukl.cz/cuer/Lekar",
|
||||
]
|
||||
|
||||
for ep in endpoints:
|
||||
print(f"\n--- {ep} ---")
|
||||
try:
|
||||
resp = sess.post(ep, data=soap_body.encode("utf-8"), headers=headers, timeout=10)
|
||||
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů")
|
||||
print(resp.text[:300])
|
||||
except Exception as e:
|
||||
print(f"CHYBA: {e}")
|
||||
@@ -0,0 +1,64 @@
|
||||
import uuid
|
||||
import xml.dom.minidom
|
||||
from datetime import datetime, timezone
|
||||
from requests import Session
|
||||
from requests_pkcs12 import Pkcs12Adapter
|
||||
|
||||
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
|
||||
PFX_PASSWORD = "Vlado7309208104++"
|
||||
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
|
||||
API_PASS = "Buzalka@Vladimir2025"
|
||||
|
||||
ENDPOINT = "https://lekar-soap.erecept.sukl.cz/cuer/Lekar"
|
||||
NAMESPACE = "http://www.sukl.cz/erp/201704"
|
||||
|
||||
def nacist_ciselnik_chyb():
|
||||
sess = Session()
|
||||
sess.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_FILE, pkcs12_password=PFX_PASSWORD))
|
||||
sess.auth = (API_USER, API_PASS)
|
||||
|
||||
id_zpravy = str(uuid.uuid4())
|
||||
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
||||
|
||||
soap_body = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
|
||||
'<soapenv:Body>'
|
||||
f'<CisChybDotaz xmlns="{NAMESPACE}">'
|
||||
f'<Zprava>'
|
||||
f'<ID_Zpravy>{id_zpravy}</ID_Zpravy>'
|
||||
f'<Verze>202501A</Verze>'
|
||||
f'<Odeslano>{odeslano}</Odeslano>'
|
||||
f'<SW_Klienta>MEDICUS_____</SW_Klienta>'
|
||||
f'</Zprava>'
|
||||
f'</CisChybDotaz>'
|
||||
'</soapenv:Body>'
|
||||
'</soapenv:Envelope>'
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Type": 'text/xml; charset="UTF-8"',
|
||||
"SOAPAction": '"NacistCiselnikChyb"',
|
||||
"User-Agent": "Medicus"
|
||||
}
|
||||
|
||||
print(f"Volám: {ENDPOINT}")
|
||||
resp = sess.post(ENDPOINT, data=soap_body.encode("utf-8"), headers=headers, timeout=15)
|
||||
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů\n")
|
||||
|
||||
if resp.status_code == 200:
|
||||
# Hezky formátovaný výpis
|
||||
dom = xml.dom.minidom.parseString(resp.content)
|
||||
pretty = dom.toprettyxml(indent=" ", encoding="utf-8").decode("utf-8")
|
||||
print(pretty)
|
||||
|
||||
# Uložit do souboru
|
||||
fname = f"ciselnik_chyb_{id_zpravy[:8]}.xml"
|
||||
with open(fname, "w", encoding="utf-8") as f:
|
||||
f.write(pretty)
|
||||
print(f"\nUloženo: {fname}")
|
||||
else:
|
||||
print(resp.text)
|
||||
|
||||
if __name__ == "__main__":
|
||||
nacist_ciselnik_chyb()
|
||||
@@ -0,0 +1,393 @@
|
||||
# NacistLekovyZaznam — Funkční SOAP klient pro IS eRecept SÚKL
|
||||
|
||||
## Status
|
||||
**OVĚŘENO FUNKČNÍ** — 5. dubna 2026
|
||||
Odpověď serveru: `HTTP 200`, velikost: ~227 KB reálných dat
|
||||
Schéma ověřeno proti: `Cuer2Schema.xsd` verze `202501A` (dokumentace SÚKL 2025-04-24)
|
||||
|
||||
---
|
||||
|
||||
## Co tato operace dělá
|
||||
|
||||
`NacistLekovyZaznam` je SOAP operace IS eRecept (SÚKL), která vrátí kompletní **lékový záznam pacienta** — tedy seznam všech předpisů a výdejů léků napříč všemi lékaři a lékárnami v ČR za zadané období.
|
||||
|
||||
Typické použití: lékař si před konzultací zobrazí, co pacient aktuálně bere, co mu bylo předepsáno a vydáno v lékárně.
|
||||
|
||||
---
|
||||
|
||||
## Klíčové informace (těžce dohledané)
|
||||
|
||||
### Endpoint (produkce)
|
||||
```
|
||||
https://lekar-soap.erecept.sukl.cz/cuer/Lekar2
|
||||
```
|
||||
> **Pozor:** Endpoint má na konci číslici `2` — tj. `/cuer/Lekar2`, nikoli `/cuer/Lekar`.
|
||||
> Tato operace **není** dostupná na starším endpointu `/cuer/Lekar`.
|
||||
> Poprvé zdokumentováno v: `eRecept_lekovy_zaznam_1v3.docx` (dokumentace SÚKL ze dne 2024-01-18).
|
||||
|
||||
### Endpoint (testovací prostředí)
|
||||
```
|
||||
https://lekar-soap.test-erecept.sukl.cz/cuer/Lekar2
|
||||
```
|
||||
|
||||
### SOAPAction
|
||||
```
|
||||
"NacistLekovyZaznam"
|
||||
```
|
||||
|
||||
### XML namespace
|
||||
```
|
||||
http://www.sukl.cz/erp/201912
|
||||
```
|
||||
> Namespace pochází z prosince 2019 a **nemění se** ani v novějších verzích rozhraní.
|
||||
> Verze rozhraní se předává v elementu `<Verze>` uvnitř zprávy, ne změnou namespace.
|
||||
> SÚKL zachovává zpětnou kompatibilitu — namespace zůstává `201912` i pro verze `202401A`, `202501A` atd.
|
||||
|
||||
---
|
||||
|
||||
## Další operace dostupné na stejném endpointu `/cuer/Lekar2`
|
||||
|
||||
Dle `CUERLekarService.wsdl` (2025-04-24) jsou na tomto endpointu celkem **4 operace**:
|
||||
|
||||
| Operace | SOAPAction | Popis |
|
||||
|---|---|---|
|
||||
| `NacistLekovyZaznam` | `NacistLekovyZaznam` | ✅ lékový záznam pacienta |
|
||||
| `OverDuplicity` | `OverDuplicity` | kontrola duplicitních předpisů |
|
||||
| `ZjistitPoznamkyHvlp` | `ZjistitPoznamkyHvlp` | poznámky k HVLP přípravkům |
|
||||
| `GetAppInfo` | `GetAppInfo` | info o verzi API |
|
||||
| `AppPing` | `AppPing` | test spojení |
|
||||
|
||||
---
|
||||
|
||||
## Autentizace (dvojitá)
|
||||
|
||||
IS eRecept vyžaduje **dvě vrstvy** autentizace současně:
|
||||
|
||||
### 1. Klientský certifikát (TLS mutual auth)
|
||||
- Soubor: `AMBSUKL214235369G_31DEC2024.pfx` (certifikát lékaře vydaný SÚKL)
|
||||
- Formát: PKCS#12 (`.pfx`)
|
||||
- Knihovna: `requests-pkcs12` → `Pkcs12Adapter`
|
||||
- Platnost certifikátu: do 31. 12. 2024 (při expiraci je nutné zažádat SÚKL o nový)
|
||||
|
||||
### 2. HTTP Basic Auth
|
||||
- Uživatel: UUID lékaře (přiděluje SÚKL, jednoznačný v celé ČR)
|
||||
- Heslo: osobní heslo lékaře do portálu eRecept
|
||||
|
||||
---
|
||||
|
||||
## Struktura SOAP dotazu
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soapenv:Body>
|
||||
<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">
|
||||
|
||||
<Doklad>
|
||||
<Pristupujici>
|
||||
<Uzivatel>E08C89C6-2B1A-4EBA-8ED9-4E3E63618379</Uzivatel> <!-- UUID lékaře, povinný -->
|
||||
<Pracoviste>00214235367</Pracoviste> <!-- IČP pracoviště, povinný -->
|
||||
</Pristupujici>
|
||||
<PocetZnakuATC>7</PocetZnakuATC> <!-- POUZE hodnoty 5 nebo 7 (enum!), povinný -->
|
||||
<PocetMesicu>60</PocetMesicu> <!-- max 99, VOLITELNÝ -->
|
||||
<Pacient>
|
||||
<Totoznost>
|
||||
<Jmeno>
|
||||
<Prijmeni>Buzalka</Prijmeni>
|
||||
<Jmena>Vladimír</Jmena>
|
||||
</Jmeno>
|
||||
<DatumNarozeni>1973-09-20</DatumNarozeni>
|
||||
</Totoznost>
|
||||
</Pacient>
|
||||
<!-- Zastupce — volitelný, pouze pokud dotazuje zastupující lékař -->
|
||||
<!-- <Zastupce>
|
||||
<DruhDokladu>OP</DruhDokladu> 1–2 znaky, volitelný -->
|
||||
<!-- <CisloDokladu>123456789</CisloDokladu> 1–9 znaků, volitelný -->
|
||||
<!-- </Zastupce> -->
|
||||
</Doklad>
|
||||
|
||||
<Zprava>
|
||||
<ID_Zpravy>dabda2ad-df61-41db-bd46-a969eced026b</ID_Zpravy> <!-- UUID, generovat pro každý dotaz -->
|
||||
<Verze>202501A</Verze> <!-- aktuální verze protokolu -->
|
||||
<Odeslano>2026-04-05T10:00:00+00:00</Odeslano> <!-- UTC timestamp -->
|
||||
<SW_Klienta>MEDICUS_____</SW_Klienta> <!-- identifikátor SW, přesně 12 znaků -->
|
||||
</Zprava>
|
||||
|
||||
</NacistLekovyZaznamLekarDotaz>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
```
|
||||
|
||||
### Parametry dotazu (ověřeno proti Cuer2Schema.xsd verze 202501A)
|
||||
|
||||
| Element | Povinný | Omezení | Poznámka |
|
||||
|---|---|---|---|
|
||||
| `Uzivatel` | ✅ ano | UUID formát | UUID lékaře přidělené SÚKL |
|
||||
| `Pracoviste` | ✅ ano | — | IČP pracoviště lékaře |
|
||||
| `PocetZnakuATC` | ✅ ano | **pouze 5 nebo 7** | enum — jiné hodnoty server odmítne |
|
||||
| `PocetMesicu` | ❌ volitelný | max 99 | počet měsíců do minulosti |
|
||||
| `Prijmeni` | ✅ ano | — | příjmení pacienta |
|
||||
| `Jmena` | ✅ ano | — | jméno/jména pacienta |
|
||||
| `DatumNarozeni` | ✅ ano | `YYYY-MM-DD` | datum narození pacienta |
|
||||
| `Zastupce` | ❌ volitelný | — | pouze pro zastupujícího lékaře |
|
||||
| `ID_Zpravy` | ✅ ano | UUID formát | nové UUID pro každý dotaz |
|
||||
| `Verze` | ✅ ano | — | aktuálně `202501A` |
|
||||
| `Odeslano` | ✅ ano | ISO 8601 | čas odeslání s časovou zónou |
|
||||
| `SW_Klienta` | ✅ ano | přesně 12 znaků | doplnit mezerami zleva nebo zprava |
|
||||
|
||||
---
|
||||
|
||||
## Struktura odpovědi (ověřeno proti Cuer2Schema.xsd verze 202501A)
|
||||
|
||||
```
|
||||
NacistLekovyZaznamOdpoved
|
||||
├── Doklad
|
||||
│ ├── Pacient potvrzení identity pacienta
|
||||
│ │ ├── Jmeno (Prijmeni, Jmena) volitelný
|
||||
│ │ └── DatumNarozeni volitelný
|
||||
│ │
|
||||
│ ├── PredepisujiciSeznam volitelný — lékaři kteří předepisovali
|
||||
│ │ └── Predepisujici[]
|
||||
│ │ ├── Lekar Kod (UUID) + Jmeno — volitelný
|
||||
│ │ ├── ICZ 8 číslic — volitelný
|
||||
│ │ ├── ICP 8 číslic — povinný
|
||||
│ │ ├── PZS volitelný
|
||||
│ │ │ ├── Nazev max 200 znaků
|
||||
│ │ │ ├── Telefon max 20 znaků — volitelný
|
||||
│ │ │ └── Adresa
|
||||
│ │ └── Telefon max 20 znaků — volitelný
|
||||
│ │
|
||||
│ ├── VydavajiciSeznam volitelný — lékárníci kteří vydávali
|
||||
│ │ └── Vydavajici[]
|
||||
│ │ ├── Lekarnik Kod (UUID) + Jmeno
|
||||
│ │ └── PZS Nazev + Adresa
|
||||
│ │
|
||||
│ ├── PredpisSeznam volitelný — všechny předpisy
|
||||
│ │ └── Predpis[]
|
||||
│ │ ├── ID_LP_Predpis UUID předpisu — povinný
|
||||
│ │ ├── KodPredepisujiciho odkaz do PredepisujiciSeznam — povinný
|
||||
│ │ ├── DatumVystaveni datum vystavení
|
||||
│ │ ├── Mnozstvi int, 1–9999
|
||||
│ │ ├── Navod max 80 znaků (dávkování)
|
||||
│ │ ├── Opakovani int — volitelný
|
||||
│ │ ├── HVLPReg volitelný (registrovaný HVLP)
|
||||
│ │ ├── HVLPNereg volitelný (neregistrovaný HVLP)
|
||||
│ │ ├── IPLP volitelný (individuálně připravovaný LP)
|
||||
│ │ ├── INN volitelný (generický název)
|
||||
│ │ └── ModryPruh boolean — volitelný
|
||||
│ │
|
||||
│ ├── VydejSeznam volitelný — výdeje z lékáren
|
||||
│ │ └── Vydej[]
|
||||
│ │ ├── ID_LP_Vydej UUID výdeje
|
||||
│ │ ├── ID_LP_Predpis UUID předpisu — volitelný (odkaz na předpis)
|
||||
│ │ ├── KodVydavajiciho odkaz do VydavajiciSeznam — povinný
|
||||
│ │ ├── DatumVydeje datum výdeje
|
||||
│ │ ├── Mnozstvi decimal, 0.01–9999.99
|
||||
│ │ ├── Navod max 80 znaků
|
||||
│ │ ├── Exspirace datum — volitelný
|
||||
│ │ ├── Sarze max 50 znaků
|
||||
│ │ ├── SerioveCislo max 20 znaků — volitelný
|
||||
│ │ ├── Pozn max 1000 znaků — volitelný
|
||||
│ │ ├── HVLPReg volitelný
|
||||
│ │ ├── HVLPNereg volitelný
|
||||
│ │ └── IPLP volitelný
|
||||
│ │
|
||||
│ └── DuplicitaSeznam volitelný — detekované duplicity výdejů
|
||||
│ └── Duplicita[]
|
||||
│ └── ID_LP[]
|
||||
│ ├── ID_LP_Vydej UUID — volitelný
|
||||
│ └── ID_LP_Predpis UUID — volitelný
|
||||
│
|
||||
└── Zprava
|
||||
├── ID_Zpravy echo zpět
|
||||
├── Verze
|
||||
├── Odeslano
|
||||
└── ...
|
||||
```
|
||||
|
||||
> **Poznámka k odkazům:** `KodPredepisujiciho` v předpisu odpovídá `Lekar.Kod` v `PredepisujiciSeznam`.
|
||||
> Stejně tak `KodVydavajiciho` odpovídá `Lekarnik.Kod` v `VydavajiciSeznam`.
|
||||
> Struktura je záměrně normalizovaná — lékaři a lékárny jsou uloženi jednou, předpisy a výdeje se na ně odkazují.
|
||||
|
||||
Reálná velikost odpovědi: **~227 KB** (pacient s 60měsíční historií).
|
||||
|
||||
---
|
||||
|
||||
## HTTP hlavičky
|
||||
|
||||
```python
|
||||
headers = {
|
||||
"Content-Type": 'text/xml; charset="UTF-8"',
|
||||
"SOAPAction": '"NacistLekovyZaznam"', # uvozovky jsou součástí hodnoty!
|
||||
"User-Agent": "Medicus"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Závislosti (Python)
|
||||
|
||||
```
|
||||
requests
|
||||
requests-pkcs12
|
||||
```
|
||||
|
||||
Instalace:
|
||||
```bash
|
||||
pip install requests requests-pkcs12
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Jak dohledat správný endpoint (postup pro budoucí operace)
|
||||
|
||||
Endpoint `/cuer/Lekar2` byl dohledán tímto postupem:
|
||||
|
||||
1. Prohledání veškeré dokumentace SÚKL (2017–2025) fulltext hledáním výrazu `soap:address`
|
||||
2. Klíčový dokument: `eRecept_lekovy_zaznam_1v3.docx` (2024-01-18):
|
||||
> *"Pro webové služby, které používá lékař a klinický farmaceut je nová verze rozhraní 202401A (původní 201912A): `https://lekar-soap.erecept.sukl.cz/cuer/Lekar2`"*
|
||||
3. Ověřeno živým voláním — HTTP 200, reálná data
|
||||
|
||||
### Mapa endpointů IS eRecept (produkce)
|
||||
|
||||
| Endpoint | Určen pro | Operace |
|
||||
|---|---|---|
|
||||
| `https://lekar-soap.erecept.sukl.cz/cuer/Lekar` | lékař | CUER operace (předpis, zrušení…) |
|
||||
| `https://lekar-soap.erecept.sukl.cz/cuer/Lekar2` | lékař | **NacistLekovyZaznam**, OverDuplicity, ZjistitPoznamkyHvlp |
|
||||
| `https://lekar-soap.erecept.sukl.cz/rlpo/Lekar` | lékař | RLPO operace |
|
||||
| `https://lekarnik-soap.erecept.sukl.cz/cuer/Lekarnik` | lékárník | CUER operace (výdej…) |
|
||||
| `https://lekarnik-soap.erecept.sukl.cz/cuer/Lekarnik2` | lékárník | nové CUER operace lékárníka |
|
||||
| `https://cuer-soap.erecept.sukl.cz/` | lékárna, ZP | doplatky, limity pojištěnce |
|
||||
|
||||
---
|
||||
|
||||
## Ztotožnění pacienta a chybové kódy
|
||||
|
||||
Zdroj: živé volání `NacistCiselnikChyb` na `https://lekar-soap.erecept.sukl.cz/cuer/Lekar` — ověřeno 5. 4. 2026.
|
||||
|
||||
### Jak funguje ztotožnění pacienta (Registr obyvatel)
|
||||
|
||||
Server každý dotaz porovná s **Registrem obyvatel (ROB)**. Lze použít dvě sady údajů:
|
||||
|
||||
| Sada | Prvky | Poznámka |
|
||||
|---|---|---|
|
||||
| **A — jméno** | Prijmeni + Jmena + DatumNarozeni | základní, stačí pokud je pacient jednoznačný |
|
||||
| **B — doklad** | DruhDokladu + CisloDokladu | jednoznačné vždy, řeší jmenovce |
|
||||
|
||||
Obě sady lze kombinovat. Pokud jsou uvedeny obě, musí být konzistentní (jinak C017).
|
||||
|
||||
### Jak přesně probíhá ztotožnění pacienta v ROB
|
||||
|
||||
> Zdroj: oficiální dokumentace SÚKL k IS eRecept
|
||||
|
||||
IS eRecept nejprve prohledá svůj interní **kmen pacientů** (vlastní databáze dříve ztotožněných osob). Teprve při neúspěchu volá **Registr obyvatel (ROB)** — což může trvat až několik sekund. U každého pacienta s korektními údaji by se to mělo stát pouze jednou. IS eRecept průběžně přebírá změny z ROB (např. přejmenování po svatbě se projeví automaticky).
|
||||
|
||||
Způsoby ztotožnění probíhají v tomto pořadí:
|
||||
|
||||
#### 1. ECD — elektronicky čitelný doklad
|
||||
Hledání dle `DruhDokladu` + `CisloDokladu`. Pokud zadáno, má přednost.
|
||||
|
||||
#### 2. JPDN — jméno, příjmení, datum narození
|
||||
Pokud nalezena právě jedna osoba → hotovo (`ROB=JPDN`).
|
||||
Pokud nalezeno více jmenovců → přechod na JPDNA.
|
||||
Pokud nenalezena žádná → upozornění (měkká chyba).
|
||||
|
||||
#### 3. JPDNA — jméno, příjmení, datum narození + adresa trvalého pobytu
|
||||
ROB umí automaticky opravit záměnu čísla popisného a orientačního.
|
||||
Pokud nalezena právě jedna osoba → hotovo (`ROB=JPDNA`).
|
||||
Pokud nenalezena žádná → upozornění, předpis se uloží s kódem **C023**.
|
||||
|
||||
> **Důležité:** Adresu není nutné uvádět, pokud proběhne úspěšné vyhledání bez ní. V takovém případě se na předpisu použije adresa dohledaná v ROB.
|
||||
|
||||
> **Cizinci:** Lze uvést zahraniční adresu, nebo adresu hotelu/lázní v ČR.
|
||||
|
||||
> **⚠️ Testování:** Ztotožňování reálných osob smí probíhat **pouze na produkci**. O každém ztotožnění je záznam v základních registrech — pacient může obdržet výpis do datové schránky. Na testovacím prostředí jsou dostupné testovací identity na: https://www.szrcr.cz/cs/sluzby/spravci-a-vyvojari/vyvojari-agendovych-informacnich-systemu#testdata (mění se denně mezi 6:00–6:30).
|
||||
|
||||
---
|
||||
|
||||
### Hodnoty DruhDokladu
|
||||
|
||||
⚠️ **Oprava předchozí dokumentace** — povolených hodnot je více než jen `ID` a `P`:
|
||||
|
||||
| Hodnota | Doklad |
|
||||
|---|---|
|
||||
| `ID` | občanský průkaz (nový formát) |
|
||||
| `OP` | občanský průkaz (starší označení) |
|
||||
| `P` | cestovní pas |
|
||||
| `IR` | povolení k pobytu |
|
||||
| `VS` | vízový štítek |
|
||||
| `PS` | pobytový štítek |
|
||||
|
||||
`CisloDokladu` — pouze číslice, max 9 znaků.
|
||||
|
||||
---
|
||||
|
||||
### Chybové kódy — ztotožnění pacienta (skupina C)
|
||||
|
||||
| Kód | Typ | Popis | Co udělat |
|
||||
|---|---|---|---|
|
||||
| **C010** | tvrdá | Jméno + příjmení + datum narození nenalezeno v ROB | Zkontrolovat překlep, záměnu jména a příjmení |
|
||||
| **C011** | tvrdá | Druh a číslo dokladu nenalezeno v ROB | Zkontrolovat číslo a platnost dokladu |
|
||||
| **C012** | tvrdá | Adresa neodpovídá — více jmenovců | Doplnit `DruhDokladu` + `CisloDokladu` |
|
||||
| **C014** | tvrdá | Není uvedeno ani jméno/datum, ani číslo dokladu | Doplnit alespoň jednu sadu |
|
||||
| **C015** | tvrdá | Více jmenovců v ROB, nelze dohledat adresu | Doplnit adresu **nebo** číslo dokladu |
|
||||
| **C016** | měkká | Pacient mladší 33 dní | Předpis uložen, ale nebude v lékovém záznamu |
|
||||
| **C017** | tvrdá | Jméno/datum neodpovídá osobě nalezené dle čísla dokladu | Opravit jméno nebo ho neuvádět |
|
||||
| **C018** | tvrdá | Nelze dohledat, chybí adresa | Doplnit adresu nebo číslo dokladu |
|
||||
| **C019** | tvrdá | Datum narození v budoucnosti | Opravit datum |
|
||||
| **C020** | tvrdá | Chybí jméno, příjmení nebo datum narození | Doplnit kompletní sadu |
|
||||
| **C022** | tvrdá | Pacient dle ROB již zemřel | — |
|
||||
| **C023** | měkká | Předpis uložen, ale kvůli chybě ztotožnění **nebude v lékovém záznamu** | Opravit identifikaci a provést změnu předpisu |
|
||||
| **C024** | měkká | Registr LP prohledán, pacient nenalezen v ROB (možný cizinec) | — |
|
||||
| **C025** | měkká | Výdej uložen, ale kvůli chybě ztotožnění nebude v lékovém záznamu | — |
|
||||
|
||||
> **Měkká chyba** = operace proběhla, ale s upozorněním. **Tvrdá chyba** = operace odmítnuta.
|
||||
|
||||
### Chybové kódy — doklad totožnosti
|
||||
|
||||
| Kód | Popis |
|
||||
|---|---|
|
||||
| **L076** | Neznámý druh dokladu — viz tabulka povolených hodnot výše |
|
||||
|
||||
### Chybové kódy — systémové
|
||||
|
||||
| Kód | Popis |
|
||||
|---|---|
|
||||
| **I005** | Registr obyvatel nedostupný (mimo kontrolu SÚKL) — zkusit znovu |
|
||||
| **I007** | Chyba dotazu na ROB — vstupní data nemají správný formát |
|
||||
|
||||
---
|
||||
|
||||
### Proč Medicus žádá o občanský průkaz
|
||||
|
||||
Typický scénář: lékař pošle dotaz pouze se jménem + datem narození. Pokud existuje více jmenovců v ROB (méně než 1 % obyvatel), server vrátí **C015**. Medicus tento kód rozpozná a zobrazí dialog *"zadejte číslo občanského průkazu"*. Lékař zadá číslo, Medicus pošle dotaz znovu s `DruhDokladu=ID` + `CisloDokladu=...` → server jednoznačně identifikuje pacienta metodou **ECD**.
|
||||
|
||||
### Identifikace pro úhradové mechanismy (ZP)
|
||||
|
||||
Oddělená od ztotožnění v ROB — probíhá přes **kód ZP + číslo pojištěnce**. IS eRecept číslo pojištěnce **neověřuje** — není garantováno, že je správné. Pokud lékárna zjistí chybu kódu pojišťovny, může ji opravit operací `ZmenitPojistovnuPredpisu`.
|
||||
|
||||
### Skript s občankou
|
||||
|
||||
`NacistLekovyZaznam_FUNKCNI_OBCANKA.py` — varianta s `DruhDokladu` + `CisloDokladu` v dotazu.
|
||||
|
||||
---
|
||||
|
||||
## Soubory skriptů
|
||||
|
||||
| Skript | Popis |
|
||||
|---|---|
|
||||
| `NacistLekovyZaznam_FUNKCNI.py` | základní dotaz — jméno + datum narození |
|
||||
| `NacistLekovyZaznam_FUNKCNI_OBCANKA.py` | dotaz s číslem občanského průkazu |
|
||||
| `NacistCiselnikChyb.py` | stáhne kompletní číselník chybových kódů ze serveru |
|
||||
|
||||
---
|
||||
|
||||
## Zdroje dokumentace SÚKL
|
||||
|
||||
| Soubor | Datum | Obsah |
|
||||
|---|---|---|
|
||||
| `eRecept_lekovy_zaznam_1v1.docx` | 2020-05-28 | původní popis lékového záznamu |
|
||||
| `eRecept_lekovy_zaznam_1v3.docx` | 2024-01-18 | endpointy Lekar2/Lekarnik2, verze 202401A |
|
||||
| `CUERLekarService.wsdl` | 2025-04-24 | nejnovější definice rozhraní, seznam operací |
|
||||
| `Cuer2Schema.xsd` | 2025-04-24 | kompletní XSD schéma dotazu i odpovědi, verze 202501A |
|
||||
| `CuerSchema.xsd` | 2025-04-24 | sdílené typy (Zprava, Pristupujici, adresy…) |
|
||||
@@ -0,0 +1,77 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from requests import Session
|
||||
from requests_pkcs12 import Pkcs12Adapter
|
||||
|
||||
# --- Konfigurace ---
|
||||
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
|
||||
PFX_PASSWORD = "Vlado7309208104++"
|
||||
|
||||
# HTTP Basic Auth - UUID lékaře (jednoznačný v ČR) + osobní heslo
|
||||
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
|
||||
API_PASS = "Buzalka@Vladimir2025"
|
||||
|
||||
UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
|
||||
PRACOVISTE = "00214235367"
|
||||
|
||||
ENDPOINTS = [
|
||||
"https://lekar-soap.erecept.sukl.cz/cuer/Lekar2",
|
||||
]
|
||||
|
||||
# --- Pacient ---
|
||||
PRIJMENI = "Buzalka"
|
||||
JMENA = "Vladimír"
|
||||
DATUM_NAROZENI = "1973-09-20"
|
||||
|
||||
POCET_ZNAKU_ATC = 7
|
||||
POCET_MESICU = 60
|
||||
|
||||
|
||||
def nacist_lekovy_zaznam():
|
||||
sess = Session()
|
||||
sess.mount("https://", Pkcs12Adapter(
|
||||
pkcs12_filename=PFX_FILE,
|
||||
pkcs12_password=PFX_PASSWORD
|
||||
))
|
||||
sess.auth = (API_USER, API_PASS)
|
||||
|
||||
id_zpravy = str(uuid.uuid4())
|
||||
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
||||
|
||||
soap_body = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
|
||||
'<soapenv:Body>'
|
||||
f'<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">'
|
||||
f'<Doklad>'
|
||||
f'<Pristupujici><Uzivatel>{UZIVATEL}</Uzivatel><Pracoviste>{PRACOVISTE}</Pracoviste></Pristupujici>'
|
||||
f'<PocetZnakuATC>{POCET_ZNAKU_ATC}</PocetZnakuATC>'
|
||||
f'<PocetMesicu>{POCET_MESICU}</PocetMesicu>'
|
||||
f'<Pacient><Totoznost><Jmeno><Prijmeni>{PRIJMENI}</Prijmeni><Jmena>{JMENA}</Jmena></Jmeno>'
|
||||
f'<DatumNarozeni>{DATUM_NAROZENI}</DatumNarozeni></Totoznost></Pacient>'
|
||||
f'</Doklad>'
|
||||
f'<Zprava><ID_Zpravy>{id_zpravy}</ID_Zpravy><Verze>202501A</Verze>'
|
||||
f'<Odeslano>{odeslano}</Odeslano><SW_Klienta>MEDICUS_____</SW_Klienta></Zprava>'
|
||||
f'</NacistLekovyZaznamLekarDotaz>'
|
||||
'</soapenv:Body>'
|
||||
'</soapenv:Envelope>'
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Type": 'text/xml; charset="UTF-8"',
|
||||
"SOAPAction": '"NacistLekovyZaznam"',
|
||||
"User-Agent": "Medicus"
|
||||
}
|
||||
|
||||
for url in ENDPOINTS:
|
||||
print(f"\n--- POST: {url} ---")
|
||||
try:
|
||||
resp = sess.post(url, data=soap_body.encode("utf-8"), headers=headers, timeout=15)
|
||||
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů")
|
||||
print(resp.text)
|
||||
except Exception as e:
|
||||
print(f"CHYBA: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
nacist_lekovy_zaznam()
|
||||
@@ -0,0 +1,93 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from requests import Session
|
||||
from requests_pkcs12 import Pkcs12Adapter
|
||||
|
||||
# --- Konfigurace ---
|
||||
PFX_FILE = r"C:\Users\vlado\PycharmProjects\Recepty\AMBSUKL214235369G_31DEC2024.pfx"
|
||||
PFX_PASSWORD = "Vlado7309208104++"
|
||||
API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
|
||||
API_PASS = "Buzalka@Vladimir2025"
|
||||
|
||||
UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
|
||||
PRACOVISTE = "00214235367"
|
||||
|
||||
ENDPOINT = "https://lekar-soap.erecept.sukl.cz/cuer/Lekar2"
|
||||
NAMESPACE = "http://www.sukl.cz/erp/201912"
|
||||
|
||||
# --- Pacient ---
|
||||
PRIJMENI = "Buzalka"
|
||||
JMENA = "Vladimír"
|
||||
DATUM_NAROZENI = "1973-09-20"
|
||||
|
||||
# --- Doklad totožnosti ---
|
||||
# DruhDokladu: "ID" = občanský průkaz, "P" = cestovní pas
|
||||
# CisloDokladu: číslo dokladu, max 9 znaků (jen číslice)
|
||||
DRUH_DOKLADU = "ID"
|
||||
CISLO_DOKLADU = "" # <-- sem vyplnit číslo občanského průkazu pacienta
|
||||
|
||||
POCET_ZNAKU_ATC = 7 # pouze 5 nebo 7
|
||||
POCET_MESICU = 60 # max 99, volitelné
|
||||
|
||||
|
||||
def nacist_lekovy_zaznam_s_obcankou():
|
||||
if not CISLO_DOKLADU:
|
||||
print("CHYBA: Není vyplněno CISLO_DOKLADU!")
|
||||
return
|
||||
|
||||
sess = Session()
|
||||
sess.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_FILE, pkcs12_password=PFX_PASSWORD))
|
||||
sess.auth = (API_USER, API_PASS)
|
||||
|
||||
id_zpravy = str(uuid.uuid4())
|
||||
odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
||||
|
||||
soap_body = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
|
||||
'<soapenv:Body>'
|
||||
f'<NacistLekovyZaznamLekarDotaz xmlns="{NAMESPACE}">'
|
||||
f'<Doklad>'
|
||||
f'<Pristupujici>'
|
||||
f'<Uzivatel>{UZIVATEL}</Uzivatel>'
|
||||
f'<Pracoviste>{PRACOVISTE}</Pracoviste>'
|
||||
f'</Pristupujici>'
|
||||
f'<PocetZnakuATC>{POCET_ZNAKU_ATC}</PocetZnakuATC>'
|
||||
f'<PocetMesicu>{POCET_MESICU}</PocetMesicu>'
|
||||
f'<Pacient>'
|
||||
f'<Totoznost>'
|
||||
f'<Jmeno><Prijmeni>{PRIJMENI}</Prijmeni><Jmena>{JMENA}</Jmena></Jmeno>'
|
||||
f'<DatumNarozeni>{DATUM_NAROZENI}</DatumNarozeni>'
|
||||
f'<DruhDokladu>{DRUH_DOKLADU}</DruhDokladu>'
|
||||
f'<CisloDokladu>{CISLO_DOKLADU}</CisloDokladu>'
|
||||
f'</Totoznost>'
|
||||
f'</Pacient>'
|
||||
f'</Doklad>'
|
||||
f'<Zprava>'
|
||||
f'<ID_Zpravy>{id_zpravy}</ID_Zpravy>'
|
||||
f'<Verze>202501A</Verze>'
|
||||
f'<Odeslano>{odeslano}</Odeslano>'
|
||||
f'<SW_Klienta>MEDICUS_____</SW_Klienta>'
|
||||
f'</Zprava>'
|
||||
f'</NacistLekovyZaznamLekarDotaz>'
|
||||
'</soapenv:Body>'
|
||||
'</soapenv:Envelope>'
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Type": 'text/xml; charset="UTF-8"',
|
||||
"SOAPAction": '"NacistLekovyZaznam"',
|
||||
"User-Agent": "Medicus"
|
||||
}
|
||||
|
||||
print(f"Pacient: {PRIJMENI} {JMENA}, nar. {DATUM_NAROZENI}")
|
||||
print(f"Doklad: {DRUH_DOKLADU} {CISLO_DOKLADU}")
|
||||
print(f"Volám: {ENDPOINT}\n")
|
||||
|
||||
resp = sess.post(ENDPOINT, data=soap_body.encode("utf-8"), headers=headers, timeout=15)
|
||||
print(f"HTTP {resp.status_code} | {len(resp.content)} bytů\n")
|
||||
print(resp.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
nacist_lekovy_zaznam_s_obcankou()
|
||||
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
Reimport vsech XML souboru z xml_archive do MySQL — bez volani API.
|
||||
|
||||
Pouziti:
|
||||
python reimport_z_xml.py # vsechna XML z 2026-04-11
|
||||
python reimport_z_xml.py xml_archive/2026-04-11 # konkretni adresar
|
||||
python reimport_z_xml.py xml_archive # vsechny podadresare rekurzivne
|
||||
"""
|
||||
|
||||
import sys
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from datetime import date
|
||||
import fdb
|
||||
import pymysql
|
||||
import pymysql.cursors
|
||||
|
||||
# Windows konzole
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(errors="replace")
|
||||
|
||||
# ── Konfigurace ───────────────────────────────────────────────────────────────
|
||||
XML_ADRESAR = Path(__file__).parent / "xml_archive" / "2026-04-11"
|
||||
|
||||
FB = dict(
|
||||
dsn = r"localhost:c:\medicus 3\data\medicus.fdb",
|
||||
user = "SYSDBA",
|
||||
password = "masterkey",
|
||||
charset = "win1250",
|
||||
)
|
||||
|
||||
DB = dict(
|
||||
host = "192.168.1.76",
|
||||
user = "root",
|
||||
password = "Vlado9674+",
|
||||
database = "medicus",
|
||||
charset = "utf8mb4",
|
||||
cursorclass = pymysql.cursors.DictCursor,
|
||||
)
|
||||
|
||||
ICP = "09305001"
|
||||
ODB = "001"
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Nacteni parsovaci logiky z 06UlozitDoMySQL.py
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
"m06", Path(__file__).parent / "06UlozitDoMySQL.py"
|
||||
)
|
||||
_m06 = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(_m06)
|
||||
|
||||
parsuj_xml = _m06.parsuj_xml
|
||||
uloz = _m06.uloz
|
||||
inicializuj_schema = _m06.inicializuj_schema
|
||||
|
||||
|
||||
def nacti_pacienty_z_fb():
|
||||
"""Vrati slovnik {(prijmeni_upper, datnar): idpac} ze vsech pacientu v Medicusu."""
|
||||
conn = fdb.connect(**FB)
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
dnes = date.today().isoformat()
|
||||
cur.execute("""
|
||||
SELECT KAR.IDPAC, KAR.PRIJMENI, KAR.JMENO, KAR.DATNAR
|
||||
FROM KAR
|
||||
WHERE KAR.vyrazen = 'N'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM registr r
|
||||
JOIN icp i ON r.idicp = i.idicp
|
||||
WHERE r.idpac = kar.idpac
|
||||
AND r.datum <= ?
|
||||
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= ?)
|
||||
AND r.priznak IN ('V','D','A')
|
||||
AND i.icp = ?
|
||||
AND i.odb = ?
|
||||
)
|
||||
""", (dnes, dnes, ICP, ODB))
|
||||
result = {}
|
||||
for row in cur.fetchall():
|
||||
idpac, prijmeni, jmeno, datnar = row
|
||||
klic = (prijmeni.strip().upper(), datnar)
|
||||
result[klic] = {"idpac": idpac, "prijmeni": prijmeni.strip(), "jmeno": jmeno.strip(), "datnar": datnar}
|
||||
print(f"Firebird: nacteno {len(result)} registrovanych pacientu")
|
||||
return result
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def upsert_pacient(cur, pac):
|
||||
cur.execute("""
|
||||
INSERT INTO pacient (idpac, prijmeni, jmena, datum_narozeni)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
prijmeni = VALUES(prijmeni),
|
||||
jmena = VALUES(jmena)
|
||||
""", (pac["idpac"], pac["prijmeni"], pac["jmeno"], pac["datnar"]))
|
||||
cur.execute("SELECT id FROM pacient WHERE idpac = %s", (pac["idpac"],))
|
||||
return cur.fetchone()["id"]
|
||||
|
||||
|
||||
def main():
|
||||
# Adresar z argumentu nebo default
|
||||
adresar = Path(sys.argv[1]) if len(sys.argv) > 1 else XML_ADRESAR
|
||||
if not adresar.is_dir():
|
||||
sys.exit(f"Adresar neexistuje: {adresar}")
|
||||
|
||||
# Najdi vsechna XML rekurzivne
|
||||
xml_soubory = sorted(adresar.rglob("*.xml"))
|
||||
if not xml_soubory:
|
||||
sys.exit(f"Zadne XML soubory nalezeny v: {adresar}")
|
||||
print(f"Nalezeno {len(xml_soubory)} XML souboru v: {adresar}")
|
||||
|
||||
# Nacti pacienty z Firebirdu
|
||||
fb_pacienti = nacti_pacienty_z_fb()
|
||||
|
||||
# Pripoj se k MySQL a inicializuj schema
|
||||
conn = pymysql.connect(**DB)
|
||||
try:
|
||||
inicializuj_schema(conn)
|
||||
|
||||
ok = chyba = preskoceno = 0
|
||||
p_celkem = v_celkem = 0
|
||||
|
||||
for i, xml_path in enumerate(xml_soubory, 1):
|
||||
rel = xml_path.relative_to(Path(__file__).parent)
|
||||
try:
|
||||
zprava, predpisy, vydeji, predepisujici, vydavajici = parsuj_xml(xml_path)
|
||||
except Exception as e:
|
||||
print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} CHYBA parsovani: {e}")
|
||||
chyba += 1
|
||||
continue
|
||||
|
||||
# Zjisti prijmeni a datum narozeni z XML odpovedi
|
||||
pac_prijmeni = (zprava.get("pacient_prijmeni") or "").upper()
|
||||
pac_datnar = zprava.get("pacient_datum_narozeni") # string YYYY-MM-DD nebo None
|
||||
|
||||
# Prevod na date objekt pro porovnani s Firebirdem
|
||||
if pac_datnar and isinstance(pac_datnar, str):
|
||||
try:
|
||||
from datetime import datetime
|
||||
pac_datnar_d = datetime.strptime(pac_datnar[:10], "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
pac_datnar_d = None
|
||||
elif hasattr(pac_datnar, "year"):
|
||||
pac_datnar_d = pac_datnar
|
||||
else:
|
||||
pac_datnar_d = None
|
||||
|
||||
klic = (pac_prijmeni, pac_datnar_d)
|
||||
fb_pac = fb_pacienti.get(klic)
|
||||
|
||||
if not fb_pac:
|
||||
# Pacient neni registrovan — uloz bez idpac (bude ignorovan pri hromadnem behu)
|
||||
# Zkus najit v MySQL podle jmena a data
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT id FROM pacient WHERE prijmeni = %s AND datum_narozeni = %s",
|
||||
(zprava.get("pacient_prijmeni"), pac_datnar)
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
pacient_id = row["id"]
|
||||
else:
|
||||
preskoceno += 1
|
||||
print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} PRESKOCENO (neni v registru)")
|
||||
continue
|
||||
else:
|
||||
with conn.cursor() as cur:
|
||||
pacient_id = upsert_pacient(cur, fb_pac)
|
||||
conn.commit()
|
||||
|
||||
try:
|
||||
stats = uloz(conn, zprava, predpisy, vydeji, predepisujici, vydavajici,
|
||||
pacient_id=pacient_id, xml_soubor=str(rel))
|
||||
conn.commit()
|
||||
p_celkem += stats["predpisy_novych"]
|
||||
v_celkem += stats["vydeji_novych"]
|
||||
print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} OK "
|
||||
f"{stats['predpisy_novych']:3}p {stats['vydeji_novych']:3}v")
|
||||
ok += 1
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
print(f"[{i:4}/{len(xml_soubory)}] {xml_path.name:<45} CHYBA ukladani: {e}")
|
||||
chyba += 1
|
||||
|
||||
print()
|
||||
print(f"Hotovo: {ok} OK, {chyba} chyb, {preskoceno} preskoceno")
|
||||
print(f"Celkem vlozeno: {p_celkem} predpisu, {v_celkem} vydejuu")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><NacistLekovyZaznamOdpoved xmlns="http://www.sukl.cz/erp/201912"><Doklad><Pacient><Jmeno><Prijmeni>ADAMEC</Prijmeni><Jmena>PETR</Jmena></Jmeno><DatumNarozeni>1981-01-19</DatumNarozeni></Pacient><PredepisujiciSeznam /><VydavajiciSeznam /><PredpisSeznam /><VydejSeznam /><DuplicitaSeznam /></Doklad><Zprava><ID_Zpravy>CC97F0EA-5522-4D5F-99B3-E187C9421C43</ID_Zpravy><Verze>202501A</Verze><Odeslano>2026-04-11T12:09:14.5490742+02:00</Odeslano><Aplikace>Informační systém eRecept, v. 1.108.2.22758</Aplikace><ID_Podani>F6BDB49A-4974-4134-AD62-645DDE5E6DE4</ID_Podani><Prijato>2026-04-11T12:09:14.3577236+02:00</Prijato></Zprava></NacistLekovyZaznamOdpoved></soap:Body></soap:Envelope>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><NacistLekovyZaznamOdpoved xmlns="http://www.sukl.cz/erp/201912"><Doklad><Pacient><Jmeno><Prijmeni>ADLEROVÁ</Prijmeni><Jmena>IRENA</Jmena></Jmeno><DatumNarozeni>1937-07-05</DatumNarozeni></Pacient><PredepisujiciSeznam><Predepisujici><Lekar><Kod>83607a3f-d44d-42e2-b4eb-57a983ec4d0c</Kod><Jmeno><Prijmeni>Buzalková</Prijmeni><Jmena>Michaela</Jmena></Jmeno></Lekar><ICZ>09305000</ICZ><ICP>09305001</ICP><PZS><Nazev>MUDr. Michaela Buzalková</Nazev><Adresa><NazevUlice>Lovosická</NazevUlice><CisloPopisne>440</CisloPopisne><CisloOrientacni>40</CisloOrientacni><NazevObce>Praha</NazevObce><PSC>19000</PSC></Adresa></PZS><Telefon>266010136</Telefon></Predepisujici><Predepisujici><Lekar><Kod>455cff31-b5ea-460c-ad81-907dec80fe0d</Kod><Jmeno><Prijmeni>Tychka</Prijmeni><Jmena>Viktoriia</Jmena></Jmeno></Lekar><ICP>09246001</ICP><PZS><Nazev>Derma CZ s.r.o.</Nazev><Adresa><NazevUlice>Lovosická</NazevUlice><CisloPopisne>440</CisloPopisne><CisloOrientacni>40</CisloOrientacni><NazevObce>Praha - Prosek</NazevObce><PSC>19000</PSC></Adresa></PZS><Telefon>266 010 225</Telefon></Predepisujici></PredepisujiciSeznam><VydavajiciSeznam><Vydavajici><Lekarnik><Kod>50957296-9aac-4366-8e82-286bbb2079a5</Kod><Jmeno><Prijmeni>Moravcová</Prijmeni><Jmena>Marie</Jmena></Jmeno></Lekarnik><PZS><Nazev>Hrouda CZ s.r.o.</Nazev><Telefon>228229843</Telefon><Adresa><NazevUlice>Chlumčanského</NazevUlice><CisloPopisne>497</CisloPopisne><CisloOrientacni>5</CisloOrientacni><NazevObce>Praha</NazevObce><PSC>18000</PSC></Adresa></PZS></Vydavajici><Vydavajici><Lekarnik><Kod>563bcaba-b5d9-4102-9086-f8cc7399b58f</Kod><Jmeno><Prijmeni>Sokolová</Prijmeni><Jmena>Pavlína</Jmena></Jmeno></Lekarnik><PZS><Nazev>ČESKÁ LÉKÁRNA HOLDING, a.s.</Nazev><Telefon>225574234</Telefon><Adresa><NazevUlice>Vysočanská</NazevUlice><CisloPopisne>242</CisloPopisne><CisloOrientacni>111</CisloOrientacni><NazevObce>Praha</NazevObce><PSC>19000</PSC></Adresa></PZS></Vydavajici></VydavajiciSeznam><PredpisSeznam><Predpis><ID_LP_Predpis>92A0A7AA-DA19-4683-9E4E-7B7038522AEA</ID_LP_Predpis><KodPredepisujiciho>83607a3f-d44d-42e2-b4eb-57a983ec4d0c</KodPredepisujiciho><DatumVystaveni>2026-04-10</DatumVystaveni><Mnozstvi>1</Mnozstvi><Navod>1-0-0</Navod><HVLPReg><Kod>0265451</Kod><ATC>A12AX</ATC><Nazev>CALCICHEW D3 LEMON</Nazev><Forma>TBL MND</Forma><Sila>500MG/400IU</Sila><CestaPodani>POR</CestaPodani><Baleni>60</Baleni></HVLPReg></Predpis><Predpis><ID_LP_Predpis>8082AD1E-69C7-4D28-AF48-271649BB8D91</ID_LP_Predpis><KodPredepisujiciho>455cff31-b5ea-460c-ad81-907dec80fe0d</KodPredepisujiciho><DatumVystaveni>2026-03-17</DatumVystaveni><Mnozstvi>1</Mnozstvi><Navod>2x týdně</Navod><HVLPReg><Kod>0045304</Kod><ATC>D01AE16</ATC><Nazev>LOCERYL</Nazev><Forma>LAC UGC</Forma><Sila>50MG/ML</Sila><CestaPodani>DRM</CestaPodani><Baleni>1X2,5ML I</Baleni></HVLPReg></Predpis></PredpisSeznam><VydejSeznam><Vydej><ID_LP_Vydej>3B60510F-548D-4B2E-9D8F-B46166DD01B4</ID_LP_Vydej><ID_LP_Predpis>92A0A7AA-DA19-4683-9E4E-7B7038522AEA</ID_LP_Predpis><KodVydavajiciho>50957296-9aac-4366-8e82-286bbb2079a5</KodVydavajiciho><DatumVydeje>2026-04-10</DatumVydeje><Mnozstvi>1</Mnozstvi><Navod>1-0-0</Navod><Sarze>12523499</Sarze><HVLPReg><Kod>0265451</Kod><ATC>A12AX</ATC><Nazev>CALCICHEW D3 LEMON</Nazev><Forma>TBL MND</Forma><Sila>500MG/400IU</Sila><CestaPodani>POR</CestaPodani><Baleni>60</Baleni></HVLPReg></Vydej><Vydej><ID_LP_Vydej>4B067971-E978-40D2-A7C3-B48044542BFF</ID_LP_Vydej><ID_LP_Predpis>8082AD1E-69C7-4D28-AF48-271649BB8D91</ID_LP_Predpis><KodVydavajiciho>563bcaba-b5d9-4102-9086-f8cc7399b58f</KodVydavajiciho><DatumVydeje>2026-03-17</DatumVydeje><Mnozstvi>1</Mnozstvi><Navod>2x týdně</Navod><Exspirace>2028-08-31</Exspirace><Sarze>5212458</Sarze><HVLPReg><Kod>0185977</Kod><ATC>D01AE16</ATC><Nazev>LOCERYL</Nazev><Forma>LAC UGC</Forma><Sila>50MG/ML</Sila><CestaPodani>DRM</CestaPodani><Baleni>1X2,5ML II</Baleni></HVLPReg></Vydej></VydejSeznam><DuplicitaSeznam /></Doklad><Zprava><ID_Zpravy>3A657B88-E7F7-4078-9A9A-E693AB1C27F7</ID_Zpravy><Verze>202501A</Verze><Odeslano>2026-04-11T12:09:31.3646059+02:00</Odeslano><Aplikace>Informační systém eRecept, v. 1.108.2.22758</Aplikace><ID_Podani>804C8B7E-6BC8-4388-BEE8-46CF9C824C4F</ID_Podani><Prijato>2026-04-11T12:09:31.1458623+02:00</Prijato></Zprava></NacistLekovyZaznamOdpoved></soap:Body></soap:Envelope>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><NacistLekovyZaznamOdpoved xmlns="http://www.sukl.cz/erp/201912"><Doklad><Pacient><Jmeno><Prijmeni>ALEXOVIČOVÁ</Prijmeni><Jmena>JULIE</Jmena></Jmeno><DatumNarozeni>1978-10-27</DatumNarozeni></Pacient><PredepisujiciSeznam /><VydavajiciSeznam /><PredpisSeznam /><VydejSeznam /><DuplicitaSeznam /></Doklad><Zprava><ID_Zpravy>3D01F477-6BB9-45E0-8063-BECC18F77F5A</ID_Zpravy><Verze>202501A</Verze><Odeslano>2026-04-11T12:10:02.5458887+02:00</Odeslano><Aplikace>Informační systém eRecept, v. 1.108.2.22758</Aplikace><ID_Podani>1805D199-B265-4C10-B011-DE8EBA7C6225</ID_Podani><Prijato>2026-04-11T12:10:02.4017063+02:00</Prijato></Zprava></NacistLekovyZaznamOdpoved></soap:Body></soap:Envelope>
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><NacistLekovyZaznamOdpoved xmlns="http://www.sukl.cz/erp/201912"><Doklad><Pacient><Jmeno><Prijmeni>ANDERLE</Prijmeni><Jmena>PETR</Jmena></Jmeno><DatumNarozeni>1975-02-07</DatumNarozeni></Pacient><PredepisujiciSeznam /><VydavajiciSeznam /><PredpisSeznam /><VydejSeznam /><DuplicitaSeznam /></Doklad><Zprava><ID_Zpravy>4E402F97-C38C-4494-9D38-5561E80D7595</ID_Zpravy><Verze>202501A</Verze><Odeslano>2026-04-11T12:10:18.6298555+02:00</Odeslano><Aplikace>Informační systém eRecept, v. 1.108.2.22758</Aplikace><ID_Podani>8A79C9BF-BBCF-42FE-9931-584D97D81FF1</ID_Podani><Prijato>2026-04-11T12:10:18.3798287+02:00</Prijato></Zprava></NacistLekovyZaznamOdpoved></soap:Body></soap:Envelope>
|
||||
+1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><NacistLekovyZaznamOdpoved xmlns="http://www.sukl.cz/erp/201912"><Doklad><Pacient><Jmeno><Prijmeni>BILLOUZ</Prijmeni><Jmena>DAVID MAURICE JEAN</Jmena></Jmeno><DatumNarozeni>1973-12-04</DatumNarozeni></Pacient><PredepisujiciSeznam><Predepisujici><Lekar><Kod>53484083-fabc-4d6a-97ae-12cf43d80783</Kod><Jmeno><Prijmeni>Buzalková</Prijmeni><Jmena>Michaela</Jmena></Jmeno></Lekar><ICZ>09305000</ICZ><ICP>09305001</ICP><PZS><Nazev>MUDr. Michaela Buzalková</Nazev><Adresa><NazevUlice>Lovosická</NazevUlice><CisloPopisne>440</CisloPopisne><CisloOrientacni>40</CisloOrientacni><NazevObce>Praha</NazevObce><PSC>19000</PSC></Adresa></PZS><Telefon>266010136</Telefon></Predepisujici><Predepisujici><Lekar><Kod>aa587d67-541f-4111-88b6-24741ab770bb</Kod><Jmeno><Prijmeni>Bludovský</Prijmeni><Jmena>Jaroslav</Jmena></Jmeno></Lekar><ICZ>09085000</ICZ><ICP>09085001</ICP><PZS><Nazev>MUDr. Jaroslav Bludovský</Nazev><Adresa><NazevUlice>Lovosická</NazevUlice><CisloPopisne>440</CisloPopisne><CisloOrientacni>40</CisloOrientacni><NazevObce>Praha 9</NazevObce><PSC>19000</PSC></Adresa></PZS><Telefon>266010235</Telefon></Predepisujici></PredepisujiciSeznam><VydavajiciSeznam><Vydavajici><Lekarnik><Kod>0edfa837-89b2-4b6d-b13f-0cdde919524a</Kod><Jmeno><Prijmeni>Škodová</Prijmeni><Jmena>Eva</Jmena></Jmeno></Lekarnik><PZS><Nazev>Poliklinika Prosek a.s.</Nazev><Telefon>266010257</Telefon><Adresa><NazevUlice>Lovosická</NazevUlice><CisloPopisne>440</CisloPopisne><CisloOrientacni>40</CisloOrientacni><NazevObce>Praha</NazevObce><PSC>19000</PSC></Adresa></PZS></Vydavajici><Vydavajici><Lekarnik><Kod>a3278ff1-5f58-446f-a1be-7abbbbe9d3d0</Kod><Jmeno><Prijmeni>Langová</Prijmeni><Jmena>Michaela</Jmena></Jmeno></Lekarnik><PZS><Nazev>BENU Česká republika s.r.o.</Nazev><Telefon>703462221</Telefon><Adresa><NazevUlice>Střelničná</NazevUlice><CisloPopisne>2270</CisloPopisne><CisloOrientacni>46</CisloOrientacni><NazevObce>Praha</NazevObce><PSC>18000</PSC></Adresa></PZS></Vydavajici><Vydavajici><Lekarnik><Kod>5390a32a-191f-403f-ae13-f34b259df0f0</Kod><Jmeno><Prijmeni>Radoňová</Prijmeni><Jmena>Daniela</Jmena></Jmeno></Lekarnik><PZS><Nazev>Poliklinika Prosek a.s.</Nazev><Telefon>266010257</Telefon><Adresa><NazevUlice>Lovosická</NazevUlice><CisloPopisne>440</CisloPopisne><CisloOrientacni>40</CisloOrientacni><NazevObce>Praha 9</NazevObce><PSC>19000</PSC></Adresa></PZS></Vydavajici></VydavajiciSeznam><PredpisSeznam><Predpis><ID_LP_Predpis>95174A02-5182-4B2B-9357-4C900E6D75AC</ID_LP_Predpis><KodPredepisujiciho>53484083-fabc-4d6a-97ae-12cf43d80783</KodPredepisujiciho><DatumVystaveni>2025-12-11</DatumVystaveni><Mnozstvi>1</Mnozstvi><Navod>dle pp max 14 dnů</Navod><HVLPReg><Kod>0262102</Kod><ATC>D07AB02</ATC><Nazev>LOCOID LIPOCREAM 0,1%</Nazev><Forma>CRM</Forma><Sila>1MG/G</Sila><CestaPodani>DRM</CestaPodani><Baleni>1X30G</Baleni></HVLPReg></Predpis><Predpis><ID_LP_Predpis>65518391-85C6-47D6-BD71-B12C2CCB6943</ID_LP_Predpis><KodPredepisujiciho>53484083-fabc-4d6a-97ae-12cf43d80783</KodPredepisujiciho><DatumVystaveni>2025-10-20</DatumVystaveni><Mnozstvi>1</Mnozstvi><Navod>dle návodu</Navod><HVLPReg><Kod>0237830</Kod><ATC>R01AX06</ATC><Nazev>BACTROBAN NASAL</Nazev><Forma>NAS UNG</Forma><Sila>20MG/G</Sila><CestaPodani>NAS</CestaPodani><Baleni>1X3G</Baleni></HVLPReg></Predpis><Predpis><ID_LP_Predpis>BB2DF7E4-18BD-4DB3-8799-DA8E5D555587</ID_LP_Predpis><KodPredepisujiciho>53484083-fabc-4d6a-97ae-12cf43d80783</KodPredepisujiciho><DatumVystaveni>2025-03-03</DatumVystaveni><Mnozstvi>1</Mnozstvi><Navod>1-1-0</Navod><HVLPReg><Kod>0092757</Kod><ATC>R05CB15</ATC><Nazev>ERDOMED</Nazev><Forma>CPS DUR</Forma><Sila>300MG</Sila><CestaPodani>POR</CestaPodani><Baleni>10</Baleni></HVLPReg></Predpis><Predpis><ID_LP_Predpis>7ABDCBD1-A5CC-4188-B9B8-0F8E005DADC7</ID_LP_Predpis><KodPredepisujiciho>aa587d67-541f-4111-88b6-24741ab770bb</KodPredepisujiciho><DatumVystaveni>2024-11-07</DatumVystaveni><Mnozstvi>1</Mnozstvi><Navod>uk</Navod><IPLP><PostupPripravy>Sol. alum. acet.-tartarici 4,0
|
||||
Sol. acidi borici 1,5% 12,0
|
||||
Spir. vini diluti ad 20,0
|
||||
MDS. ušní kapky 3x denně po 2 do obou uší</PostupPripravy><Nazev>UK Alsol</Nazev></IPLP></Predpis></PredpisSeznam><VydejSeznam><Vydej><ID_LP_Vydej>935C6BD3-ED97-49F5-BDAB-257BE1B699A4</ID_LP_Vydej><ID_LP_Predpis>95174A02-5182-4B2B-9357-4C900E6D75AC</ID_LP_Predpis><KodVydavajiciho>0edfa837-89b2-4b6d-b13f-0cdde919524a</KodVydavajiciho><DatumVydeje>2025-12-11</DatumVydeje><Mnozstvi>1</Mnozstvi><Navod>dle pp max 14 dnů</Navod><Sarze>24L17A</Sarze><Pozn>Neuvedena</Pozn><HVLPReg><Kod>0262102</Kod><ATC>D07AB02</ATC><Nazev>LOCOID LIPOCREAM 0,1%</Nazev><Forma>CRM</Forma><Sila>1MG/G</Sila><CestaPodani>DRM</CestaPodani><Baleni>1X30G</Baleni></HVLPReg></Vydej><Vydej><ID_LP_Vydej>BF36F2FB-B19A-47BB-B189-2E04ECE51CBD</ID_LP_Vydej><ID_LP_Predpis>65518391-85C6-47D6-BD71-B12C2CCB6943</ID_LP_Predpis><KodVydavajiciho>a3278ff1-5f58-446f-a1be-7abbbbe9d3d0</KodVydavajiciho><DatumVydeje>2025-10-20</DatumVydeje><Mnozstvi>1</Mnozstvi><Navod>dle návodu</Navod><Sarze>AY7Y</Sarze><HVLPReg><Kod>0237830</Kod><ATC>R01AX06</ATC><Nazev>BACTROBAN NASAL</Nazev><Forma>NAS UNG</Forma><Sila>20MG/G</Sila><CestaPodani>NAS</CestaPodani><Baleni>1X3G</Baleni></HVLPReg></Vydej><Vydej><ID_LP_Vydej>5076BA10-3929-48F0-8A86-6F934F33051E</ID_LP_Vydej><ID_LP_Predpis>BB2DF7E4-18BD-4DB3-8799-DA8E5D555587</ID_LP_Predpis><KodVydavajiciho>5390a32a-191f-403f-ae13-f34b259df0f0</KodVydavajiciho><DatumVydeje>2025-03-03</DatumVydeje><Mnozstvi>1</Mnozstvi><Navod>1-1-0</Navod><Sarze>0599</Sarze><Pozn>Neuvedena</Pozn><HVLPReg><Kod>0092757</Kod><ATC>R05CB15</ATC><Nazev>ERDOMED</Nazev><Forma>CPS DUR</Forma><Sila>300MG</Sila><CestaPodani>POR</CestaPodani><Baleni>10</Baleni></HVLPReg></Vydej><Vydej><ID_LP_Vydej>5EEC1651-1A7B-45DF-B14E-F000775EF6E3</ID_LP_Vydej><ID_LP_Predpis>7ABDCBD1-A5CC-4188-B9B8-0F8E005DADC7</ID_LP_Predpis><KodVydavajiciho>5390a32a-191f-403f-ae13-f34b259df0f0</KodVydavajiciho><DatumVydeje>2024-11-07</DatumVydeje><Mnozstvi>1</Mnozstvi><Navod>uk</Navod><Sarze>20241106037</Sarze><Pozn>Neuvedena</Pozn><IPLP><KodVZP>0001002</KodVZP><Nazev>Ušní kapky se sol.alumin.acetico tartar.</Nazev><Slozka><Mnozstvi>1</Mnozstvi><Jednotka>ks</Jednotka><Nazev>PO Příprava čistých obalů sk.2.1.1</Nazev><Sarze>NA</Sarze></Slozka><Slozka><Mnozstvi>1</Mnozstvi><Jednotka>ks</Jednotka><Nazev>Lékovka hnědá s pipetou 25ml**</Nazev><Sarze>F280324</Sarze></Slozka><Slozka><Mnozstvi>1</Mnozstvi><Jednotka>ks</Jednotka><Nazev>TP Dispenzace sk.1.11</Nazev><Sarze>NA</Sarze></Slozka><Slozka><Mnozstvi>20</Mnozstvi><Jednotka>ks</Jednotka><Nazev>Taxa laborum ...</Nazev><Sarze>NA</Sarze></Slozka><Slozka><Mnozstvi>20</Mnozstvi><Jednotka>g</Jednotka><Nazev>Ušní kapky se sol. alumin.acet.tartar.MEZIPRODUKT</Nazev><Sarze>20241106029</Sarze></Slozka><Slozka><Mnozstvi>1</Mnozstvi><Jednotka>ks</Jednotka><Nazev>SIGNATURA NOVÁ</Nazev><Sarze>260224</Sarze></Slozka></IPLP></Vydej></VydejSeznam><DuplicitaSeznam /></Doklad><Zprava><ID_Zpravy>C610F496-AF68-453E-BDB6-8640AF8E0559</ID_Zpravy><Verze>202501A</Verze><Odeslano>2026-04-11T12:20:25.5419241+02:00</Odeslano><Aplikace>Informační systém eRecept, v. 1.108.2.22758</Aplikace><ID_Podani>734EC9EB-69E6-4114-9A67-202E376837F0</ID_Podani><Prijato>2026-04-11T12:20:25.3192135+02:00</Prijato></Zprava></NacistLekovyZaznamOdpoved></soap:Body></soap:Envelope>
|
||||
+4
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+48
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><NacistLekovyZaznamOdpoved xmlns="http://www.sukl.cz/erp/201912"><Doklad><Pacient><Jmeno><Prijmeni>BUZALKA</Prijmeni><Jmena>VLADIMÍR</Jmena></Jmeno><DatumNarozeni>2003-08-02</DatumNarozeni></Pacient><PredepisujiciSeznam /><VydavajiciSeznam /><PredpisSeznam /><VydejSeznam /><DuplicitaSeznam /></Doklad><Zprava><ID_Zpravy>1C34C759-CF78-4788-A1EE-8C30AFB5A58B</ID_Zpravy><Verze>202501A</Verze><Odeslano>2026-04-11T12:32:58.4738373+02:00</Odeslano><Aplikace>Informační systém eRecept, v. 1.108.2.22758</Aplikace><ID_Podani>2C05838C-658C-4C69-8A5A-BF99D7DE5D7D</ID_Podani><Prijato>2026-04-11T12:32:58.3288862+02:00</Prijato></Zprava></NacistLekovyZaznamOdpoved></soap:Body></soap:Envelope>
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><NacistLekovyZaznamOdpoved xmlns="http://www.sukl.cz/erp/201912"><Doklad><Pacient><Jmeno><Prijmeni>BUZALKOVÁ</Prijmeni><Jmena>MICHAELA</Jmena></Jmeno><DatumNarozeni>2007-09-05</DatumNarozeni></Pacient><PredepisujiciSeznam><Predepisujici><Lekar><Kod>cae47885-9e51-468f-86b1-3a9d9bb3447b</Kod><Jmeno><Prijmeni>Buzalka</Prijmeni><Jmena>Vladimír</Jmena></Jmeno></Lekar><ICZ>09305000</ICZ><ICP>09305001</ICP><PZS><Nazev>MUDr. Michaela Buzalková</Nazev><Adresa><NazevUlice>Lovosická</NazevUlice><CisloPopisne>440</CisloPopisne><CisloOrientacni>40</CisloOrientacni><NazevObce>Praha</NazevObce><PSC>19000</PSC></Adresa></PZS><Telefon>266010136</Telefon></Predepisujici></PredepisujiciSeznam><VydavajiciSeznam><Vydavajici><Lekarnik><Kod>3b709dea-bf11-49b0-8897-9de3b57e0fb4</Kod><Jmeno><Prijmeni>Hefjuková</Prijmeni><Jmena>Anna</Jmena></Jmeno></Lekarnik><PZS><Nazev>ČESKÁ LÉKÁRNA HOLDING, a.s.</Nazev><Telefon>225574455</Telefon><Adresa><NazevUlice>Kostelecká</NazevUlice><CisloPopisne>822</CisloPopisne><CisloOrientacni>75</CisloOrientacni><NazevObce>Praha</NazevObce><PSC>19600</PSC></Adresa></PZS></Vydavajici></VydavajiciSeznam><PredpisSeznam><Predpis><ID_LP_Predpis>48721638-DFCC-47E5-B89F-FC184D60E8B8</ID_LP_Predpis><KodPredepisujiciho>cae47885-9e51-468f-86b1-3a9d9bb3447b</KodPredepisujiciho><DatumVystaveni>2026-03-13</DatumVystaveni><Mnozstvi>1</Mnozstvi><Navod>při bolesti</Navod><HVLPReg><Kod>0017187</Kod><ATC>M01AX17</ATC><Nazev>NIMESIL</Nazev><Forma>POR GRA SUS</Forma><Sila>100MG</Sila><CestaPodani>POR</CestaPodani><Baleni>30</Baleni></HVLPReg></Predpis><Predpis><ID_LP_Predpis>BCF0AA03-2603-46D7-B571-FD205186BEE5</ID_LP_Predpis><KodPredepisujiciho>cae47885-9e51-468f-86b1-3a9d9bb3447b</KodPredepisujiciho><DatumVystaveni>2026-03-13</DatumVystaveni><Mnozstvi>1</Mnozstvi><Navod>1-0-0</Navod><HVLPReg><Kod>0155685</Kod><ATC>R06AE07</ATC><Nazev>ZYRTEC</Nazev><Forma>TBL FLM</Forma><Sila>10MG</Sila><CestaPodani>POR</CestaPodani><Baleni>50</Baleni></HVLPReg></Predpis></PredpisSeznam><VydejSeznam><Vydej><ID_LP_Vydej>CAB3980B-D50B-4FB3-9BDF-D838C1A870F8</ID_LP_Vydej><ID_LP_Predpis>48721638-DFCC-47E5-B89F-FC184D60E8B8</ID_LP_Predpis><KodVydavajiciho>3b709dea-bf11-49b0-8897-9de3b57e0fb4</KodVydavajiciho><DatumVydeje>2026-03-13</DatumVydeje><Mnozstvi>1</Mnozstvi><Navod>při bolesti</Navod><Exspirace>2028-11-30</Exspirace><Sarze>54970</Sarze><HVLPReg><Kod>0017187</Kod><ATC>M01AX17</ATC><Nazev>NIMESIL</Nazev><Forma>POR GRA SUS</Forma><Sila>100MG</Sila><CestaPodani>POR</CestaPodani><Baleni>30</Baleni></HVLPReg></Vydej><Vydej><ID_LP_Vydej>E409ADF5-2F2A-4A55-858C-CC0CE97D0731</ID_LP_Vydej><ID_LP_Predpis>BCF0AA03-2603-46D7-B571-FD205186BEE5</ID_LP_Predpis><KodVydavajiciho>3b709dea-bf11-49b0-8897-9de3b57e0fb4</KodVydavajiciho><DatumVydeje>2026-03-13</DatumVydeje><Mnozstvi>1</Mnozstvi><Navod>1-0-0</Navod><Exspirace>2028-09-30</Exspirace><Sarze>381624</Sarze><HVLPReg><Kod>0155685</Kod><ATC>R06AE07</ATC><Nazev>ZYRTEC</Nazev><Forma>TBL FLM</Forma><Sila>10MG</Sila><CestaPodani>POR</CestaPodani><Baleni>50</Baleni></HVLPReg></Vydej></VydejSeznam><DuplicitaSeznam /></Doklad><Zprava><ID_Zpravy>CA219D0B-89F7-4E74-BD55-55017160D9E3</ID_Zpravy><Verze>202501A</Verze><Odeslano>2026-04-11T12:33:31.67695+02:00</Odeslano><Aplikace>Informační systém eRecept, v. 1.108.2.22758</Aplikace><ID_Podani>1192D3D7-6B00-414C-A2B6-0CB0F2EFC4F2</ID_Podani><Prijato>2026-04-11T12:33:31.5176122+02:00</Prijato></Zprava></NacistLekovyZaznamOdpoved></soap:Body></soap:Envelope>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+22
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user