Files
ordinaceprojekt/Vykony/NOTES.md
T
2026-06-01 12:17:41 +02:00

9.2 KiB
Raw Blame History

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ů, ~1020 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 520)

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):

  1. Načte celou aktivní kolekci do paměti (cislo_vykonu → dokument)
  2. 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
  3. Výkony co v novém scrape chybí → _aktivni = False, stará verze do historie (jen vykony)

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 001 atd.)
  • 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