Article
· Nov 6, 2023 11m de lecture

Utilisation de l'adaptateur FHIR pour la fourniture de services FHIR sur des systèmes existants - Lecture d'une ressource

Nous poursuivons notre série d'articles sur l'outil FHIR Adapter (adaptateur FHIR) disponible pour les utilisateurs de HealthShare HealthConnect et d'InterSystems IRIS.

Dans les articles précédents, nous avons présenté la petite application avec laquelle nous avons configuré notre atelier et montré l'architecture déployée dans notre instance IRIS après l'installation de l'adaptateur FHIR Adapter. Dans l'article d'aujourd'hui, nous allons voir un exemple de comment nous pouvons effectuer une des opérations CRUD (Créer - Lire - Mettre à jour - Supprimer) les plus courantes, l'opération de lecture, et nous allons le faire en récupérant une Ressource.

Qu'est-ce qu'une Ressource ?

Une Ressource dans FHIR correspond à un type d'information clinique pertinente, celle-ci peut être un patient (Patient), une demande à un laboratoire (ServiceRequest) ou un diagnostic (Condition), etc. Chaque ressource définit le type de données la composant, ainsi que les restrictions sur les données et les relations avec d'autres types de ressources. Chaque ressource permet l'extension des informations qu'elle contient, permettant ainsi de couvrir des besoins qui dépassent les 80% de FHIR (couvrir les besoins utilisés par plus des 80% des utilisateurs).

Pour l'exemple de cet article, nous allons utiliser la ressource la plus courante, le Patient. Examinons sa définition :

{
  "resourceType" : "Patient",
  // from Resource: id, meta, implicitRules, and language
  // from DomainResource: text, contained, extension, and modifierExtension
  "identifier" : [{ Identifier }], // An identifier for this patient
  "active" : <boolean>, // Whether this patient's record is in active use
  "name" : [{ HumanName }], // A name associated with the patient
  "telecom" : [{ ContactPoint }], // A contact detail for the individual
  "gender" : "<code>", // male | female | other | unknown
  "birthDate" : "<date>", // The date of birth for the individual
  // deceased[x]: Indicates if the individual is deceased or not. One of these 2:
  "deceasedBoolean" : <boolean>,
  "deceasedDateTime" : "<dateTime>",
  "address" : [{ Address }], // An address for the individual
  "maritalStatus" : { CodeableConcept }, // Marital (civil) status of a patient
  // multipleBirth[x]: Whether patient is part of a multiple birth. One of these 2:
  "multipleBirthBoolean" : <boolean>,
  "multipleBirthInteger" : <integer>,
  "photo" : [{ Attachment }], // Image of the patient
  "contact" : [{ // A contact party (e.g. guardian, partner, friend) for the patient
    "relationship" : [{ CodeableConcept }], // The kind of relationship
    "name" : { HumanName }, // I A name associated with the contact person
    "telecom" : [{ ContactPoint }], // I A contact detail for the person
    "address" : { Address }, // I Address for the contact person
    "gender" : "<code>", // male | female | other | unknown
    "organization" : { Reference(Organization) }, // I Organization that is associated with the contact
    "period" : { Period } // The period during which this contact person or organization is valid to be contacted relating to this patient
  }],
  "communication" : [{ // A language which may be used to communicate with the patient about his or her health
    "language" : { CodeableConcept }, // R!  The language which can be used to communicate with the patient about his or her health
    "preferred" : <boolean> // Language preference indicator
  }],
  "generalPractitioner" : [{ Reference(Organization|Practitioner|
   PractitionerRole) }], // Patient's nominated primary care provider
  "managingOrganization" : { Reference(Organization) }, // Organization that is the custodian of the patient record
  "link" : [{ // Link to a Patient or RelatedPerson resource that concerns the same actual individual
    "other" : { Reference(Patient|RelatedPerson) }, // R!  The other patient or related person resource that the link refers to
    "type" : "<code>" // R!  replaced-by | replaces | refer | seealso
  }]
}

Comme vous pouvez le constater,pratiquement tous les besoins d'information administrative d'un patient sont ainsi couverts.

Retrouver un patient de notre HIS

Si vous vous souvenez des articles précédents, nous avons déployé une base de données PostgreSQL reproduisant la base de données d'un système HIS. Jetons un coup d'œil aux exemples de tableaux que nous avons dans notre HIS particulier.

Il n'y en a pas beaucoup, mais cela suffira pour notre exemple. Examinons notre tableau patient de manière un peu plus détaillée.

Voici nos 3 exemples de patients, et comme vous voyez, chacun a un identifiant unique (ID) ainsi qu'une série de données administratives pertinentes pour l'organisme de santé. Notre premier objectif sera d'obtenir la ressource FHIR pour l'un de nos patients.

Consultation du patient

Comment pouvons-nous demander des données de patient à notre serveur ? Selon la spécification de mise en œuvre faite par FHIR, nous devons effectuer un GET via REST vers une URL avec l'adresse de notre serveur, le nom de la ressource et l'identifiant. Nous devons faire l'appel suivant :

