From 3c3a12d5a64eec5f31e4e4c72c76a608beecc6e7 Mon Sep 17 00:00:00 2001 From: Vladimir Buzalka Date: Wed, 29 Apr 2026 06:24:11 +0200 Subject: [PATCH] notebookvb --- Insurance/KdoJeLékař/NOTES.md | 138 +++++++++--------- Insurance/KdoJeLékař/_test_no_odb.py | 42 ++++++ Insurance/KdoJeLékař/_test_temp.py | 8 +- Insurance/KdoJeLékař/kdojelekar_tydenni.py | 155 +++++++++++++++++++++ Knihovny/medicus_db.py | 18 ++- Knihovny/vzpb2b_client.py | 90 ++++++++++++ 6 files changed, 374 insertions(+), 77 deletions(-) create mode 100644 Insurance/KdoJeLékař/_test_no_odb.py create mode 100644 Insurance/KdoJeLékař/kdojelekar_tydenni.py diff --git a/Insurance/KdoJeLékař/NOTES.md b/Insurance/KdoJeLékař/NOTES.md index 8bef44c..2310c90 100644 --- a/Insurance/KdoJeLékař/NOTES.md +++ b/Insurance/KdoJeLékař/NOTES.md @@ -6,6 +6,30 @@ Zjistit pro pacienty z Medicus DB, kdo je jejich registrující **praktický lé --- +## Stav k 29. 4. 2026 — hotovo + +- Certifikát ✅, tabulky ✅, produkční skript ✅ +- Připraveno ke spuštění — přepnout `TEST_MODE = False` + +--- + +## Soubory v tomto adresáři + +| Soubor | Popis | +|--------|-------| +| `kdojelekar_tydenni.py` | Produkční skript — batch všech pacientů, ukládá do MySQL | +| `_test_temp.py` | Testovací skript — dotaz na jedno RC, výpis XML + parsovaný výsledek | +| `_test_no_odb.py` | Test bez filtru odborností — sloužil k ověření struktury odpovědi | + +--- + +## Certifikát + +**`u:\ordinaceprojekt\Insurance\Certificates\picka.pfx`** / heslo **`Vlado7309208104+`** +Ověřeno 29. 4. 2026 (HTTP 200). Stejný certifikát používá i `StavPojisteni\zkontroluj_a_odesli_zlomy.py`. + +--- + ## VZP B2B služba: `RegistracePojistencePZSB2B` ### Endpoint (produkce) @@ -14,89 +38,71 @@ https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/RegistracePojistencePZSB2B ``` ### Autentizace -mTLS — klientský certifikát `.pfx` od **I.CA** (První certifikační autorita). -Stejný mechanismus jako u `stavPojisteniB2B`. - -### SOAP požadavek -```xml - - 7309208104 - 2026-04-28 - - 001 - 002 - 014 - - -``` - -XML namespace odpovědi: `http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1` +mTLS — klientský certifikát `.pfx`, stejný mechanismus jako u `stavPojisteniB2B`. ### Struktura odpovědi -Pro každou odbornost vrátí jeden `` element: +Pro každou odbornost kde má pacient lékaře vrátí jeden `` element. +Pokud lékař není, VZP element vynechá — skript ukládá placeholder řádek s `ma_lekare=0`. -| XML tag | Popis | -|---------|-------| -| `ICZ` | IČZ zdravotnického zařízení | -| `ICP` | IČP lékaře | -| `nazevICP` | Jméno lékaře | -| `nazevSZZ` | Název zdravotnického zařízení | -| `zdravotniPojistovna/kod` | Kód pojišťovny pacienta | -| `zdravotniPojistovna/zkratka` | Zkratka pojišťovny | -| `odbornost/kod` | Kód odbornosti (001/002/014) | -| `odbornost/nazev` | Název odbornosti | -| `datumRegistrace` | Datum registrace u tohoto lékaře | -| `datumZahajeni` | Začátek registrace | -| `datumUkonceni` | Konec registrace (null = stále aktivní) | -| `stavVyrizeniPozadavku` | Stavový kód odpovědi VZP | +| XML tag | Uloženo jako | Popis | +|---------|-------------|-------| +| `ICZ` | `ICZ` | IČZ zdravotnického zařízení | +| `ICP` | `ICP` | IČP lékaře | +| `nazevICP` | `nazev_lekare` | Název pracoviště | +| `nazevSZZ` | `nazev_zzz` | Jméno lékaře | +| `zdravotniPojistovna/kod` | `poj_kod` | Kód pojišťovny pacienta | +| `zdravotniPojistovna/zkratka` | `poj_zkratka` | Zkratka pojišťovny | +| `odbornost/kod` | `kod_odbornosti` | Kód odbornosti (001/002/014) | +| `datumRegistrace` | `datum_registrace` | Kdy pacient podepsal registraci | +| `datumZahajeni` | `datum_zahajeni` | Od kdy registrace platí u VZP | +| `datumUkonceni` | `datum_ukonceni` | Do kdy (3000-01-01 = bez konce) | +| `stavVyrizeniPozadavku` | `stav_vyrizeni` | Stavový kód odpovědi VZP | -Pokud pacient nemá v dané odbornosti registrovaného lékaře, VZP vrátí prázdný seznam — je třeba uložit placeholder řádek (viz vzor v `u:\insurance\02.2 Testík.py`, funkce `upsert_rows`, negativní cesta). +**Poznámka k parsování:** VZP vrací pro každý nalezený záznam dva `` elementy — +vnější (s ICZ/ICP/jménem) a vnořený subelement (jen kód+název). Parser používá +`findall(".//seznamOdbornosti/odbornost")` který zachytí jen vnější. --- -## Zdrojový kód pro inspiraci +## MySQL tabulky -Vše je hotové v `u:\insurance\`, stačí přenést a adaptovat: +### `vzp_registrace_lekari` +Jeden řádek na `(rc, k_datu, kod_odbornosti)`. UNIQUE klíč = `(rc, k_datu, kod_odbornosti)`. +Historie se hromadí — každý týdenní běh přidá nové řádky. -| Soubor | Co obsahuje | -|--------|-------------| -| `u:\insurance\02 testík.py` | Hlavní logika: `build_envelope`, `parse_registrace`, `upsert_rows`, batch smyčka přes Medicus KAR | -| `u:\insurance\02.2 Testík.py` | Vylepšená verze — ukládá i negativní výsledky (pacient bez lékaře) | -| `u:\insurance\functions.py` | `get_medicus_connection()`, `get_mysql_connection()` — připojení k Firebird a MySQL | -| `u:\insurance\knihovny\vzpb2b_client.py` | Třída `VZPB2BClient` — obecný mTLS klient pro VZP B2B | +### `vzp_registrace_raw` +Jeden řádek na `(rc, k_datu)` — celé raw XML odpovědi. +Slouží k případnému přepočtu bez opakování API dotazů. UNIQUE klíč = `(rc, k_datu)`. --- -## Certifikáty — stav k 28. 4. 2026 +## Produkční skript `kdojelekar_tydenni.py` -| Soubor | Platnost | Stav | -|--------|----------|------| -| `u:\insurance\Certificates\MBcert.pfx` | do 22. 1. 2026 | **EXPIROVAL** | -| `u:\insurance\10 Tests\MBcert.pfx` | do 22. 1. 2026 | **EXPIROVAL** | -| `u:\insurance\Certificates\ICAPublicMBdo16JAN2027.pfx` | do cca. 16. 1. 2027 | Platný, ale **neznáme heslo** — zjistit! | -| `u:\ordinaceprojekt\Insurance\Certificates\MBQualifiedCert.pfx` | do 16. 1. 2027 | Platný, ale **VZP odmítá** (kvalifikovaný certifikát, ne komerční) | -| `u:\ordinaceprojekt\Insurance\Certificates\picka.pfx` | neznámá | Neznáme heslo | -| `u:\insurance\Certificates\picka.pfx` | neznámá | Neznáme heslo | +### Konfigurace (začátek souboru) +| Proměnná | Výchozí | Popis | +|----------|---------|-------| +| `API_PAUSE` | `2` | Sekundy mezi VZP dotazy | +| `TEST_MODE` | `True` | False = produkční běh | +| `ODBORNOSTI` | `["001","002","014"]` | Dotazované odbornosti | -### Klíčový problém k vyřešení jako první -**`ICAPublicMBdo16JAN2027.pfx` je pravděpodobně správný certifikát** (název naznačuje I.CA, správná CA pro VZP B2B, platný do 2027). Vyzkoušená hesla nepasují: -- `Vlado7309208104++`, `vlado7309208104++`, `7309208104`, `Vlado9674+`, `masterkey`, `""` +### Logika +1. Načte aktivně registrované pacienty z Medicus (přesný select dle SELECTS.md, IČP 09305001) +2. V produkčním běhu přeskočí pacienty, kteří už mají záznam v `vzp_registrace_raw` pro dnešní datum — **resumovatelný běh** +3. Pro každého pacienta zavolá VZP B2B, uloží raw XML + parsované záznamy +4. Placeholdery pro odbornosti bez lékaře ukládá s `ma_lekare=0` -→ **Zjistit heslo** pod kterým byl exportován (nebo exportovat znovu z Windows certstore). - ---- - -## Testovací skript - -`u:\ordinaceprojekt\Insurance\KdoJeLékař\_test_temp.py` -Dotáže se na RC `7309208104`, odbornosti 001+002+014, vypíše raw XML i parsovaný výsledek bez zápisu do DB. -Aktuálně nastavený na `MBQualifiedCert.pfx` — po zjištění hesla přepnout na správný certifikát. +### Knihovny +- `Knihovny/vzpb2b_client.py` → metody `registrace_lekare()` a `parse_registrace_lekare()` +- `Knihovny/medicus_db.py` → `get_active_registered_patients()` (opraveno 29. 4. 2026) +- `Knihovny/mysql_db.py` → `connect_mysql()` --- ## Plán dalšího postupu -1. **Zjistit heslo k `ICAPublicMBdo16JAN2027.pfx`** (nebo exportovat nový .pfx z Windows certstore) -2. Ověřit funkčnost — spustit `_test_temp.py` s funkčním certifikátem -3. Vytvořit produkční skript v tomto adresáři — batch zpracování všech pacientů z Medicus KAR -4. Navrhnout cílovou MySQL tabulku `vzp_registrace_lekari` (nebo přidat sloupce do existující `vzp_registrace`) +1. ~~Certifikát~~ — vyřešeno, `picka.pfx` / `Vlado7309208104+` +2. ~~Ověřit funkčnost~~ — hotovo, HTTP 200 s daty +3. ~~Produkční skript~~ — hotovo, `kdojelekar_tydenni.py` +4. ~~MySQL tabulky~~ — hotovo, `vzp_registrace_lekari` + `vzp_registrace_raw` +5. Naplánovat týdenní spouštění (Windows Task Scheduler nebo Claude schedule) +6. Zvážit detekci změn lékaře (analogie zlomů u StavPojisteni) — zatím není v plánu diff --git a/Insurance/KdoJeLékař/_test_no_odb.py b/Insurance/KdoJeLékař/_test_no_odb.py new file mode 100644 index 0000000..3941dd5 --- /dev/null +++ b/Insurance/KdoJeLékař/_test_no_odb.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import sys, requests +sys.stdout.reconfigure(encoding='utf-8') +from requests_pkcs12 import Pkcs12Adapter +from datetime import date +import xml.etree.ElementTree as ET + +ENDPOINT = "https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/RegistracePojistencePZSB2B" +PFX_PATH = r"u:\ordinaceprojekt\Insurance\Certificates\picka.pfx" +PFX_PASS = "Vlado7309208104+" +NS = { + "soap": "http://schemas.xmlsoap.org/soap/envelope/", + "rp": "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1", +} + +envelope = """ + + + + 7309208104 + 2026-04-29 + + +""" + +session = requests.Session() +session.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_PATH, pkcs12_password=PFX_PASS)) +resp = session.post(ENDPOINT, data=envelope.encode("utf-8"), + headers={"Content-Type": "text/xml; charset=utf-8", "SOAPAction": "process"}, + timeout=30, verify=True) + +print(f"HTTP: {resp.status_code}") +root = ET.fromstring(resp.text) +items = root.findall(".//rp:odbornost", NS) +print(f"Pocet odbornosti: {len(items)}") +for i, it in enumerate(items): + print(f"\n--- odbornost #{i+1} ---") + print(ET.tostring(it, encoding="unicode")) + +st = root.find(".//rp:stavVyrizeniPozadavku", NS) +print(f"stav: {st.text if st is not None else '?'}") diff --git a/Insurance/KdoJeLékař/_test_temp.py b/Insurance/KdoJeLékař/_test_temp.py index 159fa10..84150f8 100644 --- a/Insurance/KdoJeLékař/_test_temp.py +++ b/Insurance/KdoJeLékař/_test_temp.py @@ -9,26 +9,22 @@ import xml.etree.ElementTree as ET from datetime import date ENDPOINT = "https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/RegistracePojistencePZSB2B" -PFX_PATH = r"u:\ordinaceprojekt\Insurance\Certificates\MBQualifiedCert.pfx" -PFX_PASS = "Vlado7309208104++" +PFX_PATH = r"u:\ordinaceprojekt\Insurance\Certificates\picka.pfx" +PFX_PASS = "Vlado7309208104+" RC = "7309208104" K_DATU = date.today().isoformat() -ODBORNOSTI = ["001", "002", "014"] # VPL, gynekologie, stomatologie - NS = { "soap": "http://schemas.xmlsoap.org/soap/envelope/", "rp": "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1", } -odb_xml = "".join(f"{k}" for k in ODBORNOSTI) envelope = f""" {RC} {K_DATU} - {odb_xml} """ diff --git a/Insurance/KdoJeLékař/kdojelekar_tydenni.py b/Insurance/KdoJeLékař/kdojelekar_tydenni.py new file mode 100644 index 0000000..8c82944 --- /dev/null +++ b/Insurance/KdoJeLékař/kdojelekar_tydenni.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +kdojelekar_tydenni.py +====================== +Projde aktivně registrované pacienty z Medicus a pro každého zjistí +registrujícího lékaře u VZP (odbornosti 001, 002, 014). +Výsledky uloží do MySQL tabulky vzp_registrace_lekari. + +Spouštět týdně. Mezi dotazy 2s prodleva (API_DELAY). + +TEST_MODE = True → zpracuje jen 3 náhodné pacienty, bez zápisu do DB. +TEST_MODE = False → produkční běh, zapíše vše. +""" + +import sys +import time +import random +from pathlib import Path +from datetime import date + +PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent +sys.path.insert(0, str(PROJECT_ROOT)) + +from Knihovny.vzpb2b_client import VZPB2BClient +from Knihovny.mysql_db import connect_mysql +from Knihovny.medicus_db import get_medicus_db + +# ── KONFIGURACE ─────────────────────────────────────────────────────────────── + +API_PAUSE = 2 # sekundy mezi VZP dotazy + +TEST_MODE = True # False = produkční běh + +PFX_PATH = Path(__file__).resolve().parent.parent / "Certificates" / "picka.pfx" +PFX_PASS = "Vlado7309208104+" +ODBORNOSTI = ["001", "002", "014"] # VPL, gynekologie, stomatologie + +TODAY = date.today() + +# ── INIT ────────────────────────────────────────────────────────────────────── + +vzp = VZPB2BClient("prod", str(PFX_PATH), PFX_PASS) +mysql = connect_mysql() + +# ── PACIENTI Z MEDICUS ──────────────────────────────────────────────────────── + +medicus = get_medicus_db() +pacienti = medicus.get_active_registered_patients(as_dict=True) +medicus.close() + +print(f"Aktivně registrovaných pacientů: {len(pacienti)}") + +if TEST_MODE: + pacienti = random.sample(pacienti, min(3, len(pacienti))) + print(f"TEST MODE — zpracuji {len(pacienti)} náhodné pacienty, BEZ zápisu do DB\n") +else: + # Načti RC která už dnes mají uložený raw XML — ty přeskočíme + with mysql.cursor() as cur: + cur.execute("SELECT rc FROM vzp_registrace_raw WHERE k_datu = %s", (TODAY,)) + hotove = {row[0] for row in cur.fetchall()} + pacienti = [p for p in pacienti if (p.get("rodcis") or "").strip() not in hotove] + print(f"PRODUKČNÍ běh — k_datu={TODAY}, zbývá zpracovat: {len(pacienti)} pacientů" + f" ({len(hotove)} již hotovo dnes)\n") + +# ── BATCH ───────────────────────────────────────────────────────────────────── + +call_count = 0 + +for i, pac in enumerate(pacienti): + rc = (pac.get("rodcis") or "").strip() + prijmeni = (pac.get("prijmeni") or "").strip() + jmeno = (pac.get("jmeno") or "").strip() + + if not rc: + continue + + if call_count > 0: + time.sleep(API_PAUSE) + call_count += 1 + + print(f"[{i+1}/{len(pacienti)}] {prijmeni} {jmeno} ({rc}) ...", end=" ", flush=True) + + try: + xml = vzp.registrace_lekare(rc=rc, k_datu=TODAY.isoformat(), odbornosti=ODBORNOSTI) + zaznamy = vzp.parse_registrace_lekare(xml) + except Exception as e: + print(f"CHYBA: {e}") + continue + + print(f"{len(zaznamy)} lékař(ů)") + + for z in zaznamy: + print(f" {z['kod_odbornosti']}: {z['nazev_lekare']} / {z['nazev_zzz']}" + f" [{z['datum_zahajeni']} – {z['datum_ukonceni']}]") + + # Pokud VZP nevrátila žádného lékaře pro některou odbornost, uložíme placeholder + nalezene_kody = {z["kod_odbornosti"] for z in zaznamy} + for kod in ODBORNOSTI: + if kod not in nalezene_kody: + zaznamy.append({ + "ma_lekare": False, + "kod_odbornosti": kod, + "nazev_odbornosti": None, + "ICZ": None, "ICP": None, + "nazev_lekare": None, "nazev_zzz": None, + "poj_kod": None, "poj_zkratka": None, + "datum_registrace": None, "datum_zahajeni": None, "datum_ukonceni": None, + "stav_vyrizeni": None, + }) + print(f" {kod}: (žádný lékař)") + + if TEST_MODE: + continue + + with mysql.cursor() as cur: + cur.execute(""" + INSERT INTO vzp_registrace_raw (rc, k_datu, raw_xml) + VALUES (%s, %s, %s) + ON DUPLICATE KEY UPDATE raw_xml=VALUES(raw_xml) + """, (rc, TODAY, xml)) + + with mysql.cursor() as cur: + for z in zaznamy: + cur.execute(""" + INSERT INTO vzp_registrace_lekari + (rc, prijmeni, jmeno, k_datu, kod_odbornosti, ma_lekare, + ICZ, ICP, nazev_lekare, nazev_zzz, + poj_kod, poj_zkratka, + datum_registrace, datum_zahajeni, datum_ukonceni, + stav_vyrizeni) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) + ON DUPLICATE KEY UPDATE + prijmeni=VALUES(prijmeni), jmeno=VALUES(jmeno), + ma_lekare=VALUES(ma_lekare), + ICZ=VALUES(ICZ), ICP=VALUES(ICP), + nazev_lekare=VALUES(nazev_lekare), nazev_zzz=VALUES(nazev_zzz), + poj_kod=VALUES(poj_kod), poj_zkratka=VALUES(poj_zkratka), + datum_registrace=VALUES(datum_registrace), + datum_zahajeni=VALUES(datum_zahajeni), + datum_ukonceni=VALUES(datum_ukonceni), + stav_vyrizeni=VALUES(stav_vyrizeni) + """, ( + rc, prijmeni, jmeno, TODAY, z["kod_odbornosti"], 1 if z["ma_lekare"] else 0, + z["ICZ"], z["ICP"], z["nazev_lekare"], z["nazev_zzz"], + z["poj_kod"], z["poj_zkratka"], + z["datum_registrace"], z["datum_zahajeni"], z["datum_ukonceni"], + z["stav_vyrizeni"], + )) + +print(f"\nHotovo. VZP dotazů: {call_count}") +if TEST_MODE: + print("(TEST MODE — nic nebylo zapsáno do DB)") + +mysql.close() diff --git a/Knihovny/medicus_db.py b/Knihovny/medicus_db.py index 6ca2fb6..6c58171 100644 --- a/Knihovny/medicus_db.py +++ b/Knihovny/medicus_db.py @@ -70,13 +70,21 @@ class MedicusDB: kar.prijmeni, kar.jmeno, kar.poj - FROM registr - JOIN kar ON registr.idpac = kar.idpac - WHERE registr.datum_zruseni IS NULL - AND registr.priznak IN ('A','D','V') + FROM kar + WHERE kar.vyrazen = 'N' AND kar.rodcis IS NOT NULL AND kar.rodcis <> '' - AND kar.vyrazen <> 'A' + AND EXISTS ( + SELECT r.id FROM registr r + JOIN icp i ON r.idicp = i.idicp + WHERE r.idpac = kar.idpac + AND r.datum <= CURRENT_DATE + AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= CURRENT_DATE) + AND r.priznak IN ('V','D','A') + AND i.icp = '09305001' + AND i.odb = '001' + ) + ORDER BY kar.prijmeni, kar.rodcis """ if as_dict: return self.query_dict(sql) diff --git a/Knihovny/vzpb2b_client.py b/Knihovny/vzpb2b_client.py index a8ea67f..1d31f2b 100644 --- a/Knihovny/vzpb2b_client.py +++ b/Knihovny/vzpb2b_client.py @@ -162,6 +162,96 @@ class VZPB2BClient: print("HTTP:", resp.status_code) return resp.text + def registrace_lekare(self, rc: str, k_datu: str = None, + odbornosti: list = None) -> str: + """ + Calls RegistracePojistencePZSB2B — vrátí registrující lékaře pojištěnce. + odbornosti: seznam kódů, např. ["001","002","014"]. None = bez filtru (vrátí vše). + """ + service = "RegistracePojistencePZSB2B" + endpoint = self._build_endpoint(service) + + if not k_datu: + k_datu = date.today().isoformat() + + ns = "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1" + odb_xml = "" + if odbornosti: + kody = "".join(f"{k}" + for k in odbornosti) + odb_xml = f"{kody}" + + soap = f""" + + + + {rc} + {k_datu} + {odb_xml} + + +""" + + resp = self.session.post( + endpoint, + data=soap.encode("utf-8"), + headers={"Content-Type": "text/xml; charset=utf-8", "SOAPAction": "process"}, + timeout=30, + ) + return resp.text + + def parse_registrace_lekare(self, xml_text: str) -> list[dict]: + """ + Parsuje odpověď RegistracePojistencePZSB2B. + Vrátí seznam diktů — jeden na odbornost (i prázdné = ma_lekare=False). + """ + import xml.etree.ElementTree as ET + + NS = { + "soap": "http://schemas.xmlsoap.org/soap/envelope/", + "rp": "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1", + } + + root = ET.fromstring(xml_text) + stav_el = root.find(".//rp:stavVyrizeniPozadavku", NS) + stav_vyrizeni = stav_el.text.strip() if stav_el is not None and stav_el.text else None + + # Jen vnější elementy (ty s ICZ), ne vnořené subelementy + results = [] + for it in root.findall(".//rp:seznamOdbornosti/rp:odbornost", NS): + def g(tag): + el = it.find(f"rp:{tag}", NS) + return el.text.strip() if el is not None and el.text else None + + odb = it.find("rp:odbornost", NS) + + if odb is not None: + # Záznam s lékařem + kod = odb.find("rp:kod", NS) + naz = odb.find("rp:nazev", NS) + poj = it.find("rp:zdravotniPojistovna", NS) + results.append({ + "ma_lekare": True, + "kod_odbornosti": kod.text.strip() if kod is not None and kod.text else None, + "nazev_odbornosti": naz.text.strip() if naz is not None and naz.text else None, + "ICZ": g("ICZ"), + "ICP": g("ICP"), + "nazev_lekare": g("nazevICP"), + "nazev_zzz": g("nazevSZZ"), + "poj_kod": poj.find("rp:kod", NS).text.strip() if poj is not None and poj.find("rp:kod", NS) is not None else None, + "poj_zkratka": poj.find("rp:zkratka", NS).text.strip() if poj is not None and poj.find("rp:zkratka", NS) is not None else None, + "datum_registrace": g("datumRegistrace"), + "datum_zahajeni": g("datumZahajeni"), + "datum_ukonceni": g("datumUkonceni"), + "stav_vyrizeni": stav_vyrizeni, + }) + else: + # Prázdný placeholder — pacient nemá lékaře v této odbornosti + # (VZP vrací element bez ICZ/ICP — ignorujeme, zaznamená skript sám) + pass + + return results + def parse_stav_pojisteni(self, xml_text: str): """ Parses stavPojisteniB2B SOAP response into a Python dict.