Article
· Jul 11, 2023 8m read

Installation and adaptation of EMPI in Standalone mode - Feeding the beast with FHIR - Configuration

We return to the attack with our EMPI!

In previous articles we have seen how to configure and customize our EMPI, we have seen how we can include new patients in our system through HL7 messaging, but of course, not everything is HL7 v.2 in this life! How could we configure our EMPI instance to work with FHIR messaging?

What is FHR?

For those of you who are not too familiar with the term FHIR, just indicate that they are the initials of Fast Healthcare Interoperability Resource. FHIR is a healthcare interoperability standard developed by HL7 in which, based on the JSON format and REST communications, a series of "resources" are established with different types of information (patient data, hospital centers, diagnoses, medical appointments... .) You can take a look at all these resources on their official page 

InterSystems and FHIR

From InterSystems we are aware of the utility that FHIR provides for the world of healthcare interoperability and for this reason we have a wide range of products and functionalities that allow us to take advantage of and squeeze its full potential:

Well, for our article we are going to take advantage of the functionality provided by the Adapter to receive FHIR messages.

EMPI configuration

FHIR Adapter Installation

Let's first review what services we have in our standalone EMPI installation:

As you can see, after installing the EMPI in standalone mode, a Registry service was created with all the necessary options to configure our EMPI, as well as a namespace that we call HSPIDATA and in which we have a production to manage the interoperability functionalities that need.

For our case we have created a new namespace called WEBINAR in which we will deploy normal interoperability production. It will be in this namespace where we will install the FHIR Interoperability Adapter, this installation will publish a web application to which we will send our REST calls with the FHIR messages. To do this we will execute the following commands from the terminal:

zn "WEBINAR"
set status = ##class(HS.FHIRServer.Installer).InteropAdapterConfig("/csp/healthshare/webinar/fhir/r4")

Once installed, we will check the new application created from the Management Portal:

Let's check our production in the WEBINAR namespace:

Two new Business Components have been created by default:

  1. InteropService: In our production, we are using directly the name of its class HS.FHIRServer.Interop.Service, which will be the Business Service in charge of receiving the FHIR message sent to our endpoint. /csp/healthshare/webinar/fhir/r4
  2. InteropOperation: we have changed this name to HS.FHIRServer.Interop.Operation. For our example we have ignored this Business Operation since we are going to send the information of the FHIR message by TCP to our EMPI production

Creation of Business Components to manage FHIR messaging

Very well, we already have our Business Service enabled to receive FHIR messages, now what we are interested in is extracting the information from the message to send it to our EMPI production. Let's take a look at the type of message that the HS.FHIRServer.Interop.Service is going to receive:

Include HS.FHIRServer