http://SERVER_PATH/Patient/{id}

Dans notre exemple, nous allons effectuer une recherche de Juan López Hurtado, avec son identifiant = 1, et nous devons donc invoquer l'URL suivante :

http://localhost:52774/Adapter/r4/Patient/1

Pour les tests, nous utiliserons Postman comme client. Et voici la réponse du serveur :

{
    "resourceType": "Patient",
    "address": [
        {
            "city": "TERUEL",
            "line": [
                "CALLE SUSPIROS 39 2ºA"
            ],
            "postalCode": "98345"
        }
    ],
    "birthDate": "1966-11-23",
    "gender": "M",
    "id": "1",
    "identifier": [
        {
            "type": {
                "text": "ID"
            },
            "value": "1"
        },
        {
            "type": {
                "text": "NHC"
            },
            "value": "588392"
        },
        {
            "type": {
                "text": "DNI"
            },
            "value": "12345678X"
        }
    ],
    "name": [
        {
            "family": "LÓPEZ HURTADO",
            "given": [
                "JUAN"
            ]
        }
    ],
    "telecom": [
        {
            "system": "phone",
            "value": "844324239"
        },
        {
            "system": "email",
            "value": "juanitomaravilla@terra.es"
        }
    ]
}

Analysons ensuite le chemin parcouru par notre demande au sein de notre production :

Voici le chemin :

  1. Réception de la demande par notre service d'interopérabilité BS InteropService.
  2. Transmission de la demande à la BP que nous avons configuré comme destination de notre BS où l'identifiant du patient de l'appel reçu sera récupéré.
  3. Une requête de notre BO FromAdapterToHIS vers notre base de données HIS.
  4. Transmission des données du patient à notre BP et leur transformation en une ressource FHIR Patient.
  5. Transmission de la réponse à la BS.

Examinons le type de message que nous recevons dans notre BP ProcessFHIRBP :

Examinons trois attributs qui seront essentiels pour identifier le type d'opération demandé par le client :

  • Request.RequestMethod: cet attribut nous indique le type d'opération que nous allons effectuer. Dans cet exemple, pour rechercher un patient, nous utiliserons un GET.
  • Request.RequestPath: cet attribut contient le chemin de la demande qui est arrivée à notre serveur, cet attribut indiquera la ressource sur laquelle nous travaillerons et dans ce cas il inclura l'identifiant spécifique pour sa récupération.
  • Quick.StreamId: L'adaptateur FHIR Adapter transformera chaque message FHIR reçu en un flux et lui attribuera un identifiant qui sera sauvegardé dans cet attribut. Pour cet exemple, il n'est pas nécessaire puisque nous effectuons un GET et que nous n'envoyons pas d'objet FHIR.

Poursuivons le parcours de notre message en analysant en profondeur les GLP responsables du traitement.

ProcessFHIRBP:

Nous avons mis en place une BPL dans notre production qui gère la messagerie FHIR reçue depuis notre service métier (Business Service). Voici la manière dont elle est mise en œuvre :

Examinons les opérations que nous effectuerons à chaque étape :

Gestion de l'objet FHIR :

Nous invoquerons le BO FromAdapterToHIS responsable de la connexion à la base de données HIS et de l'interrogation de la base de données.

Method ManageFHIR(requestData As HS.FHIRServer.Interop.Request, response As Adapter.Message.FHIRResponse) As %Status
{
  set sc = $$$OK
  set response = ##class(Adapter.Message.FHIRResponse).%New()

  if (requestData.Request.RequestPath = "Bundle")
  {
    If requestData.QuickStreamId '= "" {
        Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)
        
        set dynamicBundle = ##class(%DynamicAbstractObject).%FromJSON(quickStreamIn)

        set sc = ..GetBundle(dynamicBundle, .response)
      }
    
  }
  elseif (requestData.Request.RequestPath [ "Patient")
  {
    if (requestData.Request.RequestMethod = "POST")
    {
      If requestData.QuickStreamId '= "" {
        Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)
        
        set dynamicPatient = ##class(%DynamicAbstractObject).%FromJSON(quickStreamIn)

        set sc = ..InsertPatient(dynamicPatient, .response)
      }      
    }
    elseif (requestData.Request.RequestMethod = "GET")
    {
      set patientId = $Piece(requestData.Request.RequestPath,"/",2)
      set sc = ..GetPatient(patientId, .response)
    }

  }
  Return sc
}

Notre BO va vérifier le message reçu de type HS.FHIRServer.Interop.Request, dans ce cas en mettant un GET et en indiquant le chemin qui correspond à la ressource Patient, la méthode GetPatient sera invoquée, ce que nous verrons plus bas :

