添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
旅行中的蛋挞  ·  高性能 Oracle JDBC 编程·  1 年前    · 
豁达的小马驹  ·  Spring Boot 2.3.1 ...·  2 年前    · 
呐喊的便当  ·  Excel - 知乎·  2 年前    · 
python解析hl7协议的那点事儿

python解析hl7协议的那点事儿

一、HL7协议理解及其主流解析方式对比

1.HL7简介及结构组成

1)背景简介

  • HL7 缩写于Health Level Seven(健康信息交换第七层协议),是创建于1987年,用来发展独立卫生保健行业的电子交换交换标准。HL7组织参考了国际标准组织ISO(International Standards Organization),采用开放式系统互联OSI(Open System Interconnection)的通信模式,将HL7纳为最高的一层,也就是应用层。
  • 简单的理解是正如市场上XML,JSON格式比较广泛一样,HL7也是一种数据格式,可以理解为一个字符串,只是这个字符串分为了好几个段而已。
  • 主流解析语言为C、Java,以Python作为解析语言的库及其示例较少。

2)两个非常有用的参考文档

a. 关于HL7解析内容的文档:

What is HL7?

b. 关于HL7通信协议的介绍:

3)设计理念及结构 [1]

Chapter: 将医院中的流程分割成好几大块(如抽象数据集,ADT)。
触发事件(Trigger events): 当现实世界中发生的事件产生了系统间数据流动的需求,则称其为触发事件。运用所有找出的三个英文字头来凑成这些消息(Message)。
消息(Message): 将每个流程定位出好几个触发事件(Trigger Events),它是系统间 传输数据的基本单位 ,由一组有规定次序的段组成。每个消息都是用一个消息类型来表示其用途。
段(Segment): 将医院中需要存放的资料分类到无法分割的项目(如病患资料,将其命名为PID),它是数据字段的一个逻辑组合。每个段都用一个唯一的三字符代码所标志,这个代码称作段标志。
域(Field): 它是一个字符串,是段的最小组成单位。
HL7消息结构图

4)HL7数据示例

message = 'MSH|^~\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\r'
message += 'PID|||555-44-4444||EVERYWOMAN^EVE^E^^^^L|JONES|196203520|F|||153 FERNWOOD DR.^^STATESVILLE^OH^35292||(206)3345232|(206)752-121||||AC555444444||67-A4335^OH^20030520\r'
message += 'PV1||I|W^389^1^UABH^^^^3||||12345^MORGAN^REX^J^^^MD^0010^UAMC^L||67890^GRAINGER^LUCY^X^^^MD^0010^UAMC^L|MED|||||A0||13579^POTTER^SHERMAN^T^^^MD^0010^UAMC^L|||||||||||||||||||||||||||200605290900'
message += 'OBR|1|845439^GHH OE|1045813^GHH LAB|1554-5^GLUCOSE|||200202150730||||||||555-55-5555^PRIMARY^PATRICIA P^^^^MD^^LEVEL SEVEN HEALTHCARE, INC.|||||||||F||||||444-44-4444^HIPPOCRATES^HOWARD H^^^^MD\r'
message += 'OBX|1|SN|1554-5^GLUCOSE^POST 12H CFST:MCNC:PT:SER/PLAS:QN||^182|mg/dl|70_105|H|||F'
message +=  'OBX||NM|AG_FiO2||21.00|%|18.00-100.00||||\r'
  • 段落符号 :HL7中每一段都是以一段三个大写字母开头的,代表整个段落的含义,MSH(头信息),PID(患者信息),PV1(开单信息)等等。
  • | 符号 (域分隔符):HL7中每个段落中的位置分格符,两个“|”符号之间表示一个位置,可以填上相关结构的内容,比如字符串等,以“段落-数字”表示该位置。
  • ^ 符号 (成分分隔符):HL7中许多段落的位置中是多个成分的,比如执行科室代码与执行科室名称,一般用^符号分隔,分隔后形成两个新的段。
  • ~ 符号 (子成分分隔符):HL7中在分了成分之后,子成分如果分为多个成分,则用~符号进行分隔,分隔后形成两个新的段。
  • & 符号 (循环分隔符):表示该段位置放置的是数组结构,类型相同,可以循环。

5)HL7数据类型 [2]

HL7数据类型

2.HL7的几种解析方式简述

  • python-hl7 (pip install hl7)
    • 解析方式不是很友好,索引全部使用下标,不易理解,目前查阅官方文档仅支持MLLP通讯协议方式。
    • 如果想要了解使用,推荐此文档:
    • GitHub参阅:
  • HL7apy (pip install hl7apy) ---> ( 本人推荐使用 )
    • 查阅官方文档相对较完善,此库具备解析的基础功能点,也支持http协议方式进行数据传输,需自行构建相关Massage对象(如ACK、MDMT02...等)
    • 推荐文档:
    • HL7apy GitHub源码地址:
  • hl7-parser (pip install hl7parser)
    • 目前查阅官方文档不完善,可参阅资料较少。

二、基于Python的HL7协议解析(示例以HL7apy为主)

1.简单解析示例 [3]

