Artículo
· 16 oct, 2023 Lectura de 10 min

Usando FHIR Adapter para ofrecer servicios FHIR sobre sistemas legacy - Lectura de un Recurso

Retomamos nuestra serie de artículos sobre la herramienta de FHIR Adapter disponible para los usuarios de HealthShare HealthConnect e InterSystems IRIS.

En los artículos anteriores hemos presentado la pequeña aplicación sobre la que montamos nuestro workshop y mostramos la arquitectura desplegada en nuestra instancia de IRIS tras instalar FHIR Adapter. En el artículo de hoy veremos un ejemplo de como podemos realizar una de las operaciones CRUD (Create - Read - Update - Delete) más comunes, la lectura, y lo haremos recuperando un Resource.

¿Qué es un Resource?

Un Resource en FHIR corresponde con un tipo de información clínica que es relevante, esta información puede ser desde un paciente (Patient), una petición a un laboratorio (ServiceRequest) o un diagnóstico (Condition). Cada resource define el tipo de datos que lo conforman, así como las restricciones sobre dichos datos y las relaciones con otros tipos de resources. Cada resource permite la extensión de la información que contiene, permitiendo así cubrir necesidades que queden fuera de la política de FHIR 80% (cubrir los requisitos que usaran el 80% de los usuarios).

Para el ejemplo de este artículo vamos a utilizar el recurso más común, el Patient. Veamos su definición:

{
  "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
  }]
}

Cómo podéis ver, cubre prácticamente todas las necesidades de información administrativa de un paciente (salvo el uso del segundo apellido).

Recuperando un paciente de nuestro HIS

Si recordáis los artículos anteriores hemos desplegado una base de datos PostgreSQL que simula la base de datos de un sistema HIS, echemos un vistazo a las tablas de ejemplo que tenemos en nuestro particular HIS.

No son muchas, pero serán suficientes para nuestro ejemplo. Veamos un poco más en detalle nuestra tabla patient.

Aquí tenemos nuestros 3 pacientes de ejemplo, como véis cada uno dispone de un identificador (id) único así como una serie de datos administrativos relevantes para la organización de salud. Nuestro primer objetivo será obtener el recurso FHIR de uno de nuestros pacientes.

Consulta de pacientes

¿Cómo podemos solicitar a nuestro servidor los datos de un paciente? Conforme a la especificación de la implementación que hace FHIR deberemos realizar un GET vía REST a una URL con la dirección de nuestro servidor, el nombre del recurso y el identificador ello deberemos invocar:

http://SERVER_PATH/Patient/{id}

Para nuestro ejemplo realizaremos una búsqueda de Juan López Hurtado, con su id = 1, por lo que deberemos invocar la siguiente URL:

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

Para las pruebas usaremos Postman como cliente. Veamos cual es la respuesta del servidor:

{
    "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"
        }
    ]
}

Ahora analicemos el recorrido que ha hecho nuestra petición dentro de nuestra producción:

Aquí tenemos el recorrido:

  1. Llegada de la petición a nuestro BS InteropService.
  2. Reenvío de la petición al BP que hemos configurado como destino de nuestro BS donde se recuperará el identificador del paciente de la llamada recibida.
  3. Consulta desde nuestro BO FromAdapterToHIS a la base de datos de nuestro HIS.
  4. Reenvío de los datos del paciente a nuestro BP y transformación a un recurso Patient de FHIR.
  5. Reenvío de la respuesta al BS.

Echemos un vistazo al tipo de mensaje que recibimos en nuestro BP ProcessFHIRBP:

Fijémonos en tres atributos que serán claves para identificar que tipo de operación se nos ha solicitado desde el cliente:

  • Request.RequestMethod: que nos indica que tipo de operación vamos a realizar. En este ejemplo como se trata la búsqueda de un paciente será un GET.
  • Request.RequestPath: este atributo contiene el path de la solicitud que llegó a nuestro servidor, este atributo nos indicará el recurso sobre el que trabajaremos y en este caso incluirá el identificador específico para su recuperación.
  • Quick.StreamId: FHIR Adapter transformará todo mensaje FHIR recibido en un Stream y le asignará un identificador que guardará en este atributo. Para este ejemplo no lo necesitaremos ya que estamos realizando un GET y no enviamos ningún objeto FHIR, por eso está vacío.

