Compare commits

...

59 Commits

Author SHA1 Message Date
administrator 06b1f87107 z230 2026-05-06 13:24:43 +02:00
administrator 15f70988dc z230 2026-05-06 09:44:08 +02:00
Vladimir Buzalka 0fe37c2434 notebookvb 2026-05-06 07:19:20 +02:00
Vladimir Buzalka 7a4847e1cc notebookvb 2026-05-06 05:42:22 +02:00
Vladimir Buzalka 4f13f075ff notebookvb 2026-05-05 17:33:33 +02:00
administrator a5a4b7c349 z230 2026-05-05 12:55:31 +02:00
administrator 14accd3d78 z230 2026-05-05 09:02:04 +02:00
administrator 4112b5d3d4 z230 2026-05-05 08:38:57 +02:00
Vladimir Buzalka ffb3db1e07 notebookvb 2026-05-04 21:26:44 +02:00
administrator 417cf86b2d z230 2026-05-04 16:39:51 +02:00
administrator 194ac6c62e z230 2026-05-04 12:47:00 +02:00
administrator eed6e192f1 z230 2026-05-04 12:10:10 +02:00
administrator 804dce8794 z230 2026-05-04 08:08:13 +02:00
Vladimir Buzalka 371eed9971 notebookvb 2026-05-03 07:02:22 +02:00
Vladimir Buzalka d013e43d34 notebookvb 2026-05-03 05:51:43 +02:00
Vladimir Buzalka 88602cb406 notebookvb 2026-05-01 09:43:21 +02:00
Vladimir Buzalka 1b904e3da0 notebookvb 2026-04-30 07:09:02 +02:00
administrator 2e929f1d77 z230 2026-04-29 12:33:19 +02:00
administrator b58232b7d4 z230 2026-04-29 08:36:04 +02:00
Vladimir Buzalka daad4adeab notebookvb 2026-04-29 06:55:23 +02:00
Vladimir Buzalka a9c143ba24 notebookvb 2026-04-29 06:51:47 +02:00
Vladimir Buzalka a1b9c93506 notebookvb 2026-04-29 06:41:45 +02:00
Vladimir Buzalka 3c3a12d5a6 notebookvb 2026-04-29 06:24:11 +02:00
administrator 4aee1a05bd z230 2026-04-28 16:53:36 +02:00
administrator b1f246bc54 z230 2026-04-28 16:40:04 +02:00
Vladimir Buzalka 6cff5f1b91 notebookvb 2026-04-28 06:25:20 +02:00
Vladimir Buzalka ef5d837f34 notebookvb 2026-04-27 07:09:32 +02:00
Vladimir Buzalka 4c81529718 notebookvb 2026-04-27 07:02:24 +02:00
michaela.buzalkova c98001ae93 lenovo 2026-04-27 06:52:09 +02:00
michaela.buzalkova 4f3c774469 lenovo 2026-04-26 20:32:17 +02:00
michaela.buzalkova 7ec3fcedea lenovo 2026-04-26 20:27:57 +02:00
michaela.buzalkova 47c4789a06 lenovo 2026-04-26 15:40:48 +02:00
Vladimir Buzalka 1f9d7bbe78 notebookvb 2026-04-26 09:47:47 +02:00
Vladimir Buzalka 2447b4cf8e notebookvb 2026-04-26 08:36:18 +02:00
Vladimir Buzalka 78ed84209c notebookvb 2026-04-26 08:32:14 +02:00
michaela.buzalkova 0bfa9c48e4 lenovo 2026-04-25 12:55:21 +02:00
michaela.buzalkova 718d27aad5 fix: odstranění hardcoded cesty U:\OrdinaceProjekt z CLAUDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 09:24:58 +02:00
Vladimir Buzalka e2c61eddb9 notebookvb 2026-04-25 09:19:49 +02:00
Vladimir Buzalka 9812d48ce9 notebookvb 2026-04-25 09:18:09 +02:00
Vladimir Buzalka c29ff51209 notebookvb 2026-04-25 09:16:24 +02:00
Vladimir Buzalka add3b46223 notebookvb 2026-04-25 09:06:59 +02:00
administrator 5785ceecbc z230 2026-04-24 09:54:59 +02:00
administrator 365fcd16ba z230 2026-04-24 09:51:45 +02:00
Vladimir Buzalka 7a7c35f778 Merge remote-tracking branch 'origin/master' 2026-04-24 06:46:57 +02:00
Vladimir Buzalka 66addefcf8 notebookvb 2026-04-24 06:45:51 +02:00
michaela.buzalkova 4b6e091709 lenovo 2026-04-24 06:00:37 +02:00
michaela.buzalkova bb2973aa6d lenovo 2026-04-24 06:00:26 +02:00
administrator f8b7741f12 reporter 2026-04-23 10:50:12 +02:00
administrator df36516193 z230 2026-04-23 10:23:50 +02:00
administrator 8481a1b6f1 z230 2026-04-21 10:41:29 +02:00
administrator fa201467ad z230 2026-04-21 08:00:14 +02:00
Vladimir Buzalka 9eea870ab6 notebookvb 2026-04-21 07:01:02 +02:00
Vladimir Buzalka c14a4c21b2 notebookvb 2026-04-21 06:45:23 +02:00
Vladimir Buzalka 32aabcbe6d notebookvb 2026-04-20 21:04:56 +02:00
administrator 1f66388064 z230 2026-04-20 15:41:13 +02:00
administrator d0c16e6497 Přidej POSTUP.md — dokumentace VoZP přihlášení a stahování
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 14:40:28 +02:00
administrator bd6272c8d5 Přidej settings.local.json do .gitignore (per-machine konfigurace)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 14:33:40 +02:00
administrator c12f6ca394 z230 2026-04-20 14:33:30 +02:00
administrator e6c740744b Přidání stažených zpráv VoZP (první stažení)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 14:31:27 +02:00
11803 changed files with 77424 additions and 21118 deletions
+1
View File
@@ -10,6 +10,7 @@ __pycache__/
# Claude Code
.claude/worktrees/
.claude/settings.local.json
# Certifikáty (soukromé klíče - nikdy do gitu!)
**/*.pfx
+17 -1
View File
@@ -1,3 +1,19 @@
# OrdinaceProjekt
Paměť projektu je v `.claude/memory/` — přečti ji na začátku každé konverzace.
## DŮLEŽITÉ — pracovní adresář
Hlavní projekt je **adresář obsahující tento soubor CLAUDE.md** (kořen projektu OrdinaceProjekt).
Výsledné soubory (skripty, knihovny, data) vždy ukládej do tohoto kořenového adresáře nebo jeho podadresářů.
Worktree (`.claude/worktrees/*`) slouží jen pro interní práci Claude, ne jako výstup.
## Přečti na začátku každé konverzace
Každý adresář se skriptem má vlastní `NOTES.md` s technickými detaily. Přečti relevantní NOTES.md podle toho, čeho se konverzace týká.
## Přehled skriptů
| Skript | Adresář | Popis |
|--------|---------|-------|
| `stahni_str8ts.py` | `SběrDatRůzné/DailyStr8ts/` | Stahuje daily Str8ts puzzle jako PDF, odesílá emailem — viz [NOTES.md](SběrDatRůzné/DailyStr8ts/NOTES.md) |
| `10_StahnoutXML.py`, `11_ParseXML.py` | `Recepty/NačteníPředpisuWithClaude/` | Pipeline pro stahování detailů receptů z eRecept SÚKL — viz [NacistPredpis_DOKUMENTACE.md](Recepty/NačteníPředpisuWithClaude/NacistPredpis_DOKUMENTACE.md) |
+108
View File
@@ -0,0 +1,108 @@
# KdoJeLékař — poznámky k vývoji
## Cíl
Zjistit pro pacienty z Medicus DB, kdo je jejich registrující **praktický lékař (001)**, **gynekolog (002)** a **stomatolog (014)** — dotazem na VZP B2B portá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)
```
https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/RegistracePojistencePZSB2B
```
### Autentizace
mTLS — klientský certifikát `.pfx`, stejný mechanismus jako u `stavPojisteniB2B`.
### Struktura odpovědi
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 | 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 |
**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ší.
---
## MySQL tabulky
### `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.
### `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)`.
---
## Produkční skript `kdojelekar_tydenni.py`
### 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 |
### 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`
### 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. ~~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
@@ -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"/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 '?'}")
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
sys.path.insert(0, r"u:\insurance")
from requests_pkcs12 import Pkcs12Adapter
import requests
import xml.etree.ElementTree as ET
from datetime import date
ENDPOINT = "https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/RegistracePojistencePZSB2B"
PFX_PATH = r"/Insurance/Certificates/picka.pfx"
PFX_PASS = "Vlado7309208104+"
RC = "7309208104"
K_DATU = date.today().isoformat()
NS = {
"soap": "http://schemas.xmlsoap.org/soap/envelope/",
"rp": "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1",
}
envelope = f"""<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="{NS['soap']}">
<soap:Body>
<ns1:registracePojistencePZSB2B xmlns:ns1="{NS['rp']}">
<ns1:cisloPojistence>{RC}</ns1:cisloPojistence>
<ns1:kDatu>{K_DATU}</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}\n")
print("=== RAW XML ===")
print(resp.text)
print("\n=== PARSED ===")
root = ET.fromstring(resp.text)
items = root.findall(".//rp:seznamOdbornosti/rp:odbornost", NS)
if not items:
st = root.find(".//rp:stavVyrizeniPozadavku", NS)
print(f"Žádné záznamy. stavVyrizeniPozadavku={st.text if st is not None else '?'}")
else:
for it in items:
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)
odb_kod = odb.find("rp:kod", NS).text.strip() if odb is not None and odb.find("rp:kod", NS) is not None else None
odb_naz = odb.find("rp:nazev", NS).text.strip() if odb is not None and odb.find("rp:nazev", NS) is not None else None
print(f" odbornost: {odb_kod} {odb_naz}")
print(f" ICZ: {g('ICZ')}")
print(f" ICP: {g('ICP')}")
print(f" nazevICP: {g('nazevICP')}")
print(f" nazevSZZ: {g('nazevSZZ')}")
print(f" datumRegistrace: {g('datumRegistrace')}")
print(f" datumZahajeni: {g('datumZahajeni')}")
print(f" datumUkonceni: {g('datumUkonceni')}")
print()
st = root.find(".//rp:stavVyrizeniPozadavku", NS)
print(f"stavVyrizeniPozadavku: {st.text.strip() if st is not None and st.text else '?'}")
@@ -0,0 +1,362 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Exportuje 151 pacientů registrovaných v Medicusu k 1.1.2025,
u nichž VZP k tomuto datu nevykazuje registraci v odbornosti 001 u IČP 09305001.
Výstup: Excel s komentářem a aktuálním stavem v Medicusu.
"""
import sys
from pathlib import Path
from datetime import date
from collections import defaultdict
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
from Knihovny.mysql_db import connect_mysql
import fdb, socket
from openpyxl import Workbook
from openpyxl.styles import (Font, PatternFill, Alignment, Border, Side,
GradientFill)
from openpyxl.utils import get_column_letter
# ── Konfigurace ────────────────────────────────────────────────────────────────
K_DATU_HIST = "2025-01-01"
TODAY = date.today()
OUT_FILE = Path(__file__).resolve().parent / f"neregistrovani_vzp_20250101.xlsx"
POJ_NAZVY = {
"111": "VZP",
"201": "ČPZP",
"205": "ČPZP (ex-OZP)",
"207": "OZP",
"209": "ZPŠ",
"211": "ZPMV",
"213": "RBP",
}
# ── Barvy ──────────────────────────────────────────────────────────────────────
BLUE_HEADER = "1F497D"
WHITE = "FFFFFF"
LIGHT_BLUE = "DCE6F1"
LIGHT_GREEN = "EBF1DE"
LIGHT_YELLOW = "FFFFC0"
LIGHT_RED = "FCE4D6"
LIGHT_GREY = "F2F2F2"
ORANGE = "F4B942"
# ── Data z MySQL ───────────────────────────────────────────────────────────────
mysql = connect_mysql()
cur = mysql.cursor()
cur.execute("""
SELECT rc FROM vzp_registrace_raw WHERE k_datu = %s
AND rc NOT IN (
SELECT rc FROM vzp_registrace_lekari
WHERE k_datu = %s AND kod_odbornosti = '001'
AND ICP = '09305001' AND ma_lekare = 1
)
""", (K_DATU_HIST, K_DATU_HIST))
problematicke_rcs = [row[0] for row in cur.fetchall()]
ph = ",".join(["%s"] * len(problematicke_rcs))
cur.execute(f"""
SELECT rc, prijmeni, jmeno, kod_odbornosti, ma_lekare, ICP,
nazev_lekare, nazev_zzz, poj_kod, poj_zkratka
FROM vzp_registrace_lekari
WHERE k_datu = %s AND rc IN ({ph}) AND kod_odbornosti = '001'
""", (K_DATU_HIST, *problematicke_rcs))
vzp = {}
for rc, prijmeni, jmeno, odb, ma, icp, nazev_lek, nazev_zzz, poj_kod, poj_zkr in cur.fetchall():
vzp[rc] = {"prijmeni": prijmeni or "", "jmeno": jmeno or "",
"ma_lekare": bool(ma), "ICP": icp or "",
"nazev_lekare": nazev_lek or "", "nazev_zzz": nazev_zzz or "",
"poj_kod": poj_kod or "", "poj_zkratka": poj_zkr or ""}
mysql.close()
# ── Data z Medicusu ────────────────────────────────────────────────────────────
computer_name = socket.gethostname().upper()
dsn_map = {
"LEKAR": r"localhost:M:\medicus\data\medicus.fdb",
"SESTRA": r"192.168.1.10:m:\medicus\data\medicus.fdb",
"LENOVO": r"192.168.1.10:m:\medicus\data\medicus.fdb",
}
dsn = dsn_map.get(computer_name, r"localhost:c:\medicus 3\data\medicus.fdb")
fb_conn = fdb.connect(dsn=dsn, user="SYSDBA", password="masterkey", charset="win1250")
fb_cur = fb_conn.cursor()
# Aktuálně aktivní pacienti
fb_cur.execute("""
SELECT kar.rodcis FROM kar
WHERE kar.vyrazen = 'N' AND kar.rodcis IS NOT NULL AND kar.rodcis <> ''
AND EXISTS (
SELECT 1 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'
)
""")
aktualne_aktivni = {(row[0] or "").strip() for row in fb_cur.fetchall()}
# Detail všech 151 pacientů
ph_fb = ",".join(["?" for _ in problematicke_rcs])
fb_cur.execute(f"""
SELECT kar.rodcis, kar.prijmeni, kar.jmeno, kar.poj, kar.vyrazen,
r.datum, r.datum_zruseni, r.priznak
FROM kar
LEFT JOIN registr r ON r.idpac = kar.idpac
LEFT JOIN icp i ON r.idicp = i.idicp AND i.icp = '09305001' AND i.odb = '001'
WHERE kar.rodcis IN ({ph_fb})
ORDER BY kar.rodcis, r.datum DESC
""", problematicke_rcs)
medicus = {}
for row in fb_cur.fetchall():
rc = (row[0] or "").strip()
if rc not in medicus:
medicus[rc] = {
"prijmeni": (row[1] or "").strip(),
"jmeno": (row[2] or "").strip(),
"poj": str(row[3] or "").strip(),
"vyrazen": (row[4] or "").strip(),
"reg_datum": row[5],
"reg_datum_zruseni":row[6],
"reg_priznak": (row[7] or "").strip(),
}
fb_conn.close()
# ── Kategorizace ───────────────────────────────────────────────────────────────
def kategorie(rc, vzp_row, med_row, aktivni_set):
poj = med_row.get("poj", "") if med_row else ""
if not vzp_row:
if poj != "111":
return "JINÁ POJIŠŤOVNA", "VZP neregistruje — pojištěnec jiné pojišťovny.", LIGHT_BLUE
return "BEZ VZP ZÁZNAMU", "VZP nevrátila žádný záznam přesto, že jde o pojištěnce VZP. Pravděpodobně chybí registrace u VZP.", LIGHT_RED
if vzp_row["ma_lekare"]:
return "REGISTROVÁN JINDE", f"VZP eviduje registraci u jiného lékaře: {vzp_row['nazev_zzz']} (ICP {vzp_row['ICP']}).", LIGHT_RED
return "BEZ LÉKAŘE U VZP", "VZP eviduje pojištěnce, ale bez registrujícího lékaře v odbornosti 001.", LIGHT_YELLOW
# ── Sestavení řádků ────────────────────────────────────────────────────────────
rows = []
for rc in problematicke_rcs:
vzp_row = vzp.get(rc)
med_row = medicus.get(rc)
aktivni = rc in aktualne_aktivni
prijmeni = (med_row or vzp_row or {}).get("prijmeni", "")
jmeno = (med_row or vzp_row or {}).get("jmeno", "")
poj_kod = med_row.get("poj", "") if med_row else (vzp_row or {}).get("poj_kod", "")
poj_nazev = POJ_NAZVY.get(poj_kod, poj_kod)
med_stav = "Aktivní" if aktivni else ("Odregistrován" if med_row else "Nenalezen v Medicusu")
reg_datum = med_row.get("reg_datum") if med_row else None
reg_datum_zrus = med_row.get("reg_datum_zruseni") if med_row else None
kat, komentar, barva = kategorie(rc, vzp_row, med_row, aktualne_aktivni)
vzp_icp = vzp_row["ICP"] if vzp_row and vzp_row["ma_lekare"] else ""
vzp_lek = vzp_row["nazev_zzz"] if vzp_row and vzp_row["ma_lekare"] else ""
vzp_zzz = vzp_row["nazev_lekare"] if vzp_row and vzp_row["ma_lekare"] else ""
rows.append({
"prijmeni": prijmeni,
"jmeno": jmeno,
"rc": rc,
"poj_kod": poj_kod,
"poj_nazev": poj_nazev,
"med_stav": med_stav,
"reg_datum": reg_datum.strftime("%d.%m.%Y") if reg_datum else "",
"reg_zruseni": reg_datum_zrus.strftime("%d.%m.%Y") if reg_datum_zrus else "",
"kategorie": kat,
"komentar": komentar,
"vzp_icp": vzp_icp,
"vzp_lek": vzp_lek,
"vzp_zzz": vzp_zzz,
"barva": barva,
})
rows.sort(key=lambda r: (r["kategorie"], r["prijmeni"], r["jmeno"]))
# ── Excel ──────────────────────────────────────────────────────────────────────
wb = Workbook()
# ────────────────────────────────────────────────────────────────────────────────
# SHEET 1: Přehled
# ────────────────────────────────────────────────────────────────────────────────
ws_info = wb.active
ws_info.title = "Přehled"
def hdr_cell(ws, row, col, value):
c = ws.cell(row=row, column=col, value=value)
c.font = Font(name="Arial", bold=True, color=WHITE, size=11)
c.fill = PatternFill("solid", fgColor=BLUE_HEADER)
c.alignment = Alignment(horizontal="center", vertical="center")
return c
def val_cell(ws, row, col, value, bold=False, bg=None):
c = ws.cell(row=row, column=col, value=value)
c.font = Font(name="Arial", bold=bold, size=10)
c.alignment = Alignment(wrap_text=True, vertical="top")
if bg:
c.fill = PatternFill("solid", fgColor=bg)
return c
ws_info.column_dimensions["A"].width = 36
ws_info.column_dimensions["B"].width = 18
ws_info.column_dimensions["C"].width = 60
# Titulek
ws_info.merge_cells("A1:C1")
t = ws_info["A1"]
t.value = f"Pacienti registrovaní v Medicusu k 1. 1. 2025, ale dle VZP bez registrace u IČP 09305001"
t.font = Font(name="Arial", bold=True, size=13, color=WHITE)
t.fill = PatternFill("solid", fgColor=BLUE_HEADER)
t.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
ws_info.row_dimensions[1].height = 36
ws_info.merge_cells("A2:C2")
ws_info["A2"].value = f"Vygenerováno: {TODAY.strftime('%d. %m. %Y')} | Stav v Medicusu k dnešnímu dni"
ws_info["A2"].font = Font(name="Arial", italic=True, size=9, color="595959")
ws_info["A2"].alignment = Alignment(horizontal="center")
# Souhrn počtů
counts = defaultdict(int)
counts_aktivni = defaultdict(int)
for r in rows:
counts[r["kategorie"]] += 1
if r["med_stav"] == "Aktivní":
counts_aktivni[r["kategorie"]] += 1
hdr_cell(ws_info, 4, 1, "Kategorie")
hdr_cell(ws_info, 4, 2, "Počet pacientů")
hdr_cell(ws_info, 4, 3, "Komentář")
KAT_BARVY = {
"REGISTROVÁN JINDE": LIGHT_RED,
"BEZ LÉKAŘE U VZP": LIGHT_YELLOW,
"JINÁ POJIŠŤOVNA": LIGHT_BLUE,
"BEZ VZP ZÁZNAMU": LIGHT_RED,
}
KAT_POPIS = {
"REGISTROVÁN JINDE": "VZP k 1.1.2025 eviduje registraci u jiného praktického lékaře. Pacient se pravděpodobně přeregistroval jinam, aniž by byl v Medicusu odregistrován.",
"BEZ LÉKAŘE U VZP": "VZP eviduje pojištěnce, ale v odbornosti 001 mu neeviduje žádného lékaře. Může jít o opožděné zpracování přihlášky nebo technickou chybu.",
"JINÁ POJIŠŤOVNA": "Pacient není pojištěncem VZP — VZP o něm data nemá, proto nebylo vráceno nic. To je očekávané chování.",
"BEZ VZP ZÁZNAMU": "VZP pojištěnec (111), ale VZP nevrátila žádný záznam. Může jít o nesprávné RC, neaktivní pojistný vztah nebo chybu v komunikaci.",
}
for i, kat in enumerate(["REGISTROVÁN JINDE", "BEZ LÉKAŘE U VZP", "JINÁ POJIŠŤOVNA", "BEZ VZP ZÁZNAMU"]):
r = 5 + i
bg = KAT_BARVY[kat]
val_cell(ws_info, r, 1, kat, bold=True, bg=bg)
val_cell(ws_info, r, 2, f"{counts[kat]} ({counts_aktivni[kat]} stále aktivní)", bg=bg)
val_cell(ws_info, r, 3, KAT_POPIS[kat], bg=bg)
ws_info.row_dimensions[r].height = 42
ws_info.row_dimensions[4].height = 20
# Celkem
val_cell(ws_info, 10, 1, "CELKEM", bold=True)
val_cell(ws_info, 10, 2, f"{len(rows)} ({sum(counts_aktivni.values())} aktivní)", bold=True)
# Metodika
ws_info.merge_cells("A12:C12")
ws_info["A12"].value = "Metodika"
ws_info["A12"].font = Font(name="Arial", bold=True, size=11, color=BLUE_HEADER)
metodika_text = (
"Skript kdojelekar_20250101.py dotázal VZP B2B na registrujícího lékaře (odbornost 001) "
"pro každého pacienta registrovaného k 1. 1. 2025 v Medicusu u IČP 09305001. "
"Pacienti v tomto souboru jsou ti, u nichž VZP k danému datu nevrátila záznam s ICP=09305001 a ma_lekare=1. "
"Stav v Medicusu je aktuální k dnešnímu dni (" + TODAY.strftime("%d. %m. %Y") + ")."
)
ws_info.merge_cells("A13:C13")
c = ws_info["A13"]
c.value = metodika_text
c.font = Font(name="Arial", size=9, color="595959")
c.alignment = Alignment(wrap_text=True, vertical="top")
ws_info.row_dimensions[13].height = 56
# Doporučení
ws_info.merge_cells("A15:C15")
ws_info["A15"].value = "Doporučení"
ws_info["A15"].font = Font(name="Arial", bold=True, size=11, color=BLUE_HEADER)
ws_info.merge_cells("A16:C16")
c = ws_info["A16"]
c.value = (
"1. REGISTROVÁN JINDE — ověřit s pacientem při návštěvě, zda se přeregistroval; pokud ano, odregistrovat v Medicusu.\n"
"2. BEZ LÉKAŘE U VZP — zkontrolovat, zda přihláška registrace byla správně odeslána a VZP ji eviduje.\n"
"3. JINÁ POJIŠŤOVNA — tyto pacienty prověřit u příslušné pojišťovny (ČPZP, OZP…) analogickým dotazem.\n"
"4. BEZ VZP ZÁZNAMU — ověřit správnost RC a aktivitu pojistného vztahu přímo u VZP."
)
c.font = Font(name="Arial", size=10)
c.alignment = Alignment(wrap_text=True, vertical="top")
ws_info.row_dimensions[16].height = 80
# ────────────────────────────────────────────────────────────────────────────────
# SHEET 2: Data
# ────────────────────────────────────────────────────────────────────────────────
ws = wb.create_sheet("Pacienti")
COLS = [
("Příjmení", 20),
("Jméno", 14),
("Rodné číslo", 14),
("Pojišťovna", 12),
("Stav v Medicusu\ndnes", 16),
("Datum registrace\nv Medicusu", 16),
("Datum zrušení\nv Medicusu", 16),
("Kategorie VZP problému", 22),
("Komentář", 52),
("VZP ICP jiného lékaře", 18),
("VZP — jméno lékaře", 28),
("VZP — název ZZZ", 36),
]
for col_idx, (header, width) in enumerate(COLS, 1):
c = ws.cell(row=1, column=col_idx, value=header)
c.font = Font(name="Arial", bold=True, color=WHITE, size=10)
c.fill = PatternFill("solid", fgColor=BLUE_HEADER)
c.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
ws.column_dimensions[get_column_letter(col_idx)].width = width
ws.row_dimensions[1].height = 32
ws.freeze_panes = "A2"
thin = Side(style="thin", color="BFBFBF")
border = Border(left=thin, right=thin, top=thin, bottom=thin)
for row_idx, r in enumerate(rows, 2):
bg = r["barva"]
data = [
r["prijmeni"], r["jmeno"], r["rc"], f"{r['poj_kod']} {r['poj_nazev']}",
r["med_stav"], r["reg_datum"], r["reg_zruseni"],
r["kategorie"], r["komentar"],
r["vzp_icp"], r["vzp_lek"], r["vzp_zzz"],
]
for col_idx, value in enumerate(data, 1):
c = ws.cell(row=row_idx, column=col_idx, value=value)
c.font = Font(name="Arial", size=9)
c.fill = PatternFill("solid", fgColor=bg)
c.border = border
c.alignment = Alignment(vertical="top", wrap_text=(col_idx in (9, 11, 12)))
if r["med_stav"] == "Aktivní" and col_idx == 5:
c.font = Font(name="Arial", size=9, bold=True, color="375623")
elif r["med_stav"] != "Aktivní" and col_idx == 5:
c.font = Font(name="Arial", size=9, color="843C0C")
ws.row_dimensions[row_idx].height = 32
# AutoFilter
ws.auto_filter.ref = f"A1:{get_column_letter(len(COLS))}1"
wb.save(OUT_FILE)
print(f"Uloženo: {OUT_FILE}")
print(f"Celkem řádků: {len(rows)}")
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
kdojelekar_20250101.py
======================
Jednorázový skript: vybere pacienty registrované k 01.01.2025
a dotáže se VZP B2B na jejich registrujícího lékaře k tomuto datu.
Výsledky uloží do stejných MySQL tabulek jako týdenní skript
(vzp_registrace_lekari, vzp_registrace_raw) s k_datu = 2025-01-01.
Resumovatelný — přeskočí pacienty, kteří již mají raw XML pro k_datu=2025-01-01.
"""
import sys
import time
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_connection
# ── KONFIGURACE ───────────────────────────────────────────────────────────────
K_DATU = date(2025, 1, 1)
API_PAUSE = 2
PFX_PATH = Path(__file__).resolve().parent.parent / "Certificates" / "picka.pfx"
PFX_PASS = "Vlado7309208104+"
ODBORNOSTI = None # None = bez filtru odborností
# ── INIT ──────────────────────────────────────────────────────────────────────
vzp = VZPB2BClient("prod", str(PFX_PATH), PFX_PASS)
mysql = connect_mysql()
# ── PACIENTI Z MEDICUS (registrovaní k 01.01.2025) ───────────────────────────
import fdb, socket
computer_name = socket.gethostname().upper()
dsn_map = {
"LEKAR": r"localhost:M:\medicus\data\medicus.fdb",
"SESTRA": r"192.168.1.10:m:\medicus\data\medicus.fdb",
"LENOVO": r"192.168.1.10:m:\medicus\data\medicus.fdb",
}
dsn = dsn_map.get(computer_name, r"localhost:c:\medicus 3\data\medicus.fdb")
fb_conn = fdb.connect(dsn=dsn, user="SYSDBA", password="masterkey", charset="win1250")
fb_cur = fb_conn.cursor()
fb_cur.execute("""
SELECT kar.rodcis, kar.prijmeni, kar.jmeno, kar.poj
FROM kar
WHERE kar.vyrazen = 'N'
AND kar.rodcis IS NOT NULL
AND kar.rodcis <> ''
AND EXISTS (
SELECT r.id FROM registr r
JOIN icp i ON r.idicp = i.idicp
WHERE r.idpac = kar.idpac
AND r.datum <= ?
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= ?)
AND r.priznak IN ('V', 'D', 'A')
AND i.icp = '09305001'
AND i.odb = '001'
)
ORDER BY kar.prijmeni, kar.rodcis
""", (K_DATU.isoformat(), K_DATU.isoformat()))
cols = [d[0].strip().lower() for d in fb_cur.description]
pacienti = [dict(zip(cols, row)) for row in fb_cur.fetchall()]
fb_conn.close()
print(f"Pacientu registrovanych k {K_DATU}: {len(pacienti)}")
# ── RESUME: přeskočit již hotové ─────────────────────────────────────────────
with mysql.cursor() as cur:
cur.execute("SELECT rc FROM vzp_registrace_raw WHERE k_datu = %s", (K_DATU,))
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"Zbyvá zpracovat: {len(pacienti)} ({len(hotove)} již hotovo)\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=K_DATU.isoformat(), odbornosti=ODBORNOSTI)
zaznamy = vzp.parse_registrace_lekare(xml)
except Exception as e:
print(f"CHYBA: {e}")
continue
print(f"{len(zaznamy)} lekar(u)")
for z in zaznamy:
print(f" {z['kod_odbornosti']}: {z['nazev_lekare']} / {z['nazev_zzz']}"
f" [{z['datum_zahajeni']} - {z['datum_ukonceni']}]")
if not zaznamy:
print(" (zadny lekar v zadne odbornosti)")
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, K_DATU, 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, K_DATU, 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 dotazu: {call_count}")
mysql.close()
@@ -0,0 +1,142 @@
#!/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 = False # False = produkční běh
PFX_PATH = Path(__file__).resolve().parent.parent / "Certificates" / "picka.pfx"
PFX_PASS = "Vlado7309208104+"
ODBORNOSTI = None # None = bez filtru, VZP vrátí všechny odbornosti
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']}]")
if not zaznamy:
print(f" (žádný lékař v žádné odbornosti)")
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()
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys as _sys
_sys.stdout.reconfigure(encoding="utf-8", errors="replace")
_sys.stderr.reconfigure(encoding="utf-8", errors="replace")
"""
01_parse_seznam.py
==================
Najde a rozparsuje dávky VZP Seznam registrovaných pojištěnců (III-1.1.2).
Formát souboru: Fxxxmmur.nnn, pevná šířka, kódování CP852.
Záhlaví (typ H, délka 20):
HTYP C 1 0 typ věty 'H'
HICP N 8 1 IČP lékaře
HPUP N 5 9 počet uznávaných pojištěnců
HROK N 2 14 poslední dvojčíslí roku
HMES N 2 16 měsíc
HDEN N 2 18 den
Registrace (typ I, délka 82):
ITYP C 1 0 typ věty 'I'
IPOR N 4 1 pořadové číslo
IVS N 2 5 věková skupina
IPRI C 30 7 příjmení
IJME C 24 37 jméno
ICIP C 10 61 číslo pojištěnce
IDUOD D 8 71 datum uznání registrace (DDMMRRRR)
ICPO C 3 79 kód pojišťovny
"""
import re
from pathlib import Path
from datetime import date, timedelta
DAVKY_DIR = Path(r"U:\Dropbox\Ordinace\Dokumentace_ke_zpracování\Zúčtovací zprávy\111 VZP Podání")
ENCODING = "cp852"
def parse_davku(path: Path) -> dict:
"""Vrátí dict s klíči 'hlavicka' a 'pojistenci'."""
lines = path.read_bytes().splitlines()
hlavicka = None
pojistenci = []
for raw in lines:
line = raw.decode(ENCODING, errors="replace")
if not line:
continue
if line[0] == "H":
hlavicka = {
"icp": line[1:9].strip(),
"pocet": int(line[9:14].strip() or 0),
"rok": 2000 + int(line[14:16]),
"mesic": int(line[16:18]),
"den": int(line[18:20]),
}
elif line[0] == "I":
if len(line) < 82:
continue
dat_raw = line[71:79] # DDMMRRRR
try:
datum = date(int(dat_raw[4:8]), int(dat_raw[2:4]), int(dat_raw[0:2]))
except ValueError:
datum = None
pojistenci.append({
"por": int(line[1:5].strip() or 0),
"vs": line[5:7].strip(),
"prijmeni": line[7:37].strip(),
"jmeno": line[37:61].strip(),
"cip": line[61:71].strip(),
"datum_od": datum,
"pojistovna": line[79:82].strip(),
})
return {"hlavicka": hlavicka, "pojistenci": pojistenci, "soubor": path.name}
def najdi_davky(adresar: Path) -> list[Path]:
"""Vrátí seřazený seznam souborů odpovídajících vzoru Fxxx*.nnn."""
return sorted(
[p for p in adresar.iterdir()
if re.search(r"F\d{3}.*\.\d{3}$", p.name, re.IGNORECASE)],
key=lambda p: p.name
)
def tiskni_davku(d: dict) -> None:
h = d["hlavicka"]
if h:
print(f"\n=== {d['soubor']} ===")
print(f" IČP: {h['icp']} | datum: {h['den']:02d}.{h['mesic']:02d}.{h['rok']} | pojištěnců: {h['pocet']}")
print(f" {'#':>4} {'Příjmení':<30} {'Jméno':<24} {'ČIP':<10} {'Datum od':>10} Pojiš.")
print(f" {'-'*4} {'-'*30} {'-'*24} {'-'*10} {'-'*10} {'-'*6}")
else:
print(f"\n=== {d['soubor']} === (záhlaví chybí)")
for p in d["pojistenci"]:
datum_str = p["datum_od"].strftime("%d.%m.%Y") if p["datum_od"] else "?"
print(f" {p['por']:>4} {p['prijmeni']:<30} {p['jmeno']:<24} {p['cip']:<10} {datum_str:>10} {p['pojistovna']}")
# ── MAIN ──────────────────────────────────────────────────────────────────────
davky = najdi_davky(DAVKY_DIR)
print(f"Nalezeno dávek: {len(davky)}")
for cesta in davky:
data = parse_davku(cesta)
tiskni_davku(data)
print(f"\nCelkem dávek: {len(davky)}")
@@ -0,0 +1,165 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys as _sys
_sys.stdout.reconfigure(encoding="utf-8", errors="replace")
_sys.stderr.reconfigure(encoding="utf-8", errors="replace")
"""
02_import_do_db.py
==================
Vytvoří tabulku seznam_pojistencu_davky v medevio DB a naimportuje
všechny dávky F111*.nnn ze složky Podání.
Spuštění:
python 02_import_do_db.py # import všech dávek
python 02_import_do_db.py --reset # smaže a znovu vytvoří tabulku před importem
"""
import re
import sys
import argparse
from pathlib import Path
from datetime import date
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "Knihovny"))
from mysql_db import connect_mysql
DAVKY_DIR = Path(r"U:\Dropbox\Ordinace\Dokumentace_ke_zpracování\Zúčtovací zprávy\111 VZP Podání")
ENCODING = "cp852"
CREATE_SQL = """
CREATE TABLE IF NOT EXISTS seznam_pojistencu_davky (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
soubor VARCHAR(60) NOT NULL COMMENT 'Název souboru dávky',
icp VARCHAR(8) NOT NULL COMMENT 'IČP lékaře (HICP)',
davka_rok SMALLINT NOT NULL COMMENT 'Rok dávky (HROK)',
davka_mesic TINYINT NOT NULL COMMENT 'Měsíc dávky (HMES)',
davka_den TINYINT NOT NULL COMMENT 'Den pořízení seznamu (HDEN)',
por SMALLINT NOT NULL COMMENT 'Pořadové číslo v dávce (IPOR)',
vs VARCHAR(2) NOT NULL COMMENT 'Věková skupina (IVS)',
prijmeni VARCHAR(30) NOT NULL COMMENT 'Příjmení pojištěnce (IPRI)',
jmeno VARCHAR(24) NOT NULL COMMENT 'Jméno pojištěnce (IJME)',
cip VARCHAR(10) NOT NULL COMMENT 'Číslo pojištěnce (ICIP)',
datum_od DATE NULL COMMENT 'Datum uznání registrace (IDUOD)',
pojistovna VARCHAR(3) NOT NULL COMMENT 'Kód pojišťovny (ICPO)',
vytvoreno TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_soubor_por (soubor, por)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_czech_ci
COMMENT='VZP seznam registrovaných pojištěnců III-1.1.2';
"""
def parse_davku(path: Path) -> dict:
lines = path.read_bytes().splitlines()
hlavicka = None
pojistenci = []
for raw in lines:
line = raw.decode(ENCODING, errors="replace")
if not line:
continue
if line[0] == "H":
hlavicka = {
"icp": line[1:9].strip(),
"pocet": int(line[9:14].strip() or 0),
"rok": 2000 + int(line[14:16]),
"mesic": int(line[16:18]),
"den": int(line[18:20]),
}
elif line[0] == "I":
if len(line) < 82:
continue
dat_raw = line[71:79] # DDMMRRRR
try:
datum = date(int(dat_raw[4:8]), int(dat_raw[2:4]), int(dat_raw[0:2]))
except ValueError:
datum = None
pojistenci.append({
"por": int(line[1:5].strip() or 0),
"vs": line[5:7].strip(),
"prijmeni": line[7:37].strip(),
"jmeno": line[37:61].strip(),
"cip": line[61:71].strip(),
"datum_od": datum,
"pojistovna": line[79:82].strip(),
})
return {"hlavicka": hlavicka, "pojistenci": pojistenci, "soubor": path.name}
def najdi_davky(adresar: Path) -> list[Path]:
return sorted(
[p for p in adresar.iterdir()
if re.search(r"F\d{3}.*\.\d{3}$", p.name, re.IGNORECASE)],
key=lambda p: p.name
)
def import_davku(cur, davka: dict) -> tuple[int, int]:
"""Vrátí (vloženo, přeskočeno duplicit)."""
h = davka["hlavicka"]
if not h:
print(f" SKIP {davka['soubor']} — chybí záhlaví")
return 0, 0
rows = [
(davka["soubor"], h["icp"], h["rok"], h["mesic"], h["den"],
p["por"], p["vs"], p["prijmeni"], p["jmeno"],
p["cip"], p["datum_od"], p["pojistovna"])
for p in davka["pojistenci"]
]
# INSERT IGNORE přeskočí duplicity (unikátní klíč soubor+por) bez chyby
affected = cur.executemany(
"""INSERT IGNORE INTO seznam_pojistencu_davky
(soubor, icp, davka_rok, davka_mesic, davka_den,
por, vs, prijmeni, jmeno, cip, datum_od, pojistovna)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
rows
)
vlozeno = affected if affected else 0
preskoceno = len(rows) - vlozeno
return vlozeno, preskoceno
# ── MAIN ──────────────────────────────────────────────────────────────────────
parser = argparse.ArgumentParser(description="Import VZP dávek do MySQL")
parser.add_argument("--reset", action="store_true",
help="Smaže a znovu vytvoří tabulku před importem")
args = parser.parse_args()
conn = connect_mysql()
cur = conn.cursor()
if args.reset:
print("DROP TABLE seznam_pojistencu_davky ...")
cur.execute("DROP TABLE IF EXISTS seznam_pojistencu_davky")
print("Vytváření tabulky (pokud neexistuje) ...")
cur.execute(CREATE_SQL)
davky = najdi_davky(DAVKY_DIR)
print(f"Nalezeno dávek: {len(davky)}\n")
celkem_vlozeno = celkem_preskoceno = 0
for cesta in davky:
davka = parse_davku(cesta)
h = davka["hlavicka"]
if h:
print(f"{davka['soubor']} ({h['den']:02d}.{h['mesic']:02d}.{h['rok']}, {len(davka['pojistenci'])} záznamů)")
else:
print(f"{davka['soubor']} (záhlaví chybí)")
vlozeno, preskoceno = import_davku(cur, davka)
celkem_vlozeno += vlozeno
celkem_preskoceno += preskoceno
print(f" → vloženo: {vlozeno}, duplicit přeskočeno: {preskoceno}")
cur.close()
conn.close()
print(f"\nHotovo. Celkem vloženo: {celkem_vlozeno}, přeskočeno: {celkem_preskoceno}")
@@ -0,0 +1,226 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys as _sys
_sys.stdout.reconfigure(encoding="utf-8", errors="replace")
_sys.stderr.reconfigure(encoding="utf-8", errors="replace")
"""
03_porovnani_davek.py
=====================
Porovná všechny po sobě jdoucí dávky VZP a vytvoří Excel se třemi listy:
1. Přehled souhrnná tabulka přechodů (kolik odešlo / přibylo)
2. Odešli detail všech, kdo v dané dávce chyběli oproti předchozí
3. Přibylo detail všech, kdo v dané dávce přibyly oproti předchozí
Pro data s více soubory se použijí unikátní CIPy (dávky jsou totožné).
"""
import sys
from datetime import date
from pathlib import Path
from collections import defaultdict
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "Knihovny"))
from mysql_db import connect_mysql
OUTPUT = Path(__file__).parent / "porovnani_davek.xlsx"
# ── Barvy ─────────────────────────────────────────────────────────────────────
CLR_HEADER = "1F4E79" # tmavě modrá
CLR_SUBHDR = "2E75B6" # střední modrá
CLR_ODEŠLI = "FCE4D6" # světle lososová
CLR_PŘIBYLO = "E2EFDA" # světle zelená
CLR_ZEBRA = "F2F2F2" # šedá pro zebra řádky
CLR_SUMMARY = "DEEAF1" # světle modrá pro souhrn
# ── Styly ─────────────────────────────────────────────────────────────────────
def hdr_font(white=True):
return Font(bold=True, color="FFFFFF" if white else "000000", size=11)
def cell_border():
thin = Side(style="thin", color="BFBFBF")
return Border(left=thin, right=thin, top=thin, bottom=thin)
def set_header(cell, text, bg=CLR_HEADER, white=True):
cell.value = text
cell.font = hdr_font(white)
cell.fill = PatternFill("solid", fgColor=bg)
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
cell.border = cell_border()
def set_cell(cell, value, bg=None, bold=False, align="left", num_fmt=None):
cell.value = value
cell.font = Font(bold=bold, size=10)
cell.alignment = Alignment(horizontal=align, vertical="center")
cell.border = cell_border()
if bg:
cell.fill = PatternFill("solid", fgColor=bg)
if num_fmt:
cell.number_format = num_fmt
# ── Načtení dat z DB ──────────────────────────────────────────────────────────
print("Připojuji se k DB ...")
conn = connect_mysql()
cur = conn.cursor()
# Unikátní CIPy per datum + jméno (z prvního souboru daného data)
cur.execute("""
SELECT davka_rok, davka_mesic, davka_den, cip,
MIN(prijmeni) AS prijmeni, MIN(jmeno) AS jmeno
FROM seznam_pojistencu_davky
GROUP BY davka_rok, davka_mesic, davka_den, cip
ORDER BY davka_rok, davka_mesic, davka_den
""")
rows = cur.fetchall()
conn.close()
# Seskupení do dict: datum -> {cip: (prijmeni, jmeno)}
davky: dict[date, dict[str, tuple]] = defaultdict(dict)
for rok, mes, den, cip, pri, jme in rows:
d = date(rok, mes, den)
davky[d][cip] = (pri, jme)
data = sorted(davky.keys())
print(f"Nalezeno {len(data)} unikátních datumů dávek.")
# ── Porovnání ────────────────────────────────────────────────────────────────
prehled = [] # (dat_od, dat_do, stav_od, odešlo, přibylo, stav_do)
odešli = [] # (dat_do, cip, prijmeni, jmeno)
přibylo = [] # (dat_do, cip, prijmeni, jmeno)
for i in range(1, len(data)):
d1, d2 = data[i-1], data[i]
cip1 = set(davky[d1])
cip2 = set(davky[d2])
vyšli = cip1 - cip2
vstou = cip2 - cip1
prehled.append((d1, d2, len(cip1), len(vyšli), len(vstou), len(cip2)))
for cip in sorted(vyšli):
pri, jme = davky[d1][cip]
odešli.append((d2, cip, pri, jme))
for cip in sorted(vstou):
pri, jme = davky[d2][cip]
přibylo.append((d2, cip, pri, jme))
# ── Excel ─────────────────────────────────────────────────────────────────────
wb = openpyxl.Workbook()
# ── List 1: Přehled ───────────────────────────────────────────────────────────
ws = wb.active
ws.title = "Přehled"
ws.freeze_panes = "A3"
# Nadpis
ws.merge_cells("A1:F1")
t = ws["A1"]
t.value = "Porovnání po sobě jdoucích dávek VZP seznam registrovaných pojištěnců"
t.font = Font(bold=True, size=13, color="FFFFFF")
t.fill = PatternFill("solid", fgColor=CLR_HEADER)
t.alignment = Alignment(horizontal="center", vertical="center")
ws.row_dimensions[1].height = 28
# Záhlaví sloupců
headers = ["Datum od", "Datum do", "Stav\nna začátku", "Odešlo", "Přibylo", "Stav\nna konci"]
for col, h in enumerate(headers, 1):
set_header(ws.cell(2, col), h)
ws.row_dimensions[2].height = 32
# Data
for row_i, (d1, d2, stav_od, vyšli, vstou, stav_do) in enumerate(prehled, 3):
bg = CLR_ZEBRA if row_i % 2 == 0 else None
set_cell(ws.cell(row_i, 1), d1.strftime("%d.%m.%Y"), bg, align="center")
set_cell(ws.cell(row_i, 2), d2.strftime("%d.%m.%Y"), bg, align="center")
set_cell(ws.cell(row_i, 3), stav_od, bg, align="right")
set_cell(ws.cell(row_i, 4), -vyšli, CLR_ODEŠLI if vyšli else bg, bold=bool(vyšli), align="right")
set_cell(ws.cell(row_i, 5), vstou, CLR_PŘIBYLO if vstou else bg, bold=bool(vstou), align="right")
set_cell(ws.cell(row_i, 6), stav_do, bg, align="right")
# Souhrnný řádek
sr = len(prehled) + 3
ws.merge_cells(f"A{sr}:B{sr}")
sc = ws.cell(sr, 1)
sc.value = "CELKEM pohybů"
sc.font = Font(bold=True, size=10)
sc.fill = PatternFill("solid", fgColor=CLR_SUMMARY)
sc.alignment = Alignment(horizontal="center", vertical="center")
sc.border = cell_border()
celk_odešlo = sum(p[3] for p in prehled)
celk_přibylo = sum(p[4] for p in prehled)
set_cell(ws.cell(sr, 3), "", CLR_SUMMARY)
set_cell(ws.cell(sr, 4), -celk_odešlo, CLR_SUMMARY, bold=True, align="right")
set_cell(ws.cell(sr, 5), celk_přibylo, CLR_SUMMARY, bold=True, align="right")
set_cell(ws.cell(sr, 6), "", CLR_SUMMARY)
# Šířky sloupců
for col, w in zip(range(1, 7), [14, 14, 14, 10, 10, 12]):
ws.column_dimensions[get_column_letter(col)].width = w
# ── List 2: Odešli ────────────────────────────────────────────────────────────
ws2 = wb.create_sheet("Odešli")
ws2.freeze_panes = "A3"
ws2.merge_cells("A1:D1")
t2 = ws2["A1"]
t2.value = f"Pacienti, kteří odešli celkem {len(odešli)} pohybů"
t2.font = Font(bold=True, size=13, color="FFFFFF")
t2.fill = PatternFill("solid", fgColor="C55A11")
t2.alignment = Alignment(horizontal="center", vertical="center")
ws2.row_dimensions[1].height = 28
for col, h in enumerate(["Datum dávky", "Číslo pojištěnce", "Příjmení", "Jméno"], 1):
set_header(ws2.cell(2, col), h, bg="C55A11")
ws2.row_dimensions[2].height = 24
for row_i, (dat, cip, pri, jme) in enumerate(odešli, 3):
bg = CLR_ODEŠLI if row_i % 2 == 0 else None
set_cell(ws2.cell(row_i, 1), dat.strftime("%d.%m.%Y"), bg, align="center")
set_cell(ws2.cell(row_i, 2), cip, bg, align="center")
set_cell(ws2.cell(row_i, 3), pri, bg)
set_cell(ws2.cell(row_i, 4), jme, bg)
for col, w in zip(range(1, 5), [14, 16, 28, 22]):
ws2.column_dimensions[get_column_letter(col)].width = w
# ── List 3: Přibylo ───────────────────────────────────────────────────────────
ws3 = wb.create_sheet("Přibylo")
ws3.freeze_panes = "A3"
ws3.merge_cells("A1:D1")
t3 = ws3["A1"]
t3.value = f"Pacienti, kteří přibylo celkem {len(přibylo)} pohybů"
t3.font = Font(bold=True, size=13, color="FFFFFF")
t3.fill = PatternFill("solid", fgColor="375623")
t3.alignment = Alignment(horizontal="center", vertical="center")
ws3.row_dimensions[1].height = 28
for col, h in enumerate(["Datum dávky", "Číslo pojištěnce", "Příjmení", "Jméno"], 1):
set_header(ws3.cell(2, col), h, bg="375623")
ws3.row_dimensions[2].height = 24
for row_i, (dat, cip, pri, jme) in enumerate(přibylo, 3):
bg = CLR_PŘIBYLO if row_i % 2 == 0 else None
set_cell(ws3.cell(row_i, 1), dat.strftime("%d.%m.%Y"), bg, align="center")
set_cell(ws3.cell(row_i, 2), cip, bg, align="center")
set_cell(ws3.cell(row_i, 3), pri, bg)
set_cell(ws3.cell(row_i, 4), jme, bg)
for col, w in zip(range(1, 5), [14, 16, 28, 22]):
ws3.column_dimensions[get_column_letter(col)].width = w
# ── Uložení ───────────────────────────────────────────────────────────────────
wb.save(OUTPUT)
print(f"\nExcel uložen: {OUTPUT}")
print(f" Přehled: {len(prehled)} přechodů")
print(f" Odešli: {len(odešli)} pohybů")
print(f" Přibylo: {len(přibylo)} pohybů")
@@ -0,0 +1,233 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys as _sys
_sys.stdout.reconfigure(encoding="utf-8", errors="replace")
_sys.stderr.reconfigure(encoding="utf-8", errors="replace")
"""
04_najdi_zlomy.py
=================
Pro pacienty z seznam_pojistencu_davky, kteří NEMAJÍ záznam v vzp_registrace_lekari
(skript kdojelekar je nezachytil), najde bod zlomu registrace u naší ambulance.
Algoritmus:
1. Dotaz VZP dnes — je pacient stále registrován u nás (ICP=09305001)?
ANO → datum_ukonceni z odpovědi = výsledek
2. NE → hledáme rokem dozadu (od dnes, 1 rok, 2 roky …)
dokud nenajdeme rok kdy BYL registrován → tím ohraničíme interval [lo, hi]
3. V intervalu [lo, hi] binární hledání na den přesně
(nebo pokud datum_ukonceni z VZP odpovědi není 3000, použijeme ho přímo)
Výsledek se uloží do tabulky seznam_pojistencu_zlomy a vytiskne na konzoli.
"""
import time
import sys
from datetime import date, timedelta
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "Knihovny"))
from mysql_db import connect_mysql
from vzpb2b_client import VZPB2BClient
# ── Konfigurace ───────────────────────────────────────────────────────────────
PFX_PATH = str(Path(__file__).resolve().parents[1] / "Certificates" / "picka.pfx")
PFX_PASSWORD = "Vlado7309208104+"
ICZ = "09305000"
NASA_ICP = "09305001"
API_PAUSE = 2 # sekundy mezi VZP dotazy
CREATE_SQL = """
CREATE TABLE IF NOT EXISTS seznam_pojistencu_zlomy (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
cip VARCHAR(12) NOT NULL,
prijmeni VARCHAR(30) NOT NULL,
jmeno VARCHAR(24) NOT NULL,
posledni_davka DATE NOT NULL COMMENT 'Poslední měsíc kdy byl v dávce',
zlom_datum DATE NULL COMMENT 'Poslední den registrace u nás (NULL=stále aktivní)',
zlom_zdroj VARCHAR(60) NULL COMMENT 'Jak byl zlom určen',
stav VARCHAR(20) NOT NULL COMMENT 'aktivní / ukončen / nenalezen',
dotazeno_dne DATE NOT NULL,
UNIQUE KEY uq_cip (cip)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Zlomy registrace pro pacienty bez záznamu v kdojelekar';
"""
# ── VZP klient ────────────────────────────────────────────────────────────────
vzp = VZPB2BClient("prod", PFX_PATH, PFX_PASSWORD, icz=ICZ)
def je_registrovan(rc: str, k_datu: date) -> tuple[bool, date | None]:
"""
Vrátí (registrován_u_nas: bool, datum_ukonceni: date|None).
datum_ukonceni = None pokud nelze parsovat nebo pacient není u nás.
"""
xml = vzp.registrace_lekare(rc, k_datu.isoformat(), odbornosti=["001"])
time.sleep(API_PAUSE)
try:
zaznamy = vzp.parse_registrace_lekare(xml)
except Exception as e:
print(f" [CHYBA parsování] {e}")
return False, None
for z in zaznamy:
if z.get("ma_lekare") and z.get("ICP") == NASA_ICP and z.get("kod_odbornosti") == "001":
du_str = z.get("datum_ukonceni")
try:
du = date.fromisoformat(du_str) if du_str else None
except ValueError:
du = None
return True, du
return False, None
def najdi_zlom(rc: str, posledni_davka: date) -> tuple[date | None, str, str]:
"""
Vrátí (zlom_datum, zlom_zdroj, stav).
zlom_datum = poslední den kdy byl registrován (None = stále aktivní).
"""
today = date.today()
# ── Krok 1: dotaz dnes ────────────────────────────────────────────────────
print(f" [dnes {today}]", end=" ", flush=True)
reg, du = je_registrovan(rc, today)
if reg:
if du and du.year < 3000:
print(f"registrován, ukončení {du}")
return du, "VZP datum_ukonceni (dnes)", "ukončen"
else:
print("registrován, bez data ukončení → stále aktivní")
return None, "VZP dnes aktivní", "aktivní"
print("NENÍ registrován")
# ── Krok 2: hledání po rocích dozadu ─────────────────────────────────────
# lo = víme, že tam BYL (posledni_davka)
# hi = víme, že tam NENÍ (today)
lo: date = posledni_davka
hi: date = today
probe = today.replace(year=today.year - 1)
while probe >= posledni_davka:
print(f" [rok {probe}]", end=" ", flush=True)
reg_p, du_p = je_registrovan(rc, probe)
if reg_p:
lo = probe
print(f"registrován")
if du_p and du_p.year < 3000:
print(f" → datum_ukonceni z VZP: {du_p}")
return du_p, f"VZP datum_ukonceni (dotaz {probe})", "ukončen"
break
else:
hi = probe
print("není")
try:
probe = probe.replace(year=probe.year - 1)
except ValueError:
break
else:
# Ani v posledni_davka není registrován — neobvyklé
print(f" ! Ani k datu {posledni_davka} není registrován — zkouším přímo")
reg_lo, du_lo = je_registrovan(rc, posledni_davka)
if not reg_lo:
return None, "nenalezen ani k datu poslední dávky", "nenalezen"
lo = posledni_davka
if du_lo and du_lo.year < 3000:
return du_lo, f"VZP datum_ukonceni ({posledni_davka})", "ukončen"
# ── Krok 3: binární hledání v intervalu [lo, hi] ─────────────────────────
print(f" Binární hledání: {lo}{hi}")
iterace = 0
while (hi - lo).days > 1:
iterace += 1
mid = lo + timedelta(days=(hi - lo).days // 2)
print(f" [{iterace}. iterace: {mid}]", end=" ", flush=True)
reg_m, du_m = je_registrovan(rc, mid)
if reg_m:
lo = mid
print("registrován")
if du_m and du_m.year < 3000:
print(f" → datum_ukonceni z VZP: {du_m}")
return du_m, f"VZP datum_ukonceni (binární {mid})", "ukončen"
else:
hi = mid
print("není")
print(f" → Zlom: poslední den registrace = {lo}")
return lo, f"binární hledání ({iterace} kroků)", "ukončen"
# ── Načtení pacientů ──────────────────────────────────────────────────────────
conn = connect_mysql()
cur = conn.cursor()
cur.execute(CREATE_SQL)
# Unikátní CIP v seznamu (VZP, pojišťovna 111)
cur.execute("SELECT DISTINCT cip FROM seznam_pojistencu_davky WHERE pojistovna='111'")
vsechny_cip = {r[0] for r in cur.fetchall()}
# CIP které jsou v registrace_lekari (u nás, odb 001)
cur.execute("""
SELECT DISTINCT rc FROM vzp_registrace_lekari
WHERE kod_odbornosti='001' AND ICP='09305001' AND ma_lekare=1
""")
zname_cip = {r[0] for r in cur.fetchall()}
# Nespárované
nesparovane_cip = vsechny_cip - zname_cip
# Doplním jméno a posledni_davka
cur.execute("""
SELECT cip, MIN(prijmeni), MIN(jmeno),
MAX(DATE(CONCAT(davka_rok, '-', LPAD(davka_mesic,2,'0'), '-01')))
FROM seznam_pojistencu_davky
WHERE pojistovna='111'
GROUP BY cip
""")
info = {r[0]: (r[1], r[2], r[3]) for r in cur.fetchall()}
pacienti = [
(cip, *info[cip])
for cip in sorted(nesparovane_cip)
if cip in info
]
print(f"Pacientů ke zpracování: {len(pacienti)}\n")
print("=" * 70)
# ── Hlavní smyčka ─────────────────────────────────────────────────────────────
vysledky = []
for cip, prijmeni, jmeno, posledni_davka in pacienti:
print(f"\n{prijmeni} {jmeno} (CIP: {cip}, poslední dávka: {posledni_davka})")
try:
zlom, zdroj, stav = najdi_zlom(cip, posledni_davka)
except Exception as e:
print(f" CHYBA: {e}")
zlom, zdroj, stav = None, f"chyba: {e}", "chyba"
vysledky.append((cip, prijmeni, jmeno, posledni_davka, zlom, zdroj, stav))
cur.execute("""
INSERT INTO seznam_pojistencu_zlomy
(cip, prijmeni, jmeno, posledni_davka, zlom_datum, zlom_zdroj, stav, dotazeno_dne)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
posledni_davka=VALUES(posledni_davka),
zlom_datum=VALUES(zlom_datum),
zlom_zdroj=VALUES(zlom_zdroj),
stav=VALUES(stav),
dotazeno_dne=VALUES(dotazeno_dne)
""", (cip, prijmeni, jmeno, posledni_davka, zlom, zdroj, stav, date.today()))
cur.close()
conn.close()
# ── Výsledky ──────────────────────────────────────────────────────────────────
print("\n" + "=" * 70)
print(f"\n{'Příjmení':<25} {'Jméno':<20} {'CIP':<12} {'Poslední dávka':<15} {'Zlom':<12} Stav")
print("-" * 95)
for cip, pri, jme, pd, zd, zdroj, stav in vysledky:
zlom_str = str(zd) if zd else ""
print(f"{pri:<25} {jme:<20} {cip:<12} {str(pd):<15} {zlom_str:<12} {stav}")
@@ -0,0 +1,219 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys as _sys
_sys.stdout.reconfigure(encoding="utf-8", errors="replace")
_sys.stderr.reconfigure(encoding="utf-8", errors="replace")
"""
06_nasledny_lekar.py
====================
Pro všechny ukončené pacienty (103) se dotáže VZP ke dni ukonceni+1
na jejich nového praktického lékaře (odbornost 001).
Výsledek je jeden ze tří stavů:
nenalezen → pacient u VZP neexistuje (zemřel / přestal být pojištěný)
bez_lekare → pacient existuje, ale nemá GP (dosud se nepřehlásil)
prehlasil → přehlásil se k novému lékaři (ukládáme ICP, jméno, datum)
Ukládá do tabulky seznam_pojistencu_nasledny_lekar.
Přeskočí pacienty, kteří tam už jsou (resumovatelný běh).
"""
import sys
import time
import xml.etree.ElementTree as ET
from datetime import date, timedelta
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "Knihovny"))
from mysql_db import connect_mysql
from vzpb2b_client import VZPB2BClient
PFX_PATH = str(Path(__file__).resolve().parents[1] / "Certificates" / "picka.pfx")
PFX_PASSWORD = "Vlado7309208104+"
ICZ = "09305000"
API_PAUSE = 2
CREATE_SQL = """
CREATE TABLE IF NOT EXISTS seznam_pojistencu_nasledny_lekar (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
cip VARCHAR(12) NOT NULL,
prijmeni VARCHAR(60) NOT NULL,
jmeno VARCHAR(40) NOT NULL,
datum_ukonceni DATE NOT NULL COMMENT 'Datum ukončení u nás',
datum_dotazu DATE NOT NULL COMMENT 'Dotaz k datu ukonceni+1',
stav_vzp VARCHAR(20) NOT NULL COMMENT 'nenalezen / bez_lekare / prehlasil',
stav_vyrizeni VARCHAR(10) NULL COMMENT 'stavVyrizeniPozadavku z VZP',
novy_icp VARCHAR(20) NULL,
novy_icz VARCHAR(20) NULL,
novy_nazev VARCHAR(200) NULL COMMENT 'nazevSZZ — jméno lékaře',
novy_ordinace VARCHAR(200) NULL COMMENT 'nazevICP — název ordinace',
datum_prehlaseni DATE NULL COMMENT 'datumRegistrace u nového lékaře',
dotazeno_dne DATE NOT NULL,
UNIQUE KEY uq_cip (cip)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COMMENT='Stav pojištěnce ke dni ukončení registrace u naší ordinace';
"""
NS = "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1"
def parse_nasledny(xml_text: str) -> dict:
"""
Vrátí dict s klíči: stav_vzp, stav_vyrizeni, novy_icp, novy_icz,
novy_nazev, novy_ordinace, datum_prehlaseni.
"""
try:
root = ET.fromstring(xml_text)
except ET.ParseError:
return {"stav_vzp": "chyba_xml", "stav_vyrizeni": None,
"novy_icp": None, "novy_icz": None, "novy_nazev": None,
"novy_ordinace": None, "datum_prehlaseni": None}
def find(el, tag):
e = el.find(f"{{{NS}}}{tag}")
return e.text.strip() if e is not None and e.text else None
stav_vyrizeni = find(root, "stavVyrizeniPozadavku")
# Hledám odbornost 001 s jiným lékařem
odbornosti = root.findall(f".//{{{NS}}}odbornost")
for odb in odbornosti:
# Vnější element — má ICZ
icz = find(odb, "ICZ")
icp = find(odb, "ICP")
if not icp:
continue
# Ověřím odbornost (vnořený subelement)
sub = odb.find(f"{{{NS}}}odbornost")
if sub is None:
continue
kod = find(sub, "kod")
if kod != "001":
continue
# Nalezen nový GP
dr = date.fromisoformat(find(odb, "datumRegistrace")) \
if find(odb, "datumRegistrace") else None
return {
"stav_vzp": "prehlasil",
"stav_vyrizeni": stav_vyrizeni,
"novy_icp": icp,
"novy_icz": icz,
"novy_nazev": find(odb, "nazevSZZ"),
"novy_ordinace": find(odb, "nazevICP"),
"datum_prehlaseni": dr,
}
# Žádná odbornost 001 nenalezena
if stav_vyrizeni == "0":
stav = "nenalezen"
elif stav_vyrizeni == "1":
stav = "bez_lekare"
else:
# Zkusím ještě — pokud jsou nějaké záznamy ale ne 001, je to bez_lekare
stav = "nenalezen" if not odbornosti else "bez_lekare"
return {"stav_vzp": stav, "stav_vyrizeni": stav_vyrizeni,
"novy_icp": None, "novy_icz": None, "novy_nazev": None,
"novy_ordinace": None, "datum_prehlaseni": None}
# ── Načtení ukončených pacientů ───────────────────────────────────────────────
conn = connect_mysql()
cur = conn.cursor()
cur.execute(CREATE_SQL)
# Ukončení z vzp_registrace_lekari
cur.execute("""
SELECT r.rc, r.prijmeni, r.jmeno, r.datum_ukonceni
FROM vzp_registrace_lekari r
INNER JOIN (
SELECT rc, MAX(k_datu) mk
FROM vzp_registrace_lekari
WHERE kod_odbornosti='001' AND ICP='09305001' AND ma_lekare=1
GROUP BY rc
) l ON r.rc=l.rc AND r.k_datu=l.mk
WHERE r.kod_odbornosti='001' AND r.ICP='09305001'
AND r.datum_ukonceni < '3000-01-01' AND r.datum_ukonceni < CURDATE()
""")
pacienti = [(r[0], r[1] or "", r[2] or "", r[3]) for r in cur.fetchall()]
# Doplním ze zlomů (13 nespárovaných)
cur.execute("""
SELECT cip, prijmeni, jmeno, zlom_datum
FROM seznam_pojistencu_zlomy
WHERE stav='ukončen' AND zlom_datum IS NOT NULL AND zlom_datum < CURDATE()
""")
for r in cur.fetchall():
if r[0] not in {p[0] for p in pacienti}:
pacienti.append((r[0], r[1], r[2], r[3]))
# Přeskočím již zpracované
cur.execute("SELECT cip FROM seznam_pojistencu_nasledny_lekar")
hotovi = {r[0] for r in cur.fetchall()}
ke_zpracovani = [(c, p, j, d) for c, p, j, d in pacienti if c not in hotovi]
print(f"Ukončených celkem: {len(pacienti)}")
print(f"Již zpracováno: {len(hotovi)}")
print(f"Ke zpracování: {len(ke_zpracovani)}")
if not ke_zpracovani:
print("Vše již zpracováno.")
cur.close(); conn.close(); sys.exit(0)
vzp = VZPB2BClient("prod", PFX_PATH, PFX_PASSWORD, icz=ICZ)
# ── Hlavní smyčka ─────────────────────────────────────────────────────────────
print()
sirka = max(len(f"{p} {j}") for _, p, j, _ in ke_zpracovani) + 2
for i, (cip, pri, jme, du) in enumerate(ke_zpracovani, 1):
datum_dotazu = du + timedelta(days=1)
jmeno_str = f"{pri} {jme}"
print(f"[{i:>3}/{len(ke_zpracovani)}] {jmeno_str:<{sirka}} ({cip}) k {datum_dotazu}", end=" ", flush=True)
try:
xml = vzp.registrace_lekare(cip, datum_dotazu.isoformat(), odbornosti=["001"])
time.sleep(API_PAUSE)
vysl = parse_nasledny(xml)
except Exception as e:
print(f"CHYBA: {e}")
vysl = {"stav_vzp": "chyba", "stav_vyrizeni": str(e),
"novy_icp": None, "novy_icz": None, "novy_nazev": None,
"novy_ordinace": None, "datum_prehlaseni": None}
# Výpis
stav = vysl["stav_vzp"]
if stav == "prehlasil":
print(f"→ přehlásil se: {vysl['novy_nazev']} (ICP {vysl['novy_icp']}, od {vysl['datum_prehlaseni']})")
elif stav == "bez_lekare":
print("→ bez nového lékaře")
elif stav == "nenalezen":
print("→ nenalezen (zemřel / nepojištěný)")
else:
print(f"{stav}")
cur.execute("""
INSERT INTO seznam_pojistencu_nasledny_lekar
(cip, prijmeni, jmeno, datum_ukonceni, datum_dotazu, stav_vzp,
stav_vyrizeni, novy_icp, novy_icz, novy_nazev, novy_ordinace,
datum_prehlaseni, dotazeno_dne)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
datum_ukonceni=VALUES(datum_ukonceni),
datum_dotazu=VALUES(datum_dotazu),
stav_vzp=VALUES(stav_vzp), stav_vyrizeni=VALUES(stav_vyrizeni),
novy_icp=VALUES(novy_icp), novy_icz=VALUES(novy_icz),
novy_nazev=VALUES(novy_nazev), novy_ordinace=VALUES(novy_ordinace),
datum_prehlaseni=VALUES(datum_prehlaseni),
dotazeno_dne=VALUES(dotazeno_dne)
""", (cip, pri, jme, du, datum_dotazu, stav,
vysl["stav_vyrizeni"], vysl["novy_icp"], vysl["novy_icz"],
vysl["novy_nazev"], vysl["novy_ordinace"], vysl["datum_prehlaseni"],
date.today()))
cur.close()
conn.close()
# ── Souhrn ────────────────────────────────────────────────────────────────────
print(f"\nHotovo. Zpracováno {len(ke_zpracovani)} pacientů.")
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys as _sys
_sys.stdout.reconfigure(encoding="utf-8", errors="replace")
_sys.stderr.reconfigure(encoding="utf-8", errors="replace")
"""
07_doplnit_zahajeni.py
======================
Pro nové pacienty (první dávka po 31.12.2024) kteří jsou již ukončeni
a nemají záznam v vzp_registrace_lekari pro naše ICP=09305001,
dotáže se VZP k datu jejich první dávky — tehdy tam ještě byli
a odpověď obsahuje datumZahajeni registrace u nás.
Výsledek uloží do vzp_registrace_lekari (stejná tabulka jako kdojelekar).
"""
import sys
import time
from datetime import date
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "Knihovny"))
from mysql_db import connect_mysql
from vzpb2b_client import VZPB2BClient
PFX_PATH = str(Path(__file__).resolve().parents[1] / "Certificates" / "picka.pfx")
PFX_PASSWORD = "Vlado7309208104+"
ICZ = "09305000"
NASA_ICP = "09305001"
API_PAUSE = 2
PRVNI_DAVKA = date(2024, 12, 1)
# ── Načtení kandidátů ─────────────────────────────────────────────────────────
conn = connect_mysql()
cur = conn.cursor()
# Noví pacienti (prvni_davka > 31.12.2024) bez záznamu v registrace pro naše ICP
cur.execute("""
SELECT
s.cip,
MIN(s.prijmeni) AS prijmeni,
MIN(s.jmeno) AS jmeno,
MIN(DATE(CONCAT(s.davka_rok,'-',LPAD(s.davka_mesic,2,'0'),'-',LPAD(s.davka_den,2,'0')))) AS prvni_davka
FROM seznam_pojistencu_davky s
WHERE s.pojistovna = '111'
GROUP BY s.cip
HAVING prvni_davka > %s
""", (PRVNI_DAVKA,))
vsichni_novi = {r[0]: (r[1], r[2], r[3]) for r in cur.fetchall()}
# Kteří z nich už mají záznam v registrace pro naše ICP
cur.execute("""
SELECT DISTINCT rc FROM vzp_registrace_lekari
WHERE ICP = %s AND kod_odbornosti = '001' AND ma_lekare = 1
""", (NASA_ICP,))
uz_maji = {r[0] for r in cur.fetchall()}
kandidati = {
cip: info
for cip, info in vsichni_novi.items()
if cip not in uz_maji
}
print(f"Noví pacienti celkem: {len(vsichni_novi)}")
print(f"Již mají datum zahájení: {len(uz_maji & vsichni_novi.keys())}")
print(f"Ke doplnění: {len(kandidati)}")
if not kandidati:
print("Vše je kompletní.")
cur.close(); conn.close(); sys.exit(0)
vzp = VZPB2BClient("prod", PFX_PATH, PFX_PASSWORD, icz=ICZ)
print()
sirka = max(len(f"{p} {j}") for p, j, _ in kandidati.values()) + 2
for i, (cip, (prijmeni, jmeno, prvni_davka)) in enumerate(kandidati.items(), 1):
print(f"[{i:>2}/{len(kandidati)}] {prijmeni+' '+jmeno:<{sirka}} ({cip}) k {prvni_davka}", end=" ", flush=True)
try:
xml = vzp.registrace_lekare(cip, prvni_davka.isoformat(), odbornosti=["001"])
time.sleep(API_PAUSE)
zaznamy = vzp.parse_registrace_lekare(xml)
except Exception as e:
print(f"CHYBA: {e}")
continue
nas_zaznam = next(
(z for z in zaznamy
if z.get("ma_lekare") and z.get("ICP") == NASA_ICP and z.get("kod_odbornosti") == "001"),
None
)
if not nas_zaznam:
print("→ nenalezen k tomuto datu")
continue
def to_date(s):
try:
return date.fromisoformat(s) if s else None
except ValueError:
return None
poj_el = nas_zaznam
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
datum_zahajeni = VALUES(datum_zahajeni),
datum_ukonceni = VALUES(datum_ukonceni),
nazev_lekare = VALUES(nazev_lekare),
nazev_zzz = VALUES(nazev_zzz)
""", (
cip, prijmeni, jmeno, prvni_davka, "001", 1,
nas_zaznam.get("ICZ"), nas_zaznam.get("ICP"),
nas_zaznam.get("nazev_lekare"), nas_zaznam.get("nazev_zzz"),
nas_zaznam.get("poj_kod"), nas_zaznam.get("poj_zkratka"),
to_date(nas_zaznam.get("datum_registrace")),
to_date(nas_zaznam.get("datum_zahajeni")),
to_date(nas_zaznam.get("datum_ukonceni")),
nas_zaznam.get("stav_vyrizeni"),
))
dz = nas_zaznam.get("datum_zahajeni") or "?"
du = nas_zaznam.get("datum_ukonceni") or "?"
print(f"→ zahájení: {dz} ukončení: {du}")
cur.close()
conn.close()
print(f"\nHotovo.")
@@ -0,0 +1,361 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys as _sys
_sys.stdout.reconfigure(encoding="utf-8", errors="replace")
_sys.stderr.reconfigure(encoding="utf-8", errors="replace")
"""
05_report_excel.py
==================
Kompletní Excel report všech VZP pojištěnců z dávek.
Kombinuje čtyři zdroje:
- seznam_pojistencu_davky → první/poslední dávka, zda v aktuální
- vzp_registrace_lekari → datum_ukonceni od VZP (989 pacientů)
- seznam_pojistencu_zlomy → bod zlomu pro 13 nespárovaných
- seznam_pojistencu_nasledny_lekar → nový lékař / nenalezen po ukončení
Listy:
1. Všichni pojištěnci kompletní přehled, řazeno příjmení
2. Aktivní stále registrováni
3. Ukončení registrace ukončena + nový lékař / osud
4. Nenalezeni bez dostatečných dat
"""
import sys
from datetime import date
from pathlib import Path
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "Knihovny"))
from mysql_db import connect_mysql
TODAY = date.today()
OUTPUT = Path(__file__).parent / "report_pojistenci.xlsx"
# ── Barvy ─────────────────────────────────────────────────────────────────────
C_HDR_DARK = "1F4E79"
C_HDR_MED = "2E75B6"
C_AKTIVNI = "E2EFDA" # zelená
C_UKONCEN = "FCE4D6" # lososová
C_NENALEZEN = "FFF2CC" # žlutá
C_ZEBRA = "F2F2F2"
C_TITLE_AKT = "375623"
C_TITLE_UKO = "C55A11"
C_TITLE_NEN = "7F6000"
THIN = Side(style="thin", color="BFBFBF")
def border():
return Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
def hdr(cell, text, bg=C_HDR_DARK, fg="FFFFFF", size=10):
cell.value = text
cell.font = Font(bold=True, color=fg, size=size)
cell.fill = PatternFill("solid", fgColor=bg)
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
cell.border = border()
def cell(ws, row, col, value, bg=None, bold=False, align="left", fmt=None, color="000000"):
c = ws.cell(row, col, value)
c.font = Font(bold=bold, size=10, color=color)
c.alignment = Alignment(horizontal=align, vertical="center")
c.border = border()
if bg:
c.fill = PatternFill("solid", fgColor=bg)
if fmt:
c.number_format = fmt
return c
def title_row(ws, row, text, ncols, bg, fg="FFFFFF", height=26):
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=ncols)
c = ws.cell(row, 1)
c.value = text
c.font = Font(bold=True, size=13, color=fg)
c.fill = PatternFill("solid", fgColor=bg)
c.alignment = Alignment(horizontal="center", vertical="center")
ws.row_dimensions[row].height = height
def autofit(ws, widths):
for i, w in enumerate(widths, 1):
ws.column_dimensions[get_column_letter(i)].width = w
# ── Načtení dat ───────────────────────────────────────────────────────────────
print("Načítám data z DB ...")
conn = connect_mysql()
cur = conn.cursor()
# 1. Ze seznam_pojistencu_davky: rozsah přítomnosti + počet dávek
cur.execute("""
SELECT
cip,
MIN(prijmeni) AS prijmeni,
MIN(jmeno) AS jmeno,
MIN(DATE(CONCAT(davka_rok,'-',LPAD(davka_mesic,2,'0'),'-',LPAD(davka_den,2,'0')))) AS prvni_davka,
MAX(DATE(CONCAT(davka_rok,'-',LPAD(davka_mesic,2,'0'),'-',LPAD(davka_den,2,'0')))) AS posledni_davka,
COUNT(DISTINCT CONCAT(davka_rok,davka_mesic)) AS pocet_davek
FROM seznam_pojistencu_davky
WHERE pojistovna='111'
GROUP BY cip
""")
seznam = {r[0]: {"prijmeni": r[1], "jmeno": r[2],
"prvni": r[3], "posledni": r[4], "pocet_davek": r[5]}
for r in cur.fetchall()}
# Nejnovější datum dávky (pro sloupec "v aktuální dávce")
cur.execute("""
SELECT MAX(DATE(CONCAT(davka_rok,'-',LPAD(davka_mesic,2,'0'),'-',LPAD(davka_den,2,'0'))))
FROM seznam_pojistencu_davky WHERE pojistovna='111'
""")
nejnovejsi_davka = cur.fetchone()[0]
cur.execute("""
SELECT DISTINCT cip FROM seznam_pojistencu_davky
WHERE pojistovna='111'
AND CONCAT(davka_rok,LPAD(davka_mesic,2,'0'),LPAD(davka_den,2,'0')) = (
SELECT MAX(CONCAT(davka_rok,LPAD(davka_mesic,2,'0'),LPAD(davka_den,2,'0')))
FROM seznam_pojistencu_davky WHERE pojistovna='111'
)
""")
v_aktualni = {r[0] for r in cur.fetchall()}
# 2. Z vzp_registrace_lekari: nejnovější záznam na pacienta (u nás, odb 001)
cur.execute("""
SELECT r.rc, r.datum_zahajeni, r.datum_ukonceni, r.k_datu
FROM vzp_registrace_lekari r
INNER JOIN (
SELECT rc, MAX(k_datu) AS max_k
FROM vzp_registrace_lekari
WHERE kod_odbornosti='001' AND ICP='09305001' AND ma_lekare=1
GROUP BY rc
) latest ON r.rc = latest.rc AND r.k_datu = latest.max_k
WHERE r.kod_odbornosti='001' AND r.ICP='09305001' AND r.ma_lekare=1
""")
registrace = {r[0]: {"zahajeni": r[1], "ukonceni": r[2], "k_datu": r[3]}
for r in cur.fetchall()}
# 3. Ze seznam_pojistencu_zlomy (13 nespárovaných)
cur.execute("SELECT cip, zlom_datum, zlom_zdroj, stav FROM seznam_pojistencu_zlomy")
zlomy = {r[0]: {"ukonceni": r[1], "zdroj": r[2], "stav": r[3]}
for r in cur.fetchall()}
# 4. Následný lékař po ukončení
cur.execute("""
SELECT cip, stav_vzp, novy_nazev, novy_ordinace, novy_icp, datum_prehlaseni
FROM seznam_pojistencu_nasledny_lekar
""")
nasledni = {r[0]: {"stav_vzp": r[1], "novy_nazev": r[2],
"novy_ordinace": r[3], "novy_icp": r[4],
"datum_prehlaseni": r[5]}
for r in cur.fetchall()}
cur.close()
conn.close()
# ── Sestavení řádků ───────────────────────────────────────────────────────────
print(f"Pacientů celkem: {len(seznam)}")
radky = []
for cip, s in seznam.items():
v_akt = cip in v_aktualni
if cip in registrace:
reg = registrace[cip]
du = reg["ukonceni"]
zdroj = f"vzp_registrace ({reg['k_datu']})"
elif cip in zlomy:
z = zlomy[cip]
du = z["ukonceni"]
zdroj = z["zdroj"]
else:
du = None
zdroj = ""
if du is None:
stav = "aktivní"
elif du.year >= 3000:
stav = "aktivní"
du = None # nezobrazujeme 3000-01-01
elif du >= TODAY:
stav = "aktivní"
else:
stav = "ukončen"
if zdroj == "" and not v_akt:
stav = "nenalezen"
# Následný lékař
nl = nasledni.get(cip)
if nl:
po_ukonceni = nl["stav_vzp"] # prehlasil / nenalezen / bez_lekare
novy_lekar = nl["novy_nazev"] or nl["novy_ordinace"] or ""
if nl["novy_icp"]:
novy_lekar += f" (ICP {nl['novy_icp']})"
datum_prehl = nl["datum_prehlaseni"]
else:
po_ukonceni = ""
novy_lekar = ""
datum_prehl = None
radky.append({
"cip": cip,
"prijmeni": s["prijmeni"],
"jmeno": s["jmeno"],
"prvni": s["prvni"],
"posledni": s["posledni"],
"pocet_davek": s["pocet_davek"],
"v_aktualni": v_akt,
"ukonceni": du,
"zdroj": zdroj,
"stav": stav,
"po_ukonceni": po_ukonceni,
"novy_lekar": novy_lekar,
"datum_prehl": datum_prehl,
})
DATUM_REGISTRACE_OD = date(2025, 1, 1)
radky.sort(key=lambda r: (r["prijmeni"], r["jmeno"]))
aktivni = [r for r in radky if r["stav"] == "aktivní"]
ukonceni = sorted([r for r in radky if r["stav"] == "ukončen"],
key=lambda r: r["ukonceni"] or date.min)
nenalezeni = [r for r in radky if r["stav"] == "nenalezen"]
# Noví: datum_zahajeni registrace u nás >= 01.01.2025, řazeno chronologicky
novi = sorted(
[r for r in radky
if registrace.get(r["cip"], {}).get("zahajeni") is not None
and registrace[r["cip"]]["zahajeni"] >= DATUM_REGISTRACE_OD],
key=lambda r: (registrace[r["cip"]]["zahajeni"], r["prijmeni"], r["jmeno"])
)
print(f" Aktivní: {len(aktivni)}")
print(f" Ukončení: {len(ukonceni)}")
print(f" Noví: {len(novi)}")
print(f" Nenalezeni: {len(nenalezeni)}")
# ── Excel ─────────────────────────────────────────────────────────────────────
SLOUPCE_ALL = ["Příjmení", "Jméno", "ČIP", "První\ndávka", "Poslední\ndávka",
"Počet\ndávek", "V aktuální\ndávce", "Ukončení\nregistrace",
"Stav", "Po ukončení", "Nový lékař / poznámka", "Datum\npřehlášení"]
WIDTHS_ALL = [24, 18, 13, 12, 13, 9, 10, 14, 10, 13, 40, 13]
SLOUPCE = SLOUPCE_ALL # alias pro funkci zapsat_list
WIDTHS = WIDTHS_ALL
NCOLS = len(SLOUPCE)
DATE_FMT = "DD.MM.YYYY"
def zapsat_list(ws, nadpis, bg_title, seznam_radku, stav_bg):
ws.freeze_panes = "A3"
title_row(ws, 1, nadpis, NCOLS, bg_title)
ws.row_dimensions[2].height = 30
for col, h in enumerate(SLOUPCE, 1):
hdr(ws.cell(2, col), h)
autofit(ws, WIDTHS)
PO_CLR = {"prehlasil": "375623", "nenalezen": "C55A11",
"bez_lekare": "7F6000", "": "000000"}
PO_TXT = {"prehlasil": "přehlásil se", "nenalezen": "nenalezen",
"bez_lekare": "bez lékaře", "": ""}
for ri, r in enumerate(seznam_radku, 3):
bg = stav_bg if ri % 2 == 0 else None
cell(ws, ri, 1, r["prijmeni"], bg)
cell(ws, ri, 2, r["jmeno"], bg)
cell(ws, ri, 3, r["cip"], bg, align="center")
cell(ws, ri, 4, r["prvni"], bg, align="center", fmt=DATE_FMT)
cell(ws, ri, 5, r["posledni"], bg, align="center", fmt=DATE_FMT)
cell(ws, ri, 6, r["pocet_davek"], bg, align="right")
akt_txt = "" if r["v_aktualni"] else ""
akt_clr = "375623" if r["v_aktualni"] else "C55A11"
cell(ws, ri, 7, akt_txt, bg, bold=True, align="center", color=akt_clr)
cell(ws, ri, 8, r["ukonceni"], bg, align="center", fmt=DATE_FMT)
cell(ws, ri, 9, r["stav"], bg, align="center", bold=True,
color=("375623" if r["stav"]=="aktivní" else
"C55A11" if r["stav"]=="ukončen" else "7F6000"))
po = r.get("po_ukonceni", "")
cell(ws, ri, 10, PO_TXT.get(po, po), bg, align="center", bold=bool(po),
color=PO_CLR.get(po, "000000"))
cell(ws, ri, 11, r.get("novy_lekar", ""), bg)
cell(ws, ri, 12, r.get("datum_prehl"), bg, align="center", fmt=DATE_FMT)
C_REGISTRACE = "DAEEF3" # světle modrá
C_TITLE_REG = "17375E"
SLOUPCE_REG = ["Příjmení", "Jméno", "ČIP", "Zahájení\nregistrace",
"Ukončení\nregistrace", "Počet\ndávek", "V aktuální\ndávce",
"Stav", "Po ukončení", "Nový lékař / poznámka", "Datum\npřehlášení"]
WIDTHS_REG = [24, 18, 13, 16, 14, 9, 10, 10, 13, 40, 13]
PO_CLR_REG = {"prehlasil": "375623", "nenalezen": "C55A11",
"bez_lekare": "7F6000", "": "000000"}
PO_TXT_REG = {"prehlasil": "přehlásil se", "nenalezen": "nenalezen",
"bez_lekare": "bez lékaře", "": ""}
def zapsat_registrace(ws, nadpis, seznam_radku):
ncols = len(SLOUPCE_REG)
ws.freeze_panes = "A3"
title_row(ws, 1, nadpis, ncols, C_TITLE_REG)
ws.row_dimensions[2].height = 30
for col, h in enumerate(SLOUPCE_REG, 1):
hdr(ws.cell(2, col), h, bg=C_TITLE_REG)
autofit(ws, WIDTHS_REG)
for ri, r in enumerate(seznam_radku, 3):
bg = C_REGISTRACE if ri % 2 == 0 else None
cell(ws, ri, 1, r["prijmeni"], bg)
cell(ws, ri, 2, r["jmeno"], bg)
cell(ws, ri, 3, r["cip"], bg, align="center")
zahajeni = registrace.get(r["cip"], {}).get("zahajeni")
cell(ws, ri, 4, zahajeni, bg, align="center", fmt=DATE_FMT)
cell(ws, ri, 5, r["ukonceni"], bg, align="center", fmt=DATE_FMT)
cell(ws, ri, 6, r["pocet_davek"],bg, align="right")
akt_txt = "" if r["v_aktualni"] else ""
akt_clr = "375623" if r["v_aktualni"] else "C55A11"
cell(ws, ri, 7, akt_txt, bg, bold=True, align="center", color=akt_clr)
cell(ws, ri, 8, r["stav"], bg, align="center", bold=True,
color=("375623" if r["stav"] == "aktivní" else "C55A11"))
po = r.get("po_ukonceni", "")
cell(ws, ri, 9, PO_TXT_REG.get(po, po), bg, align="center", bold=bool(po),
color=PO_CLR_REG.get(po, "000000"))
cell(ws, ri, 10, r.get("novy_lekar", ""), bg)
cell(ws, ri, 11, r.get("datum_prehl"), bg, align="center", fmt=DATE_FMT)
wb = openpyxl.Workbook()
# List 1 Všichni
ws1 = wb.active
ws1.title = "Všichni pojištěnci"
zapsat_list(ws1,
f"VZP pojištěnci — kompletní přehled ({TODAY.strftime('%d.%m.%Y')}) | "
f"celkem: {len(radky)} | aktivní: {len(aktivni)} | ukončení: {len(ukonceni)}",
C_HDR_DARK, radky, C_ZEBRA)
# List 2 Aktivní
ws2 = wb.create_sheet("Aktivní")
zapsat_list(ws2,
f"Aktivní pojištěnci ({TODAY.strftime('%d.%m.%Y')}) — celkem: {len(aktivni)}",
C_TITLE_AKT, aktivni, C_AKTIVNI)
# List 3 Ukončení
ws3 = wb.create_sheet("Ukončení")
zapsat_list(ws3,
f"Ukončená registrace — celkem: {len(ukonceni)}",
C_TITLE_UKO, ukonceni, C_UKONCEN)
# List 4 Registrace (noví po 31.12.2024)
ws4 = wb.create_sheet("Registrace")
zapsat_registrace(ws4,
f"Noví pojištěnci — datum zahájení registrace od 1.1.2025 — celkem: {len(novi)}",
novi)
# List 5 Nenalezeni
if nenalezeni:
ws5 = wb.create_sheet("Nenalezeni")
zapsat_list(ws5,
f"Bez dostatečných dat — celkem: {len(nenalezeni)}",
C_TITLE_NEN, nenalezeni, C_NENALEZEN)
wb.save(OUTPUT)
print(f"\nExcel uložen: {OUTPUT}")
@@ -0,0 +1,167 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys as _sys
_sys.stdout.reconfigure(encoding="utf-8", errors="replace")
_sys.stderr.reconfigure(encoding="utf-8", errors="replace")
"""
zadej_davku.py
==============
Odešle asynchronní požadavek na VZP B2B službu SeznamRegPojistencuB2B
(seznam zakapitovaných/registrovaných pojištěnců za dané období).
Použití:
python zadej_davku.py [mesic] [rok]
python zadej_davku.py 2 2025 # únor 2025
python zadej_davku.py # předchozí měsíc (výchozí)
Výstup:
- korelační ID zprávy (pro pozdější spárování asynchronní odpovědi)
- stavVyrizeniPozadavku=2 znamená "přijato, VZP zpracovává"
- finální odpověď přijde asynchronně na AS2 endpoint partnera
"""
import sys
import uuid
import argparse
from pathlib import Path
from datetime import date, timedelta
from requests_pkcs12 import Pkcs12Adapter
import requests
import xml.etree.ElementTree as ET
# ── KONFIGURACE ───────────────────────────────────────────────────────────────
PFX_PATH = Path(__file__).resolve().parent.parent / "Certificates" / "picka.pfx"
PFX_PASSWORD = "Vlado7309208104+"
ICZ = "09305000"
ENV = "prod"
NS_VZP = "http://xmlns.gemsystem.cz/SeznamRegPojistencuB2B"
NS_COMMON = "http://xmlns.gemsystem.cz/CommonB2B"
NS_SOAP = "http://schemas.xmlsoap.org/soap/envelope/"
ENDPOINT_SIMU = "https://simu.b2b.vzp.cz/B2BProxy/HttpProxy/SIMUSeznamRegPojistencuB2B?sluzba=SIMUSeznamRegPojistencuB2B"
ENDPOINT_PROD = "https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/SeznamRegPojistencuB2B"
# ── ARGUMENTY ────────────────────────────────────────────────────────────────
parser = argparse.ArgumentParser(description="Požadavek na seznam registrovaných pojištěnců VZP")
parser.add_argument("mesic", nargs="?", type=int, help="Měsíc (1-12)")
parser.add_argument("rok", nargs="?", type=int, help="Rok (např. 2025)")
parser.add_argument("--simu", action="store_true", help="Použít simulační prostředí")
parser.add_argument("--pdf", action="store_true", help="Výstup jako PDF (výchozí: text/plain)")
args = parser.parse_args()
# Výchozí: předchozí měsíc
if args.mesic and args.rok:
mesic, rok = args.mesic, args.rok
else:
prvni_tohoto = date.today().replace(day=1)
predchozi = prvni_tohoto - timedelta(days=1)
mesic, rok = predchozi.month, predchozi.year
format_vystupu = "application/pdf" if args.pdf else "text/plain"
endpoint = ENDPOINT_SIMU if args.simu else ENDPOINT_PROD
# ── KONTROLY ─────────────────────────────────────────────────────────────────
if not PFX_PATH.exists():
print(f"CHYBA: certifikát nenalezen: {PFX_PATH}")
sys.exit(1)
if not (1 <= mesic <= 12):
print(f"CHYBA: neplatný měsíc: {mesic}")
sys.exit(1)
# ── SESTAVENÍ POŽADAVKU ───────────────────────────────────────────────────────
id_zpravy = uuid.uuid4().hex[:12].upper()
soap = f"""<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="{NS_SOAP}"
xmlns:vzp="{NS_VZP}"
xmlns:com="{NS_COMMON}">
<soap:Header>
<com:idZpravy>{id_zpravy}</com:idZpravy>
<com:idSubjektu>
<com:icz>{ICZ}</com:icz>
</com:idSubjektu>
</soap:Header>
<soap:Body>
<vzp:seznamRegistrovanychPojistencuB2BPozadavek>
<vzp:idZpravy>{id_zpravy}</vzp:idZpravy>
<vzp:idSubjektu>{ICZ}</vzp:idSubjektu>
<vzp:typSubjektu>zp</vzp:typSubjektu>
<vzp:seznam>
<vzp:obdobiDavky>
<vzp:mesic>{mesic}</vzp:mesic>
<vzp:rok>{rok}</vzp:rok>
</vzp:obdobiDavky>
<vzp:formatVystupu>{format_vystupu}</vzp:formatVystupu>
</vzp:seznam>
</vzp:seznamRegistrovanychPojistencuB2BPozadavek>
</soap:Body>
</soap:Envelope>"""
# ── ODESLÁNÍ ─────────────────────────────────────────────────────────────────
print(f"Období: {mesic:02d}/{rok}")
print(f"Formát: {format_vystupu}")
print(f"Prostředí: {'SIMU' if args.simu else 'PROD'}")
print(f"IČZ: {ICZ}")
print(f"ID zprávy: {id_zpravy}")
print(f"Endpoint: {endpoint}")
print()
session = requests.Session()
session.mount("https://", Pkcs12Adapter(
pkcs12_filename=str(PFX_PATH),
pkcs12_password=PFX_PASSWORD
))
try:
resp = session.post(
endpoint,
data=soap.encode("utf-8"),
headers={"Content-Type": "text/xml; charset=utf-8", "SOAPAction": "process"},
timeout=30
)
except requests.RequestException as e:
print(f"CHYBA při spojení: {e}")
sys.exit(1)
print(f"HTTP status: {resp.status_code}")
print()
# ── PARSOVÁNÍ ODPOVĚDI ────────────────────────────────────────────────────────
if not resp.content:
print("→ Požadavek přijat (prázdná odpověď = asynchronní služba).")
print(f"→ VZP zpracuje a odešle výsledek na AS2 endpoint.")
print(f"→ ID zprávy pro spárování: {id_zpravy}")
else:
try:
root = ET.fromstring(resp.text)
NS = {"soap": NS_SOAP, "vzp": NS_VZP}
korelace = root.find(".//vzp:korelaceZpravy", NS)
text_odp = root.find(".//vzp:textOdpovedi", NS)
stav = root.find(".//vzp:stavVyrizeniPozadavku", NS)
print(f"Korelace zprávy: {korelace.text if korelace is not None else ''}")
print(f"Text odpovědi: {text_odp.text if text_odp is not None else ''}")
print(f"Stav vyřízení: {stav.text if stav is not None else ''}")
if stav is not None and stav.text == "2":
print()
print("→ VZP zpracovává — finální odpověď přijde asynchronně na AS2 endpoint.")
print(f"→ ID zprávy pro spárování: {id_zpravy}")
except ET.ParseError:
print("Nepodařilo se parsovat XML odpověď:")
print(resp.text)
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,135 @@
"""
Stahování seznamu registrovaných pojištěnců ČPZP.
Použij po 01_prihlaseni.py (ten uloží cpzp_cookies.json).
Co dělá:
- Načte cookies z cpzp_cookies.json
- Otevře prohlížeč jednou, projde všechny zadané měsíce
- Pro každý měsíc vyplní formulář, klikne Hledat, stáhne soubor
- Přeskočí měsíce kde soubor v cílovém adresáři už existuje
- Uloží jako: YYYY-MM-DD f205MMRR.123
NASTAVENÍ:
OD_MESIC / OD_ROK — první měsíc rozsahu
DO_MESIC / DO_ROK — poslední měsíc rozsahu (včetně)
"""
import glob
import json
import os
import sys
import time
from datetime import date
from playwright.sync_api import sync_playwright
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
from Knihovny.najdi_dropbox import get_dropbox_root
OD_MESIC = 12
OD_ROK = 2024
DO_MESIC = 3
DO_ROK = 2026
BASE_URL = "https://portal.cpzp.cz"
COOKIES_FILE = os.path.join(os.path.dirname(__file__), "..", "..", "StahováníZpráv", "205 ČPZP", "cpzp_cookies.json")
DEST_DIR = os.path.join(
get_dropbox_root(),
"Ordinace", "Dokumentace_ke_zpracování", "Zúčtovací zprávy", "205 ČPZP",
)
def mesice_v_rozsahu(od_m, od_r, do_m, do_r):
"""Generuje (mesic, rok) od od_m/od_r do do_m/do_r včetně."""
m, r = od_m, od_r
while (r, m) <= (do_r, do_m):
yield m, r
m += 1
if m > 12:
m = 1
r += 1
def uz_stazeno(mesic: int, rok: int) -> bool:
"""Vrátí True pokud soubor pro daný měsíc/rok už existuje v DEST_DIR."""
mm = f"{mesic:02d}"
rr = str(rok)[-2:]
pattern = os.path.join(DEST_DIR, f"* f205{mm}{rr}.*")
return bool(glob.glob(pattern))
def stahni_mesic(page, mesic: int, rok: int) -> bool:
"""Stáhne soubor pro jeden měsíc. Vrátí True pokud staženo."""
today = date.today().strftime("%Y-%m-%d")
if uz_stazeno(mesic, rok):
print(f" [{mesic:02d}/{rok}] přeskočeno — soubor už existuje")
return False
# Vyplň formulář
inputs = page.query_selector_all("input[type=text]")
if len(inputs) < 2:
print(f" [{mesic:02d}/{rok}] CHYBA — inputy nenalezeny")
return False
inputs[0].fill(str(mesic))
inputs[1].fill(str(rok))
page.get_by_text("Hledat", exact=True).click()
page.wait_for_load_state("networkidle")
dl_selector = "a:has-text('Seznam registrovaných pojištěnců')"
if not page.query_selector(dl_selector):
print(f" [{mesic:02d}/{rok}] CHYBA — download odkaz nenalezen")
return False
with page.expect_download() as dl_info:
page.click(dl_selector)
download = dl_info.value
original_name = download.suggested_filename
dest_path = os.path.join(DEST_DIR, f"{today} {original_name}")
download.save_as(dest_path)
print(f" [{mesic:02d}/{rok}] OK — {os.path.basename(dest_path)}")
return True
def hlavni() -> None:
if not os.path.exists(COOKIES_FILE):
raise SystemExit(f"Soubor s cookies nenalezen: {COOKIES_FILE}\nNejdřív spusť 01_prihlaseni.py")
with open(COOKIES_FILE, encoding="utf-8") as f:
cookies = json.load(f)
os.makedirs(DEST_DIR, exist_ok=True)
mesice = list(mesice_v_rozsahu(OD_MESIC, OD_ROK, DO_MESIC, DO_ROK))
print(f"Celkem měsíců: {len(mesice)} ({OD_MESIC:02d}/{OD_ROK} {DO_MESIC:02d}/{DO_ROK})")
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
context.add_cookies(cookies)
page = context.new_page()
print("Otevírám stránku klientely...")
page.goto(f"{BASE_URL}/app/prohlizeni-klientely/")
page.wait_for_load_state("networkidle")
if "frmPrihlasCert" in page.content():
raise SystemExit("Cookies expirovala — nejdřív spusť 01_prihlaseni.py")
stazeno = 0
for mesic, rok in mesice:
if stahni_mesic(page, mesic, rok):
stazeno += 1
time.sleep(2)
browser.close()
print(f"\nHotovo: {stazeno} staženo, {len(mesice) - stazeno} přeskočeno.")
if __name__ == "__main__":
hlavni()
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

@@ -0,0 +1,756 @@
<!DOCTYPE html><html lang="cs"><head>
<meta charset="utf-8">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<title>
Prohlížení klientely - E-přepážka ČPZP
</title>
<!-- Libraries -->
<script>
CPZP = {
settings : {
certificateLoginKey : 'Prohlášení:'+ String.fromCharCode(13, 10) + 'Tímto se přihlašuji k Portálu ČPZP'+ String.fromCharCode(13, 10) + ''+ String.fromCharCode(13, 10) + 'Okamžik vygenerování tohoto prohlášení: 03.05.2026 10:37:40',
maxHeightSelectDropDown : null
},
runtimeConfig: {
logged : '1',
loggedUser: 'Michaela Buzalková',
useCertificate : true },
signerIsLoaded : false
};
</script>
<script type="text/javascript" src="/app/js/util.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/SimpleEventBroker.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/json3.min.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/lib/jquery/jquery-3.7.1.min.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/lib/jquery/jquery-migrate-3.4.1.min.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/lib/jquery/jquery-ui-1.13.2.min.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/jquery.ui.datepicker-cs.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/jquery.nicefileinput.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/jquery.loadmask.min.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/jquery.timer.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/jquery.cookie.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/lib_java_sign.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/lib_signer_utf8.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/browser.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/menu.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/help.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/form.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/generate-js/analytics.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/toastr.min.js?v=1v2.162.0"></script>
<script type="text/javascript" src="/app/js/prohlizeniklientely/list.js?v=1v2.162.0"></script>
<link rel="stylesheet" href="/app/css/style.css?v=v2.162.0" type="text/css">
<link rel="stylesheet" href="/app/css/style-print.css?v=v2.162.0" type="text/css" media="print">
<link rel="stylesheet" href="/app/extcss/jquery-ui-1.13.2.min.css?v=v2.162.0" type="text/css">
<link rel="stylesheet" href="/app/css/all.css?v=v2.162.0" type="text/css">
<link rel="stylesheet" href="/app/css/solid.css?v=v2.162.0" type="text/css">
<link rel="stylesheet" href="/app/extcss/toastr.min.css?v=v2.162.0" type="text/css">
<link rel="shortcut icon" href="/images/favicon.ico?v=v2.162.0">
<script>
/* (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-46493716-1', 'cpzp.cz');
ga('send', 'pageview'); */
</script>
</head>
<body>
<div class="main">
<div class="header">
<div class="logo-info">
<div class="logo">
<a href="/" class="logo" alt="Česká průmyslová zdravotní pojištovna" title="Úvodní stránka"></a>
<div class="code">
<div class="text">kód pojišťovny: </div>
<div class="number">205</div>
</div>
</div>
<div class="info-panel">
<div><span>Uživatel:</span>Michaela Buzalková</div>
<div class="message-state"><span>Nepřečtených zpráv:</span><a href="/app/schranka/">0</a></div>
<div class="current-date" style="display: none"><span>Datum:</span>3.5.2026 10:40:30</div>
<div class="logout">
<a href="/app/logout/?new=1" onclick="sessionStorage.clear();">Odhlásit</a>
</div>
</div>
<div class="search-box">
<div class="head-line">Elektronická přepážka ČPZP</div>
<div class="message">Právě se nacházíte v Portálu ČPZP. Kliknutím zvolíte Portál:</div>
<div class="zone-href">
<a href="/app/redirect/?destination=ozp">OZP</a> | <a href="/app/redirect/?destination=rbp">RBP</a> | <a href="/app/redirect/?destination=vozp">VoZP ČR</a> | <a href="/app/redirect/?destination=zpskoda">ZPŠ</a> | <a href="/app/redirect/?destination=spol">Společná zóna</a>
</div>
</div>
<div class="clearboth"></div>
</div>
</div>
<div class="clearboth"></div>
<div class="menu" role="navigation" aria-label="hlavní menu">
<ul class="navigation">
<li class="menu-main-?vod" id="menu1">
<a href="/app/" class="level1">úvod</a>
</li>
<li class="menu-main-poji?t?nci" id="menu2">
<a href="/app/pojistenci-rozcestnik/" class="level1"><span>pojištěnci</span></a>
<ul>
<li class="menu-main-osobn?nastaven?">
<a href="/app/osobni-nastaveni-rozcestnik/" class="level2">Osobní nastavení<img src="/app/img/dot-white.png" style="float:right;margin:3px 0 0 0"></a>
<ul class="submenu2">
<li class="menu-main-zm?naadresyakontaktn?ch?daj?">
<a href="/app/zmena-adresy-a-kontaktnich-udaju/pojistenec/">Změna adresy a kontaktních údajů</a>
</li>
<li class="sub-disabled menu-main-bankovn???ty">
<a href="/app/bankovni-ucty/">Bankovní účty</a>
</li>
<li class="menu-main-mojeopr?vn?n?">
<a href="/app/prehled-opravneni/pojistenec/">Moje oprávnění</a>
</li>
</ul>
</li>
<li class="menu-main-preventivn?programy">
<a href="/app/preventivni-programy-rozcestnik/" class="level2">Preventivní programy<img src="/app/img/dot-white.png" style="float:right;margin:3px 0 0 0"></a>
<ul class="submenu2">
<li class="sub-disabled menu-main-registracedobonus+">
<a href="/app/bonus-plus-registrace/">Registrace do Bonus +</a>
</li>
<li class="sub-disabled menu-main-v?piskontabonus+">
<a href="/app/bonus-plus-vypis/">Výpis konta Bonus +</a>
</li>
<li class="sub-disabled menu-main-propl?cen?preventivn?chprogram?">
<a href="/app/proplaceni-preventivnich-programu/">Proplácení preventivních programů</a>
</li>
<li class="menu-main-informaceopreventivn?chprogramech">
<a href="https://www.cpzp.cz/programy/" target="_self">Informace o preventivních programech</a>
</li>
</ul>
</li>
<li class="menu-main-preventivn?prohl?dky">
<a href="/app/preventivni-prohlidky-rozcestnik/" class="level2">Preventivní prohlídky<img src="/app/img/dot-white.png" style="float:right;margin:3px 0 0 0"></a>
<ul class="submenu2">
<li class="sub-disabled menu-main-p?ehledpreventivn?chprohl?dek">
<a href="/app/preventivni-prohlidky/">Přehled preventivních prohlídek</a>
</li>
<li class="sub-disabled menu-main-zas?l?n?preventivn?chsms">
<a href="/app/prevence-v-mobilu/">Zasílání preventivních SMS</a>
</li>
</ul>
</li>
<li class="sub-disabled menu-main-??dostopr?kazpoji?t?nce">
<a href="/app/prukaz-pojistence/" class="level2">Žádost o průkaz pojištěnce</a>
</li>
<li class="sub-disabled menu-main-osobn???etzdravotn?p??e">
<a href="/app/osobni-ucet-pojistence/" class="level2">Osobní účet zdravotní péče</a>
</li>
<li class="sub-disabled menu-main-z?znamodlouhodob?mpobytuvcizin?">
<a href="/app/zaznam-o-dlouhodobem-pobytu-v-cizine/" class="level2">Záznam o dlouhodobém pobytu v cizině</a>
</li>
<li class="menu-main-pojistn?">
<a href="/app/pojistne-rozcestnik/" class="level2">Pojistné<img src="/app/img/dot-white.png" style="float:right;margin:3px 0 0 0"></a>
<ul class="submenu2">
<li class="sub-disabled menu-main-stavpojistn?ho">
<a href="/app/stav-pojistneho/">Stav pojistného<img src="/app/img/dot-white.png" style="float:right;margin:3px 0 0 0"></a>
<ul>
<li class="menu-main-??dostovr?cen?p?eplatk?">
<a href="/app/stav-pojistneho/preplatek">Žádost o vrácení přeplatků</a>
</li>
<li class="menu-main-??dostop?e??tov?n?platby">
<a href="/app/stav-pojistneho/platba">Žádost o přeúčtování platby</a>
</li>
<li class="menu-main-??dostopotvrzen?obezdlu?nosti">
<a href="/app/stav-pojistneho/bezdluznost">Žádost o potvrzení o bezdlužnosti</a>
</li>
<li class="menu-main-??dostov?pispohled?vekaz?vazk?">
<a href="/app/stav-pojistneho/vypis">Žádost o výpis pohledávek a závazků</a>
</li>
</ul>
</li>
<li class="sub-disabled menu-main-pl?tcipojistn?ho">
<a href="/app/zadost-o-prehled-platcu-pojistneho/">Plátci pojistného</a>
</li>
<li class="sub-disabled menu-main-osv?-pod?n?p?ehleduzarok">
<a href="/app/prehled-osvc/2025/">OSVČ - podání přehledu za rok</a>
</li>
<li class="sub-disabled menu-main-osv?-p?ehledp?ijat?chplateb">
<a href="/app/prehled-plateb-osvc/">OSVČ - přehled přijatých plateb</a>
</li>
<li class="sub-disabled menu-main-osv?-??dostosn??en?z?loh">
<a href="/app/snizeni-zaloh-osvc/">OSVČ - žádost o snížení záloh</a>
</li>
</ul>
</li>
</ul>
</li>
<li class="disabled menu-main-zam?stnavatel?" id="menu3">
<a href="/app/zamestnavatele-rozcestnik/" class="level1">zaměstnavatelé</a>
<ul>
<li class="sub-disabled menu-main-hromadn?ozn?men?zam?stnavatele">
<a href="/app/hromadne-oznameni-zamestnavatele/" class="level2">Hromadné oznámení zaměstnavatele</a>
</li>
<li class="sub-disabled menu-main-p?ehledoplatb?pojistn?ho">
<a href="/app/prehled-o-platbe-pojistneho/" class="level2">Přehled o platbě pojistného</a>
</li>
<li class="sub-disabled menu-main-??dostoseznamzam?stnanc?">
<a href="/app/zadost-o-seznam-zamestnancu/" class="level2">Žádost o seznam zaměstnanců</a>
</li>
<li class="sub-disabled menu-main-zm?naadresyakontaktn?ch?daj?">
<a href="/app/zmena-adresy-a-kontaktnich-udaju/zamestnavatel/" class="level2">Změna adresy a kontaktních údajů</a>
</li>
<li class="sub-disabled menu-main-stavpojistn?ho">
<a href="/app/zamestnavatele-stav-pojistneho/" class="level2">Stav pojistného</a>
</li>
<li class="sub-disabled menu-main-mojeopr?vn?n?">
<a href="/app/prehled-opravneni/zamestnavatel/" class="level2">Moje oprávnění</a>
</li>
</ul>
</li>
<li class="active menu-main-poskytovatel?zdrslu?eb" id="menu4">
<a href="/app/pzs-rozcestnik/" class="level1"><span>poskytovatelé zdr. služeb</span></a>
<ul>
<li class="sub-disabled menu-main-?pzppro">
<a href="/app/cpzp-pro/" class="level2">ČPZP PRO</a>
</li>
<li class="menu-main-schr?nkapzs">
<a href="/app/schranka-pzs/" class="level2">Schránka PZS</a>
</li>
<li class="menu-main-profilpzs">
<a href="/app/profil-pzs/" class="level2">Profil PZS</a>
</li>
<li class="menu-main-odesl?n?vy??tov?n?">
<a href="/app/odeslani-vyuctovani/" class="level2">Odeslání vyúčtování</a>
</li>
<li class="menu-main-odeslanivy??tov?n?antigenn?chtest?">
<a href="/app/odeslani-vyuctovani/antigenni-testy/" class="level2">Odeslani vyúčtování antigenních testů</a>
</li>
<li class="menu-main-odesl?n?registra?n?chl?stk?">
<a href="/app/registracni-listky/" class="level2">Odeslání registračních lístků</a>
</li>
<li class="menu-main-odesl?n?hromadn?sn??en?cen">
<a href="/app/hromadne-snizeni-cen/" class="level2">Odeslání hromadné snížení cen</a>
</li>
<li class="menu-main-odesl?n?l?ze?sk?chn?vrh?">
<a href="/app/lazenske-navrhy/" class="level2">Odeslání lázeňských návrhů</a>
</li>
<li class="menu-main-ov??en?poji?t?nce">
<a href="https://www.cpzp.cz/ehic/" target="_blank" class="level2">Ověření pojištěnce</a>
</li>
<li class="menu-main-ov??en?registruj?c?hopzs">
<a href="/app/overeni-registrujiciho-pzs/" class="level2">Ověření registrujícího PZS</a>
</li>
<li class="menu-main-prohl??en?/stornofaktur">
<a href="/app/prohlizeni-faktur/" class="level2">Prohlížení / Storno faktur</a>
</li>
<li class="menu-main-prohl??en?plateb">
<a href="/app/prohlizeni-plateb/" class="level2">Prohlížení plateb</a>
</li>
<li class="menu-main-prohl??en?vy??tov?n?zaobdob?">
<a href="/app/prohlizeni-vyuctovani/" class="level2">Prohlížení vyúčtování za období</a>
</li>
<li class="menu-main-prohl??en?zulp">
<a href="/app/prohlizeni-zulp/" class="level2">Prohlížení ZULP</a>
</li>
<li class="active menu-main-prohl??en?klientely">
<a href="/app/prohlizeni-klientely/" class="level2">Prohlížení klientely</a>
</li>
<li class="sub-disabled menu-main-pzs-osobn???etpoji?t?nce">
<a href="/app/pzs-osobni-ucet-pojistence/" class="level2">PZS - osobní účet pojištěnce</a>
</li>
<li class="sub-disabled menu-main-statistiky-n?kladyzdrslu?eb">
<a href="/app/dikap/statistika/" class="level2">Statistiky - náklady zdr. služeb</a>
</li>
<li class="menu-main-parametrydohodyocen?">
<a href="/app/dikap/dohody/" class="level2">Parametry dohody o ceně</a>
</li>
<li class="menu-main-mojeopr?vn?n?">
<a href="/app/prehled-opravneni/pzs/" class="level2">Moje oprávnění</a>
</li>
</ul>
</li>
<li class="menu-main-servis" id="menu5">
<a href="/app/servis-rozcestnik/" class="level1"><span>servis</span></a>
<ul>
<li class="menu-main-schr?nkaklienta">
<a href="/app/schranka/" class="level2">Schránka klienta</a>
</li>
<li class="menu-main-elektronick?podatelna">
<a href="/app/elektronicka-podatelna/" class="level2">Elektronická podatelna</a>
</li>
<li class="menu-main-p?ehledopr?vn?n?">
<a href="/app/prehled-opravneni/vse/" class="level2">Přehled oprávnění</a>
</li>
<li class="menu-main-p?ehledsouhlas?">
<a href="/app/prehled-souhlasu/" class="level2">Přehled souhlasů</a>
</li>
<li class="menu-main-aktiva?n?k?d(opr?vn?n?)">
<a href="/app/aktivacni-kod/" class="level2">Aktivační kód (oprávnění)</a>
</li>
<li class="menu-main-historiepod?n?">
<a href="/app/historie-podani/" class="level2">Historie podání</a>
</li>
<li class="menu-main-prohl??en?certifik?t?">
<a href="/app/prohlizeni-certifikatu/" class="level2">Prohlížení certifikátů</a>
</li>
<li class="menu-main-p?id?n?certifik?tu">
<a href="/app/pridani-certifikatu/" class="level2">Přidání certifikátu</a>
</li>
<li class="menu-main-zm?nakontaktn?ch?daj?">
<a href="/app/zmena-osobnich-udaju/" class="level2">Změna kontaktních údajů</a>
</li>
<li class="menu-main-zm?nasmsp?ihla?ov?n?">
<a href="/app/zmena-sms-konta/" class="level2">Změna SMS přihlašování</a>
</li>
<li class="menu-main-potvrzen?emailov?adresy">
<a href="/app/potvrzeni-emailu/" class="level2">Potvrzení emailové adresy</a>
</li>
</ul>
</li>
<li class="menu-main-informace" id="menu6">
<a href="/app/informace-rozcestnik/" class="level1"><span>informace</span></a>
<ul>
<li class="menu-main-aktuality">
<a href="/app/aktuality/" class="level2">aktuality</a>
</li>
<li class="menu-main-registracedoe-p?ep??ky">
<a href="/app/registrace-klienta/" class="level2">registrace do e-přepážky</a>
</li>
<li class="menu-main-jaksep?ihl?sit">
<a href="/app/clanek/jak-se-prihlasit/" class="level2">jak se přihlásit</a>
</li>
<li class="menu-main-?astokladen?ot?zky(faq)">
<a href="/app/clanek/casto-kladene-otazky/" class="level2">často kladené otázky (FAQ)</a>
</li>
<li class="menu-main-certifika?n?autority">
<a href="/app/clanek/certifikacni-autority/" class="level2">certifikační autority</a>
</li>
<li class="menu-main-ochranaosobn?ch?daj?">
<a href="/app/clanek/ochrana-osobnich-udaju/" class="level2">ochrana osobních údajů</a>
</li>
<li class="menu-main-e-podatelna?pzp">
<a href="/app/clanek/elektronicka-podatelna-cpzp/" class="level2">e-podatelna ČPZP</a>
</li>
<li class="menu-main-smlouvysposkytovatelizdravotn?chslu?eb">
<a href="/app/smlouvy-s-pzs/" class="level2">Smlouvy s poskytovateli zdravotních služeb</a>
</li>
</ul>
</li>
<li class="menu-main-n?pov?da" id="menu7">
<a href="/app/napoveda/" class="level1">nápověda</a>
</li>
<li class="menu-main-web?pzp" id="menu8">
<a href="http://www.cpzp.cz" class="level1">web ČPZP</a>
</li>
</ul> </div>
<div class="clearboth"></div>
<div class="content">
<div>
<div class="breadcrumb">
<a href="/app/">úvod</a><span class="separator"></span><a href="/app/pzs-rozcestnik/">poskytovatelé zdr. služeb</a><span class="separator"></span>Prohlížení klientely </div>
<div class="spacer" style="height: 20px;"></div>
<h1>Prohlížení klientely</h1>
<p>Aktuální klientela je zobrazována vždy až po interní uzávěrce pro Kapitační centrum, tzn. s cca měsíčním zpožděním oproti aktuálnímu datu.</p>
<form action="" method="post" name="prohlizeni-klientely" id="prohlizeni-klientely"><input type="hidden" name="csrf" value="8029b24e0b7c3607ed34303c892ff308" style=""><script>
CPZP.icpSort = '';
</script>
<fieldset>
<h3>Filtrování</h3>
<div class="field top">
<div class="label" style="width: 70px;">IČZ:</div>
<div class="input w600">
<select name="icz" id="icz"><option value="09305000">09305000 - MUDr. Michaela Buzalková</option></select> </div>
</div>
<div class="clearboth"></div>
<p></p>
<div class="field top" style="float: left; width: 46%">
<div class="label" style="width: 70px;">Měsíc:</div>
<div class="input w100">
<input name="mesic" type="text" id="mesic" value="3" style=""> </div>
<div class="clearboth"></div>
</div>
<div class="field top" style="float: left; width: 50%">
<div class="label" style="width: 70px;">Rok:</div>
<div class="input w200">
<input name="rok" type="text" id="rok" value="2026" style=""> </div>
<div class="clearboth"></div>
</div>
<div class="clearboth"></div>
<div class="field top" style="float: left; width: 46%">
<div class="label" style="width: 70px;">IČP:</div>
<div class="input w300">
<select name="icp" id="icp" class=""><option value="">---</option><option value="null"> - </option></select> </div>
</div>
<div class="field top" style="float: left; width: 50%">
<div class="label" style="width: 70px;">Řadit dle:</div>
<div class="input w200">
<select name="sortBy" id="sortBy"><option value="ICP_RC" selected="selected">IČP</option>
<option value="RC">Číslo pojištěnce</option>
<option value="PRIJMENI_JMENO">Příjmení a jméno</option></select> </div>
</div>
<div class="clearboth"></div>
<div class="top">
<div class="center">
<div class="red-btn">
<div class="wrap"><input name="submitbutton" type="submit" id="submitbutton" value="Hledat" class="" style=""></div>
</div>
</div>
</div>
<div class="clearboth"></div>
</fieldset>
</form><p></p>
<div class="action-panel navigation">
<a href="javascript:;" id="print-to-html" class="print-to-html-no-text x-href-btn" style="width: 47px;">tisknout sestavu</a>
<form action="" method="post" target="_blank" id="export-form" name="export-form">
<input type="hidden" name="icz" id="export-icz" style="">
<input type="hidden" name="icp" id="export-icp" style="">
<input type="hidden" name="mesic" id="export-mesic" style="">
<input type="hidden" name="rok" id="export-rok" style="">
<input type="hidden" name="sortBy" id="export-sortBy" style="">
<input type="hidden" name="csrf" value="8029b24e0b7c3607ed34303c892ff308" style=""> </form>
<a href="#" class="export-to-csv-no-text x-href-btn" style="width: 205px;" id="export-to-vzp">Seznam registrovaných pojištěnců ve formátu podle datového rozhraní VZP</a>
<div class="navigation">
&nbsp;&nbsp;Celkem 15 záznamů </div>
<div class="clearboth"></div>
</div>
<p></p>
<table class="list">
<thead>
<tr class="odd">
<th class="center">IČP</th>
<th class="right">Odbornost</th>
<th class="right">Číslo pojištěnce</th>
<th class="left">Příjmení</th>
<th class="left">Jméno</th>
<th class="center">Registrace od</th>
<th class="center">Registrace do</th>
</tr>
</thead>
<tbody>
<tr class="even">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">0105072528</td>
<td class="left">Vinický</td>
<td class="left">Ondřej</td>
<td class="center">01.03.2026</td>
<td class="center"></td>
</tr>
<tr class="odd">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">0301214925</td>
<td class="left">Štefanský</td>
<td class="left">Daniel</td>
<td class="center">01.05.2025</td>
<td class="center"></td>
</tr>
<tr class="even">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">0657650510</td>
<td class="left">Krehul</td>
<td class="left">Valeriia</td>
<td class="center">01.02.2026</td>
<td class="center"></td>
</tr>
<tr class="odd">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">435614435</td>
<td class="left">Strnadová</td>
<td class="left">Vítězslava</td>
<td class="center">01.07.2025</td>
<td class="center"></td>
</tr>
<tr class="even">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">446228471</td>
<td class="left">Feoktistova</td>
<td class="left">Natalia</td>
<td class="center">01.09.2018</td>
<td class="center"></td>
</tr>
<tr class="odd">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">6504141149</td>
<td class="left">Bečica</td>
<td class="left">Josef</td>
<td class="center">01.05.2010</td>
<td class="center"></td>
</tr>
<tr class="even">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">6510130792</td>
<td class="left">Šuhaj</td>
<td class="left">Petr</td>
<td class="center">01.06.2013</td>
<td class="center"></td>
</tr>
<tr class="odd">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">6758120446</td>
<td class="left">Bečicová</td>
<td class="left">Markéta</td>
<td class="center">01.05.2010</td>
<td class="center"></td>
</tr>
<tr class="even">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">6861010288</td>
<td class="left">Štefanská</td>
<td class="left">Renáta</td>
<td class="center">01.03.2025</td>
<td class="center"></td>
</tr>
<tr class="odd">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">7909054780</td>
<td class="left">Babáček</td>
<td class="left">Marek</td>
<td class="center">01.02.2017</td>
<td class="center"></td>
</tr>
<tr class="even">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">8509170802</td>
<td class="left">Neumann</td>
<td class="left">Jakub</td>
<td class="center">01.09.2015</td>
<td class="center"></td>
</tr>
<tr class="odd">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">8554125360</td>
<td class="left">Grygarová</td>
<td class="left">Jana</td>
<td class="center">01.04.2011</td>
<td class="center"></td>
</tr>
<tr class="even">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">9355042466</td>
<td class="left">Bečicová</td>
<td class="left">Tereza</td>
<td class="center">01.03.2012</td>
<td class="center"></td>
</tr>
<tr class="odd">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">9355071297</td>
<td class="left">Dobrohrušková</td>
<td class="left">Lucie</td>
<td class="center">01.11.2014</td>
<td class="center"></td>
</tr>
<tr class="even">
<td class="center">09305001</td>
<td class="right">001</td>
<td class="right">9651301253</td>
<td class="left">Kut Citores</td>
<td class="left">Markéta</td>
<td class="center">01.11.2021</td>
<td class="center"></td>
</tr>
</tbody>
</table>
<p></p>
<script>
$(document).ready(function() {
analyticsPzs($('#icz').val(), 'PZS_PROHLIZENI_KLIENTELY');
});
</script>
</div>
<div class="clearboth"></div>
</div>
</div>
<div class="footer">
<p class="copy">© ČPZP | Infocentrum: 810 800 000 | <a href="http://www.cpzp.cz/pobocky/">Pobočky ČPZP</a> | <a href="#" class="show-cookies">Cookies</a>
</p>
</div>
<section class="cookie-policy">
<article class="base cookie-container">
<div>
<h2>Povolení cookies</h2>
<p>V ČPZP používáme cookies a jiné technologie za účelem poskytování našich služeb, vylepšení
vašeho uživatelského zážitku, analýzy používání
našich stránek a při cílení reklamy. </p>
</div>
<div class="cookie-buttons">
<div class="red-btn disabled cookie-setup"><div class="wrap"><a style="color: rgb(19, 35, 57)" href="#">Nastavit cookies</a></div></div>
<div class="red-btn disabled cookie-deny"><div class="wrap"><a style="color: rgb(19, 35, 57)" href="#">Odmítnout vše kromě nutných</a></div></div>
<div class="red-btn cookie-accept"><div class="wrap"><a href="#">Přijmout vše</a></div></div>
</div>
</article>
<article class="detail cookie-container none">
<div class="cookie-detail">
<h2 style="grid-area: h">Nastavení cookies</h2>
<p style="grid-area: p1">V ČPZP používáme cookies a jiné technologie za účelem poskytování našich služeb, vylepšení
vašeho uživatelského zážitku, analýzy používání
našich stránek a při cílení reklamy. </p>
<p style="grid-area: p2">Vyberte vámi preferované povolení cookie, přičemž <b>základní jsou nezbytné pro fungování</b>, jiné můžeme používat jen s vaším souhlasem.
<br>
Vaše osobní údaje budou zpracovány a informace z vašeho zařízení (soubory cookie,
jidinečné identifikátory a další údaje zařízená) mohou být uchovávány.
<br>
Vaše preference můžete kdykoliv <b>změnit v dolní části naší webové stránky
s názvem Cookies</b>. Pro více informací o používání cookies prosím naštivte
<a href="/app/clanek/ochrana-osobnich-udaju/" target="__blank">Zásady ochrany osobních údajů</a>
.</p>
<div style="grid-area: b1; justify-self: center;" class="red-btn disabled cookie-deny"><div class="wrap"><a style="color: rgb(19, 35, 57)" href="#">Odmítnout vše kromě nutných</a></div></div>
<div style="grid-area: b2; justify-self: center;" class="red-btn cookie-accept-selected"><div class="wrap"><a href="#">Souhlasím a uložit nastavení</a></div></div>
</div>
<div class="cookie-options">
<label for="zakladni"><input type="checkbox" id="zakladni" value="1" checked="" disabled="" style="">Základní <span>Nezbytné pro správné fungování webu.</span></label>
<label for="analyticke"><input type="checkbox" id="analyticke" value="2" style="">Analytické <span>Umožňují měření výkonu webu a reklamních kampaní.</span></label>
<label for="preferencni"><input type="checkbox" id="preferencni" value="4" style="">Preferenční <span>Slouží k přizpůsobení potřeb a zájmů</span></label>
<label for="reklamni"><input type="checkbox" id="reklamni" value="8" style="">Reklamní <span>Slouží k zobrazení vhodného obsahu nebo reklamy, jak
na našich stránkách, tak na stránkách třetích subjektů.</span></label>
</div>
</article>
</section>
<div id="ie-warning-dialog" style="display: none;">
<p>
Používáte zastaralý prohlížeč Microsoft Internet Explorer. Z bezpečnostních a výkonových důvodů Vám důrazně doporučujeme přechod na modernější prohlížeč - např. Google Chrome, Microsoft Edge či Mozilla Firefox. Jedním z důvodů je i skutečnost, že společnost Microsoft již avizovala, že k 15.6.2022 ukončuje podporu Microsoft Internet Exploreru.
</p>
</div>
<script>
$(document).ready(function() {
const dialog = $("#ie-warning-dialog");
// MSIE for IE version <=10, trident/ for IE 11
if ((navigator.userAgent.indexOf('MSIE') > -1 || navigator.appVersion.indexOf('Trident/') > -1) && !sessionStorage.visited) {
dialog.dialog({
modal: true,
title: 'Upozornění na zastaralý prohlížeč',
show: {
effect: 'fold',
duration: 400
},
hide: {
effect: 'fold',
duration: 200
},
width: '50%',
buttons: {
'Zavřít': function () {
dialog.dialog('close');
}
}
});
sessionStorage.visited = true;
}
});
</script>
<script>
const CONSENT_KEY = 'cookieConsent';
function getCookie(cKey) {
const key = cKey + "=";
const decoded = decodeURIComponent(document.cookie);
const allCookies = decoded .split('; ');
let res;
allCookies.forEach(function (val) {
if (val.indexOf(key) === 0) res = val.substring(key.length);
})
return res;
}
function setCookie(cKey, value, duration) {
let date = new Date();
date.setTime(date.getTime() + (30 * 24 * 60 * 60 * 1000)); // days * hours * minutes * seconds * milliseconds
const expires = "expires=" + date.toUTCString();
document.cookie = cKey + "=" + value + "; " + expires + "; path=/";
}
function hideCookieConsent() {
if (!$('.cookie-policy .base').hasClass('none')) {
$('.cookie-policy .base').addClass('none');
}
if (!$('.cookie-policy > .detail').hasClass('none')) {
$('.cookie-policy > .detail').addClass('none');
}
if (!$('.cookie-policy').hasClass('none')) {
$('.cookie-policy').addClass('none');
}
}
function setConsentCheckboxes() {
const cookieConsent = parseInt(getCookie(CONSENT_KEY));
const binConsent = cookieConsent.toString(2);
if (binConsent.charAt(binConsent.length - 2) === '1') {
$("#analyticke").prop('checked', true);
}else {
$("#analyticke").prop('checked', false);
}
if (binConsent.charAt(binConsent.length - 3) === '1') {
$("#preferencni").prop('checked', true);
}else {
$("#preferencni").prop('checked', false);
}
if (binConsent.charAt(binConsent.length - 4) === '1') {
$("#reklamni").prop('checked', true);
}else {
$("#reklamni").prop('checked', false);
}
}
$(document).ready(function() {
const cookieConsent = getCookie(CONSENT_KEY);
if (!cookieConsent) {
$('.cookie-policy .base').removeClass('none');
$('.cookie-policy').removeClass('none');
}else {
const val = parseInt(cookieConsent);
const bin = val.toString(2);
if (bin.charAt(bin.length - 2) === '1') {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-46493716-1', 'cpzp.cz');
ga('send', 'pageview');
}
}
$(document).on('click', '.show-cookies', function(e) {
e.preventDefault();
$('.cookie-policy .base').removeClass('none');
$('.cookie-policy').removeClass('none');
});
$(document).on('click', '.cookie-setup', function(e) {
e.preventDefault();
setConsentCheckboxes();
$('.cookie-policy .base').addClass('none');
$('.cookie-policy > .detail').removeClass('none');
});
$(document).on('click', '.cookie-accept', function(e) {
e.preventDefault();
setCookie(CONSENT_KEY, '15');
hideCookieConsent();
});
$(document).on('click', '.cookie-deny', function(e) {
e.preventDefault();
setCookie(CONSENT_KEY, '1');
hideCookieConsent();
});
$(document).on('click', '.cookie-accept-selected', function(e) {
e.preventDefault();
let consentValue = 1;
consentValue += $('#analyticke').is(":checked") ? parseInt($('#analyticke').val()) : 0;
consentValue += $('#preferencni').is(":checked") ? parseInt($('#preferencni').val()) : 0;
consentValue += $('#reklamni').is(":checked") ? parseInt($('#reklamni').val()) : 0;
setCookie(CONSENT_KEY, consentValue.toString());
hideCookieConsent();
});
});
</script>
<div id="x-portal-0" class="help-box" style="right: 10px;"><div class="help-box-wrap"><span>NÁPOVĚDA</span></div></div><div id="x-portal-1" class="help-tooltip" style="right: 10px;"><a hre="javascript:;"></a><div class="help-tooltip-content">Pokud si nevíte s touto funkcí rady, zkuste se podívat na naši nápovědu</div><div class="arrow bottom center"></div></div></body></html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

