# -*- 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ř ) p = ip.find("p") if p is None: pv = ip.find("pv") p = pv.find("p") if pv is not None else None pacient = Pacient( id_pac=ip.get("id_pac"), rodne_cislo=_text(ip, "rodcis"), jmeno=_text(ip, "jmeno"), prijmeni=_text(ip, "prijmeni"), datum_narozeni=_text(ip, "dat_dn"), sex=_text(ip, "sex"), pojistovna=_text(p, "kodpoj") if p is not None else None, typ_pojisteni=_text(p, "typpoj") if p is not None else None, ) # diagnózy dg = ip.find("dg") if dg is not None: for diag in dg.iter("diag"): if diag.text: pacient.diagnozy.append(diag.text.strip()) # výsledky v = ip.find("v") if v is not None: for vr in v.findall("vr"): vrn = vr.find("vrn") nazvy = vrn.find("nazvy") if vrn is not None else None skala = vrn.find("skala") if vrn is not None else None meze = [] interpret = None if skala is not None: for i in range(1, 9): meze.append(_text(skala, f"s{i}") or "") interpret = _text(skala, "interpret_g_z") pacient.vysledky.append(Vysledek( klic_nclp=vr.get("klic_nclp"), nazev=_text(vr, "nazev_lclp"), hodnota=_text(vrn, "hodnota") if vrn is not None else None, jednotka=nazvy.get("jednotka") if nazvy is not None else None, dat_odber=_text(vr, "dat_du"), dat_vydani=_text(vr, "dat_vv"), autor=_text(vr, "autor"), stav=vr.get("stav_vys"), typ_kvant=vrn.get("priznak_kvant") if vrn is not None else None, interpret=interpret, meze=meze, )) zprava.pacienti.append(pacient) return zprava # --------------------------------------------------------------------------- # Výpis přehledu # --------------------------------------------------------------------------- def vypis_prehled(z: DastaZprava) -> None: print("=" * 70) print(f"DASTA dávka {z.ozn_soub} (DS {z.verze_ds}, typ {z.typ_odesilatele})") print(f"Vytvořeno: {z.datum_vytvoreni}") print(f"Program: {z.zdroj_program} {z.zdroj_verze}") print(f"ID souboru: {z.id_soubor}") print("-" * 70) if z.odesilatel: print(f"Odesílatel (laboratoř) IČP {z.odesilatel_icp} IČO {z.odesilatel_ico}") print(f" {z.odesilatel.jmeno} | {', '.join(z.odesilatel.radky)}") print(f" {z.odesilatel.psc} {z.odesilatel.mesto}") if z.prijemce: print(f"Příjemce (ordinace) IČP {z.prijemce_icp}") print(f" {z.prijemce.jmeno} | {', '.join(z.prijemce.radky)}") print("=" * 70) for pac in z.pacienti: print(f"\nPacient: {pac.prijmeni} {pac.jmeno} r.č. {pac.rodne_cislo}" f" nar. {pac.datum_narozeni} {pac.sex}") print(f" Pojišťovna {pac.pojistovna} (typ {pac.typ_pojisteni})" f" Diagnózy: {', '.join(pac.diagnozy) or '—'}") print(f" Výsledků: {len(pac.vysledky)}") print() print(f" {'Položka':<14}{'Hodnota':>10} {'Jedn.':<9}" f"{'Ref. mez':<18}{'Interpr.':<12}NČLP") print(" " + "-" * 75) for vys in pac.vysledky: ref = vys.referencni_mez or "" interp = (vys.interpret or "").strip() print(f" {vys.nazev or '':<14}{vys.hodnota or '':>10} " f"{vys.jednotka or '':<9}{ref:<18}{interp:<12}{vys.klic_nclp}") if __name__ == "__main__": if len(sys.argv) > 1: cesta = sys.argv[1] else: cesta = r"u:\Dropbox\Ordinace\pomoc\DASTA\RLB05E6T.xml" zprava = parse_dasta(cesta) vypis_prehled(zprava)