Files
Vladimir Buzalka 4c81529718 notebookvb
2026-04-27 07:02:24 +02:00

222 lines
7.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 | ~5001 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"
```