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解析内容的文档:
b. 关于HL7通信协议的介绍:
3)设计理念及结构 [1]
◆ Chapter: 将医院中的流程分割成好几大块(如抽象数据集,ADT)。
◆ 触发事件(Trigger events): 当现实世界中发生的事件产生了系统间数据流动的需求,则称其为触发事件。运用所有找出的三个英文字头来凑成这些消息(Message)。
◆ 消息(Message): 将每个流程定位出好几个触发事件(Trigger Events),它是系统间 传输数据的基本单位 ,由一组有规定次序的段组成。每个消息都是用一个消息类型来表示其用途。
◆ 段(Segment): 将医院中需要存放的资料分类到无法分割的项目(如病患资料,将其命名为PID),它是数据字段的一个逻辑组合。每个段都用一个唯一的三字符代码所标志,这个代码称作段标志。
◆ 域(Field): 它是一个字符串,是段的最小组成单位。
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]
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 中填入自己的数据。)
需要注意的点:
- Content-type 别写错
- 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