Files
ordinaceprojekt/DASTA/parse_dasta.py
T
2026-06-15 16:10:24 +02:00

261 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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 (<?xml ... encoding=...?>).
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> (přímo nebo uvnitř <pv>)
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)