# Ověření pojistného statusu pacientů (VZP B2B) Systém pro automatické ověřování, zda jsou registrovaní pacienti platně pojištěni u zdravotní pojišťovny. Dotazuje VZP B2B API, ukládá historii do MySQL a generuje PDF reporty o neshodách. --- ## Architektura ``` Medicus Firebird DB (192.168.1.4) └─ get_active_registered_patients() │ ▼ VZPB2BClient.stav_pojisteni() ←─ mTLS (PFX certifikát) prod.b2b.vzp.cz/stavPojisteniB2B │ ▼ MySQL medevio.vzp_stav_pojisteni │ ▼ Reportovací skript └─ binární hledání data zlomu pojištění └─ Jinja2 + WeasyPrint → PDF ``` --- ## Soubory ``` 20 Ověření proti Medicus/ ├── 10 FinalSaveInsuranceStatusScript(R).py ← sběr dat (spouštět denně) ├── 10 Ověření proti medicus.py ← generování PDF reportu ├── insurance_check.log ← log sběru dat └── Output/ └── kontrola_pojisteni_YYYY-MM-DD_HH-MM-SS.pdf ``` ### Sdílené knihovny (`knihovny/`) | Soubor | Třída | Popis | |---|---|---| | `medicus_db.py` | `MedicusDB` | Wrapper pro Firebird připojení k Medicus | | `vzpb2b_client.py` | `VZPB2BClient` | VZP B2B SOAP API klient s mTLS | --- ## Skript 1: FinalSaveInsuranceStatusScript **Soubor:** `10 FinalSaveInsuranceStatusScript(R).py` Inkrementální sběr pojistných stavů. Lze spouštět opakovaně — při druhém spuštění ve stejný den přeskočí již zkontrolované pacienty. ### Postup 1. Načte všechny aktivně registrované pacienty z Medicus (`registr.priznak IN ('A','D','V')`, `kar.vyrazen <> 'A'`) 2. Pro každého pacienta zkontroluje `MAX(k_datu)` v MySQL — pokud je dnešní, přeskočí ho 3. Zavolá VZP B2B `stavPojisteniB2B` SOAP endpoint 4. Uloží XML odpověď + parsovaná data do tabulky `vzp_stav_pojisteni` 5. Mezi dotazy čeká 1,4 s (ochrana proti přetížení VZP API) ### Konfigurace | Proměnná | Hodnota | Popis | |---|---|---| | `HOST` | `192.168.1.4` | Medicus Firebird server | | `DB_PATH` | `c:\Medicus 3\data\MEDICUS.FDB` | Cesta k Firebird databázi | | `PFX_PATH` | `certificates/picka.pfx` | Klientský certifikát pro mTLS | | `PFX_PASSWORD` | — | Heslo k PFX souboru | | `ENV` | `prod` | Prostředí VZP (`prod` / `simu`) | | MySQL host | `192.168.1.76:3306` | MySQL server, databáze `medevio` | ### Logování Výstup jde současně do `insurance_check.log` i na konzoli. Formát: ``` 2026-01-26 08:15:23 [INFO] Loaded 1620 registered patients 2026-01-26 08:15:23 [INFO] Incremental run: 1620 patients to check today 2026-01-26 08:15:25 [INFO] [1/1620] Checking Novák Jan (1234567890) 2026-01-26 08:15:26 [INFO] ✔ OK (VZP) ``` --- ## Skript 2: Ověření proti medicus (report) **Soubor:** `10 Ověření proti medicus.py` Porovná registrované pacienty v Medicus s posledním zaznamenaným pojistným stavem v MySQL. Pacienty s neaktivním pojištěním (`stav != "1"`) vypíše do PDF reportu včetně přesného data zlomu pojištění. ### Postup 1. Načte registrované pacienty z Medicus 2. Z MySQL vezme **poslední** stav pojištění pro každé RC (pomocí `ROW_NUMBER() OVER PARTITION BY rc ORDER BY k_datu DESC`) 3. Vybere "podezřelé" — registrovaní v Medicus, ale stav `!= "1"` 4. Pro každého podezřelého provede **binární hledání** data zlomu: - hledá mezi `MAX(k_datu WHERE stav='1')` a dneškem - výsledek: `insured_to` (poslední den pojištění) a `uninsured_from` (první den bez pojištění) 5. Vygeneruje HTML přes Jinja2 šablonu a převede na PDF přes WeasyPrint 6. PDF otevře automaticky v systémovém prohlížeči ### Šablona a výstup - Šablona: `Templates/vzp_console_report.html` - Fonty: `Templates/fonts/DejaVuSans*.ttf` - Výstup: `Output/kontrola_pojisteni_YYYY-MM-DD_HH-MM-SS.pdf` --- ## Databáze ### Tabulka `vzp_stav_pojisteni` (MySQL, `medevio`) | Sloupec | Typ | Popis | |---|---|---| | `id` | INT AUTO_INCREMENT | Primární klíč | | `rc` | VARCHAR(20) | Rodné číslo pacienta | | `prijmeni` | VARCHAR(100) | Příjmení | | `jmeno` | VARCHAR(100) | Jméno | | `k_datu` | DATE | Datum, ke kterému byl stav zjišťován | | `stav` | VARCHAR(10) | `"1"` = pojištěn, ostatní = problém | | `kod_pojistovny` | VARCHAR(10) | Kód pojišťovny dle VZP odpovědi | | `nazev_pojistovny` | VARCHAR(100) | Název pojišťovny | | `pojisteni_kod` | VARCHAR(20) | Kód druhu pojištění | | `stav_vyrizeni` | INT | `stavVyrizeniPozadavku` ze SOAP odpovědi | | `response_xml` | MEDIUMTEXT | Celá SOAP XML odpověď od VZP | | `created_at` | TIMESTAMP | Čas vložení záznamu | **Indexy:** `idx_rc (rc)`, `idx_rc_k_datu (rc, k_datu)`, `idx_rc_stav (rc, stav)` ### Klíčové SQL dotazy skriptů ```sql -- Inkrementální filtr (skript 1) SELECT MAX(k_datu) AS last_check FROM vzp_stav_pojisteni WHERE rc = ? -- Poslední stav každého pacienta (skript 2) SELECT rc, stav FROM ( SELECT rc, stav, ROW_NUMBER() OVER (PARTITION BY rc ORDER BY k_datu DESC) AS rn FROM vzp_stav_pojisteni ) t WHERE rn = 1 -- Poslední den aktivního pojištění (pro binární hledání) SELECT MAX(k_datu) AS last_insured FROM vzp_stav_pojisteni WHERE rc = ? AND stav = '1' ``` --- ## VZP B2B API ### Třída `VZPB2BClient` **Autentizace:** mTLS — klientský PFX certifikát přes `requests_pkcs12.Pkcs12Adapter` **Metody:** | Metoda | SOAP služba | Popis | |---|---|---| | `stav_pojisteni(rc, k_datu)` | `stavPojisteniB2B` | Ověří pojistný stav pacienta k datu | | `parse_stav_pojisteni(xml)` | — | Parsuje XML odpověď na dict | | `over_prukaz_pojistence(cislo, k_datu)` | `OverPrukazPojistenceB2B` | Ověří průkaz pojištěnce (EHIC) | **Prostředí:** | ENV | URL | |---|---| | `prod` | `https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/stavPojisteniB2B` | | `simu` | `https://simu.b2b.vzp.cz/B2BProxy/HttpProxy/SIMUstavPojisteniB2B` | **Návratová struktura `parse_stav_pojisteni()`:** ```python { "stavVyrizeni": int, # stavVyrizeniPozadavku ze SOAP "stav": str, # "1" = pojištěn "kodPojistovny": str, # např. "111" "nazevPojistovny": str, # např. "VZP" "pojisteniKod": str } ``` --- ## Medicus DB ### Třída `MedicusDB` Připojení: Firebird (`fdb`), `192.168.1.4`, charset `WIN1250`, user `SYSDBA`. **Metoda `get_active_registered_patients()`** — vrací pacienty splňující: - `registr.datum_zruseni IS NULL` — registrace není zrušená - `registr.priznak IN ('A','D','V')` — aktivní typy registrace - `kar.rodcis IS NOT NULL AND kar.rodcis <> ''` — má rodné číslo - `kar.vyrazen <> 'A'` — pacient není vyřazen --- ## Kapacitní odhad | Parametr | Hodnota | |---|---| | Počet pacientů | ~1 600 | | Velikost XML odpovědi | ~500–1 500 B | | Přírůstek dat | ~1,6 MB/den | | Přírůstek řádků | ~1 600/den | | Roční objem | ~580 MB / ~580 000 řádků | | Limit MEDIUMTEXT | 16 MB/řádek — bez problémů | --- ## Spuštění ```bash # 1. Denní sběr dat (ideálně naplánovat přes Task Scheduler) python "10 FinalSaveInsuranceStatusScript(R).py" # 2. Vygenerování PDF reportu (na vyžádání) python "10 Ověření proti medicus.py" ```