Find

Article
· Aug 6, 2024 10m read

Example of Overriding SDA to FHIR Transform Process to Include "RequestMethod" Setting

When building a bundle from legacy data, I (and others) wanted to be able to control whether or not the resources were generated with a FHIR Request Method of PUT instead of the hard coded POST.  I have extended the two classes responsible for transforming SDA to FHIR in an Interoperability Production to accomodate a setting that lets the user control the Request Method.

First is the Busines Process class.  This includes a new parameter exposed to the "Settings" tab in Interoperability called FHIRRequestMethod.  This also needs to pass the FHIRRequestMethod property to the transform class method as a parameter.

Class Demo.FHIR.DTL.Util.HC.SDA.FHIR.ProcessV2 Extends HS.FHIR.DTL.Util.HC.SDA3.FHIR.Process
{

/*  ; **********************************************************
	; *                   ** N O T I C E **                    *
	; *                - TEST/DEMO SOFTWARE -                  *
	; * This class is not supported by InterSystems as part    *
	; * of any released product.  It is supplied by            *
	; * InterSystems as a demo/test tool for a specific        *
	; * product and version.  The user or customer is fully    *
	; * responsible for the maintenance of this software       *
	; * after delivery, and InterSystems shall bear no         *
	; * responsibility nor liabilities for errors or misuse    *
	; * of this class.                                         *
	; **********************************************************/
Parameter SETTINGS = "FHIRRequestMethod:Basic";
/// This property can override the request method generated with each FHIR resource <br>
/// This property will only apply to new resources that do not have an identifier from the source data.
Property FHIRRequestMethod As %String(MAXLEN = 10) [ InitialExpression = "POST" ];
/// This is an instance method because it needs to SendSync to a business host and get
/// the response from the host.
Method ProcessSDARequest(pSDAStream, pSessionApplication As %String, pSessionId As %String, pPatientResourceId As %String = "") As %Status
{
	New %HSIncludeTimeZoneOffsets
	Set %HSIncludeTimeZoneOffsets = 1
	Set tSC = $$$OK
	Try {
		// Check the base class for the Target business host. Determine if it is
		// a FHIRServer Interop business host or not.
		If '$Data(%healthshare($$$CurrentClass, "isInteropHost"))#10 {
			$$$ThrowOnError(##class(HS.Director).OpenCurrentProduction(.tProdObj))
			Set tClassName = ""
			For i = 1:1:tProdObj.Items.Count() {
				If tProdObj.Items.GetAt(i).Name = ..TargetConfigName {
					Set tClassName = tProdObj.Items.GetAt(i).ClassName
					Quit
				}
			}
			Kill tProdObj
			
			Set tIsInteropHost = 0
			Set tRequiredHostBases("HS.FHIRServer.Interop.Operation") = ""
			Set tRequiredHostBases("HS.FHIRServer.Interop.HTTPOperation") = ""
			Set tHostBase = ""
			For {
				Set tHostBase = $Order(tRequiredHostBases(tHostBase))
				If tHostBase="" Quit
				If $ClassMethod(tClassName, "%IsA", tHostBase) {
					Set tIsInteropHost = 1
					Quit
				}
			}
			Set %healthshare($$$CurrentClass, "isInteropHost") = tIsInteropHost
			
		} Else {
			Set tIsInteropHost = %healthshare($$$CurrentClass, "isInteropHost")
		}
		
		// Get the host and web server port of the current instance, to be used for populating
		// the FHIR request message HOST header.  The HOST header is needed in the FHIR request
		// message when the message is being routed for processing in the local production, as
		// opposed to being passed to an external server.
		Do ..GetHostAndPort(.tHost, .tPort)
		Set tLocalHostAndPort = tHost_$Select(tPort'="":":",1:"")_tPort
		
		If ..FHIRFormat="JSON" {
			Set tMessageContentType = "application/fhir+json"
		} ElseIf ..FHIRFormat="XML" {
			Set tMessageContentType = "application/fhir+xml"
		}
		
		Set tFHIRMetadataSetKey = $ZStrip($Piece(..FHIRMetadataSet, "/", 1), "<>W")
		
		Set tSchema = ##class(HS.FHIRServer.Schema).LoadSchema(tFHIRMetadataSetKey)
		
		If '..FormatFHIROutput {
			Set tIndentChars = ""
			Set tLineTerminator = ""
			Set tFormatter = ""
		} Else {
			Set tIndentChars = $Char(9)
			Set tLineTerminator = $Char(13,10)
			Set tFormatter = ##class(%JSON.Formatter).%New()
			Set tFormatter.IndentChars = tIndentChars
			Set tFormatter.LineTerminator = tLineTerminator
		}
		
		#dim tTransformObj As HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR
		
		Set tTransformObj = $ClassMethod(..TransformClass, "TransformStream", pSDAStream, "HS.SDA3.Container", tFHIRMetadataSetKey, pPatientResourceId, "", ..FHIRRequestMethod)
		
		// tTransformObj.bundle is a %DynamicObject.
		Set tBundleObj = tTransformObj.bundle
		
		$$$HSTRACE("Bundle object", "tBundleObj", tBundleObj.%ToJSON())
		
		// "individual" is not a transaction type or interaction.
		// This mode causes each entry in the Bundle to be sent
		// to TargetConfigName individually, not as a transaction.
		If ..TransmissionMode="individual" {
			For i = 0:1:tBundleObj.entry.%Size()-1 {
				If tIsInteropHost {
					Set tSC = ..CreateAndSendInteropMessage(tBundleObj.entry.%Get(i), tSchema, tMessageContentType, tFormatter, tIndentChars, tLineTerminator, pSessionApplication, pSessionId)
				} Else {
					Set tSC = ..CreateAndSendFHIRMessage(tBundleObj.entry.%Get(i), tSchema, tLocalHostAndPort, tMessageContentType, tFormatter, tIndentChars, tLineTerminator, pSessionApplication, pSessionId)
				}
			}
		} Else {
			If tIsInteropHost {
				Set tSC = ..CreateAndSendInteropMessage(tBundleObj, tSchema, tMessageContentType, tFormatter, tIndentChars, tLineTerminator, pSessionApplication, pSessionId)
			} Else {
				Set tSC = ..CreateAndSendFHIRMessage(tBundleObj, tSchema, tLocalHostAndPort, tMessageContentType, tFormatter, tIndentChars, tLineTerminator, pSessionApplication, pSessionId)
			}
		}
		
	} Catch eException {
		Set tSC = eException.AsStatus()
	}
	
	Quit tSC
}

Storage Default
{
<Data name="ProcessV2DefaultData">
<Subscript>"ProcessV2"</Subscript>
<Value name="1">
<Value>FHIRRequestMethod</Value>
</Value>
</Data>
<DefaultData>ProcessV2DefaultData</DefaultData>
<Type>%Storage.Persistent</Type>
}

}

 

