HL7

HL7的缩写Health Level Seven,HL7主要是用于医疗信息交换的一种交互格式。标准的HL7示例如下所示:

MSH|^~\&|HIS_InPatient|WinningSoft|TECH|WinningSoft|2022092929|pv1|ADT^A01|B763F986-920B-4D11-829F-0058F2A21818|P|2.4|||||CHN
EVN|A01|20220905092929
PID|1|5321^^^&PATID|5321^^^&PATID~0054749^^^&BLH~330102803012^^^&BRKH^2~3301241212021415^^^&SFZH~0^^^&YEXH||张三||192000000|M^^^^^^15867113807|||S^未知
NK1||张三|SEL^自己|||||||其他职业|19
PV1|1|I|4050^血液科^^^^^2928^血液科||||||||||1||||||68111|202~本地医保(住院)|||||||
PV2||||||||||||||||||||||||||||||||||||N
DG1|||D72.802^白细胞增多症|||0|0

什么是段?我理解类似与xml的节点头,每一段都会有一个父节点,如上面示例的MSH,EVN,PID,PV1,OBX,DG1, 不同类型的HL7消息包含不同的HL7段。
前面介绍HL7介绍了主要是用于医疗信息交换的格式,所有每一个段也代表了不同的含义,关于段名称的解释如下:

  1. MSH

包含有关消息本身的信息。该信息包括消息的发送者和接收者、消息的类型以及发送的日期和时间

  1. PID

包含患者基本信息,例如姓名、患者ID和地址等一些基本信息

  1. NK1

包含患者的亲属信息,如父/母的基本信息

  1. PV1

包含患者的就诊信息,如床号,病区,送检科室,送检医生等

  1. PV2

包含患者的患者就诊附加信息

  1. ORC

包含患者的患者医嘱信息,如医嘱项目,医嘱代码,相关的人员信息等

  1. OBR

包含患者的诊断以及观察的请求信息,用于记录医嘱信息

  1. OBX

包含患者观察的结果,如临床诊断,肉眼所见,电镜所见等

HL7 的每一段都包含多个域,每个域用“ ”来区分
域可以是原始数据类型(例如字符串或数字),也可以包含多个元素(Component)。如果某个域(fields)包含多个元素,则这些元素(Component)通常以^字符分隔。如果元素还包含子元素(Subcomponent),则这些子元素通常以&字符分隔,子元素(Subcomponent)必须 是原始数据类型(例如字符串或数字)。
  1. “分隔符中可以包含其他的分隔符:
  2. “^” 成分分隔符,表示该位置有多个属性,例如: 101761^张三 该位置是患者信息,101761是患者编号 张三是患者名字
  3. “~” 子成分分隔符,成分的下一分级
  4. “&” 表示该位置是数组结构,类型相同可以循环 例如: &张三 &李四

消息类型

常用的消息类型可以分为以下几类,目前接口对接用到的最多的是ADT、ACK、ORM、ORU,列举这些主要是做一个备注

  1. ADT admit disCharge transfer 入院、出院、转院
  2. ACK acknowledgement message 应答消息
  3. BAR biling account record 账单账户记录
  4. DFT detailed financial transactions 详细的金融交易
  5. MDM Medical document management 医疗文件管理
  6. ORM order entry 订单录入
  7. ORU Observation result (unsolicited) 观察结果 非请求观察
  8. RDS pharmacy/treatment dispense 药房/治疗 配药
  9. RDE pharmacy/treatment encoded order 药房/治疗 编码顺序
  10. SIU schedlued information unsolicited 调度信息 非请求观察

XML的转换

