9.2 KiB
Vykony — stahování a zpracování zdravotních výkonů
Přehled
Kompletní pipeline pro stahování zdravotních výkonů ze szv.mzd.gov.cz do MongoDB, včetně detailů každého výkonu a exportu do XLSX reportu.
szv.mzd.gov.cz
│
├── /Vykon/ → stahni_vykony.py → MongoDB: vykony + vykony_historie
└── /Vykon/Detail/{id}/ → stahni_detaily.py → MongoDB: detaily + detaily_historie
↓
report_vykony.py → vykony_report.xlsx
Skripty
| Skript | Co dělá |
|---|---|
stahni_vykony.py |
Stahuje seznam všech výkonů (přehledová data), ukládá do vykony s historií |
stahni_detaily.py |
Stahuje detailní stránku každého výkonu, ukládá do detaily s historií |
report_vykony.py |
Exportuje výkony + detaily do XLSX, jeden list na odbornost |
debug_detail.py |
Ladící skript — stáhne HTML jednoho výkonu a vypíše co parser vidí |
Doporučené pořadí spuštění
python stahni_vykony.py # 1. seznam výkonů (~4246 kusů, ~1 min)
python stahni_detaily.py # 2. detaily (~4246 HTTP requestů, ~10–20 min)
python report_vykony.py # 3. export do XLSX
Nastavení skriptů
Každý skript má na začátku blok konfiguračních proměnných — není potřeba sahat do kódu:
stahni_detaily.py
KOLIK = 0 # 0 = vše; jinak max. počet ke stažení (pro testování: 10)
FORCE = False # True = přestáhni i už stažené záznamy
WORKERS = 5 # počet paralelních vláken (doporučeno 5–20)
report_vykony.py
ODBORNOSTI = ["001", "002"] # [] = všechny odbornosti
VYSTUP = Path(__file__).parent / "vykony_report.xlsx"
MongoDB
Host: 192.168.1.76:27017
DB: zdravotni_vykony
GUI: MongoDB Compass (zdarma, od MongoDB Inc.)
Kolekce vykony — aktuální přehledový stav
Vždy obsahuje poslední verzi každého výkonu. Hodnoty jsou zkrácené (kódy bez popisů).
| Pole | Typ | Popis |
|---|---|---|
cislo_vykonu |
str | Unikátní klíč (např. 01021) |
odbornost |
str | Kód odbornosti (např. 001) |
nazev_vykonu |
str | Název výkonu |
kategorie |
str | Kód kategorie (např. P) |
typ_vykonu |
str | Kód typu (např. A) |
doba_trvani |
float|null | Minuty |
omezeni_mistem |
str|null | Kód omezení (např. A) |
omezeni_frekvenci |
str|null | Např. 1/1 den |
prime_naklady |
float|null | Body |
osobni |
float|null | Body |
body_rezijni |
float|null | Body |
body_celkem |
float|null | Body |
revize |
datetime|null | Datum poslední revize výkonu |
detail_url |
str|null | URL detailu na szv.mzd.gov.cz |
_aktivni |
bool | false pokud výkon zmizel ze scrape |
_platny_od |
datetime | Kdy tato verze začala platit |
_scraped_at |
datetime | Čas posledního úspěšného scrape |
_deaktivovano |
datetime | Kdy byl výkon deaktivován (jen pokud _aktivni=false) |
Indexy: cislo_vykonu (unique), odbornost, _aktivni, _platny_od
Kolekce vykony_historie — archiv změn přehledu
Každý záznam = jedna historická verze výkonu (stav před změnou).
Obsahuje stejná pole jako vykony, navíc:
| Pole | Typ | Popis |
|---|---|---|
_platny_do |
datetime | Kdy tato verze přestala platit |
_zmenena_pole |
list[str] | Která pole se změnila (nebo ["_aktivni"] při deaktivaci) |
Indexy: cislo_vykonu, _platny_do, _zmenena_pole
Kolekce detaily — aktuální detailní stav
Obsahuje kompletní data z detailní stránky každého výkonu. Hodnoty jsou dlouhé (s popisy). Skalární pole + vnořené sub-tabulky jako pole objektů.
Skalární pole:
| Pole | Typ | Popis |
|---|---|---|
cislo_vykonu |
str | Unikátní klíč, propojení s vykony |
nazev |
str | Plný název výkonu |
kategorie |
str | Dlouhý popis (např. P - hrazen plně) |
typ_formulare |
str | Např. ambulantní |
omezeni_mistem |
str | Dlouhý popis (např. A - pouze ambulantně) |
omezeni_frekvenci |
str | Např. 1/1 den |
doba_trvani |
float|null | Minuty |
nepocitat_rezii |
bool | |
popis |
str | Obecný popis (často prázdný) |
poznamka |
str | Poznámka |
podminky |
str | Podmínky pro vykázání |
cim_zacina |
str | Popis začátku výkonu |
obsah_rozsah |
str | Obsah a rozsah výkonu |
cim_konci |
str | Popis konce výkonu |
body_prime |
float|null | Bodová hodnota — přímé náklady |
body_osobni |
float|null | Bodová hodnota — osobní náklady |
body_rezijni |
float|null | Bodová hodnota — režijní náklady |
body_celkem |
float|null | Bodová hodnota — celkem |
detail_url |
str | URL zdroje |
_platny_od |
datetime | Kdy byla tato verze detailu uložena |
_scraped_at |
datetime | Čas posledního scrape |
Sub-tabulky (pole objektů):
| Pole | Klíče objektů |
|---|---|
autorska_odbornost |
Kód, Název, Pořadí, Sazba režie |
dalsi_odbornost |
Kód, Název, Sazba režie |
nositele |
Kategorie, Funkce, Praxe, Cas, Bodyaktualni, Poznamka |
materialy |
Kód, Název, Doplněk, Množství, Jednotka, Cena, DPH %, Body |
pripravky |
Kód, Název, Doplněk, ATC, Omezení, Množství, Jednotka, Cena, Body |
pristroje |
Kód, Název, D.Ž., N.Ú., D.P., DPH %, Body |
zum |
Kód, Název |
zulp |
Kód, Název |
bodova_hodnota |
Přímé, Osobní, Režijní, Celkem (1 řádek, flatten na skalární body_*) |
Indexy: cislo_vykonu (unique), _scraped_at, _platny_od
Kolekce detaily_historie — archiv změn detailů
Každý záznam = stav detailu před změnou.
Obsahuje stejná pole jako detaily, navíc:
| Pole | Typ | Popis |
|---|---|---|
_platny_do |
datetime | Kdy tato verze přestala platit |
_zmenena_pole |
list[str] | Která pole se změnila |
Indexy: cislo_vykonu, _platny_do, _zmenena_pole
Logika detekce změn
Stejná logika platí pro oba páry kolekcí (vykony/vykony_historie i detaily/detaily_historie):
- Načte celou aktivní kolekci do paměti (
cislo_vykonu → dokument) - Pro každý nově stažený záznam:
- Nový → vloží s
_platny_od = run_at - Změněn (liší se v některém z
COMPARE_FIELDS) → stará verze jde do historie s_platny_do+_zmenena_pole, nová verze se zapíše - Nezměněn → jen aktualizuje
_scraped_at
- Nový → vloží s
- Výkony co v novém scrape chybí →
_aktivni = False, stará verze do historie (jenvykony)
Technické poznámky
Proč ne MVCGridHandler.axd?
Ajax endpoint (/MVCGridHandler.axd?Name=VykonGrid&pageSize=9999) ignoruje pageSize
a vrací max 50 záznamů. Navíc po vyčerpání dat cyklí od začátku.
Správný endpoint je přímo stránka /Vykon/.
HTML struktura detailní stránky
- Hlavní tabulka:
<table class="detailTabulka"> - Řádky:
<th>label</th><td>hodnota</td>(ne<td><td>jak by se čekalo) - Sub-tabulky:
<table class="VnitrniTabulka">uvnitř<td> - Parser iteruje
find_all("tr", recursive=False)— bez rekurze nevleze do vnořených tabulek
Encoding
Server vrací latin znaky i při UTF-8 hlavičce → resp.apparent_encoding místo resp.encoding.
Rate limiting
stahni_detaily.py dělá time.sleep(0.1) po každém requestu per worker.
Při 20 workerech ≈ 200 requestů/s — server to bez problémů zvládá.
Typické MongoDB dotazy
// Všechny aktivní výkony odbornosti 001
db.vykony.find({ odbornost: "001", _aktivni: true })
// Detail konkrétního výkonu
db.detaily.findOne({ cislo_vykonu: "01021" })
// Výkony s podmínkami (neprázdné pole)
db.detaily.find({ podminky: { $ne: "" } })
// Výkony které se někdy změnily
db.vykony_historie.distinct("cislo_vykonu")
// Historie konkrétního výkonu (detail)
db.detaily_historie.find({ cislo_vykonu: "01021" }).sort({ _platny_do: 1 })
// Co se změnilo při posledním runu
db.detaily_historie.find({ _platny_do: { $gte: ISODate("2026-06-01") } })
// Výkony s nositelem kategorie L3
db.detaily.find({ "nositele.Kategorie": "L3" })
// Výkony s materiálem (neprázdné materialy)
db.detaily.find({ "materialy.0": { $exists: true } })
// Deaktivované výkony
db.vykony.find({ _aktivni: false })
XLSX Report
Skript report_vykony.py generuje vykony_report.xlsx:
- Jeden list na odbornost (název listu:
Odbornost 001atd.) - Výkony řazené podle čísla výkonu
- Více nositelů → více řádků pro jeden výkon (střídavé zbarvení po výkonech)
- Sloupec Postup výkonu = čím začíná + obsah a rozsah + čím končí (odděleno prázdným řádkem)
- Záhlaví zmrazeno (freeze panes)
Sloupce reportu: Číslo výkonu · Název · Kategorie · Typ formuláře · Doba trvání · Omezení místem · Omezení frekvencí · Nepočítat režii · Body přímé · Body osobní · Body režijní · Body celkem · Postup výkonu · Nositel kategorie · Nositel funkce · Nositel čas · Nositel body
Závislosti
requests
beautifulsoup4
lxml
pymongo
openpyxl