Second is the transformation class, for which we need to also add a new property to store the FHIRRequestMethod parameter.  The value of FHIRRequestMethod comes from the Class Method call to ..TransformStream.  Once this parameter from the Business Process is passed into ..TransformStream, I store it in the class property so that all methods of this transform class have access to the value.

Class Demo.FHIR.DTL.Util.API.Transform.SDA3ToFHIRV2 Extends HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR
{

/*  ; **********************************************************
	; *                   ** N O T I C E **                    *
	; *                - TEST/DEMO SOFTWARE -                  *
	; * This class is not supported by InterSystems as part    *
	; * of any released product.  It is supplied by            *
	; * InterSystems as a demo/test tool for a specific        *
	; * product and version.  The user or customer is fully    *
	; * responsible for the maintenance of this software       *
	; * after delivery, and InterSystems shall bear no         *
	; * responsibility nor liabilities for errors or misuse    *
	; * of this class.                                         *
	; **********************************************************/
/// Property to override the Request Method for unidentified resources
Property FHIRRequestMethod As %String(MAXLEN = 10);
/// Transforms an SDA stream (Container or SDA class) to the specified FHIR version. Returns an instance of this class
/// which contains a "bundle" property. That property will contain a FHIR Bundle with all the resources
/// generated during the transformation, and with all references resolved. If <var>patientId</var> or
/// <var>encounterId</var> are specified, those values will go into any applicable Patient and Encounter
/// references.
/// @API.Method
/// @Argument	stream			%Stream representation of an SDA object or Container
/// @Argument	SDAClassname	Classname for the object contained in the stream (eg. HS.SDA3.Container)
/// @Argument	fhirVersion		Version of FHIR used by the resource, eg. "STU3", "R4"
/// @Argument	patientId		(optional) FHIR resource id to be assigned to the Patient resource
/// @Argument	encounterId		(optional) FHIR resource id to be assigned to the Encounter resource, if not transforming a Container
ClassMethod TransformStream(stream As %Stream.Object, SDAClassname As %String, fhirVersion As %String, patientId As %String = "", encounterId As %String = "", FHIRRequestMethod As %String) As HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR
{
	set source = $classmethod(SDAClassname, "%New")
	if SDAClassname = "HS.SDA3.Container" {
		$$$ThrowOnError(source.InitializeXMLParse(stream, "SDA3"))
	}
	else {
		$$$ThrowOnError(source.XMLImportSDAString(stream.Read(3700000)))
	}
	return ..TransformObject(source, fhirVersion, patientId, encounterId, FHIRRequestMethod)
}

/// Transforms an SDA object (Container or SDA class) to the specified FHIR version. Returns an instance of this class
/// which contains a "bundle" property. That property will contain a FHIR Bundle with all the resources
/// generated during the transformation, and with all references resolved. If <var>patientId</var> or
/// <var>encounterId</var> are specified, those values will go into any applicable Patient and Encounter
/// references.
/// @API.Method
/// @Argument	source			SDA object or Container
/// @Argument	fhirVersion		Version of FHIR used by the resource, eg. "STU3", "R4"
/// @Argument	patientId		(optional) FHIR resource id to be assigned to the Patient resource
/// @Argument	encounterId		(optional) FHIR resource id to be assigned to the Encounter resource, if not transforming a Container
ClassMethod TransformObject(source, fhirVersion As %String, patientId As %String = "", encounterId As %String = "", FHIRRequestMethod As %String) As HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR
{
	set schema = ##class(HS.FHIRServer.Schema).LoadSchema(fhirVersion)
	set transformer = ..%New(schema)
	
    // Updated from parent class to set the FHIRRequestMethod for use of any class method
    Set transformer.FHIRRequestMethod = FHIRRequestMethod

	//SDA gets patient and encounter id and Container only gets patient id
	//because a Container can have multiple encounters and we can't assume which one they're referring to
	if source.%ClassName(1) = "HS.SDA3.Container" {
		do transformer.TransformContainer(source, patientId)
	}
	else {
		do transformer.TransformSDA(source, patientId, encounterId)
	}
	
	return transformer
}

/// Ensures the resource is valid FHIR, adds the resource to the output Bundle,
/// and returns a reference to that resource. Will also output the resource as a
/// %DynamicObject.
/// @Inputs
/// source			SDA object which created this resource
/// resource		Object model version of the resource
/// resourceJson	%DynamicObject version of the resource
/// One of <var>resource</var> or <var>resourceJson</var> must be provided. If both are provided,
/// the %DynamicObject representation will be given precedence 
Method AddResource(source As HS.SDA3.SuperClass, resource As %RegisteredObject = "", ByRef resourceJson As %DynamicObject = "") As HS.FHIR.DTL.vR4.Model.Base.Reference [ Internal ]
{
	if '$isobject(resourceJson) {
		set resourceJson = ##class(%DynamicObject).%FromJSON(resource.ToJSON())
	}
	
	try {
		do ..%resourceValidator.ValidateResource(resourceJson)
	} catch ex {
		do ..HandleInvalidResource(resourceJson, ex)
		return ""
	}
	
	set entry = ##class(%DynamicObject).%New()
	set entry.request = ##class(%DynamicObject).%New()
	
	set id = ..GetId(source, resourceJson) 
	if id '= "" {
		set resourceJson.id = id
	}
	
	//Check for an SDA identifier->id mapping to maintain references
	//Note: Provenance assigns a GUID to ExternalId for internal use, it is not an external id and shouldn't influence id assignment 
	set sourceIdentifier = ""
	if resourceJson.resourceType = "Encounter" {
		set sourceIdentifier = source.EncounterNumber
	}
	elseif ((source.%Extends("HS.SDA3.SuperClass")) && (resourceJson.resourceType '= "Provenance")) {
		set sourceIdentifier = source.ExternalId
	}
	
	if id = "" {
		if (resourceJson.resourceType = "Patient") && (..%patientId '= "") {
			set id = ..%patientId
		}
		elseif $get(..%resourceIds(resourceJson.resourceType)) '= "" {
			set id = ..%resourceIds(resourceJson.resourceType)
		}
		elseif (sourceIdentifier '= "") && $data(..%resourceIds(resourceJson.resourceType, sourceIdentifier)) {
			set id = ..%resourceIds(resourceJson.resourceType, sourceIdentifier)
		}
		
		if id '= "" {
			set resource.id = id
			set resourceJson.id = id
		}
	}
	
	if resourceJson.id '= "" {
		set id = resourceJson.id
		set entry.fullUrl = $select(..GetBaseURL()'="":..GetBaseURL() _ "/", 1:"") _ resourceJson.resourceType _ "/" _ resourceJson.id
		set entry.request.method = "PUT"
		set entry.request.url = resourceJson.resourceType _ "/" _ resourceJson.id
	}
	else {
		set id = $zconvert($system.Util.CreateGUID(), "L")
		set entry.fullUrl = "urn:uuid:" _ id
        // changed from parent class to accept parameter as input instead of hard coding "POST"
		set entry.request.method = ..FHIRRequestMethod
		set entry.request.url = resourceJson.resourceType
	}
	
	//Save id mappings for later access
	if resourceJson.resourceType = "Patient" {
		set ..%patientId = id
	}
	elseif sourceIdentifier '= "" {
		set ..%resourceIds(resourceJson.resourceType, sourceIdentifier) = id
	}
	
	set duplicate = ..IsDuplicate(resourceJson, id)
	if duplicate '= "" {
		return duplicate
	}
	
	//Index for O(1) lookup if needed for post-processing
	set ..%resourceIndex(resourceJson.resourceType, id) = resourceJson
	
	set entry.resource = resourceJson
	do ..%bundle.entry.%Push(entry)
	
	return ..CreateReference(resourceJson.resourceType, id)
}

}

 