目前在与其它信息系统交互的时候,因为涉及到HL7的读写,我使用的方法是通过一个封装类将HL7转换为XML,通过XML的节点来获取需要拿到的节点信息,代码片段如下所示:

 public static XmlDocument ConvertToXmlObject(string sHL7)
    {
        _xmlDoc = CreateXmlDoc();

        //hl7的处理,先把\r\n \n\r \r统一替换成\n,然后用\n来分隔行避免不同接口传过来的hl7换行方式不一致
        sHL7 = sHL7.Replace("\r\n", "\n").Replace("\n\r", "\n").Replace("\r", "\n");
        //把HL7分成段
        string[] sHL7Lines = sHL7.Split('\n');//经过测试,TCP方式接收的用\r分隔,webService方式接收的用\n分隔

        //遍历每一段
        for (int i = 0; i < sHL7Lines.Length; i++)
        {
            // 判断是否空行
            if (sHL7Lines[i] != string.Empty)
            {
                string sHL7Line = sHL7Lines[i];//某一段

                //通过“|”分隔
                string[] sFields = GetMessgeFields(sHL7Line);

                // 为段(一行)创建第一级节点
                XmlElement el = _xmlDoc.CreateElement(sFields[0]);
                _xmlDoc.DocumentElement.AppendChild(el);

                //遍历每个“|”与“|”间的内容
                for (int a = 0; a < sFields.Length; a++)
                {
                    // 为字段创建第二级节点
                    XmlElement fieldEl = _xmlDoc.CreateElement(sFields[0] + "." + a.ToString());

                    //是否包括HL7的连接符
                    if (sFields[a] != @"^~\&")//0:如果这一行有任何分隔符,继续分隔。如果没有任何分隔符,可以直接写节点值。
                    {
                        //通过"~"分隔
                        string[] sComponents = GetRepetitions(sFields[a]);
                        if (1 == 1)//1:如果可以用“~”分隔,继续分隔;如果没有“~”符,开始用“~”分隔。不管有没有"~"符,都循环分隔
                        {
                            for (int b = 0; b < sComponents.Length; b++)
                            {
                                XmlElement componentEl = _xmlDoc.CreateElement(sFields[0] + "." + a.ToString() + "." + b.ToString());

                                //通过"^"分隔
                                string[] subComponents = GetComponents(sComponents[b]);
                                if (subComponents.Length > 1)//2.如果有字组,大部分是没有的
                                {
                                    for (int c = 0; c < subComponents.Length; c++)
                                    {
                                        //修改了一个错误
                                        string[] subComponentRepetitions = GetSubComponents(subComponents[c]);
                                        if (subComponentRepetitions.Length > 1)
                                        {
                                            for (int d = 0; d < subComponentRepetitions.Length; d++)
                                            {
                                                XmlElement subComponentRepEl = _xmlDoc.CreateElement(sFields[0] + "." + a.ToString() + "." + b.ToString() + "." + c.ToString() + "." + d.ToString());
                                                subComponentRepEl.InnerText = subComponentRepetitions[d];
                                                componentEl.AppendChild(subComponentRepEl);
                                            }
                                        }
                                        else
                                        {
                                            XmlElement subComponentEl = _xmlDoc.CreateElement(sFields[0] + "." + a.ToString() + "." + b.ToString() + "." + c.ToString());
                                            subComponentEl.InnerText = subComponents[c];
                                            componentEl.AppendChild(subComponentEl);

                                        }
                                    }
                                    fieldEl.AppendChild(componentEl);
                                }
                                else    //2.如果没有字组了,大部分是没有
                                {
                                    string[] sRepetitions = GetSubComponents(sComponents[b]);
                                    if (sRepetitions.Length > 1)
                                    {
                                        XmlElement repetitionEl = null;
                                        for (int c = 0; c < sRepetitions.Length; c++)
                                        {
                                            repetitionEl = _xmlDoc.CreateElement(sFields[0] + "." + a.ToString() + "." + b.ToString() + "." + c.ToString());
                                            repetitionEl.InnerText = sRepetitions[c];
                                            componentEl.AppendChild(repetitionEl);
                                        }
                                        fieldEl.AppendChild(componentEl);
                                        el.AppendChild(fieldEl);
                                    }
                                    else
                                    {
                                        componentEl.InnerText = sComponents[b];
                                        fieldEl.AppendChild(componentEl);
                                        el.AppendChild(fieldEl);
                                    }
                                }
                            }
                            el.AppendChild(fieldEl);
                        }
                    }
                    else
                    {
                        //0:如果不可以分隔,可以直接写节点值了。
                        fieldEl.InnerText = sFields[a];
                        el.AppendChild(fieldEl);
                    }
                }
            }
        }

        return _xmlDoc;
    }

     /// <summary>
    /// 通过|分隔
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    private static string[] GetMessgeFields(string s)
    {
        return s.Split('|');
    }

    /// <summary>
    /// 通过^分隔
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    private static string[] GetComponents(string s)
    {
        return s.Split('^');
    }

    /// <summary>
    /// 通过某连接符分隔
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    private static string[] GetSubComponents(string s)
    {
        return s.Split('&');
    }

    /// <summary>
    /// 通过~分隔 重复
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    private static string[] GetRepetitions(string s)
    {
        return s.Split('~');
    }

    /// <summary>
    /// 创建XML对象
    /// </summary>
    /// <returns></returns>
    private static XmlDocument CreateXmlDoc()
    {
        XmlDocument output = new XmlDocument();
        XmlElement rootNode = output.CreateElement("HL7Message");
        output.AppendChild(rootNode);
        return output;
    }
大概逻辑就是段来设置父节点,根据关键字符(^ ~ )来设置字段,这样可以直接通过指定的节点来获取所需要的信息,代码片段如下:
log.Info("接收到的xml 信息为" + xml);

XmlDocument objDoc = new XmlDocument();
objDoc.LoadXml(xml);
XmlNode rootNode = objDoc.DocumentElement;
SynApplyInfo Syn = new SynApplyInfo();

//患者ID
Syn.PatientID = rootNode.SelectSingleNode("/HL7Message/PID/PID.2/PID.2.0/PID.2.0.0").InnerText;
//病历号
Syn.HisID = rootNode.SelectSingleNode("/HL7Message/PV1/PV1.19/PV1.19.0").InnerText;
//身份证号
Syn.IdentityCard = rootNode.SelectSingleNode("/HL7Message/PID/PID.3/PID.3.3/PID.3.3.0").InnerText;
//姓名
Syn.PatientName = rootNode.SelectSingleNode("/HL7Message/PID/PID.5/PID.5.0").InnerText;