# -*- coding: utf-8 -*- """ Parser DASTA XML (Datový standard MZ ČR, verze DS 03.01.01). Rozebírá laboratorní zprávy (typ odesílatele LB) do strukturovaných dat. Soubory jsou v kódování windows-1250 (uvedeno v XML deklaraci) a obsahují DOCTYPE odkaz na lokální DTD (ds030101.dtd), který při parsování ignorujeme. Struktura DASTA dávky (zjednodušeně): dasta kořen, hlavička dávky (datum, verze, odesílatel) zdroj_is informační systém, který dávku vytvořil pm (icp) příjemce zprávy (ordinace) + adresa (a typ="P") is (icp, ico) odesílatel = laboratoř + adresa (a typ="O") ip (id_pac) pacient: rodné číslo, jméno, dat. narození, sex pv / p pojišťovna (kodpoj, typpoj) dg / dgz / diag diagnózy v blok výsledků vr (klic_nclp...) jeden laboratorní výsledek (analyt) nazev_lclp název položky (WBC, RBC, ...) dat_du/dat_pl/dat_vv odběr / příjem / vydání výsledku autor validující lékař vrn číselný výsledek hodnota naměřená hodnota nazvy@jednotka měrná jednotka skala s1..s8 referenční pásma (meze) interpret_g_z grafická interpretace | | * | | """ from __future__ import annotations import sys from dataclasses import dataclass, field, asdict from pathlib import Path from xml.etree import ElementTree as ET # --------------------------------------------------------------------------- # Datové třídy # --------------------------------------------------------------------------- @dataclass class Vysledek: klic_nclp: str # kód NČLP (Národní číselník laboratorních položek) nazev: str # lokální název položky (WBC, RBC, ...) hodnota: str | None jednotka: str | None dat_odber: str | None # dat_du – datum a čas odběru dat_vydani: str | None # dat_vv – datum a čas vydání výsledku autor: str | None stav: str | None # stav_vys (A = definitivní) typ_kvant: str | None # priznak_kvant (R = reálné číslo) interpret: str | None # grafická interpretace mezí meze: list[str] = field(default_factory=list) # s1..s8 @property def referencni_mez(self) -> str | None: """Klinicky relevantní referenční rozmezí = s4 (dolní) až s5 (horní).""" if len(self.meze) >= 5: return f"{self.meze[3]} – {self.meze[4]}" return None @dataclass class Pacient: id_pac: str | None rodne_cislo: str | None jmeno: str | None prijmeni: str | None datum_narozeni: str | None sex: str | None pojistovna: str | None typ_pojisteni: str | None diagnozy: list[str] = field(default_factory=list) vysledky: list[Vysledek] = field(default_factory=list) @dataclass class Adresa: jmeno: str | None = None radky: list[str] = field(default_factory=list) psc: str | None = None mesto: str | None = None @dataclass class DastaZprava: ozn_soub: str | None id_soubor: str | None datum_vytvoreni: str | None verze_ds: str | None typ_odesilatele: str | None zdroj_program: str | None zdroj_verze: str | None prijemce_icp: str | None prijemce: Adresa | None odesilatel_icp: str | None odesilatel_ico: str | None odesilatel: Adresa | None pacienti: list[Pacient] = field(default_factory=list) # --------------------------------------------------------------------------- # Pomocné funkce # --------------------------------------------------------------------------- def _text(el, tag: str) -> str | None: """Vrátí text potomka `tag` nebo None.""" if el is None: return None child = el.find(tag) return child.text.strip() if (child is not None and child.text) else None def _parse_adresa(el) -> Adresa | None: if el is None: return None a = el.find("a") if a is None: return None radky = [v for v in (_text(a, "adr"), _text(a, "dop1"), _text(a, "dop2")) if v] return Adresa( jmeno=_text(a, "jmeno"), radky=radky, psc=_text(a, "psc"), mesto=_text(a, "mesto"), ) # --------------------------------------------------------------------------- # Hlavní parser # --------------------------------------------------------------------------- def parse_dasta(cesta: str | Path) -> DastaZprava: cesta = Path(cesta) # XML obsahuje DOCTYPE s odkazem na DTD; vypneme načítání externích entit # tím, že parsujeme bez resolveru. ElementTree DTD ignoruje automaticky. raw = cesta.read_bytes() # ElementTree si kódování přečte z XML deklarace (). root = ET.fromstring(raw) zdroj = root.find("zdroj_is") pm = root.find("pm") is_el = root.find("is") zprava = DastaZprava( ozn_soub=root.get("ozn_soub"), id_soubor=root.get("id_soubor"), datum_vytvoreni=root.get("dat_vb"), verze_ds=root.get("verze_ds"), typ_odesilatele=root.get("typ_odesm"), zdroj_program=zdroj.get("kod_prog") if zdroj is not None else None, zdroj_verze=zdroj.get("verze_prog") if zdroj is not None else None, prijemce_icp=pm.get("icp") if pm is not None else None, prijemce=_parse_adresa(pm), odesilatel_icp=is_el.get("icp") if is_el is not None else None, odesilatel_ico=is_el.get("ico") if is_el is not None else None, odesilatel=_parse_adresa(is_el), ) if is_el is None: return zprava for ip in is_el.findall("ip"): # pojišťovna – bere se z elementu
(přímo nebo uvnitř