notebookvb

This commit is contained in:
2026-04-11 11:56:54 +02:00
parent 24635b955d
commit c2d94b2362
10 changed files with 507 additions and 35 deletions
+84 -19
View File
@@ -1,12 +1,14 @@
""" """
Nacte odpoved lekoveho zaznamu (XML) a ulozi ji do MySQL. Nacte odpoved lekoveho zaznamu (XML) a ulozi ji do MySQL.
Schema: zprava / predpis / predpis_slozka / vydej / vydej_slozka Schema: pacient / zprava / predpis / predpis_slozka / vydej / vydej_slozka / predepisujici / vydavajici
Typy a delky presne dle XSD (Cuer2Schema.xsd + CuerSchema.xsd, verze 202501A) Typy a delky presne dle XSD (Cuer2Schema.xsd + CuerSchema.xsd, verze 202501A)
Spusteni: Spusteni (jednorazova inicializace + import jednoho XML):
python 06UlozitDoMySQL.py python 06UlozitDoMySQL.py
nebo: nebo:
python 06UlozitDoMySQL.py cesta/k/odpoved.xml python 06UlozitDoMySQL.py cesta/k/odpoved.xml
Pro hromadne stazeni vsech pacientu pouzij 07StahnoutVsechny.py.
""" """
import sys import sys
@@ -47,25 +49,49 @@ def ts(s):
# ── DDL ─────────────────────────────────────────────────────────────────────── # ── DDL ───────────────────────────────────────────────────────────────────────
DDL_TABULKY = [
# smazat v opacnem poradi kvuli FK # Pouziva se ve vytvor_schema() (DROP + CREATE) pro ciste spusteni
DDL_DROP = [
"DROP TABLE IF EXISTS predpis_slozka", "DROP TABLE IF EXISTS predpis_slozka",
"DROP TABLE IF EXISTS vydej_slozka", "DROP TABLE IF EXISTS vydej_slozka",
"DROP TABLE IF EXISTS vydej", "DROP TABLE IF EXISTS vydej",
"DROP TABLE IF EXISTS predpis", "DROP TABLE IF EXISTS predpis",
"DROP TABLE IF EXISTS zprava", "DROP TABLE IF EXISTS zprava",
"DROP TABLE IF EXISTS pacient",
"DROP TABLE IF EXISTS predepisujici", "DROP TABLE IF EXISTS predepisujici",
"DROP TABLE IF EXISTS vydavajici", "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 ────────────────────────────────────────────────────────────────
# zprava_odpoved_type + zprava_type: # zprava_odpoved_type + zprava_type:
# ID_Zpravy CHAR(36), Verze, Odeslano dateTime # ID_Zpravy CHAR(36), Verze, Odeslano dateTime
# Aplikace(512), ID_Podani CHAR(36), Prijato dateTime # Aplikace(512), ID_Podani CHAR(36), Prijato dateTime
# jmeno_osoby_type: Prijmeni(35), Jmena(24) # 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 zprava ( CREATE TABLE IF NOT EXISTS zprava (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
id_zpravy CHAR(36) NOT NULL UNIQUE, id_zpravy CHAR(36) NOT NULL UNIQUE,
pacient_id INT,
verze VARCHAR(20), verze VARCHAR(20),
odeslano DATETIME, odeslano DATETIME,
aplikace VARCHAR(512), aplikace VARCHAR(512),
@@ -74,7 +100,10 @@ DDL_TABULKY = [
pacient_prijmeni VARCHAR(35), pacient_prijmeni VARCHAR(35),
pacient_jmena VARCHAR(24), pacient_jmena VARCHAR(24),
pacient_datum_narozeni DATE, pacient_datum_narozeni DATE,
xml_soubor VARCHAR(255),
stazeno DATETIME DEFAULT CURRENT_TIMESTAMP, 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) INDEX idx_pacient (pacient_prijmeni, pacient_datum_narozeni)
) ENGINE=InnoDB ) ENGINE=InnoDB
""", """,
@@ -90,7 +119,7 @@ DDL_TABULKY = [
# inn_predpis_type: Nazev(200) — nejdelsi nazev mezi typy # inn_predpis_type: Nazev(200) — nejdelsi nazev mezi typy
# iplp_predpis_type: PostupPripravy(4000), Nazev(146), CestaPodani(15), Forma(27) # iplp_predpis_type: PostupPripravy(4000), Nazev(146), CestaPodani(15), Forma(27)
""" """
CREATE TABLE predpis ( CREATE TABLE IF NOT EXISTS predpis (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
zprava_id INT NOT NULL, zprava_id INT NOT NULL,
id_lp_predpis CHAR(36) NOT NULL UNIQUE, id_lp_predpis CHAR(36) NOT NULL UNIQUE,
@@ -120,7 +149,7 @@ DDL_TABULKY = [
# Mnozstvi DECIMAL(15,6) NOT NULL, Jednotka ENUM('g','ks') NOT NULL, # Mnozstvi DECIMAL(15,6) NOT NULL, Jednotka ENUM('g','ks') NOT NULL,
# Nazev(200) NOT NULL, Surovina CHAR(7)?, HVLPReg CHAR(7)? # Nazev(200) NOT NULL, Surovina CHAR(7)?, HVLPReg CHAR(7)?
""" """
CREATE TABLE predpis_slozka ( CREATE TABLE IF NOT EXISTS predpis_slozka (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
predpis_id INT NOT NULL, predpis_id INT NOT NULL,
mnozstvi DECIMAL(15,6) NOT NULL, mnozstvi DECIMAL(15,6) NOT NULL,
@@ -140,7 +169,7 @@ DDL_TABULKY = [
# lek — vzdy jen jeden z: HVLPReg / HVLPNereg / IPLP # lek — vzdy jen jeden z: HVLPReg / HVLPNereg / IPLP
# iplp_type (vydej): KodVZP CHAR(7)?, PostupPripravy(4000), Nazev(146), CestaPodani(15) # iplp_type (vydej): KodVZP CHAR(7)?, PostupPripravy(4000), Nazev(146), CestaPodani(15)
""" """
CREATE TABLE vydej ( CREATE TABLE IF NOT EXISTS vydej (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
zprava_id INT NOT NULL, zprava_id INT NOT NULL,
id_lp_vydej CHAR(36) NOT NULL UNIQUE, id_lp_vydej CHAR(36) NOT NULL UNIQUE,
@@ -175,7 +204,7 @@ DDL_TABULKY = [
# Nazev(200) NOT NULL, HrazenoZP DECIMAL(9,2)?, # Nazev(200) NOT NULL, HrazenoZP DECIMAL(9,2)?,
# Surovina CHAR(7)?, HVLPReg CHAR(7)? # Surovina CHAR(7)?, HVLPReg CHAR(7)?
""" """
CREATE TABLE vydej_slozka ( CREATE TABLE IF NOT EXISTS vydej_slozka (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
vydej_id INT NOT NULL, vydej_id INT NOT NULL,
mnozstvi DECIMAL(15,6) NOT NULL, mnozstvi DECIMAL(15,6) NOT NULL,
@@ -197,7 +226,7 @@ DDL_TABULKY = [
# Telefon(20)? # Telefon(20)?
# lekar_kod = predpis.kod_predepisujiciho # lekar_kod = predpis.kod_predepisujiciho
""" """
CREATE TABLE predepisujici ( CREATE TABLE IF NOT EXISTS predepisujici (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
lekar_kod CHAR(36) NOT NULL UNIQUE, lekar_kod CHAR(36) NOT NULL UNIQUE,
prijmeni VARCHAR(35), prijmeni VARCHAR(35),
@@ -220,7 +249,7 @@ DDL_TABULKY = [
# PZS: Nazev(200), Telefon(20)?, Adresa: NazevUlice, CisloPopisne, CisloOrientacni, NazevObce, PSC # PZS: Nazev(200), Telefon(20)?, Adresa: NazevUlice, CisloPopisne, CisloOrientacni, NazevObce, PSC
# lekarnik_kod = vydej.kod_vydavajiciho # lekarnik_kod = vydej.kod_vydavajiciho
""" """
CREATE TABLE vydavajici ( CREATE TABLE IF NOT EXISTS vydavajici (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
lekarnik_kod CHAR(36) NOT NULL UNIQUE, lekarnik_kod CHAR(36) NOT NULL UNIQUE,
prijmeni VARCHAR(35), prijmeni VARCHAR(35),
@@ -236,13 +265,38 @@ DDL_TABULKY = [
def vytvor_schema(conn): def vytvor_schema(conn):
"""DROP + CREATE vsech tabulek. Pouzij pro ciste spusteni / reset dat."""
with conn.cursor() as cur: with conn.cursor() as cur:
for stmt in DDL_TABULKY: for stmt in DDL_DROP:
cur.execute(stmt)
for stmt in DDL_CREATE:
stmt = stmt.strip() stmt = stmt.strip()
if stmt: if stmt:
cur.execute(stmt) cur.execute(stmt)
conn.commit() conn.commit()
print("Schema OK (5 tabulek smazano a vytvoreno znovu)") 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 ───────────────────────────────── # ── parsovani predepisujicich a vydavajicich ─────────────────────────────────
@@ -519,25 +573,36 @@ def _najdi_id(cur, tabulka, sloupec, hodnota):
return row["id"] if row else None return row["id"] if row else None
def uloz(conn, zprava, predpisy, vydeji, predepisujici, vydavajici): 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)
"""
iplp_predpisu = 0 iplp_predpisu = 0
iplp_vydejuu = 0 iplp_vydejuu = 0
with conn.cursor() as cur: with conn.cursor() as cur:
# ── zprava ──────────────────────────────────────────────────────────── # ── zprava ────────────────────────────────────────────────────────────
zprava_row = dict(zprava)
zprava_row["pacient_id"] = pacient_id
zprava_row["xml_soubor"] = xml_soubor
cur.execute(""" cur.execute("""
INSERT INTO zprava INSERT INTO zprava
(id_zpravy, verze, odeslano, aplikace, id_podani, prijato, (id_zpravy, pacient_id, verze, odeslano, aplikace, id_podani, prijato,
pacient_prijmeni, pacient_jmena, pacient_datum_narozeni) pacient_prijmeni, pacient_jmena, pacient_datum_narozeni, xml_soubor)
VALUES VALUES
(%(id_zpravy)s, %(verze)s, %(odeslano)s, %(aplikace)s, (%(id_zpravy)s, %(pacient_id)s, %(verze)s, %(odeslano)s, %(aplikace)s,
%(id_podani)s, %(prijato)s, %(id_podani)s, %(prijato)s,
%(pacient_prijmeni)s, %(pacient_jmena)s, %(pacient_datum_narozeni)s) %(pacient_prijmeni)s, %(pacient_jmena)s, %(pacient_datum_narozeni)s,
%(xml_soubor)s)
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
prijato = VALUES(prijato), prijato = VALUES(prijato),
xml_soubor = COALESCE(VALUES(xml_soubor), xml_soubor),
stazeno = CURRENT_TIMESTAMP stazeno = CURRENT_TIMESTAMP
""", zprava) """, zprava_row)
zprava_id = _najdi_id(cur, "zprava", "id_zpravy", zprava["id_zpravy"]) zprava_id = _najdi_id(cur, "zprava", "id_zpravy", zprava["id_zpravy"])
print(f" zprava id={zprava_id} ({zprava['id_zpravy']})") print(f" zprava id={zprava_id} ({zprava['id_zpravy']})")
@@ -0,0 +1,420 @@
"""
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
Logika poctu mesicu:
- prvni stazeni pacienta → 60 mesicu (maximum)
- opakowane 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 math
import sys
import time
import uuid
# Windows konzole — nahrad neunikatni znaky misto padu
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(errors="replace")
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
# ── 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_MEZI_VOLANIMI = 15 # 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,
)
# ── XML archiv ────────────────────────────────────────────────────────────────
XML_DIR = Path(__file__).parent / "xml_archive"
# ── 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):
"""
Vraci seznam dict {idpac, prijmeni, jmeno, datnar}.
prijmeni_filtr: list prijmeni (napr. ['Buzalka', 'Buzalkova']) nebo None = vsichni.
"""
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):
"""
Vlozi nebo aktualizuje pacienta v tabulce pacient.
Vraci MySQL id radku.
"""
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):
"""Vraci datetime posledniho stazeni, nebo None pro noveho pacienta."""
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):
"""60 pro prvni stazeni, jinak delta v mesicich + 1 (prekryv)."""
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)
# ── SOAP volani ───────────────────────────────────────────────────────────────
def extrahuj_soap_fault(xml_text):
"""
Pokud XML obsahuje SOAP Fault, vraci text chyby (str).
Pokud je odpoved v poradku, vraci 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"
# Zkontroluj SOAP Fault
fault = body.find(f"{{{NS_SOAP}}}Fault")
if fault is None:
fault = body.find("Fault") # nektery server posila bez namespace
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
# Zkontroluj, ze odpoved je spravneho typu (NacistLekovyZaznamOdpoved)
odpoved = body.find(f"{{{NS_SUKL}}}NacistLekovyZaznamOdpoved")
if odpoved is None:
# Neznamy format — vrat prvni tag jako info
first = list(body)
tag = first[0].tag if first else "prazdne Body"
return f"Neocekavana odpoved: {tag}"
return None # vse OK
except Exception as e:
return f"Chyba pri parsovani odpovedi: {e}"
def uloz_poznamku(conn, pacient_id, poznamka):
"""Ulozi nebo vymaze poznamku (chybu) u pacienta."""
with conn.cursor() as cur:
cur.execute(
"UPDATE pacient SET poznamka = %s WHERE id = %s",
(poznamka, pacient_id)
)
conn.commit()
def nacti_lekovy_zaznam(sess, prijmeni, jmena, datum_narozeni, pocet_mesicu):
"""
Zavola NacistLekovyZaznam pro jednoho pacienta.
Vraci xml_text (str). Vyhazuje RuntimeError pri HTTP chybe.
"""
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):
"""
Ulozi XML do xml_archive/YYYY-MM-DD/{Prijmeni}_{Jmena}_{datnar}.xml
Vraci relativni cestu (str) vuci adresari skriptu.
"""
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",
help="Filtr prijmeni oddelena carkou, napr: Buzalka,Buzalkova,Kusinova",
default=None,
)
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()
prijmeni_filtr = None
if args.prijmeni:
prijmeni_filtr = [p.strip() for p in args.prijmeni.split(",")]
print(f"Filtr prijmeni: {prijmeni_filtr}")
# 1. Nacti pacienty z Medicusu
print("Nacitam pacienty z Medicusu (Firebird)...")
pacienti = nacti_pacienty(prijmeni_filtr)
print(f" Nalezeno: {len(pacienti)} pacientu")
if args.offset:
pacienti = pacienti[args.offset:]
print(f" Preskoceno: {args.offset} (--offset {args.offset})")
if args.limit:
pacienti = pacienti[:args.limit]
print(f" Omezeno na: {len(pacienti)} (--limit {args.limit})")
if not pacienti:
print("Zadni pacienti — konec.")
return
# 2. Pripoj se k MySQL, over schema (CREATE IF NOT EXISTS)
print("Pripojuji k MySQL...")
conn = pymysql.connect(**DB)
inicializuj_schema(conn)
# 3. Priprav SOAP session (sdilena pro vsechny pacienty)
sess = Session()
sess.mount("https://", Pkcs12Adapter(
pkcs12_filename=PFX_FILE,
pkcs12_password=PFX_PASS,
))
sess.auth = (API_USER, API_PASS)
dnes_str = date.today().isoformat()
ok = 0
chyby = 0
celkem = len(pacienti)
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)
print(f"\n[{i}/{celkem}] {prijmeni} {jmena} (*{datnar_str})")
# UPSERT pacienta, zjisti kdy byl naposledy stazen
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)
print(f" Stahuju {pocet_mesicu} mesicu "
f"(posledni stazeni: {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]
print(f" CHYBA API: {zprava_chyby}")
uloz_poznamku(conn, pacient_id, zprava_chyby)
chyby += 1
continue
# Detekuj SOAP Fault v odpovedi (HTTP 200 ale chyba uvnitr)
soap_fault = extrahuj_soap_fault(xml_text)
if soap_fault:
print(f" SOAP FAULT: {soap_fault}")
uloz_poznamku(conn, pacient_id, soap_fault[:400])
chyby += 1
continue
# Uloz XML na disk
xml_soubor = uloz_xml_na_disk(xml_text, prijmeni, jmena, datnar_str, dnes_str)
xml_path = Path(__file__).parent / xml_soubor
print(f" XML: {xml_soubor} ({xml_path.stat().st_size // 1024} KB)")
# Parsuj + uloz do MySQL
try:
zprava_d, predpisy, vydeji, predepisujici, vydavajici = parsuj_xml(xml_path)
uloz(conn, zprava_d, predpisy, vydeji, predepisujici, vydavajici,
pacient_id=pacient_id, xml_soubor=xml_soubor)
uloz_poznamku(conn, pacient_id, None) # vymaz predchozi chybu
ok += 1
except Exception as e:
zprava_chyby = str(e)[:400]
print(f" CHYBA parsovani/ulozeni: {zprava_chyby}")
uloz_poznamku(conn, pacient_id, zprava_chyby)
chyby += 1
# Pauza mezi volanimi API (neplati po poslednim pacientovi)
if i < celkem:
print(f" Cekam {PAUZA_MEZI_VOLANIMI}s ...")
time.sleep(PAUZA_MEZI_VOLANIMI)
finally:
conn.close()
sess.close()
print(f"\n{'=' * 55}")
print(f"Hotovo: {ok} OK | {chyby} chyb | celkem {celkem} pacientu")
if __name__ == "__main__":
main()
File diff suppressed because one or more lines are too long