These classes are designed to be used in an Interoperability Production.  The demonstration that highlights to base version of these classes can be found here:

Learning Services: Converting Legacy Data to HL7 FHIR R4 in InterSystems IRIS for Health & Github Repo for Legacy To FHIR Transformation Demo
 

2 Comments
Discussion (2)2
Log in or sign up to continue
Discussion (0)1
Log in or sign up to continue
InterSystems Official
· Aug 6, 2024

IAM 2.8.4.11 & 3.4.3.11 Release Announcement

InterSystems API Manager (IAM) versions 2.8.4.11 and 3.4.3.11 are now Generally Available.  These are the latest versions of the two long-term supported versions of IAM. These releases contain important fixes, and all customers are encouraged to upgrade.

Customers on IAM 3.0 or 3.2 are encouraged to upgrade to 3.4.3.11 as support for those versions will soon come to an end.

IAM is an API gateway between your InterSystems IRIS servers and applications, providing tools to effectively monitor, control, and govern HTTP-based traffic at scale. IAM is available as a free add-on to your InterSystems IRIS license.

IAM can be downloaded from the Components area of the WRC Software Distribution site

Follow the Installation Guide for guidance on how to download, install, and get started with IAM.  The complete IAM 3.4 documentation gives you more information about IAM and using it with InterSystems IRIS. Our partner Kong provides further documentation on using IAM in the Kong Gateway (Enterprise) 3.4 documentation

