Article
· 21 hr il y a 11m de lecture

Exemple de remplacement du processus de transformation SDA en FHIR pour inclure le paramètre « RequestMethod »

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
 

Discussion (0)1
Connectez-vous ou inscrivez-vous pour continuer