Method GetPatient(patientId As %String, Output patient As Adapter.Message.FHIRResponse) As %Status
{
  Set tSC = $$$OK
  set sql="SELECT id, name, lastname, phone, address, city, email, nhc, postal_code, birth_date, dni, gender FROM his.patient WHERE id = ?"
  //perform the Select
  set tSC = ..Adapter.ExecuteQuery(.resultSet, sql, patientId)
  
  If resultSet.Next() {
     set personResult = {"id":(resultSet.GetData(1)), "name": (resultSet.GetData(2)), 
        "lastname": (resultSet.GetData(3)), "phone": (resultSet.GetData(4)), 
        "address": (resultSet.GetData(5)), "city": (resultSet.GetData(6)),
        "email": (resultSet.GetData(7)), "nhc": (resultSet.GetData(8)),
        "postalCode": (resultSet.GetData(9)), "birthDate": (resultSet.GetData(10)),
        "dni": (resultSet.GetData(11)), "gender": (resultSet.GetData(12)), "type": ("Patient")}

   } else {
     set personResult = {}
   }
  
  //create the response message
  do patient.Resource.Insert(personResult.%ToJSON())
 
    Return tSC
}

Comme vous pouvez le voir, cette méthode ne fait que lancer une requête dans la base de données de notre HIS et récupérer toutes les informations du patient, puis générer un DynamicObject qui est par la suite transformé en une Chaîne et stocké dans une variable de type Adapter.Message.FHIRResponse. Nous avons défini la propriété Ressource comme une liste de chaînes pour pouvoir afficher la réponse plus tard dans la trace. Vous pourriez la définir directement en tant que DynamicObjects, en évitant les transformations ultérieures.

Vérifiction d'un Bundle:

Suite à la réponse de notre BO, nous vérifions s'il s'agit d'un type de Bundle (nous l'expliquerons dans un prochain article) ou s'il s'agit simplement d'une Ressource.

Création d'un objet dynamique:

Nous transformons la réponse de BO en objet dynamique DynamicObject et l'assignons à une variable de contexte temporaire (context.temporalDO). La fonction utilisée pour la transformation est la suivante :

##class(%DynamicAbstractObject).%FromJSON(context.FHIRObject.Resource.GetAt(1))

Transformation FHIR:

Nous lançons une transformation de notre variable temporaire de type DynamicObject vers un objet de classe HS.FHIR.DTL.vR4.Model.Ressource.Patient. Si nous voulons rechercher d'autres types de ressources, il nous faudrait définir des transformations particulières pour chaque type. Voici notre transformation :

Cette transformation nous permet d'avoir un objet interprétable par notre InteropService BS. Le résultat sera stocké dans la variable context.PatientResponse.

Affectation d'une ressource à un flux (Stream):

Nous convertissons la variable context.PatientResponse obtenue dans la transformation FHIR en flux (Stream).

Transformation en QuickStream :

Nous assignons toutes les données que nous devons renvoyer à notre client à la variable response :

 set qs=##class(HS.SDA3.QuickStream).%New()
 set response.QuickStreamId = qs.%Id()
 set copyStatus = qs.CopyFrom(context.JSONPayloadStream)
 set response.Response.ResponseFormatCode="JSON"
 set response.Response.Status=200
 set response.ContentType="application/fhir+json"
 set response.CharSet = "utf8"
  

Dans ce cas, nous renvoyons toujours une réponse 200. Dans un environnement de production, il faudrait vérifier si nous avons correctement récupéré la ressource recherchée, et si ce n'est pas le cas, modifier l'état de la réponse de 200 à 404 correspondant à "Not found". Comme vous pouvez le voir dans ce fragment de code, l'objet HS.FHIR.DTL.vR4.Model.Ressource.Patient
est transformé en un flux (Stream) et stocké en tant que HS.SDA3.QuickStream, en ajoutant l'identifiant dudit objet à l'attribut QuickStreamID, par la suite notre service InteropService retournera correctement le résultat sous la forme d'un JSON.

Conclusion:

Résumons ce qui a été fait :

  1. Nous avons envoyé une requête de type GET pour rechercher une ressource Patient avec un ID défini.
  2. Le service BS InteropService a transmis la requête à la BP configurée.
  3. Cette dernière a invoqué la BO responsable de l'interaction avec la base de données HIS.
  4. La BO configurée a récupéré les données du patient dans la base de données HIS.
  5. La BP a transformé le résultat en un objet compréhensible par la BS créée par le service InteropService par défaut.
  6. La BS a reçu la réponse et l'a transmise au client.

Comme vous pouvez le constater, l'opération est relativement simple, si nous voulons ajouter d'autres types de ressources à notre serveur nous n'aurons qu'à ajouter dans notre BO la requête aux tableaux de notre base de données qui correspondent à la nouvelle ressource à récupérer et à inclure dans notre BP la transformation du résultat de notre BO en un objet de type **HS.FHIR.DTL.vR4.Model.Ressource.*** qui lui corresponde.

In our next article we will review how we can add new FHIR resources of the Patient type to our HIS database.

Je vous remercie de votre attention !

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