IAM is only available in OCI (Open Container Initiative) a.k.a. Docker container format. Container images are available for OCI compliant run-time engines for Linux x86-64 and Linux ARM64, as detailed in the Supported Platforms document.

Discussion (0)1
Log in or sign up to continue
Announcement
· Aug 6, 2024

[Video] Updating Legacy UIs for Modern Browsers - Lessons Learned & Steps Forward

Hey Developers,

Play the new video on InterSystems Developers YouTube:

⏯ Updating Legacy UIs for Modern Browsers - Lessons Learned & Steps Forward @ Global Summit 2023

Sometimes the rip-and-replace method of refreshing web user interfaces (UIs) just isn't feasible. The problem is existing UIs will stop working as web browsers continue to remove older features. Learn how our application services team modernized a legacy Internet Explorer dependent application to work in modern browsers.

Presenters:
🗣 @Vivian Lee, Applications Developer, InterSystems
🗣 @Dominic Chui, Applications Developer, InterSystems

Enjoy watching this video! 👍

Discussion (0)1
Log in or sign up to continue
Article
· Aug 6, 2024 4m read

Construindo um frontend usando apenas Python

Desenvolvimento de frontend pode ser uma tarefa muito difícil, especialmente para desenvolvedores focados em backend. Mais cedo na minha carreira, as linhas entre frontend e backend eram borradas, e se experava de todos que conseguissem manejar ambos. CSS em particular era um desafio constante, parecia uma missão impossível.

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