/// FHIRServer REST Business Service
Class HS.FHIRServer.Interop.Service Extends (Ens.BusinessService, HS.HC.Util.Trace.Helper)
{

Parameter SETTINGS = "TargetConfigName:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},Timeout:Basic";
/// Configuration item to which to send inbound messages.
Property TargetConfigName As Ens.DataType.ConfigName [ InitialExpression = "HS.FHIRServer.Interop.Operation" ];
/// Timeout for dispatch (so we don't hold up the HTTP service too long or hang up a production shutdown).
Property Timeout As %Integer [ InitialExpression = 25 ];
/// Process an incoming message into the production; dispatch it to the configured target.
/// The Interoperability contract requires that errors be returned as %Status here.
Method OnProcessInput(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status

The message is of type HS.FHIRServer.Interop.Request and if we consult the documentation we will see that it has an associated property called QuickStreamId which we will use to extract the associated FHIR message. To do this, we will redirect the message received in the Business Service to the Business Process Webinar.BP.FHIRToHubRequest

This Business Process will only have the function of redirecting the message to the Business Operation FromFHIRToMPI in charge of sending the message via TCP to the production of the EMPI.

Method OnRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status
{
	Set tSC = ..SendRequestSync("FromFHIRToMPI", pRequest, .pResponse)
	Quit $$$OK
}

Let's take a look at the Webinar.BO.ToMPI class and explain how it gets the message from FHIR and transforms it into a String that we will send to our EMPI production:

Include HS.FHIRServer

Class Webinar.BO.ToMPI Extends Ens.BusinessOperation
{

Parameter ADAPTER = "EnsLib.TCP.CountedOutboundAdapter";
Property Adapter As EnsLib.TCP.CountedOutboundAdapter;
Parameter INVOCATION = "Queue";
Parameter SETTINGS = "FHIRMetadataSet::selector?context={HS.FHIRServer.Util.ContextSearch/FHIRMetadataSets}";
/// FHIR Metadata Set. These are defined in HS_FHIRServer.FHIRMetadataSet.
Property FHIRMetadataSet As %String(MAXLEN = 256);
Method SendFHIRRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status
{
	// Get version of FHIR message configured
	Set tFHIRMetadataSetKey = $ZStrip($Piece(..FHIRMetadataSet, "/", 1), "<>W")
	
	// We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request
	If pRequest.QuickStreamId'="" {
		
		// Recover QuickStream of the FHIR message
		Set tQuickStream = ##class(HS.SDA3.QuickStream).%OpenId(pRequest.QuickStreamId)
		
		// Checking if message is in JSON format or XML
		If pRequest.Request.RequestFormatCode'="" {
			Set tFHIRFormat = pRequest.Request.RequestFormatCode
		} Else {
			$$$ThrowOnError(##class(HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat))
			Do tQuickStream.Rewind()
			If tFHIRFormat="json" {
				Set tFHIRFormat = $$$FHIRContentCodeJSON
			} ElseIf tFHIRFormat="xml" {
				Set tFHIRFormat = $$$FHIRContentCodeXML
			}
		}
		
		// Transform QuickStream to DynamicObject
		Set tDynObj = ..GetDynObj(tQuickStream, tFHIRMetadataSetKey, tFHIRFormat)
		
		
	} ElseIf (($IsObject(pRequest.Request.Json))&&(pRequest.Request.Json.%GetIterator().%GetNext())) {
		// Could have Json %DynamicObject if this host is called InProc.
		Set tDynObj = ..GetDynObj(pRequest.Request.Json, tFHIRMetadataSetKey)
		
	} Else {
		$$$ThrowStatus($$$ERROR($$$GeneralError, "FHIR interop request message missing FHIR content"))
	}
	
	// Transform Dynamic Object to string
	set tRequest = tDynObj.%ToJSON()
	
	// Send message to EMPI production
	Set tSC= ..Adapter.SendMessageString(tRequest,.tResponse)
	
	$$$ThrowOnError(pRequest.NewResponse(.pResponse))
	//Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New()
	Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode
	
	set pResponse.Response.Status = 200
	set pResponse.ContentType = "text/plain"
	Quit tSC
}

ClassMethod GetDynObj(stream As %Stream.Object, fhirVersion As %String, fhirFormat As %String) As %DynamicObject
{
	
	set schema = ##class(HS.FHIRServer.Schema).LoadSchema(fhirVersion)
	if fhirFormat = $$$FHIRContentCodeJSON {
		set dynObj = {}.%FromJSON(stream)
	} elseif fhirFormat = $$$FHIRContentCodeXML {
		set dynObj = ##class(HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema)
	}
	Quit dynObj
}

XData MessageMap
{
<MapItems>
  <MapItem MessageType="HS.FHIRServer.Interop.Request">
    <Method>SendFHIRRequest</Method>
  </MapItem>
</MapItems>
}

}

Let's analyze our class step by step:

  1. The first point will be to know the version of our FHIR message that we will have configured in the options of our Business Operation.
    Set tFHIRMetadataSetKey = $ZStrip($Piece(..FHIRMetadataSet, "/", 1), "<>W")
    We recover the type of FHIR message we are dealing with, in our case it will be R4.
  2. Next we extract the content of the FHIR message:
    // We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request
    	If pRequest.QuickStreamId'="" {
    		
    		// Recover QuickStream of the FHIR message
    		Set tQuickStream = ##class(HS.SDA3.QuickStream).%OpenId(pRequest.QuickStreamId)
    		
    		// Checking if message is in JSON format or XML
    		If pRequest.Request.RequestFormatCode'="" {
    			Set tFHIRFormat = pRequest.Request.RequestFormatCode
    		} Else {
    			$$$ThrowOnError(##class(HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat))
    			Do tQuickStream.Rewind()
    			If tFHIRFormat="json" {
    				Set tFHIRFormat = $$$FHIRContentCodeJSON
    			} ElseIf tFHIRFormat="xml" {
    				Set tFHIRFormat = $$$FHIRContentCodeXML
    			}
    		}
    		
    		// Transform QuickStream to DynamicObject
    		Set tDynObj = ..GetDynObj(tQuickStream, tFHIRMetadataSetKey, tFHIRFormat)
    		
    		
    	} ElseIf (($IsObject(pRequest.Request.Json))&&(pRequest.Request.Json.%GetIterator().%GetNext())) {
    		// Could have Json %DynamicObject if this host is called InProc.
    		Set tDynObj = ..GetDynObj(pRequest.Request.Json, tFHIRMetadataSetKey)
    		
    	} Else {
    		$$$ThrowStatus($$$ERROR($$$GeneralError, "FHIR interop request message missing FHIR content"))
    	}
  3. We get the %DynamicObject from our FHIR message:
    ClassMethod GetDynObj(stream As %Stream.Object, fhirVersion As %String, fhirFormat As %String) As %DynamicObject
    {
    	
    	set schema = ##class(HS.FHIRServer.Schema).LoadSchema(fhirVersion)
    	if fhirFormat = $$$FHIRContentCodeJSON {
    		set dynObj = {}.%FromJSON(stream)
    	} elseif fhirFormat = $$$FHIRContentCodeXML {
    		set dynObj = ##class(HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema)
    	}
    	Quit dynObj
    }
  4. Finally we transform it into a String and send it via TCP to our EMPI production, returning a 200 to the system that sent us the original message.:
    // Transform Dynamic Object to string
    	set tRequest = tDynObj.%ToJSON()
    	
    	// Send message to EMPI production
    	Set tSC= ..Adapter.SendMessageString(tRequest,.tResponse)
    	
    	$$$ThrowOnError(pRequest.NewResponse(.pResponse))
    	//Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New()
    	Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode
    	
    	set pResponse.Response.Status = 200
    	set pResponse.ContentType = "text/plain"
    	Quit tSC

Well, we already have our message sent to our EMPI production. Let's look at the trace of an example message:

In the next article we will retrieve the submitted String, transform it back to a %DynamicObject and feed the EMPI to execute the register/update operation with the new data.

I hope it is useful to you!

Discussion (0)1
Log in or sign up to continue