Continuemos con el recorrido de nuestro mensaje analizando en profundidad el BPL responsable del procesamiento.

ProcessFHIRBP:

Hemos implementado en nuestra producción un BPL que va a gestionar la mensajería FHIR que recibamos desde nuestro Business Service. Veamos como está implementado:

Veamos las operaciones que realizaremos en cada paso:

Manage FHIR object:

Invocaremos al BO FromAdapterToHIS responsable de la conexión a la base de datos del HIS y que se encargará de la consulta a la base de datos. Veamos el código del BO:

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
}

Nuestro BO comprobará el mensaje recibido del tipo HS.FHIRServer.Interop.Request, en este caso al set un GET e indicar en el path que corresponde al recurso Patient nos invocará el Method GetPatient que veremos a continuación:

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
}

Como véis dicho método únicamente lanza una consulta sobre la base de datos de nuestro HIS y recupera toda la información del paciente, a continuación genera en un DynamicObject que posteriormente transforma en un String y almacena en una variable del tipo Adapter.Message.FHIRResponse creado por nosotros. Hemos definido la propiedad Resource como una lista de String para poder visualizar posteriormente en la traza la respuesta, podríais definirla directamente como DynamicObjects ahorrando transformaciones posteriores.

Check if Bundle:

Con la respuesta de nuestro BO comprobamos si es de tipo Bundle (lo explicaremos en un próximo artículo) o si es sólo un recurso.

Create Dynamic Object:

Transformamos la respuesta del BO en un DynamicObject y lo asignamos a una variable de contexto temporal (context.temporalDO). La función utilizada para la transformación es la siguiente:

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

FHIR transform:

Con nuestra variable temporal del tipo DynamicObject lanzamos una transformación de la misma a un objeto de la clase HS.FHIR.DTL.vR4.Model.Resource.Patient. Si quisieramos buscar otro tipo de recursos deberíamos definir transformaciones particulares para cada tipo. Veamos nuestra transformación:

Esta transformación nos permite disponer de un objeto que nuestro BS InteropService pueda interpretar. Almacenaremos el resultado en la variable context.PatientResponse.

Assign resource to Stream:

Convertimos a Stream la variable context.PatientResponse obtenido en FHIR transform.

Transform to QuickStream:

Asignamos a la variable response todos los datos que debemos retornar a nuestro cliente:

 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"
  

En este caso estamos siempre devolviendo una respuesta 200. En un entorno de producción deberíamos comprobar si hemos recuperado correctamente el recurso buscado, y en caso contrario modificar el status de la respuesta de 200 a un 404 correspondiente a un "Not found". Como podéis observar en dicho fragmento de código, el objeto HS.FHIR.DTL.vR4.Model.Resource.Patient
es transformado a un Stream y almacenado como un HS.SDA3.QuickStream, añadiendo el identificador de dicho objeto al atributo QuickStreamID, posteriormente nuestro servicio InteropService se encargará de retornar el resultado correctamente como un JSON.

Conclusión:

Resumamos los pasos realizados:

  1. Se ha enviado la petición GET de búsqueda de recurso Patient con un determinado Id a IRIS.
  2. El BS InteropService ha reenviado la petición al BP configurado.
  3. El BP ha invocado al BO responsable de interactuar con la base de datos HIS.
  4. El BO configurado ha recuperado los datos de paciente de la base de datos de HIS.
  5. El BP ha transformado el resultado a un objeto comprensible por el BS creado por defecto InteropService.
  6. El BS ha recibido la respuesta y se lo ha remitido al cliente.

Como véis la operativa es relativamente sencilla, si queremos añadir más tipos de recursos a nuestro servidor sólo tendremos que añadir en nuestro BO la consulta a las tablas de nuestra base de datos que correspondan al nuevo recurso a recuperar e incluir en nuestro BP la transformación del resultado de nuestro BO a un objeto del tipo HS.FHIR.DTL.vR4.Model.Resource.* que corresponda.

En nuestra próxima entrega revisaremos como podemos añadir nuevos recursosd FHIR de tipo Patient a nuestra base de datos HIS.

¡Gracias a todos por vuestra atención!

Comentarios (0)1
Inicie sesión o regístrese para continuar