@@ -1,7 +1,13 @@
"""
01 - Přihlášení na VZP Point (Inbox)
Otevře Chrome, přihlásí se certifikátem a naviguje na schránku zpráv.
Okno zůstane otevřené — skript čeká na stisk Enter.
01 - Přihlášení na VZP Point (plně automatizované)
Jak to funguje:
1. Nastaví Chrome politiku AutoSelectCertificateForUrls — Chrome vybere
certifikát automaticky bez dialogu (certifikát musí být v Windows store)
2. Otevře Chrome, klikne na 'Certifikát', počká na přesměrování a uloží cookies
Certifikát ve Windows store: MUDr. Michaela Buzalková (I.CA EU Qualified CA2, platný do 16.1.2027)
Použití: python 01_prihlaseni.py
"""
@@ -9,16 +15,33 @@ import json
import os
import re
import sys
import time
import winreg
INBOX_URL = "https://point.vzp.cz/Inbox/Message"
CHROME_PROFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "chrome_profile"))
COOKIES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "vzp_cookies.json"))
# Issuer CN certifikátu v Windows store (CurrentUser\My)
CERT_ISSUER_CN = "I.CA Public CA/RSA 06/2022"
def _set_chrome_cert_policy() -> None:
"""Nastaví Chrome politiku AutoSelectCertificateForUrls pro vzp.cz."""
policy = json.dumps({
"pattern": "https://[*.]vzp.cz",
"filter": {"ISSUER": {"CN": CERT_ISSUER_CN}},
})
key_path = r"SOFTWARE\Policies\Google\Chrome\AutoSelectCertificateForUrls"
try:
key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, key_path)
winreg.SetValueEx(key, "1", 0, winreg.REG_SZ, policy)
winreg.CloseKey(key)
print(f" Chrome politika nastavena (issuer: {CERT_ISSUER_CN})")
except Exception as e:
print(f" Varování: nelze nastavit Chrome politiku: {e}")
def load_cookies(context) -> int:
"""Načte dříve uložené cookies (včetně session-only) zpět do kontextu."""
if not os.path.exists(COOKIES_FILE):
return 0
try:
@@ -32,7 +55,6 @@ def load_cookies(context) -> int:
def save_cookies(context) -> int:
"""Uloží VZP cookies (i session-only) do JSON souboru."""
try:
all_cookies = context.cookies()
vzp = [c for c in all_cookies if "vzp.cz" in c.get("domain", "")]
@@ -44,17 +66,6 @@ def save_cookies(context) -> int:
return 0
def _delete_chrome_cert_policy() -> None:
"""Smaže AutoSelectCertificateForUrls politiku — Chrome pak zobrazí dialog přirozeně."""
key_path = r"SOFTWARE\Policies\Google\Chrome\AutoSelectCertificateForUrls"
try:
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, access=winreg.KEY_SET_VALUE)
winreg.DeleteValue(key, "1")
winreg.CloseKey(key)
except Exception:
pass
def main() -> None:
try:
from playwright.sync_api import sync_playwright
@@ -62,7 +73,7 @@ def main() -> None:
print("Chybí playwright: pip install playwright && playwright install chrome")
sys.exit(1)
_delete_chrome_cert_policy()
_set_chrome_cert_policy()
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
@@ -74,15 +85,10 @@ def main() -> None:
args=["--force-renderer-accessibility"],
)
try:
# Načti dříve uložené cookies (vč. session-only) z JSON
loaded = load_cookies(context)
startup = context.cookies()
vzp_start = [c for c in startup if "vzp.cz" in c.get("domain", "")]
print(f"Profil: {CHROME_PROFILE}")
print(f"Cookies z JSON: {loaded}, VZP v kontextu: {len(vzp_start)}")
print(f"Cookies z JSON: {loaded}")
page = context.new_page()
print("Naviguji na VZP Point...")
try:
page.goto(INBOX_URL, wait_until="domcontentloaded", timeout=30_000)
@@ -95,34 +101,24 @@ def main() -> None:
cert_btn.wait_for(state="visible", timeout=10_000)
cert_btn.click(no_wait_after=True)
print("Pokud se zobrazí dialog výběru certifikátu, vyberte ho ručně (max 60 s)...")
time.sleep(30)
print()
print("=" * 60)
print(" Pokud se zobrazil dialog výběru certifikátu,")
print(" vyberte certifikát MUDr. Buzalkové a klikněte OK.")
print(" Čekám 60 sekund...")
print("=" * 60)
page = context.new_page()
# Čekáme na přesměrování — buď auto-výběr přes politiku, nebo ruční klik
try:
page.goto(INBOX_URL, wait_until="domcontentloaded", timeout=30_000)
except Exception as e:
print(f"Navigace po auth: {e}")
page.wait_for_url("https://point.vzp.cz/**", timeout=60_000)
except Exception:
print(f" Timeout čekání na přesměrování. URL: {page.url}")
if not page.url.startswith("https://point.vzp.cz"):
print(f"Přihlášení selhalo. URL: {page.url}")
return
print(f"OK — přihlášení úspěšné. URL: {page.url}")
# Diagnostika: cookies po auth
after = context.cookies()
vzp_after = [c for c in after if "vzp.cz" in c.get("domain", "")]
print(f"Cookies po auth: VZP: {len(vzp_after)}")
for c in vzp_after:
exp = c.get("expires", -1)
persistent = "PERSISTENT" if exp > 0 else "SESSION-ONLY"
# Zkrácený název pro přehlednost
name = c["name"][:60]
print(f" - {name} ({c['domain']}) [{persistent}]")
print("Okno zůstane otevřené. Stiskněte Enter pro zavření...")
input()
print(f"Přihlášení úspěšné. URL: {page.url}")
finally:
saved = save_cookies(context)
@@ -14,10 +14,13 @@ import winreg
from datetime import datetime
from pathlib import Path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
from Knihovny.najdi_dropbox import get_dropbox_root
INBOX_URL = "https://point.vzp.cz/Inbox/Message"
CHROME_PROFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "chrome_profile"))
COOKIES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "vzp_cookies.json"))
DOWNLOAD_DIR = os.path.join(os.path.dirname(__file__), "Staženo")
DOWNLOAD_DIR = os.path.join(get_dropbox_root(), "Ordinace", "Dokumentace_ke_zpracování", "Zúčtovací zprávy", "111 VZP")
def load_cookies(context) -> int:
@@ -15,10 +15,13 @@ import winreg
from datetime import datetime
from pathlib import Path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
from Knihovny.najdi_dropbox import get_dropbox_root
INBOX_URL = "https://point.vzp.cz/Inbox/Message"
CHROME_PROFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "chrome_profile"))
COOKIES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "vzp_cookies.json"))
DOWNLOAD_DIR = os.path.join(os.path.dirname(__file__), "Staženo")
DOWNLOAD_DIR = os.path.join(get_dropbox_root(), "Ordinace", "Dokumentace_ke_zpracování", "Zúčtovací zprávy", "111 VZP")
def load_cookies(context) -> int:
@@ -0,0 +1,45 @@
"""
Přihlásí se na VZP Point, stáhne nové zprávy a aktualizuje číselníky.
Kombinuje 01_prihlaseni.py + 03_stahuj_nove.py + 01_stahni_ciselniky.py.
Přihlášení probíhá plně automaticky (Chrome auto-vybere certifikát).
POUŽITÍ:
python 04_prihlaseni_a_stahuj_nove.py
"""
import subprocess
import sys
import os
DIR = os.path.dirname(os.path.abspath(__file__))
CISELNIKY_SCRIPT = os.path.abspath(
os.path.join(DIR, "..", "..", "..", "Recepty", "StahovánízVZPWithClaude", "01_stahni_ciselniky.py")
)
def run(script: str) -> None:
result = subprocess.run(
[sys.executable, script],
check=False,
)
if result.returncode != 0:
raise SystemExit(f"Skript {os.path.basename(script)} skončil s chybou (kód {result.returncode})")
def main() -> None:
print("=== Přihlášení ===")
run(os.path.join(DIR, "01_prihlaseni.py"))
print("\n=== Stahování nových zpráv ===")
run(os.path.join(DIR, "03_stahuj_nove.py"))
print("\n=== Stahování odeslaných podání ===")
run(os.path.join(DIR, "stahovanipodani.py"))
print("\n=== Stahování číselníků VZP ===")
run(CISELNIKY_SCRIPT)
if __name__ == "__main__":
main()
@@ -0,0 +1,30 @@
IČZ: 9305000 - MUDr. Michaela Buzalková
Období: 3/2026
IČP: 9305001 - Ordinace praktického lékaře pro dospělé
Věková skupina Počet reg. placených poj. Koeficient Počet jednicových pojištěnců
15 - 19 let 7 1,06 7,42
20 - 24 let 35 0,90 31,50
25 - 29 let 32 0,95 30,40
30 - 34 let 37 1,00 37,00
35 - 39 let 56 1,05 58,80
40 - 44 let 54 1,05 56,70
45 - 49 let 103 1,10 113,30
50 - 54 let 105 1,43 150,15
55 - 59 let 60 1,54 92,40
60 - 64 let 58 1,59 92,22
65 - 69 let 46 1,80 82,80
70 - 74 let 52 2,12 110,24
75 - 79 let 118 2,54 299,72
80 - 84 let 71 3,07 217,97
nad 85 let 65 3,60 234,00
Celkem registrovných placených pojištěnců: 899
Celkem jednicových pojištěnců: 1 614,62
Navýšení kapitačního paušálu: 1,00 Kč
Navýšení kapitační platby: 1 614,62 Kč
Degresní koeficient ve výši 0,984831 nebyl uplatněn.
Navýšení kapitační platby za období: 3/2026 1 614,62 Kč
Navýšení kapitační platby za IČZ: 9305000 1 614,62 Kč
@@ -0,0 +1,30 @@
IÈZ: 9305000 - MUDr. Michaela Buzalková
Období: 3/2026
IÈP: 9305001 - Ordinace praktického lékaøe pro dospìlé
Vìková skupina Poèet reg. placených poj. Koeficient Poèet jednicových poji¹tìncù
15 - 19 let 7 1,06 7,42
20 - 24 let 35 0,90 31,50
25 - 29 let 32 0,95 30,40
30 - 34 let 37 1,00 37,00
35 - 39 let 56 1,05 58,80
40 - 44 let 54 1,05 56,70
45 - 49 let 103 1,10 113,30
50 - 54 let 105 1,43 150,15
55 - 59 let 60 1,54 92,40
60 - 64 let 58 1,59 92,22
65 - 69 let 46 1,80 82,80
70 - 74 let 52 2,12 110,24
75 - 79 let 118 2,54 299,72
80 - 84 let 71 3,07 217,97
nad 85 let 65 3,60 234,00
Celkem registrovných placených poji¹tìncù: 899
Celkem jednicových poji¹tìncù: 1 614,62
Sazba kapitaèního pau¹álu: 76,00 Kè
Celková kapitaèní platba: 122 711,12 Kè
Degresní koeficient ve vý¹i 0,984831 nebyl uplatnìn.
Celková kapitaèní platba za období: 3/2026 122 711,12 Kè
Celková kapitaèní platba za IÈZ: 9305000 122 711,12 Kè
@@ -0,0 +1,265 @@
"""
Stáhni odeslaná podání z VZP Point (sekce „Odeslaná podání").
Načte Bearer token ze stránky Desk/FormDashboard, pak volá REST API /api/desk/form.
Stahuje podání s přiloženým výsledkovým souborem — přeskočí ty, co už existují.
Použití: python stahovanipodani.py [--dry-run]
"""
import json
import os
import re
import sys
import time
import winreg
try:
import requests as req_lib
except ImportError:
print("Chybí requests: pip install requests")
sys.exit(1)
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
from Knihovny.najdi_dropbox import get_dropbox_root
DASHBOARD_URL = "https://point.vzp.cz/Desk/FormDashboard"
API_BASE = "https://point.vzp.cz/api/desk/form"
PAGE_SIZE = 50
CHROME_PROFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "chrome_profile"))
COOKIES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "vzp_cookies.json"))
DOWNLOAD_DIR = os.path.join(
get_dropbox_root(),
"Ordinace", "Dokumentace_ke_zpracování", "Zúčtovací zprávy", "111 VZP Podání"
)
DRY_RUN = False
def load_cookies(context) -> int:
if not os.path.exists(COOKIES_FILE):
return 0
try:
with open(COOKIES_FILE, "r", encoding="utf-8") as f:
cookies = json.load(f)
context.add_cookies(cookies)
return len(cookies)
except Exception:
return 0
def save_cookies(context) -> int:
try:
all_cookies = context.cookies()
vzp = [c for c in all_cookies if "vzp.cz" in c.get("domain", "")]
with open(COOKIES_FILE, "w", encoding="utf-8") as f:
json.dump(vzp, f, indent=2, ensure_ascii=False)
return len(vzp)
except Exception:
return 0
CERT_ISSUER_CN = "I.CA Public CA/RSA 06/2022"
def _set_chrome_cert_policy() -> None:
policy = json.dumps({
"pattern": "https://[*.]vzp.cz",
"filter": {"ISSUER": {"CN": CERT_ISSUER_CN}},
})
key_path = r"SOFTWARE\Policies\Google\Chrome\AutoSelectCertificateForUrls"
try:
key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, key_path)
winreg.SetValueEx(key, "1", 0, winreg.REG_SZ, policy)
winreg.CloseKey(key)
print(f" Chrome politika nastavena (issuer: {CERT_ISSUER_CN})")
except Exception as e:
print(f" Varování: nelze nastavit Chrome politiku: {e}")
def extract_bearer_token(page) -> str | None:
"""Extrahuje Bearer token z inline <script> tagu vloženého do HTML stránky."""
scripts = page.evaluate(
"() => Array.from(document.querySelectorAll('script:not([src])')).map(s => s.textContent)"
)
for text in scripts:
m = re.search(r'"bearerToken"\s*:\s*"([^"]+)"', text)
if m:
return m.group(1)
return None
def fetch_all_forms(token: str) -> list[dict]:
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
all_items: list[dict] = []
page_num = 1
while True:
url = f"{API_BASE}?pageNumber={page_num}&pageSize={PAGE_SIZE}"
r = req_lib.get(url, headers=headers, timeout=30)
r.raise_for_status()
data = r.json()
items = data.get("items", [])
all_items.extend(items)
print(f" Stránka {page_num}: {len(items)} podání (celkem {len(all_items)})")
if not data.get("canLoadMore", False):
break
page_num += 1
return all_items
def parse_date(iso: str) -> str:
return iso[:10] if iso else "0000-00-00"
def download_file(token: str, form_id: int, file_id: str, dest: str) -> bool:
# Krok 1: získej publicUri z API
meta_url = f"{API_BASE}/{form_id}/result/{file_id}"
try:
r = req_lib.get(meta_url, headers={"Authorization": f"Bearer {token}"}, timeout=30)
r.raise_for_status()
public_uri = r.json().get("publicUri")
if not public_uri:
print(f" Chyba: odpověď neobsahuje publicUri")
return False
except Exception as e:
print(f" Chyba načítání publicUri: {e}")
return False
# Krok 2: stáhni soubor přímo z publicUri (bez auth hlavičky)
try:
r = req_lib.get(public_uri, stream=True, timeout=60)
r.raise_for_status()
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
return True
except Exception as e:
print(f" Chyba stahování souboru: {e}")
return False
def main() -> None:
dry_run = DRY_RUN or "--dry-run" in sys.argv
if dry_run:
print("[dry-run] Pouze zobrazuji co by se stáhlo, nic nestahuju.\n")
try:
from playwright.sync_api import sync_playwright
except ImportError:
print("Chybí playwright: pip install playwright && playwright install chrome")
sys.exit(1)
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
_set_chrome_cert_policy()
token = None
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir=CHROME_PROFILE,
channel="chrome",
headless=False,
slow_mo=100,
ignore_https_errors=True,
accept_downloads=True,
args=["--force-renderer-accessibility"],
)
try:
loaded = load_cookies(context)
print(f"Cookies načtené z JSON: {loaded}")
page = context.new_page()
print("Naviguji na VZP Point Odeslaná podání...")
try:
page.goto(DASHBOARD_URL, wait_until="domcontentloaded", timeout=30_000)
except Exception as e:
print(f"Navigace: {e}")
if page.url.startswith("https://auth.vzp.cz/signin"):
print("Přihlašovací stránka — klikám na 'Certifikát'...")
cert_btn = page.locator("a, button").filter(has_text=re.compile(r"certifikát", re.I)).first
cert_btn.wait_for(state="visible", timeout=10_000)
cert_btn.click(no_wait_after=True)
print("Pokud se zobrazí dialog výběru certifikátu, vyberte ho ručně (max 60 s)...")
time.sleep(60)
page = context.new_page()
try:
page.goto(DASHBOARD_URL, wait_until="domcontentloaded", timeout=30_000)
except Exception as e:
print(f"Navigace po auth: {e}")
if not page.url.startswith("https://point.vzp.cz"):
print(f"Přihlášení selhalo. URL: {page.url}")
return
print("Přihlášení OK.")
page.wait_for_load_state("networkidle", timeout=15_000)
token = extract_bearer_token(page)
if token:
print("Bearer token načten.")
else:
print("Nepodařilo se načíst Bearer token ze stránky.")
finally:
saved = save_cookies(context)
print(f"Uloženo {saved} VZP cookies.")
context.close()
if not token:
sys.exit(1)
print("\nNačítám seznam podání...")
try:
forms = fetch_all_forms(token)
except Exception as e:
print(f"Chyba načítání podání: {e}")
sys.exit(1)
existing = set(os.listdir(DOWNLOAD_DIR))
print(f"\nV archivu: {len(existing)} souborů.")
print(f"Celkem podání v API: {len(forms)}\n")
downloaded = 0
skipped = 0
no_file = 0
for form in forms:
result = form.get("result") or {}
result_file = result.get("resultFile") or {}
file_id = result_file.get("fileId")
orig_name = result_file.get("name", "")
if not file_id or not orig_name:
no_file += 1
continue
date_str = parse_date(form.get("created", ""))
filename = f"{date_str} {orig_name}"
state = form.get("state", "")
if filename in existing:
print(f"{filename}")
skipped += 1
continue
size = result_file.get("size", 0)
print(f"{filename} ({size:,} B) [{state}]")
if dry_run:
downloaded += 1
continue
dest = os.path.join(DOWNLOAD_DIR, filename)
if download_file(token, form["id"], file_id, dest):
existing.add(filename)
downloaded += 1
print()
if dry_run:
print(f"[dry-run] Ke stažení: {downloaded}, přeskočeno: {skipped}, bez souboru: {no_file}")
else:
print(f"Staženo: {downloaded}, přeskočeno (již existovalo): {skipped}, bez souboru: {no_file}")
if __name__ == "__main__":
main()
@@ -1,94 +1,87 @@
"""
01 - Přihlášení na portál VoZP — extrakce cookies (jako admin).
Přihlášení na portál VoZP pomocí certifikátu (bez NMSigneru).
POSTUP:
1. Spusť: python 01_prihlaseni.py
2. Vyskočí UAC — schval (skript potřebuje admin pro čtení Chrome cookies
kvůli App-Bound Encryption v Chrome v130+).
3. Otevře se nové okno s běžícím skriptem (admin).
4. Skript otevře Chrome (jako user) na VoZP login.
5. Přihlas se certifikátem (NMSigner vyskočí — klikni ANO).
6. Vrať se k Chrome → ZAVŘI ho (všechna okna).
7. Stiskni Enter v admin okně skriptu.
8. Cookies se uloží do vozp_cookies.json.
JAK TO FUNGUJE:
Portál používá challenge-response autentizaci:
1. Skript načte přihlašovací stránku → dostane session cookie (SID)
2. Požádá server o zprávu k podpisu (challenge s časovým razítkem)
3. Podepíše ji certifikátem jako PKCS7/CMS detached signature (RSA + SHA-256)
4. Odešle podpis na server → server ověří a vrátí přihlášení
5. Výsledné cookies uloží do vozp_cookies.json pro další skripty
POŽADAVKY:
pip install rookiepy
POUŽITÍ:
pip install requests cryptography
python 01_prihlaseni.py
CERTIFIKÁT:
Exportuj z Windows: certmgr.msc → Osobní → pravý klik → Exportovat
Formát: PKCS #12 (.PFX), bez CA řetězu
Uložit do: ../../Certificates/MBQualifiedCert.pfx
"""
import ctypes
import json
import os
import subprocess
import sys
LOGIN_URL = "https://portal.vozp.cz/app/prihlaseni"
INBOX_URL = "https://portal.vozp.cz/app/prehled-zprav-ve-schrankach"
import requests
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import pkcs7, pkcs12
PFX_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../Certificates/MBQualifiedCert.pfx"))
PFX_PASSWORD = b"Vlado7309208104++"
COOKIES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "vozp_cookies.json"))
def is_admin() -> bool:
try:
return ctypes.windll.shell32.IsUserAnAdmin() != 0
except Exception:
return False
BASE_URL = "https://portal.vozp.cz"
CHALLENGE_URL = f"{BASE_URL}/json-api/prihlaseni/prihlasovaci-zprava"
CERTLOGIN_URL = f"{BASE_URL}/json-api/prihlaseni/prihlaseni-certifikatem"
def relaunch_as_admin() -> None:
print("Skript potřebuje admin práva (Chrome v130+ App-Bound Encryption).")
print("Otevírám UAC...")
params = " ".join(f'"{a}"' for a in sys.argv)
ret = ctypes.windll.shell32.ShellExecuteW(
None, "runas", sys.executable, params, None, 1
def main() -> None:
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"X-Requested-With": "XMLHttpRequest",
"Origin": BASE_URL,
"Referer": BASE_URL + "/",
})
# 1. Načti login stránku → SID cookie
r = session.get(f"{BASE_URL}/app/prihlaseni")
r.raise_for_status()
session.cookies.set("pzp_sign", "CERT", domain="portal.vozp.cz", path="/")
# 2. Získej challenge
r = session.post(CHALLENGE_URL, json={"login_sign": "CERT"},
headers={"Content-Type": "application/json; charset=UTF-8"})
r.raise_for_status()
zprava = r.json()["data"]["zprava"]
# 3. Podepis (PKCS7 detached, RSA + SHA-256, bez CA řetězu)
with open(PFX_PATH, "rb") as f:
private_key, cert, _ = pkcs12.load_key_and_certificates(f.read(), PFX_PASSWORD)
podpis = (
pkcs7.PKCS7SignatureBuilder()
.set_data(zprava.encode("utf-8"))
.add_signer(cert, private_key, hashes.SHA256())
.sign(serialization.Encoding.PEM, [pkcs7.PKCS7Options.DetachedSignature])
.decode("ascii").strip()
)
if ret <= 32:
print(f"UAC selhalo (kód {ret}). Spusť ručně jako admin.")
sys.exit(1)
sys.exit(0)
# 4. Přihlas se
r = session.post(CERTLOGIN_URL, json={"zprava": zprava, "podpis": podpis},
headers={"Content-Type": "application/json; charset=UTF-8"})
r.raise_for_status()
data = r.json()["data"]
def open_chrome_as_user(url: str) -> None:
"""Spustí default browser jako standarduser, i když Python běží jako admin."""
# explorer.exe běží jako user a otevře URL v defaultním prohlížeči
try:
subprocess.Popen(["explorer.exe", url])
if not data.get("prihlasen"):
print(f"Přihlášení selhalo: {r.json()['errMsg']}")
return
except Exception:
pass
# Fallback
import webbrowser
webbrowser.open(url)
print(f"Přihlášení úspěšné — {data.get('url', '')}")
def read_cookies_rookiepy() -> list[dict]:
import rookiepy
raw = rookiepy.chrome(["vozp.cz", ".vozp.cz", "portal.vozp.cz", "portalzp.cz"])
out = []
for c in raw:
dom = c["domain"]
if not any(d in dom for d in ["vozp.cz", "portalzp.cz"]):
continue
out.append({
"name": c["name"],
"value": c["value"],
"domain": dom if dom.startswith(".") else "." + dom,
"path": c.get("path") or "/",
"expires": int(c["expires"]) if c.get("expires") else -1,
"secure": bool(c.get("secure", False)),
"httpOnly": bool(c.get("http_only", False)),
"sameSite": "Lax",
})
return out
def read_cookies_browser_cookie3() -> list[dict]:
import browser_cookie3
cj = browser_cookie3.chrome(domain_name="vozp.cz")
out = []
for c in cj:
if not any(d in c.domain for d in ["vozp.cz", "portalzp.cz"]):
continue
out.append({
# 5. Ulož cookies
cookies = [
{
"name": c.name,
"value": c.value,
"domain": c.domain if c.domain.startswith(".") else "." + c.domain,
@@ -97,70 +90,12 @@ def read_cookies_browser_cookie3() -> list[dict]:
"secure": bool(c.secure),
"httpOnly": False,
"sameSite": "Lax",
})
return out
def main() -> None:
if not is_admin():
relaunch_as_admin()
return # nedosažitelné
print("=" * 60)
print("BĚŽÍM JAKO ADMIN — PŘIHLÁŠENÍ DO VoZP")
print("=" * 60)
print(f"1. Otevírám Chrome (jako user) na: {LOGIN_URL}")
print("2. Přihlas se certifikátem (NMSigner → klikni ANO)")
print("3. Po přihlášení ZAVŘI Chrome úplně (všechna okna)")
print("4. Vrať se sem a stiskni Enter")
print("=" * 60)
open_chrome_as_user(LOGIN_URL)
input("\nAž jsi přihlášen/a a Chrome ZAVŘENÝ, stiskni Enter...")
cookies: list[dict] = []
last_err = ""
print("\nČtu cookies (jako admin)...")
try:
cookies = read_cookies_rookiepy()
print(f" rookiepy OK: {len(cookies)} cookies")
except ImportError:
last_err = "rookiepy není nainstalovaný"
print(f" {last_err} — zkouším browser_cookie3...")
except Exception as e:
last_err = f"rookiepy: {e}"
print(f" rookiepy selhalo: {e} — zkouším browser_cookie3...")
if not cookies:
try:
cookies = read_cookies_browser_cookie3()
print(f" browser_cookie3 OK: {len(cookies)} cookies")
except ImportError:
print(" browser_cookie3 není nainstalovaný.")
except Exception as e:
print(f" browser_cookie3: {e}")
if not cookies:
print("\nCookies se nepodařilo přečíst ani jako admin.")
print("Zkontroluj, že:")
print(" - Chrome je úplně zavřený (Task Manager → žádný chrome.exe)")
print(" - Jsi se na VoZP přihlásil/a (DB má nové záznamy)")
print(" - Máš nainstalované: pip install rookiepy browser-cookie3")
input("\nEnter pro zavření...")
sys.exit(1)
}
for c in session.cookies
]
with open(COOKIES_FILE, "w", encoding="utf-8") as f:
json.dump(cookies, f, indent=2, ensure_ascii=False)
print(f"\nUloženo {len(cookies)} cookies do {COOKIES_FILE}")
for c in cookies:
exp = c["expires"]
tag = "PERSISTENT" if exp > 0 else "SESSION"
print(f" - {c['name'][:50]} ({c['domain']}) [{tag}]")
input("\nHotovo. Enter pro zavření...")
print(f"Uloženo {len(cookies)} cookies → {COOKIES_FILE}")
if __name__ == "__main__":
@@ -14,7 +14,8 @@ import winreg
from datetime import datetime
from pathlib import Path
import requests as req
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
from Knihovny.najdi_dropbox import get_dropbox_root
LOGIN_URL = "https://portal.vozp.cz/app/prihlaseni"
BASE_URL = "https://portal.vozp.cz"
@@ -23,7 +24,7 @@ DOWNLOAD_URL = f"{BASE_URL}/html/prehled-zprav-ve-schrankach/zobrazit-prilohu"
CHROME_PROFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "chrome_profile"))
COOKIES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "vozp_cookies.json"))
DOWNLOAD_DIR = os.path.join(os.path.dirname(__file__), "Staženo")
DOWNLOAD_DIR = os.path.join(get_dropbox_root(), "Ordinace", "Dokumentace_ke_zpracování", "Zúčtovací zprávy", "201 VoZP")
# Všechny schránky — ID-segment : zobrazovaný název
SCHRANKY = {
@@ -150,41 +151,28 @@ def collect_rows(page) -> list[dict]:
return [r for r in data if r["fileId"]]
def make_requests_session(context) -> req.Session:
"""Vytvoří requests.Session se cookies z Playwright kontextu."""
session = req.Session()
for c in context.cookies():
session.cookies.set(
c["name"], c["value"],
domain=c.get("domain", "").lstrip(".")
)
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Referer": INBOX_URL,
})
return session
def download_file(session: req.Session, file_id: str, target: str) -> bool:
"""Stáhne soubor přes přímý HTTP požadavek. Vrací True při úspěchu."""
def download_file(context, file_id: str, target: str) -> bool:
"""Stáhne soubor přes Playwright request context (sdílí cookies se session)."""
url = f"{DOWNLOAD_URL}?zprava_id={file_id}"
try:
r = session.get(url, timeout=30, stream=True)
r.raise_for_status()
r = context.request.get(url, headers={"Referer": INBOX_URL})
if not r.ok:
print(f" HTTP {r.status} (id={file_id})")
return False
with open(target, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
f.write(r.body())
return True
except Exception as e:
print(f" Chyba stahování (id={file_id}): {e}")
return False
def process_schránka(page, session: req.Session, segment: str, name: str, already: set) -> tuple[int, int]:
def process_schránka(page, context, segment: str, name: str, already: set) -> tuple[int, int]:
"""Projde všechny stránky schránky a stáhne soubory. Vrací (staženo, přeskočeno)."""
downloaded = 0
skipped = 0
page_num = 1
seen_ids: set = set()
while True:
url = f"{INBOX_URL}/{segment}/stranka-{page_num}"
@@ -202,6 +190,13 @@ def process_schránka(page, session: req.Session, segment: str, name: str, alrea
print(f" Stránka {page_num} — žádné řádky, končím schránku.")
break
# Portál vrátí poslední stránku znovu místo prázdné — detekuj opakování
current_ids = {r["fileId"] for r in rows}
if current_ids & seen_ids:
print(f" Stránka {page_num} — opakující se obsah, končím schránku.")
break
seen_ids.update(current_ids)
print(f" Nalezeno {len(rows)} zpráv.")
for row in rows:
@@ -213,19 +208,11 @@ def process_schránka(page, session: req.Session, segment: str, name: str, alrea
continue
print(f" Stahuji: {info['filename']}")
if download_file(session, row["fileId"], target):
if download_file(context, row["fileId"], target):
already.add(info["filename"])
downloaded += 1
time.sleep(0.3)
# Zkontroluj, jestli existuje další stránka
has_next = page.evaluate("""() => {
return !!Array.from(document.querySelectorAll('a')).find(
a => a.innerText.trim() === 'Další stránka' && !a.closest('[aria-disabled]')
);
}""")
if not has_next:
break
page_num += 1
return downloaded, skipped
@@ -268,6 +255,7 @@ def main() -> None:
ignore_https_errors=True,
args=["--force-renderer-accessibility"],
)
logged_in = False
try:
loaded = load_cookies(context)
print(f"Cookies načtené z JSON: {loaded}")
@@ -277,7 +265,7 @@ def main() -> None:
if not ensure_logged_in(page, context):
return
session = make_requests_session(context)
logged_in = True
already = set(os.listdir(DOWNLOAD_DIR))
print(f"V archivu: {len(already)} souborů.\n")
@@ -286,7 +274,7 @@ def main() -> None:
for segment, name in SCHRANKY.items():
print(f"\n=== Schránka: {name} ===")
dl, sk = process_schránka(page, session, segment, name, already)
dl, sk = process_schránka(page, context, segment, name, already)
print(f" Schránka {name}: staženo {dl}, přeskočeno {sk}")
total_dl += dl
total_skip += sk
@@ -295,8 +283,9 @@ def main() -> None:
print(f"Hotovo. Celkem staženo: {total_dl}, přeskočeno: {total_skip}")
finally:
saved = save_cookies(context)
print(f"Uloženo {saved} VoZP cookies.")
if logged_in:
saved = save_cookies(context)
print(f"Uloženo {saved} VoZP cookies.")
context.close()
@@ -17,6 +17,9 @@ from pathlib import Path
import requests as req
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
from Knihovny.najdi_dropbox import get_dropbox_root
LOGIN_URL = "https://portal.vozp.cz/app/prihlaseni"
BASE_URL = "https://portal.vozp.cz"
INBOX_URL = f"{BASE_URL}/app/prehled-zprav-ve-schrankach"
@@ -24,7 +27,7 @@ DOWNLOAD_URL = f"{BASE_URL}/html/prehled-zprav-ve-schrankach/zobrazit-prilohu"
CHROME_PROFILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "chrome_profile"))
COOKIES_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "vozp_cookies.json"))
DOWNLOAD_DIR = os.path.join(os.path.dirname(__file__), "Staženo")
DOWNLOAD_DIR = os.path.join(get_dropbox_root(), "Ordinace", "Dokumentace_ke_zpracování", "Zúčtovací zprávy", "201 VoZP")
SCHRANKY = {
"171-schranka-poskytovatele-zdravotnich-sluzeb": "Schránka PZS",
@@ -264,6 +267,7 @@ def main() -> None:
ignore_https_errors=True,
args=["--force-renderer-accessibility"],
)
logged_in = False
try:
loaded = load_cookies(context)
print(f"Cookies načtené z JSON: {loaded}")
@@ -273,6 +277,7 @@ def main() -> None:
if not ensure_logged_in(page, context):
return
logged_in = True
session = make_requests_session(context)
already = set(os.listdir(DOWNLOAD_DIR))
print(f"V archivu: {len(already)} souborů.\n")
@@ -289,8 +294,9 @@ def main() -> None:
print(f"Hotovo. Celkem nových souborů: {total_dl}")
finally:
saved = save_cookies(context)
print(f"Uloženo {saved} VoZP cookies.")
if logged_in:
saved = save_cookies(context)
print(f"Uloženo {saved} VoZP cookies.")
context.close()
@@ -0,0 +1,35 @@
"""
Přihlásí se na portál VoZP a stáhne nové zprávy.
Kombinuje 01_prihlaseni.py + 03_stahuj_nove.py do jednoho spuštění.
POUŽITÍ:
python 04_prihlaseni_a_stahuj_nove.py
"""
import subprocess
import sys
import os
DIR = os.path.dirname(os.path.abspath(__file__))
def run(script: str) -> None:
result = subprocess.run(
[sys.executable, os.path.join(DIR, script)],
check=False,
)
if result.returncode != 0:
raise SystemExit(f"Skript {script} skončil s chybou (kód {result.returncode})")
def main() -> None:
print("=== Přihlášení ===")
run("01_prihlaseni.py")
print("\n=== Stahování nových zpráv ===")
run("03_stahuj_nove.py")
if __name__ == "__main__":
main()
@@ -0,0 +1,146 @@
# VoZP — Automatické stahování zpráv
## Co to dělá
Dva skripty které se přihlásí na portál VoZP a stáhnou všechny zprávy ze schránek:
1. `01_prihlaseni.py` — přihlásí se certifikátem, uloží cookies do `vozp_cookies.json`
2. `02_stahuj_vse.py` — použije cookies, projde všechny schránky, stáhne soubory do `Staženo/`
---
## Jak funguje přihlášení (challenge-response)
Portál **nepoužívá heslo** — autentizuje certifikátem přes 4 kroky:
### Krok 1 — Získej session
```
GET https://portal.vozp.cz/app/prihlaseni
→ server nastaví cookie: SID=...
```
Ručně nastav cookie: `pzp_sign=CERT` (říká serveru, že chceš certifikát)
### Krok 2 — Získej challenge
```
POST /json-api/prihlaseni/prihlasovaci-zprava
Body: {"login_sign": "CERT"}
Odpověď:
{
"data": {
"zprava": "Prohlášení:\r\nTímto se přihlašuji k Portálu VOZP ČR\r\n\r\nOkamžik vygenerování ... mikrosekundu: 20.04.2026 13:22:34.058119"
}
}
```
### Krok 3 — Podepis certifikátem
Zprávu z kroku 2 podepíš jako **PKCS7 / CMS SignedData**:
- Algoritmus: **RSA + SHA-256**
- Typ: **DetachedSignature** (obsah není vložen do podpisu)
- **BEZ CA řetězu** — pouze end-entity certifikát, žádné CA certifikáty
```python
podpis = (
pkcs7.PKCS7SignatureBuilder()
.set_data(zprava.encode("utf-8"))
.add_signer(cert, private_key, hashes.SHA256())
.sign(Encoding.PEM, [PKCS7Options.DetachedSignature])
)
```
> ⚠️ Přidání CA řetězu způsobí chybu "neznámý klientský certifikát" — i když thumbprint sedí!
### Krok 4 — Přihlášení
```
POST /json-api/prihlaseni/prihlaseni-certifikatem
Body: {
"zprava": "<původní challenge text>",
"podpis": "-----BEGIN PKCS7-----\n...\n-----END PKCS7-----"
}
Úspěch: {"data": {"prihlasen": true, "url": "/app/uvodni-stranka"}}
```
---
## Certifikát
| Položka | Hodnota |
|---|---|
| Soubor | `U:\ordinaceprojekt\Insurance\Certificates\MBQualifiedCert.pfx` |
| Vlastník | MUDr. Michaela Buzalková |
| Vydavatel | I.CA EU Qualified CA2/RSA 06/2022 |
| Platnost | do 2027-01-16 |
| Thumbprint | `056ED80A3CDDE31DD36EECE0181B4E78D61122A7` |
| Serial (hex) | `0247068517B0049E2E` |
Jak exportovat správný certifikát z Windows:
```powershell
# Najdi certifikát podle thumbprintu
$cert = Get-ChildItem Cert:\CurrentUser\My | Where-Object {$_.Thumbprint -eq "056ED80A3CDDE31DD36EECE0181B4E78D61122A7"}
$pwd = Read-Host "Heslo" -AsSecureString
Export-PfxCertificate -Cert $cert -FilePath "MBQualifiedCert.pfx" -Password $pwd
```
> Při exportu: PKCS #12, **nezaškrtávej** "Include all certificates in the certification path"
---
## Stahování souborů
`02_stahuj_vse.py` používá **Playwright** (viditelný Chrome) pro navigaci + `context.request.get()` pro stahování.
Endpoint stahování:
```
GET /html/prehled-zprav-ve-schrankach/zobrazit-prilohu?zprava_id={id}
```
### Schránky které se prochází
| Segment URL | Název |
|---|---|
| `171-schranka-poskytovatele-zdravotnich-sluzeb` | Schránka PZS |
| `183-schranka-klientu-portalu` | Schránka klientů portálu |
| `185-schranka-pzs` | Schránka PZS2 |
| `187-schranka-klienta` | Schránka klienta |
| `198-vypis-registrovanych-pacientu` | Výpis registrovaných pacientů |
| `200-zuctovaci-zpravy` | Zúčtovací zprávy |
| `205-vypis-osobnich-uctu-pojistencu` | Výpis osobních účtů pojištěnců |
### Paginace — důležité
Portál při přetečení stránek (např. stranka-16 když existuje jen 15) **vrátí poslední stránku znovu** místo prázdné. Detekce: sleduj `fileId` napříč stránkami, zastav při opakování.
```python
current_ids = {r["fileId"] for r in rows}
if current_ids & seen_ids:
break # opakující se obsah = konec
seen_ids.update(current_ids)
```
---
## Pojmenování stažených souborů
```
YYYY-MM-DD Popis zprávy (původní_název_souboru).přípona
```
Příklad: `2026-04-15 Odpověď na podání č. 176867777 (Protokol-OK).html`
---
## Závislosti
```
pip install requests cryptography playwright
playwright install chrome
```
---
## Spuštění
```bash
python 01_prihlaseni.py # přihlásí, uloží cookies
python 02_stahuj_vse.py # stáhne vše nové
```
@@ -0,0 +1,98 @@
H0930500100097260211
I 120ćÖMA LUD·K 011125018309032021201
I 220ODLASOVµ KRISTíNA 025313312212052021201
I 315LADISLAVOVµ ERIKA 065809508630102025201
I 415KüÖ¦OVµ TEREZA 085701508214092025201
I 585LE¦µKOVµ JANA 365429074 01112016201
I 685FAM·ROVµ MARIE 395815085 01112016201
I 785KO曵KOVµ MARIE 396015429 01112016201
I 885HEUSCHNEIDER ARNOćT 400406144 23062020201
I 985FAM·RA JIüÖ 400424003 01112016201
I 1085KAMENÖK JAROSLAV 400510088 22022023201
I 1185HOLE¬KOVµ JARMILA 405101482 07022023201
I 1280JAKUćOVµ •UDMILA 415817101 01112016201
I 1380HAMERNÖK JOSEF 420622031 01112016201
I 1480SKALOVµ ALENA 435522111 01112016201
I 1580RADOVµ MARIE 436120039 01112016201
I 1680KOTíNEK STANISLAV 440201053 08032019201
I 1780HAMERNÖKOVµ EMILIE 445306004 01112016201
I 1880KUSµKOVµ JAROSLAVA 445318078 01112016201
I 1980DVORSKµ V·RA 455429053 01112016201
I 2080PECHROVµ MILUćKA 456111032 01112016201
I 2180SMETANOVµ MARIE 456114025 01112016201
I 2275ć›ASTNí LIBOR 461001479 25022022201
I 2375ćESTµKOVµ MIROSLAVA 465810001 01112016201
I 2475MUZIKµüOVµ JAROSLAVA 475123056 01112016201
I 2575ULRICHOVµ VLASTA 476208033 01112016201
I 2675PROCHµZKOVµ JAROSLAVA 476210075 01112016201
I 2775PERNICOVµ MIROSLAVA 486023438 01112016201
I 2875BIL¬IK ćT·PµN 491030140 01112016201
I 2975DROZD FRANTIćEK 500504119 01072017201
I 3075KOTíNKOVµ VLASTA 505408284 15012019201
I 3175MARHOULOVµ JITKA 506022198 01112016201
I 3270ćOTOLA RADOMÖR 510214143 12042021201
I 3370BIL¬IKOVµ JANA 516110008 01112016201
I 3470NIMEüICKµ IRENA 536117083 01012026201
I 3570PROD·LAL JIüÖ 550418077001112016201
I 3665DOLE¦AL VµCLAV 560731079702102018201
I 3765MEDKOVµ HEDVIKA 585204184401012016201
I 3865ZVµROVµ MARIE 586205138201112016201
I 3965ćABRćULA KAREL 600321077421102021201
I 4065ćULC JIüÖ 600804024701112016201
I 4160VAN·¬EK KAREL 630920061221082025201
I 4260KOUDA MIROSLAV 630927233201112016201
I 4360LEKSOVµ IVA 645902155801012018201
I 4460ćABRćULOVµ ALENA 646123109521102021201
I 4560POU¬KOVµ DANA 655311038801112016201
I 4660KURKOVµ DANIELA 656201029012112024201
I 4755TU¬KOVµ MARIE 665716055420092018201
I 4855POSPÖćIL JIüÖ 670810111401112016201
I 4955PINTÖü MILAN 680730037901112016201
I 5055AUBRECHTOVµ JITKA 685110042101112016201
I 5155MILATOVµ MARTINA 685322207901112016201
I 5255ćÖMOVµ HELENA 686024155320052019201
I 5355KITTLER RADEK 690415049501112016201
I 5455KUBELKA DAVID 690511008001112016201
I 5555RATKIEWICZOVµ ROMANA 705518041801112016201
I 5655TŢMOVµ RENµTA 705908762901112016201
I 5750¬ELÖKOVSKµ HANA 715428039701112016201
I 5850KUBELKOVµ BLANKA 716221004401112016201
I 5950SLABí MICHAL 720117008601112016201
I 6050GIERTLI PETER 721207752101112016201
I 6150KüÖ¦ JIüÖ 730628019001112016201
I 6250VOJµ¬EK ALEć 730930044901112016201
I 6350STRNAD JIüÖ 740417599901112016201
I 6450UNGER MICHAL 740612045801112016201
I 6550NE¬AS VµCLAV 740821184401112016201
I 6650ćÖPKOVSKí MARTIN 740822042401112016201
I 6750ćµMAL ROMAN 741107045801112016201
I 6850HEJKAL VµCLAV 750331043001112016201
I 6950MAJTµŐ PETR 750604586601112016201
I 7045MAJTµŐOVµ RADKA 765711592801112016201
I 7145DRAKSEL DANIEL 770627006601112016201
I 7245YATES HANA 775503537601112016201
I 7345JANYćKOVµ LUCIE 775806551301012020201
I 7445ODLASOVµ DENISA 776231028101112016201
I 7545HOMOLKOVA HANNA 795108998201112016201
I 7645NIMEüICKí PETR 801212446101112016201
I 7745LADISLAVOVµ ERIKA 805111445630102025201
I 7840KüÖ¦ MICHAL 830107055801112016201
I 7940FIćEROVµ PETRA 835602011201112016201
I 8040EKHARD PETRA 836202290001112016201
I 8140MI¬ULKA JAN 841217512301112016201
I 8240ćKOCH PETR 850220385331072019201
I 8340BARTEJSOVµ PLA¬KOVµ PETRA 865102016904102017201
I 8435VOćICKµ DAGMAR 866021042701112016201
I 8535POU¬EK KAREL 870110039701112016201
I 8635JEüµBKOVµ JANA 885809049401072023201
I 8735MOU¬KA ZDEN·K 891028042301112016201
I 8835RATKIEWICZOVµ ROMANA 905415112801112016201
I 8935PAVLŢ JANA 905709176905042018201
I 9035¬ELÖKOVSKµ KRISTíNA 905905073701112016201
I 9135ćABRćULOVµ ANNA 906110008124032022201
I 9230BRYNDA¬OVµ NIKOLA 916211043001112016201
I 9330TU¬EK JIüÖ 930918040815092022201
I 9430PROSÖKOVµ JACQUELINE 935512017001072017201
I 9530ZEMµNEK JAN 950106248327082024201
I 9625MAJTµŐ RADIM 960425624431032022201
I 9725SµGNEROVµ KRISTíNA 975927357915042025201

Some files were not shown because too many files have changed in this diff Show More