notebookvb
This commit is contained in:
@@ -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`
|
## VZP B2B služba: `RegistracePojistencePZSB2B`
|
||||||
|
|
||||||
### Endpoint (produkce)
|
### Endpoint (produkce)
|
||||||
@@ -14,89 +38,71 @@ https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/RegistracePojistencePZSB2B
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Autentizace
|
### Autentizace
|
||||||
mTLS — klientský certifikát `.pfx` od **I.CA** (První certifikační autorita).
|
mTLS — klientský certifikát `.pfx`, stejný mechanismus jako u `stavPojisteniB2B`.
|
||||||
Stejný mechanismus jako u `stavPojisteniB2B`.
|
|
||||||
|
|
||||||
### SOAP požadavek
|
|
||||||
```xml
|
|
||||||
<registracePojistencePZSB2B xmlns="http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1">
|
|
||||||
<cisloPojistence>7309208104</cisloPojistence>
|
|
||||||
<kDatu>2026-04-28</kDatu>
|
|
||||||
<seznamOdbornosti>
|
|
||||||
<kodOdbornosti>001</kodOdbornosti> <!-- VPL = praktický lékař pro dospělé -->
|
|
||||||
<kodOdbornosti>002</kodOdbornosti> <!-- gynekologie a porodnictví -->
|
|
||||||
<kodOdbornosti>014</kodOdbornosti> <!-- stomatologie -->
|
|
||||||
</seznamOdbornosti>
|
|
||||||
</registracePojistencePZSB2B>
|
|
||||||
```
|
|
||||||
|
|
||||||
XML namespace odpovědi: `http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1`
|
|
||||||
|
|
||||||
### Struktura odpovědi
|
### Struktura odpovědi
|
||||||
Pro každou odbornost vrátí jeden `<odbornost>` element:
|
Pro každou odbornost kde má pacient lékaře vrátí jeden `<odbornost>` element.
|
||||||
|
Pokud lékař není, VZP element vynechá — skript ukládá placeholder řádek s `ma_lekare=0`.
|
||||||
|
|
||||||
| XML tag | Popis |
|
| XML tag | Uloženo jako | Popis |
|
||||||
|---------|-------|
|
|---------|-------------|-------|
|
||||||
| `ICZ` | IČZ zdravotnického zařízení |
|
| `ICZ` | `ICZ` | IČZ zdravotnického zařízení |
|
||||||
| `ICP` | IČP lékaře |
|
| `ICP` | `ICP` | IČP lékaře |
|
||||||
| `nazevICP` | Jméno lékaře |
|
| `nazevICP` | `nazev_lekare` | Název pracoviště |
|
||||||
| `nazevSZZ` | Název zdravotnického zařízení |
|
| `nazevSZZ` | `nazev_zzz` | Jméno lékaře |
|
||||||
| `zdravotniPojistovna/kod` | Kód pojišťovny pacienta |
|
| `zdravotniPojistovna/kod` | `poj_kod` | Kód pojišťovny pacienta |
|
||||||
| `zdravotniPojistovna/zkratka` | Zkratka pojišťovny |
|
| `zdravotniPojistovna/zkratka` | `poj_zkratka` | Zkratka pojišťovny |
|
||||||
| `odbornost/kod` | Kód odbornosti (001/002/014) |
|
| `odbornost/kod` | `kod_odbornosti` | Kód odbornosti (001/002/014) |
|
||||||
| `odbornost/nazev` | Název odbornosti |
|
| `datumRegistrace` | `datum_registrace` | Kdy pacient podepsal registraci |
|
||||||
| `datumRegistrace` | Datum registrace u tohoto lékaře |
|
| `datumZahajeni` | `datum_zahajeni` | Od kdy registrace platí u VZP |
|
||||||
| `datumZahajeni` | Začátek registrace |
|
| `datumUkonceni` | `datum_ukonceni` | Do kdy (3000-01-01 = bez konce) |
|
||||||
| `datumUkonceni` | Konec registrace (null = stále aktivní) |
|
| `stavVyrizeniPozadavku` | `stav_vyrizeni` | Stavový kód odpovědi VZP |
|
||||||
| `stavVyrizeniPozadavku` | 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 `<odbornost>` 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 |
|
### `vzp_registrace_raw`
|
||||||
|--------|-------------|
|
Jeden řádek na `(rc, k_datu)` — celé raw XML odpovědi.
|
||||||
| `u:\insurance\02 testík.py` | Hlavní logika: `build_envelope`, `parse_registrace`, `upsert_rows`, batch smyčka přes Medicus KAR |
|
Slouží k případnému přepočtu bez opakování API dotazů. UNIQUE klíč = `(rc, k_datu)`.
|
||||||
| `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 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Certifikáty — stav k 28. 4. 2026
|
## Produkční skript `kdojelekar_tydenni.py`
|
||||||
|
|
||||||
| Soubor | Platnost | Stav |
|
### Konfigurace (začátek souboru)
|
||||||
|--------|----------|------|
|
| Proměnná | Výchozí | Popis |
|
||||||
| `u:\insurance\Certificates\MBcert.pfx` | do 22. 1. 2026 | **EXPIROVAL** |
|
|----------|---------|-------|
|
||||||
| `u:\insurance\10 Tests\MBcert.pfx` | do 22. 1. 2026 | **EXPIROVAL** |
|
| `API_PAUSE` | `2` | Sekundy mezi VZP dotazy |
|
||||||
| `u:\insurance\Certificates\ICAPublicMBdo16JAN2027.pfx` | do cca. 16. 1. 2027 | Platný, ale **neznáme heslo** — zjistit! |
|
| `TEST_MODE` | `True` | False = produkční běh |
|
||||||
| `u:\ordinaceprojekt\Insurance\Certificates\MBQualifiedCert.pfx` | do 16. 1. 2027 | Platný, ale **VZP odmítá** (kvalifikovaný certifikát, ne komerční) |
|
| `ODBORNOSTI` | `["001","002","014"]` | Dotazované odbornosti |
|
||||||
| `u:\ordinaceprojekt\Insurance\Certificates\picka.pfx` | neznámá | Neznáme heslo |
|
|
||||||
| `u:\insurance\Certificates\picka.pfx` | neznámá | Neznáme heslo |
|
|
||||||
|
|
||||||
### Klíčový problém k vyřešení jako první
|
### Logika
|
||||||
**`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í:
|
1. Načte aktivně registrované pacienty z Medicus (přesný select dle SELECTS.md, IČP 09305001)
|
||||||
- `Vlado7309208104++`, `vlado7309208104++`, `7309208104`, `Vlado9674+`, `masterkey`, `""`
|
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).
|
### 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()`
|
||||||
## 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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Plán dalšího postupu
|
## Plán dalšího postupu
|
||||||
|
|
||||||
1. **Zjistit heslo k `ICAPublicMBdo16JAN2027.pfx`** (nebo exportovat nový .pfx z Windows certstore)
|
1. ~~Certifikát~~ — vyřešeno, `picka.pfx` / `Vlado7309208104+`
|
||||||
2. Ověřit funkčnost — spustit `_test_temp.py` s funkčním certifikátem
|
2. ~~Ověřit funkčnost~~ — hotovo, HTTP 200 s daty
|
||||||
3. Vytvořit produkční skript v tomto adresáři — batch zpracování všech pacientů z Medicus KAR
|
3. ~~Produkční skript~~ — hotovo, `kdojelekar_tydenni.py`
|
||||||
4. Navrhnout cílovou MySQL tabulku `vzp_registrace_lekari` (nebo přidat sloupce do existující `vzp_registrace`)
|
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
|
||||||
|
|||||||
@@ -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 = """<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<soap:Body>
|
||||||
|
<ns1:registracePojistencePZSB2B xmlns:ns1="http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1">
|
||||||
|
<ns1:cisloPojistence>7309208104</ns1:cisloPojistence>
|
||||||
|
<ns1:kDatu>2026-04-29</ns1:kDatu>
|
||||||
|
</ns1:registracePojistencePZSB2B>
|
||||||
|
</soap:Body>
|
||||||
|
</soap:Envelope>"""
|
||||||
|
|
||||||
|
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 '?'}")
|
||||||
@@ -9,26 +9,22 @@ import xml.etree.ElementTree as ET
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
ENDPOINT = "https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/RegistracePojistencePZSB2B"
|
ENDPOINT = "https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/RegistracePojistencePZSB2B"
|
||||||
PFX_PATH = r"u:\ordinaceprojekt\Insurance\Certificates\MBQualifiedCert.pfx"
|
PFX_PATH = r"u:\ordinaceprojekt\Insurance\Certificates\picka.pfx"
|
||||||
PFX_PASS = "Vlado7309208104++"
|
PFX_PASS = "Vlado7309208104+"
|
||||||
|
|
||||||
RC = "7309208104"
|
RC = "7309208104"
|
||||||
K_DATU = date.today().isoformat()
|
K_DATU = date.today().isoformat()
|
||||||
ODBORNOSTI = ["001", "002", "014"] # VPL, gynekologie, stomatologie
|
|
||||||
|
|
||||||
NS = {
|
NS = {
|
||||||
"soap": "http://schemas.xmlsoap.org/soap/envelope/",
|
"soap": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
"rp": "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1",
|
"rp": "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1",
|
||||||
}
|
}
|
||||||
|
|
||||||
odb_xml = "".join(f"<ns1:kodOdbornosti>{k}</ns1:kodOdbornosti>" for k in ODBORNOSTI)
|
|
||||||
envelope = f"""<?xml version="1.0" encoding="utf-8"?>
|
envelope = f"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
<soap:Envelope xmlns:soap="{NS['soap']}">
|
<soap:Envelope xmlns:soap="{NS['soap']}">
|
||||||
<soap:Body>
|
<soap:Body>
|
||||||
<ns1:registracePojistencePZSB2B xmlns:ns1="{NS['rp']}">
|
<ns1:registracePojistencePZSB2B xmlns:ns1="{NS['rp']}">
|
||||||
<ns1:cisloPojistence>{RC}</ns1:cisloPojistence>
|
<ns1:cisloPojistence>{RC}</ns1:cisloPojistence>
|
||||||
<ns1:kDatu>{K_DATU}</ns1:kDatu>
|
<ns1:kDatu>{K_DATU}</ns1:kDatu>
|
||||||
<ns1:seznamOdbornosti>{odb_xml}</ns1:seznamOdbornosti>
|
|
||||||
</ns1:registracePojistencePZSB2B>
|
</ns1:registracePojistencePZSB2B>
|
||||||
</soap:Body>
|
</soap:Body>
|
||||||
</soap:Envelope>"""
|
</soap:Envelope>"""
|
||||||
|
|||||||
@@ -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()
|
||||||
+13
-5
@@ -70,13 +70,21 @@ class MedicusDB:
|
|||||||
kar.prijmeni,
|
kar.prijmeni,
|
||||||
kar.jmeno,
|
kar.jmeno,
|
||||||
kar.poj
|
kar.poj
|
||||||
FROM registr
|
FROM kar
|
||||||
JOIN kar ON registr.idpac = kar.idpac
|
WHERE kar.vyrazen = 'N'
|
||||||
WHERE registr.datum_zruseni IS NULL
|
|
||||||
AND registr.priznak IN ('A','D','V')
|
|
||||||
AND kar.rodcis IS NOT NULL
|
AND kar.rodcis IS NOT NULL
|
||||||
AND kar.rodcis <> ''
|
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:
|
if as_dict:
|
||||||
return self.query_dict(sql)
|
return self.query_dict(sql)
|
||||||
|
|||||||
@@ -162,6 +162,96 @@ class VZPB2BClient:
|
|||||||
print("HTTP:", resp.status_code)
|
print("HTTP:", resp.status_code)
|
||||||
return resp.text
|
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"<ns1:kodOdbornosti>{k}</ns1:kodOdbornosti>"
|
||||||
|
for k in odbornosti)
|
||||||
|
odb_xml = f"<ns1:seznamOdbornosti>{kody}</ns1:seznamOdbornosti>"
|
||||||
|
|
||||||
|
soap = f"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<soap:Body>
|
||||||
|
<ns1:registracePojistencePZSB2B xmlns:ns1="{ns}">
|
||||||
|
<ns1:cisloPojistence>{rc}</ns1:cisloPojistence>
|
||||||
|
<ns1:kDatu>{k_datu}</ns1:kDatu>
|
||||||
|
{odb_xml}
|
||||||
|
</ns1:registracePojistencePZSB2B>
|
||||||
|
</soap:Body>
|
||||||
|
</soap:Envelope>"""
|
||||||
|
|
||||||
|
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ší <odbornost> 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):
|
def parse_stav_pojisteni(self, xml_text: str):
|
||||||
"""
|
"""
|
||||||
Parses stavPojisteniB2B SOAP response into a Python dict.
|
Parses stavPojisteniB2B SOAP response into a Python dict.
|
||||||
|
|||||||
Reference in New Issue
Block a user