Lors de la création d'un bundle à partir de données héritées, je (et d'autres) souhaitais pouvoir contrôler si les ressources étaient générées avec une méthode de requête FHIR PUT plutôt qu'avec la méthode POST codée en dur. J'ai étendu les deux classes responsables de la transformation de SDA en FHIR dans une production d'interopérabilité afin de prendre en charge un paramètre permettant à l'utilisateur de contrôler la méthode de requête.
La première classe est la classe Processus métier. Elle inclut un nouveau paramètre exposé dans l'onglet « Paramètres » de l'interface d'interopérabilité, appelé FHIRRequestMethod. Elle doit également transmettre la propriété FHIRRequestMethod à la méthode de classe de transformation en tant que paramètre.
Class Demo.FHIR.DTL.Util.HC.SDA.FHIR.ProcessV2 Extends HS.FHIR.DTL.Util.HC.SDA3.FHIR.Process
{
Parameter SETTINGS = "FHIRRequestMethod:Basic";
/// Cette propriété peut remplacer la méthode de requête générée avec chaque ressource FHIR. <br>
/// Cette propriété s'applique uniquement aux nouvelles ressources qui ne possèdent pas d'identifiant issu des données sources.
Property FHIRRequestMethod As %String(MAXLEN = 10) [ InitialExpression = "POST" ];
/// Il s'agit d'une méthode d'instance car elle doit envoyer SendSync à un hôte professionnel et obtenir la réponse de l'hôte.
Method ProcessSDARequest(pSDAStream, pSessionApplication As %String, pSessionId As %String, pPatientResourceId As %String = "") As %Status
{
New %HSIncludeTimeZoneOffsets
Set %HSIncludeTimeZoneOffsets = 1
Set tSC = $$$OK
Try {
// Vérifiez la classe de base de l'hôte métier cible. Déterminez s'il s'agit d'un hôte métier FHIRServer Interop ou non.
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")
}
// Obtenir l'hôte et le port du serveur Web de l'instance actuelle, à utiliser pour renseigner l'en-tête
// HOST du message de requête FHIR. L'en-tête HOST est nécessaire dans le message de requête FHIR lorsque
// le message est acheminé pour traitement en production locale, contrairement à sa transmission à un serveur externe.
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 est a %DynamicObject.
Set tBundleObj = tTransformObj.bundle
$$$HSTRACE("Bundle object", "tBundleObj", tBundleObj.%ToJSON())
// « individual » n'est pas un type de transaction ni une interaction.
// Ce mode entraîne l'envoi de chaque entrée du Bundle
// à TargetConfigName individuellement, et non en tant que 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>
}
}
La deuxième classe est la classe de transformation, pour laquelle nous devons également ajouter une nouvelle propriété pour stocker le paramètre FHIRRequestMethod. La valeur de FHIRRequestMethod provient de l'appel de la méthode de classe à ..TransformStream
. Une fois que ce paramètre du processus métier est passé à ..TransformStream
, je le stocke dans la propriété de classe afin que toutes les méthodes de cette classe de transformation aient accès à la valeur.
Class Demo.FHIR.DTL.Util.API.Transform.SDA3ToFHIRV2 Extends HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR
{
/// Propriété permettant de remplacer la méthode de requête pour les ressources non identifiées
Property FHIRRequestMethod As %String(MAXLEN = 10);
/// Transforme un flux SDA (conteneur ou classe SDA) vers la version FHIR spécifiée. Renvoie une instance
/// de cette classe contenant une propriété « bundle ». Cette propriété contiendra un bundle FHIR avec
/// toutes les ressources générées lors de la transformation et toutes les références résolues. Si
/// <var>patientId</var> ou <var>encounterId</var> sont spécifiés, ces valeurs seront intégrées à toutes
/// les références Patient et Encounter applicables.
/// @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)
}
/// Transforme un objet SDA (conteneur ou classe SDA) vers la version FHIR spécifiée. Renvoie une instance
/// de cette classe contenant une propriété « bundle ». Cette propriété contiendra un bundle FHIR avec
/// toutes les ressources générées lors de la transformation et toutes les références résolues. Si
/// <var>patientId</var> ou <var>encounterId</var> sont spécifiés, ces valeurs seront intégrées à toutes
/// les références Patient et Encounter applicables.
/// @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)
// Mise à jour à partir de la classe parent pour définir la méthode FHIRRequestMethod pour l'utilisation de n'importe quelle méthode de classe
Set transformer.FHIRRequestMethod = FHIRRequestMethod
//SDA obtient l'identifiant du patient et de la rencontre, tandis que le conteneur obtient uniquement l'identifiant du patient.
//Parce qu'un conteneur peut avoir plusieurs rencontres et nous ne pouvons pas déterminer à laquelle il fait référence.
if source.%ClassName(1) = "HS.SDA3.Container" {
do transformer.TransformContainer(source, patientId)
}
else {
do transformer.TransformSDA(source, patientId, encounterId)
}
return transformer
}
/// Vérifie que la ressource est FHIR valide, l'ajoute au bundle de sortie et renvoie une référence à cette ressource.
/// La ressource est également générée sous forme de %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
}
//Vérifiez un mappage identifiant SDA->id pour maintenir les références
//Remarque : Provenance attribue un GUIDE à l'ID externe pour une utilisation interne, il ne s'agit pas d'un ID externe
// et il ne devrait pas influencer l'attribution de l'ID
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
}
//Enregistrer les mappages d'identifiants pour un accès ultérieur
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 pour la recherche O(1) si nécessaire pour le post-traitement
set ..%resourceIndex(resourceJson.resourceType, id) = resourceJson
set entry.resource = resourceJson
do ..%bundle.entry.%Push(entry)
return ..CreateReference(resourceJson.resourceType, id)
}
}
Ces classes sont conçues pour être utilisées dans une production d'interopérabilité. La démonstration qui met en évidence la version de base de ces classes peut être trouvée ici :
Learning Services: Converting Legacy Data to HL7 FHIR R4 in InterSystems IRIS for Health & Github Repo for Legacy To FHIR Transformation Demo