z230
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
# -*- 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)
|
||||
Reference in New Issue
Block a user