FHIR是标准,是规范,使用FHIR使大家可以使用同一种语言、语义进行交流,名称、API都是统一的,只要符合FHIR标准,任何系统都可交互。对业务开发者来说,大部分接口交互的定义交给FHIR来处理,效率大大提高。
学习FHIR首先是一个再学习的过程,这个过程对有业务开发基础的人员来说要有个入门,这个入门就是FHIR的各个结构定义的含义,以及与业务系统数据以及接口的对照关系。
FHIR需要用到的几个工具:
- Forge 这个是用来查看定义好的结构及自定义结构。
- https://hl7.org/FHIR/R4B/resourcelist.html 这里面可以查看FHIR定义好的资源及说明。
- PostMan :用来测试API
InterSystems IRIS对于 FHIR相关内容的开发,带来了极大的便利性,里面自带一套定义好的FHIR服务器,也就是包含了FHIR定义的资源及其操作方法。
我这里选用的是最新版本的InterSystems IRIS 2024,做了传统业务系统中科室、医院、病人信息、就诊信息的FHIR转换,主要过程如下:
1. 安装InterSystems IRIS
2. 配置FHIR 服务器
3. 在HIS数据库(IRIS2022)上写一个Restful API 接口,做为对外的统一出口
// w ##class(src.rest.inif).HttpOut({})
ClassMethod HttpOut(Para As %Library.DynamicObject, Type = "1") As %Library.DynamicObject
{
new (Para,Type)
s valueType=$classname(Para)
if (valueType="%Library.DynamicArray"){s Param=[]}
if (valueType="%Library.DynamicObject"){s Param={}}
s iter=Para.%GetIterator()
while iter.%GetNext(.key , .srcKey ) {
d Param.%Set(key,srcKey)
}
s retObj={}
if ($classname(Param)="%Library.DynamicObject") {
return:(Param.%Size()=0) {}
}else {
s Param=##class(%Library.DynamicObject).%FromJSON(Para)
}
set httpReq=##class(%Net.HttpRequest).%New()
set httpReq.HTTPVersion="1.1"
set httpReq.Timeout=5
set httpReq.SocketTimeout=10
if (Param.Port="443") {s httpReq.Https=1
s httpReq.SSLConfiguration="ISC.FeatureTracker.SSL.Config"
} else {s httpReq.Port=Param.Port}
;b ;http1
s httpReq.Server=Param.Server
//表头
if ((Param.%IsDefined("Header"))&&(Param.Header.%Size()>0)){
set iter = Param.Header.%GetIterator()
while iter.%GetNext(.key , .value ) {
do httpReq.SetHeader(key,value)
}
}
//参数
if ((Param.%IsDefined("Param"))&&(Param.Param.%Size()>0)){
set iter = Param.Param.%GetIterator()
while iter.%GetNext(.key , .value ) {
do httpReq.SetParam(key,value)
}
}
if ($classname(Param.EntityBody)="%Library.DynamicObject") {
do httpReq.EntityBody.Write((Param.EntityBody).%ToJSON())
}
;b ;http2
if '(Param.%IsDefined("type")) {s Param.type="Post"}
if (Param.type="Get") {
set sc=httpReq.Get(Param.Url)
;b ;httpget
}
if (Param.type="Post") {
set sc=httpReq.Post(Param.Url,0)
}
if (Param.type="Put") {
;w "put"_Param.Url,!
set sc=httpReq.Put(Param.Url)
}
if (Param.type="Patch") {
set sc=httpReq.Patch(Param.Url)
}
if (Param.type="Delete") {
set sc=httpReq.Delete(Param.Url)
}
;B ;httpsc
if sc=1 {
if Type="1" {
set retObj=httpReq.HttpResponse.Data.Read()
if $classname(retObj)="%Library.DynamicObject" {
set retObj=##class(%DynamicObject).%FromJSON(retObj)
}else{
return retObj
}
}
if Type="2" {
;b ;aa2
set retObj=httpReq.HttpResponse.Data.Read()
}
if Type="3" {set retObj=httpReq.HttpResponse
;B ;3
}
}else {q "0:网络不通"}
;b ;comhttp
;set retObj=$zconvert(retObj,"I","UTF8")
s httpReq=""
b:retObj="Service Unavailable"
q retObj
}
ObjectScriptObjectScript
4. 做一个对象与FHIR转换程序。(目的是以后将HIS里面的数据转换为FHIR)
5. 整理FHIR的JSON对象:通过Forge或是网站https://hl7.org/FHIR/R4B/resourcelist.html,把FHIR 定义的资源中的各个数据元素内容转换为对象的定义。在整理的过程中我们总结了下面四点要注意的地方:
- 在FHIR规范里有部分内容是多级的,而我们的业务一般都是平级,如病人信息里的地址(address)。
- 在FHIR 规范里定义的valueSet和codesystem,就是对应我们日常理解的代码表。
- 对于业务内容的部分,我们先整理医院、科室信息,再整理病人信息、就诊信息。
- 对于我们现有业务系统的主键或是关联外键值,在FHIR里面可以放在identifier里面,做为查询的条件或指向
6. 从HIS业务库里把职业、医院等代码表数据组成JSON对象,与FHIR的JSON对象做对照并上传FHIR服务器
7. 再把病人信息和就诊信息从HIS业务库取出数据组成JSON对,与FHIR的JSON对象做对照并上传FHIR服务器
下面是一个地址(address)字段组成的json格式例子。
"telecom": [
{"system": "other","value": "PAPMIMobPhone"}],
"address": [
{
"use": "home",
"type": "both",
"line": ["aaaa"],
"state":"PAPMICTProvinceDR",
"city": "PAPMICityAreaDR",
"district": "Rainbow",
"state": "PAPMICTProvinceDR",
"postalCode": "3999",
"period": {
"start": "1974-12-25"
}
},
JSONJSON
在这里面以FHIR对象为基准,HIS对象名放在值里面。用一个统一的方法把HIS里的对象转换成FHIR 格式 的JSON对象。转换完后上传到FHIR服务器:
while iter.%GetNext(.key , .value ) {
s ret=##class(src.comm.comm).objToObj(value,target)
;if (value.CTLOCStartTime<value.CTLOCEndTime){set ret.status="inactive"}else{set ret.status="active"}
s conObj={"resource":"Patient","EntityBody":(ret),"Param":{"identifier":(value.PAPMINo)}}
s tmpObj=##class(src.comm.comm).operateDataFhir(conObj)
if (tmpObj.code=1){
s ret=##class(%DynamicObject).%FromJSON(tmpObj.data)
d retArr.%Push(ret)
}
;d retArr.%Push(value)
}
ObjectScriptObjectScript
转换示例
// 根据对照做对象转换
// s ret=##class(src.comm.comm).objToObj(src,target)
ClassMethod objToObj(src, target) [ Language = objectscript ]
{
n (src, target)
;s retObj=target
s iter=target.%GetIterator()
s valueType=$classname(target)
if (valueType="%Library.DynamicArray"){s retObj=##class(%Library.DynamicArray).%New()}
if (valueType="%Library.DynamicObject"){s retObj=##class(%Library.DynamicObject).%New()}
while iter.%GetNext(.key , .srcKey ) {
if ($classname(retObj)="%Library.DynamicArray"){d retObj.%Push(srcKey)}
if ($classname(retObj)="%Library.DynamicObject"){d retObj.%Set(key,srcKey)}
s valueType=$classname(srcKey)
if ((valueType="%Library.DynamicArray")||(valueType="%Library.DynamicObject")){
s temp=##class(src.comm.comm).objToObj(src,retObj.%Get(key))
d retObj.%Set(key,temp)
}else{
//为字符时
IF ($l(srcKey,"---")>1){
s srcKey=$replace(srcKey,"---","")
if (src.%IsDefined(srcKey)){
if (src.%Get(srcKey)=""){d retObj.%Remove(key)}
else{d retObj.%Set(key,src.%Get(srcKey))}
}else{
d retObj.%Remove(key)
}
}else{
if (src.%IsDefined(srcKey)){
if (src.%Get(srcKey)=""){d retObj.%Set(key,"空")}
else{ d retObj.%Set(key,src.%Get(srcKey))}
}
}
}
}
return retObj
}
ObjectScriptObjectScript
进行查询
可以看到查出了两个符合查询条件的病人。
赞!期待更多系列分享!