Files
ordinaceprojekt/Recepty/NačteníPředpisuWithClaude/10_StahnoutXML.py
T
Vladimir Buzalka adb84523cd Přidán podprojekt Recepty (eRecept SÚKL)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 07:06:17 +02:00

240 lines
9.0 KiB
Python

"""
Stažení detailu receptů (NacistPredpis) z eRecept SÚKL.
Logika přeskakování:
- Recept je v recept_doklad se stav_terminal = 1 → přeskočit (vydaný / zrušený)
- Recept není v recept_doklad → stáhnout (nový)
- Recept je v recept_doklad se stav_terminal = 0 → stáhnout znovu (dosud nevyzvednutý)
Spuštění:
python 10_StahnoutXML.py # všechny od 2025-01-01
python 10_StahnoutXML.py --od 2026-01-01
python 10_StahnoutXML.py --limit 50 # testování
XML odpovědi se ukládají do xml_archive/YYYY-MM-DD/ERP_KOD.xml
"""
import sys
import time
import uuid
from datetime import datetime, timezone, date
from pathlib import Path
import random
import fdb
import pymysql
import pymysql.cursors
from requests import Session
from requests_pkcs12 import Pkcs12Adapter
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(errors="replace")
# ── Konfigurace eRecept ──────────────────────────────────────────────────────
PFX_FILE = Path(__file__).parent.parent / "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/Lekar"
NAMESPACE = "http://www.sukl.cz/erp/201704"
PAUZA_MIN = 4 # sekund mezi voláními API (minimum)
PAUZA_MAX = 6 # sekund mezi voláními API (maximum)
# ── Konfigurace Firebird ─────────────────────────────────────────────────────
FB_DSN = r'localhost:c:\medicus 3\data\medicus.fdb'
FB_USER = 'SYSDBA'
FB_PASS = 'masterkey'
FB_CHARSET = 'win1250'
# ── Konfigurace MySQL ────────────────────────────────────────────────────────
DB = dict(
host = "192.168.1.76",
user = "root",
password = "Vlado9674+",
database = "medicus",
charset = "utf8mb4",
cursorclass = pymysql.cursors.DictCursor,
)
# ── Adresáře ─────────────────────────────────────────────────────────────────
XML_DIR = Path(__file__).parent / "xml_archive"
# ── Parametry spuštění (uprav zde) ───────────────────────────────────────────
DATUM_OD = "2025-01-01" # recepty od tohoto data
LIMIT = None # max počet receptů ke stažení; None = bez omezení
# ─────────────────────────────────────────────────────────────────────────────
def nacti_terminal_set(mysql_conn):
"""
Vrátí set ERP kódů, které jsou již terminální (vydané / zrušené / expirované).
Jeden dotaz na začátku — pak jen O(1) lookup v Pythonu.
"""
with mysql_conn.cursor() as cur:
cur.execute("SELECT id_dokladu FROM recept_doklad WHERE stav_terminal = 1")
return {row["id_dokladu"] for row in cur.fetchall()}
def nacti_erp_kody(fb_conn, datum_od, limit=None):
"""
Načte unikátní ERP kódy z Firebirdu (recept_epodani.erp) od datum_od.
Vrací list tuplů: (datum, lek, dop, idpac, prijmeni, jmeno, erp_kod)
"""
if limit:
sql = f"SELECT FIRST {int(limit)}"
else:
sql = "SELECT"
sql += """
r.datum, r.lek, r.dop, r.idpac,
TRIM(kar.prijmeni) AS prijmeni, TRIM(kar.jmeno) AS jmeno,
ep.erp
FROM recept r
JOIN recept_epodani ep ON r.id_epodani = ep.id
JOIN kar ON r.idpac = kar.idpac
WHERE r.datum >= ? AND ep.erp IS NOT NULL AND r.STORNO = 'F'
ORDER BY r.datum DESC
"""
cur = fb_conn.cursor()
cur.execute(sql, [datum_od])
rows = cur.fetchall()
cur.close()
# deduplikace dle ERP kódu — jeden recept může mít více léků (řádků)
seen = set()
unique = []
for row in rows:
erp = row[6]
if erp not in seen:
seen.add(erp)
unique.append(row)
return unique
def volej_nacist_predpis(sess, erp_kod):
"""Zavolá NacistPredpis SOAP a vrátí (status_code, response_text)."""
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'<NacteniPredpisuDotaz xmlns="{NAMESPACE}">'
f'<Doklad>'
f'<Pristupujici>'
f'<Uzivatel>{UZIVATEL}</Uzivatel>'
f'<Pracoviste>{PRACOVISTE}</Pracoviste>'
f'</Pristupujici>'
f'<Identifikator>'
f'<ID_Dokladu>{erp_kod}</ID_Dokladu>'
f'</Identifikator>'
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'</NacteniPredpisuDotaz>'
'</soapenv:Body>'
'</soapenv:Envelope>'
)
headers = {
"Content-Type": 'text/xml; charset="UTF-8"',
"SOAPAction": '"NacistPredpis"',
"User-Agent": "Medicus",
}
resp = sess.post(ENDPOINT, data=soap_body.encode("utf-8"), headers=headers, timeout=15)
return resp.status_code, resp.text
def main():
datum_od = DATUM_OD
limit = LIMIT
dnes = date.today().isoformat()
out_dir = XML_DIR / dnes
out_dir.mkdir(parents=True, exist_ok=True)
# ── 1. Načti terminální sadu z MySQL ─────────────────────────────────────
print("Připojuji MySQL...")
mysql = pymysql.connect(**DB)
terminal = nacti_terminal_set(mysql)
mysql.close()
print(f" Terminálních receptů v DB: {len(terminal)}\n")
# ── 2. Načti ERP kódy z Firebirdu ────────────────────────────────────────
print("Připojuji Firebird...")
fb = fdb.connect(dsn=FB_DSN, user=FB_USER, password=FB_PASS, charset=FB_CHARSET)
rows = nacti_erp_kody(fb, datum_od, limit)
fb.close()
print(f" Unikátních ERP kódů v Medicusu (od {datum_od}): {len(rows)}\n")
if not rows:
print("Žádné recepty k zpracování.")
return
# ── 3. Filtruj — přeskoč terminální ──────────────────────────────────────
ke_stazeni = [r for r in rows if r[6] not in terminal]
preskoceno = len(rows) - len(ke_stazeni)
print(f" Přeskočeno (terminální): {preskoceno}")
print(f" Ke stažení: {len(ke_stazeni)}\n")
if not ke_stazeni:
print("Vše je již staženo a terminální. Hotovo.")
return
# ── 4. SOAP session ───────────────────────────────────────────────────────
sess = Session()
sess.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_FILE, pkcs12_password=PFX_PASS))
sess.auth = (API_USER, API_PASS)
ok = 0
chyby = 0
for i, row in enumerate(ke_stazeni, 1):
datum_rec, lek, dop, idpac, prijmeni, jmeno, erp_kod = row
lek_str = f"{lek} {dop}".strip() if dop else str(lek or "").strip()
label = f"{prijmeni} {jmeno}".strip()
print(f"[{i:4d}/{len(ke_stazeni)}] {label:30s} {erp_kod} ", end="", flush=True)
try:
status, text = volej_nacist_predpis(sess, erp_kod)
je_chyba = status != 200 or "<soap:Fault" in text or "Fault>" in text
if not je_chyba:
xml_file = out_dir / f"{erp_kod}.xml"
xml_file.write_text(text, encoding="utf-8")
print(f"OK {len(text.encode()) / 1024:5.1f} KB {lek_str[:40]}")
ok += 1
else:
chyba_short = text[:120].replace("\n", " ")
print(f"CHYBA HTTP {status} {chyba_short}")
xml_file = out_dir / f"{erp_kod}_CHYBA.xml"
xml_file.write_text(text, encoding="utf-8")
chyby += 1
except Exception as e:
print(f"EXCEPTION {e}")
chyby += 1
if i < len(ke_stazeni):
time.sleep(random.uniform(PAUZA_MIN, PAUZA_MAX))
print(f"\nHotovo: {ok} OK, {chyby} chyb, {preskoceno} přeskočeno")
print(f"XML: {out_dir}")
if __name__ == "__main__":
main()