z230
This commit is contained in:
@@ -0,0 +1,133 @@
|
|||||||
|
"""
|
||||||
|
Vytvoří tabulky recept_doklad a recept_plp v MySQL databázi medicus.
|
||||||
|
|
||||||
|
Spuštění:
|
||||||
|
python 09_VytvorTabulky.py
|
||||||
|
|
||||||
|
Tabulky:
|
||||||
|
recept_doklad — jeden řádek na celý recept (ID_Dokladu)
|
||||||
|
recept_plp — jeden řádek na PLP položku (id_lp = predpis.id_lp_predpis)
|
||||||
|
|
||||||
|
Bezpečné opakované spuštění — používá CREATE TABLE IF NOT EXISTS.
|
||||||
|
Neprovádí DROP.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
import pymysql.cursors
|
||||||
|
|
||||||
|
DB = dict(
|
||||||
|
host = "192.168.1.76",
|
||||||
|
user = "root",
|
||||||
|
password = "Vlado9674+",
|
||||||
|
database = "medicus",
|
||||||
|
charset = "utf8mb4",
|
||||||
|
cursorclass = pymysql.cursors.DictCursor,
|
||||||
|
)
|
||||||
|
|
||||||
|
DDL = [
|
||||||
|
|
||||||
|
# ── recept_doklad ─────────────────────────────────────────────────────────
|
||||||
|
# Jeden řádek na celý recept (ID_Dokladu = ERP kód, např. PPIBVF93285E).
|
||||||
|
# Data na úrovni dokladu: stav, platnost, pacient snapshot, předepisující.
|
||||||
|
#
|
||||||
|
# stav_terminal:
|
||||||
|
# 0 = PREDEPSANY nebo CASTECNE_VYDANY → skript má znovu stahovat XML
|
||||||
|
# 1 = PLNE_VYDANY nebo ZRUSENY nebo expirovaný → stahování ukončeno
|
||||||
|
#
|
||||||
|
# xml_soubor: relativní cesta k poslednímu naparsovanému XML souboru
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS recept_doklad (
|
||||||
|
id_dokladu VARCHAR(20) NOT NULL PRIMARY KEY,
|
||||||
|
|
||||||
|
-- stav a platnost
|
||||||
|
stav ENUM(
|
||||||
|
'PREDEPSANY',
|
||||||
|
'CASTECNE_VYDANY',
|
||||||
|
'PLNE_VYDANY',
|
||||||
|
'ZRUSENY'
|
||||||
|
) NOT NULL,
|
||||||
|
stav_terminal TINYINT(1) NOT NULL DEFAULT 0
|
||||||
|
COMMENT '1 = nepotřebuje další stahování',
|
||||||
|
datum_vystaveni DATE NOT NULL,
|
||||||
|
platnost_do DATE,
|
||||||
|
vypis_do DATE COMMENT 'prodloužení platnosti výpisem',
|
||||||
|
akutni TINYINT(1),
|
||||||
|
rodina TINYINT(1) COMMENT 'ad usum proprium',
|
||||||
|
opakovani INT COMMENT 'NULL = není opakovací',
|
||||||
|
druh_pojisteni ENUM('VEREJNE','OSTATNI'),
|
||||||
|
modry_pruh TINYINT(1),
|
||||||
|
pozn VARCHAR(1000),
|
||||||
|
zap_doplatek DECIMAL(10,2) COMMENT 'ZapocitatelnyDoplatekZbyvaDoLimitu',
|
||||||
|
|
||||||
|
-- časové razítko z eReceptu
|
||||||
|
zalozeni DATETIME,
|
||||||
|
zmena DATETIME,
|
||||||
|
|
||||||
|
-- předepisující lékař (FK na existující tabulku z lékového záznamu)
|
||||||
|
lekar_kod CHAR(36),
|
||||||
|
odbornost_kod VARCHAR(10),
|
||||||
|
odbornost_nazev VARCHAR(100),
|
||||||
|
lekar_email VARCHAR(100),
|
||||||
|
|
||||||
|
-- pacient snapshot (hodnoty platné k datu předpisu — mohou se měnit)
|
||||||
|
cp VARCHAR(10) COMMENT 'číslo pojištěnce / RČ',
|
||||||
|
zp_kod CHAR(3),
|
||||||
|
zp_nazev VARCHAR(100),
|
||||||
|
pac_telefon VARCHAR(20),
|
||||||
|
pac_notifikace ENUM('SMS','EMAIL'),
|
||||||
|
pac_pohlavi ENUM('M','Z'),
|
||||||
|
|
||||||
|
-- meta
|
||||||
|
xml_soubor VARCHAR(255) COMMENT 'cesta k poslednímu XML souboru',
|
||||||
|
stazeno DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
COMMENT 'poslední aktualizace záznamu',
|
||||||
|
|
||||||
|
FOREIGN KEY (lekar_kod) REFERENCES predepisujici (lekar_kod)
|
||||||
|
ON DELETE SET NULL,
|
||||||
|
|
||||||
|
INDEX idx_stav (stav),
|
||||||
|
INDEX idx_stav_terminal (stav_terminal),
|
||||||
|
INDEX idx_platnost (platnost_do),
|
||||||
|
INDEX idx_lekar (lekar_kod)
|
||||||
|
) ENGINE=InnoDB COMMENT='Detail receptu (NacistPredpis) — jeden řádek na ID_Dokladu'
|
||||||
|
""",
|
||||||
|
|
||||||
|
# ── recept_plp ────────────────────────────────────────────────────────────
|
||||||
|
# Jeden řádek na PLP položku (jeden lék na receptu).
|
||||||
|
# id_lp = UUID = predpis.id_lp_predpis → přímý JOIN s lékovým záznamem.
|
||||||
|
# Lékové detaily (ATC, název, forma…) jsou záměrně vynechány —
|
||||||
|
# jsou už v tabulce predpis, duplikovat je nemá smysl.
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS recept_plp (
|
||||||
|
id_lp CHAR(36) NOT NULL PRIMARY KEY
|
||||||
|
COMMENT 'UUID PLP = predpis.id_lp_predpis',
|
||||||
|
id_dokladu VARCHAR(20) NOT NULL,
|
||||||
|
|
||||||
|
uhrada ENUM('ZAKLADNI','ZVYSENA','NEHRAZENY'),
|
||||||
|
prekroceni TINYINT(1),
|
||||||
|
|
||||||
|
FOREIGN KEY (id_dokladu) REFERENCES recept_doklad (id_dokladu)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
|
||||||
|
INDEX idx_id_dokladu (id_dokladu)
|
||||||
|
) ENGINE=InnoDB COMMENT='PLP položky receptu — JOIN přes id_lp na predpis.id_lp_predpis'
|
||||||
|
""",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def vytvor_tabulky():
|
||||||
|
conn = pymysql.connect(**DB)
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
for ddl in DDL:
|
||||||
|
tabulka = ddl.strip().split()[5] # CREATE TABLE IF NOT EXISTS <name>
|
||||||
|
cur.execute(ddl)
|
||||||
|
print(f" OK {tabulka}")
|
||||||
|
conn.commit()
|
||||||
|
print("\nHotovo.")
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
vytvor_tabulky()
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
"""
|
||||||
|
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 = 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/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 = 10 # 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
|
||||||
|
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()
|
||||||
Reference in New Issue
Block a user