# message 的值见1.1.3中的数据示例
from hl7apy.parser import parse_message
from hl7apy.core import Group, Segment
from hl7apy.parser import parse_segment, parse_field, parse_component
msg = parse_message(message.replace('\n', '\r'), find_groups=True, validation_level=2)
print(msg.children)
print(msg.children[1].children)
print(msg.children[1].value)
for segment in msg.children:
    if isinstance(segment, Segment):
        for attribute in segment.children:
            print(attribute, attribute.value)
    if isinstance(segment, Group):
        for group in segment.children:
            for group_segment in group.children:
                for attribute in group_segment.children:
                    print(attribute, attribute.value)
示例代码打印结果

如上示例,可以循环遍历打印结果,也可以通过 字典方式 获取:

# 获取message_control_id示例
# 下面两种方式是一样的,也可以用这个方式在Massage对象的这个域中赋值
print(msg.MSH.msh_10)
print(msg.MSH.message_control_id)

2.Python服务端协议

基于Python的服务端常用框架有Django、Flask、Tornado,并支持Http协议数据传输方式。Django框架过于完善,不适用此项目,后尝试以Flask轻量级框架作为HL7服务端,通讯过程中发现Flask返回协议为http/1.0版本(未使用Gunicorn等高并发策略),项目客户端为http/1.1版本,无法正常通讯。最终 采用Tornado作为服务端

关于HL7通讯 即时返回ACK响应 问题,可以采用服务端异步处理策略(可参考本人另一篇文章的Tornado异步处理的部分):

3.解析类初始化(为构造Massage对象使用)

response.MSH.MSH_1 ---> field_separator
response.MSH.MSH_2 ---> encoding_characters
response.MSH.MSH_3 ---> sending_application
response.MSH.MSH_4 ---> sending_facility
response.MSH.MSH_5 ---> receiving_application
response.MSH.MSH_6 ---> receiving_facility
response.MSH.MSH_7 ---> Date/Time Of Message
response.MSH.MSH_8 ---> security
response.MSH.MSH_9 ---> message_type
response.MSH.MSH_10 ---> message_control_id
response.MSH.MSH_11 ---> processing_ID
response.MSH.MSH_12 ---> version_id

OBX.2 - Value Type
OBX.5 - Observation Value

MSA_1 - AA/AE/AR
MSA_2 - message_control_id
MSA_3 - message信息
import json
import datetime
import uuid
from hl7apy.parser import parse_message, parse_segment
from hl7apy.core import Message
from hl7apy.v2_5 import DTM
class HL7ParseFunc():
    def __init__(self,):
        self.VALID_MESSAGES = ('MDM^T02^MDM_T02', "ACK^T02^ACK",)
        self.APPLICATION_ACCEPT = "AA"
        self.APPLICATION_ERROR = "AE"
        self.APPLICATION_REJECT = "AR"
        self.APPLICATION_SENDER = "发送方"
        self.FACILITY_SENDER = "SERVER1"
        self.APPLICATION_RECEIVER = "接收方"
        self.FACILITY_RECEIVER = "SERVER1"

4.验证客户端请求

分别验证请求的发送方和接收方的 Application Facility 是否正确。也可以采用其他逻辑验证,逻辑本质基本是一样的。

def valid_request(self, message_request):
    # 验证请求类型为是否为MDM_T02(根据项目请求方式验证)
    if message_request.MSH.message_type.value in self.VALID_MESSAGES:
        msh = message_request.children[0].value.split('|')
        # 分割后按索引读取,以验证请求数据的接收方(MSH)的身份为 IKANG
        return True if msh[4] == self.APPLICATION_SENDER and msh[5] == self.FACILITY_SENDER else False
    else:
        return False

5.构造Massage对象 (以ACK响应为例)

构造ACK响应对象,也就是在ACK对象中的每个域分别写入我们的数据即可。 (构造其他对象也是同理。如MDMT02,只是在 response.MSH.MSH_9 ---> message_type 中写入"MDM^T02^MDM_T02",其他每个 Segment 中填入自己的数据。)

需要注意的点:

  1. Content-type 别写错
  2. response.to_er7() 数据转化为hl7的字符串格式
def set_default_header(self):
    self.set_header("Content-type", "application/hl7-v2")
    self.set_header('Access-Control-Allow-Methods', 'POST, GET')
class fir_Handler(tornado.web.RequestHandler):
    executor = ThreadPoolExecutor(300)
    def get(self, *args, **kwargs):
        self.write("hello")
    def post(self, *args, **kwargs):
        message = self.request.body
        message = bytes.decode(message)
        m = parse_message(message, find_groups=True, validation_level=2)
        # 获取项目中的data数据逻辑(可忽略)
        s = parse_segment(m.children[1].value)
        data = s.observation_value.value
        data = data.replace("\T\\", "&")
        data_json = eval(data)
        print(type(data_json), data_json)
        # ----- 此处若有其他逻辑,可参考异步处理 -----
        # XXX = Message('MDM_T02')
        response = Message('ACK')
        response.MSH.sending_application = m.MSH.sending_application
        response.MSH.MSH_3 = m.MSH.MSH_5
        response.MSH.MSH_5 = m.MSH.MSH_3
        response.MSH.MSH_4 = m.MSH.MSH_6
        response.MSH.MSH_6 = m.MSH.MSH_4
        # XXX.MSH.MSH_9 = "MDM^T02^MDM_T02"
        response.MSH.MSH_9 = "ACK^T02^ACK"
        response.MSH.MSH_11 = "P"
        response.MSA.MSA_1 = APPLICATION_ACCEPT
        response.MSA.MSA_2 = m.MSH.message_control_id
        # 添加其他Segment