Artículo
· 5 hr atrás Lectura de 12 min

Implementando un proyecto FHIR - ÚNICAS

¡Bienvenidos, estimados miembros de la Comunidad!

En este artículo vamos a presentar un ejemplo de un proyecto de implementación de una solución basada en FHIR. Este proyecto se basará en el proyecto nacional (nacional de España), conocido como ÚNICAS.

¿Qué es ÚNICAS?

Usando sus propias palabras:

Un proyecto cuyo objetivo es crear un ecosistema de alianzas para mejorar la atención sanitaria de pacientes pediátricos con enfermedades minoritarias (EEMM) complejas. A través de la red en el Sistema Nacional de Salud (SNS) para mejorar el diagnóstico y la asistencia de pacientes con enfermedades minoritarias.

Características técnicas de ÚNICAS

Resumiendo un poco, la arquitectura diseñada para este proyecto está compuesto por:

Nodo central

Cuyo objetivo es permitir la compartición de datos clínicos de los pacientes afectados por estas enfermedades raras. Estos datos clínicos se encontrarán en los nodos autonómicos.

Nodo autonómico

Cada comunidad autonómica contará con su propio nodo (o más de uno, dependiendo el volumen de datos). Estos nodos serán los encargados de obtener la información clínica de los pacientes de los diversos orígenes de datos disponibles y proveerlos al nodo central.

Modelo de interoperabilidad

El modelo de interoperabilidad elegido para este caso es FHIR en su versión R5, el cual proporciona un conjunto de recursos normalizados para el intercambio de datos clínicos.

Cada comunidad autónoma es libre de elegir como implementar dicho modelo, ya sea mediante el despliegue de un repositorio FHIR sobre el que ir volcando los datos clínicos para su posterior compartición o bien con una fachada FHIR que transformará la información clínica a recursos FHIR en el momento que se atienda a una petición de compartición.

Recursos FHIR en ÚNICAS

Dado que este proyecto tiene unos objetivos muy concretos, ha definido un conjunto de recursos FHIR a utilizar:

Mensajería estandardizada en ÚNICAS en FHIR

Mientras que para informes:

Mensajería existente de informes en HL7 CDA

Implementando ÚNICAS con Health Connect

Tras revisar el aspecto técnico del proyecto ÚNICAS es el momento de ver el encaje con Health Connect y las posibles adaptaciones que serían necesarias para facilitar la implementación.

Modelo FHIR R5

De cara a la implementación del modelo de interoperabilidad en FHIR R5 el cliente tiene dos opciones:

  • Fachada FHIR.
  • Repositorio FHIR.

Fachada FHIR

La fachada FHIR nos desplegará dentro de nuestra instancia de Health Connect los componentes de negocio necesarios para disponer de una API REST que nos permita recibir llamadas desde terceros sistemas con los distintos recursos FHIR. Con esta opción podremos extraer los recursos en formato %DynamicObject y transformarlo a nuestro formato particular de datos clínicos.

Las consultas recibidas sobre la fachada FHIR se deberán construir a mano, ya que al no existir un repositorio FHIR vinculado por debajo deberemos implementar nosotros mismos cada funcionalidad. Para el caso de ÚNICAS esta no sería la mejor opción ya que los requisitos a nivel de consultas nos implicaría tener que implementar la funcionalidad que tenemos ya con el repositorio FHIR.

Repositorio FHIR

Health Connect permite (consultar condiciones de la licencia) desplegar un repositorio FHIR R5 de una forma sencilla desde un namespace con la opción de interoperabilidad habilitada desde el menú Health -> FHIR Server Management

Al pulsar sobre la opción de añadir nuevo servidor se nos mostrará una pantalla con las diversas opciones de configuración:

Como veis tenemos la opción de importar Packages a nuestra configuración. Esto será útil de cara a importar los ValueSet y Profiles definidos por el IG de ÚNICAS. Esta importación podremos realizarla en cualquier momento ejecutando el siguiente comando:

// Rutas a modificar por el usuario
do ##class(HS.FHIRMeta.Load.NpmLoader).importPackages($lb("/iris-shared/packages/hl7.terminology.r5-6.5.0/package", "/iris-shared/packages/hl7.fhir.uv.extensions.r5-5.2.0/package","/iris-shared/packages/full-ig/package"))

En el IG de ÚNICAS hay una pequeña discrepancia en relación a las dependencias definidas para el full-ig. Dentro del paquete se indican las siguiente dependencias:

"dependencies" : {
    "hl7.terminology.r5" : "6.5.0",
    "hl7.fhir.uv.extensions.r5" : "5.2.0",
    "hl7.fhir.uv.ips" : "1.1.0"
  }

La versión 1.1.0 de hl7.fhir.uv.ips sólo es compatible para la R4 de FHIR y si se intenta importar sobre un servidor R5 se producirá un error ya que a su vez lleva asociada una serie de dependencias con R4 que provocarán incompatibilidades. Para nuestra implementación procederemos a eliminarlo de la lista de dependencias e importar exclusivamente las relativas a la R5...y que sea lo que Dios quiera.

Adaptando repositorio FHIR a ÚNICAS

Una de las características más interesantes del repositorio FHIR en Health Connect es la posibilidad de utilizar el motor de interoperabilidad para capturar y transformar toda interacción con el repositorio FHIR para adaptarlo a nuestras necesidades. Para ello sólo necesitamos, desde la producción ligada al namespace donde tenemos desplegado el repositorio FHIR, incluir el business service HS.FHIRServer.Interop.Service y el business operation HS.FHIRServer.Interop.Operation. Esta será la pinta de la producción para el namespace que contiene nuestro repositorio:

Con estos componentes en nuestra producción podremos indicar en la configuración de nuestro repositorio que todas las llamadas mediante la API REST pasarán por dicho servicio:

¡Perfecto! Nuestro repositorio FHIR R5 está preparado para empezar a implementar nuestro nodo autonómico ÚNICAS. Veamos un ejemplo de cómo podemos aprovechar las capacidades de interoperabilidad para transformar mensajería HL7 v2.5.1 a recursos FHIR R5.

Convirtiendo HL7 a FHIR

Un caso típico que todo implementador de un repositorio FHIR es el de la conversión de mensajería HL7 (v2 o v3) en recursos FHIR. Esta conversión no es para nada sencilla por dos motivos: 

  1. No existe equivalencia directa entre recursos FHIR y eventos de HL7. Mientras FHIR se utiliza para el intercambio de conceptos clínicos en forma de recursos, HL7 representa eventos, los cuales contienen información que puede equivaler o no a un concepto clínico.
  2. La implementación de la mensajería HL7 v2 o v3 no es para nada homogenea. Cada organización adapta los mensajes a sus necesidades, siendo posible que el mismo evento en distintas organizaciones contengan datos totalmente distintos.

¿Qué herramientas nos proporciona Health Connect?

Health Connect dispone de una serie de transformaciones predefinidas que utiliza como pasarela el modelo de datos SDA3, muy similar a los CDAs. Actualmente dichas transformaciones están desarrolladas para la versión R4, pero no es muy complicado adaptarlo para transformar los recursos R4 generados en la versión R5.

Por lo que los pasos a seguir serán HL7 v2.5.1 -> SDA3 -> FHIR R4 -> FHIR R5. 

Transformación HL7 v2.5.1 -> SDA3

Para este primer paso haremos uso de la clase HS.Gateway.HL7.HL7ToSDA3 y su método GetSDA (podéis ver un ejemplo en la documentación aquí). Esta clase transformará nuestro mensaje HL7 en un bonito SDA.

Transformación SDA3 -> FHIR R4

Una vez obtenido nuestro SDA con la información clínica extraida del mensaje HL7 usaremos la clase HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR y su método TransformStream que transformará el SDA3 en formato Stream obtenido en el paso anterior en un %DynamicObject con un recurso Bundle de FHIR R4 (más información aquí).

A continuación podéis ver un ejemplo del código necesario para transformar un mensaje HL7 a un bundle de FHIR R4 en formato %DynamicObject:

 do ##class(HS.Gateway.HL7.HL7ToSDA3).GetSDA(request, .SDAMessageTemp)
 set SDAToFHIR = #class(HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR).TransformStream(SDAMessageTemp,"HS.SDA3.Container","R4")

¿Y mi FHIR R5?

Hasta ahora hemos visto que Health Connect permite de una forma muy sencilla la conversión de HL7 a FHIR R4 (es posible que la conversión no se ajuste al 100% de tus necesidades, pero te resultará más fácil editar un recurso que construirlo de 0), genial, pero...¿DONDE ESTÁ MI RECURSO R5?

No te preocupes porque... ¡la Comunidad de Desarrolladores viene al rescate!

Verás que adjunto a este artículo hay una aplicación de OpenExchange asociada, dicha aplicación contiene el código necesario para transformar los recursos FHIR R4 a los recursos R5 utilizados en el proyecto ÚNICAS. Para instalar dicho código podéis hacer uso de IPM:

set version="latest" s r=##class(%Net.HttpRequest).%New(),r.Server="pm.community.intersystems.com",r.SSLConfiguration="ISC.FeatureTracker.SSL.Config" d r.Get("/packages/zpm/"_version_"/installer"),$system.OBJ.LoadStream(r.HttpResponse.Data,"c")
zpm "enable -community"
zpm "install spain-unicas"

Una vez instalado veréis que ha aparecido un package llamado Spain en el namespace donde hayais hecho la instalación. ¿Que podéis encontrar en dicho package?

  • Spain.BP: BPL con ejemplo de transformación  HL7 -> SDA -> FHIR R4 -> FHIR R5.
  • Spain.FHIR.DTL.vR4: Transformaciones de R4 a R5 de los recursos empleados por ÚNICAS.
  • Spain.FHIR.DTL.vR5: Clases de ObjectScript que modelan los recursos R5 de ÚNICAS.
  • Spain.Gateway.HL7: Pequeña corrección para la transformación HL7 a SDA3.

Transformando R4 a R5

Con las herramientas disponibles en el package Spain ya podemos transformar nuestros recursos R4 a R5, para ello usaremos las transformaciones de cada tipo de recurso del siguiente modo:

set obj = ##class(HS.FHIR.DTL.vR4.Model.Resource.Bundle).FromJSONHelper(context.FHIRMessage, "vR4")

for i=1:1:obj.entry.Count() {
    set item = obj.entry.GetAt(i)
    if ($CLASSNAME(item.resource) = "HS.FHIR.DTL.vR4.Model.Resource.Immunization")
    {
      Set status = ##class(Spain.FHIR.DTL.vR4.vR5.Immunization).Transform(item.resource, .immunizationR5)            
      Set item.resource = immunizationR5
      Do obj.entry.SetAt(item, i)            
    }
    elseif ($CLASSNAME(item.resource) = "HS.FHIR.DTL.vR4.Model.Resource.AllergyIntolerance")
    {
      Set status = ##class(Spain.FHIR.DTL.vR4.vR5.Allergy.AllergyIntolerance).Transform(item.resource, .allergyIntoleranceR5)            
      Set item.resource = allergyIntoleranceR5
      Do obj.entry.SetAt(item, i)            
    }
    ...
 }

Recordad que disponíamos de un %DynamicObject con un bundle de recursos R4, el primer paso que daremos será mapear dicho %DynamicObject a una clase HS.FHIR.DTL.vR4.Model.Resource.Bundle usando el método FromJSONHelper, el objetivo de esta transformación es poder reconocer posteriormente el tipo de recurso presente en el bundle por el nombre de su clase.

Con el bundle mapeado a su clase de ObjectScript nos limitaremos a recorrer cada entrada del mismo identificando el recurso, si corresponde a uno de los recursos usados en ÚNICAS procedemos a invocar a un DTL específico para realizar la transformación de R4 a R5.

Con el recurso ya transformado, lo volvemos a introducir en el Bundle sustituyendo al anterior recurso R4.

Finalmente sólo nos quedará transformar el Bundle a un Stream (en este caso llamado QuickStream) y enviarlo dentro de un mensaje de tipo HS.FHIRServer.Interop.Request al business operation HS.FHIRServer.Interop.Operation

 Set context.FHIRRequest.Request.RequestMethod = "POST"
 Set context.FHIRRequest.Request.RequestPath = ""
 Set context.FHIRRequest.Request.RequestFormatCode= "JSON"
 Set context.FHIRRequest.Request.SessionApplication = "/csp/healthshare/fhirserver/fhir/r5"
 Set context.FHIRRequest.Request.IsRecursive = 0
 set qs=##class(HS.SDA3.QuickStream).%New()
 set context.FHIRRequest.QuickStreamId = qs.%Id()
 do qs.CopyFrom(context.FHIRStream)
 

Aquí tendríamos la llamada:

¡Pues ya estaría! Ya tenemos nuestro pequeño ejemplo de conversiones de HL7 a R5 y su registro en nuestro repositorio desplegado en Health Connect, veamoslo en funcionamiento.

Ejemplo de transformación de HL7 a R5

Empecemos con un mensaje HL7 v2.5.1

MSH|^~\&|CLINIC_SYS|HOSPITAL123|EHR_SYSTEM|REGION1|20250826143000||ADT^A08^ADT_A01|MSG00002|P|2.5.1
EVN|A08|20250826143000
PID|1||123456^^^HOSPITAL123^MR||GOMEZ^MARIA^LUISA||19750523|F|||AVDA UNIVERSIDAD 45^^MADRID^^28040^ESP||(555)1234567|||S||12345678Z^^^ESP^NI
AL1|1|DA|70618^Penicillin^RXNORM|SV|Rash|20200115|Clinician noted severe rash after administration
AL1|2|FA|256349002^Peanut protein^SCT|SE|Anaphylaxis|20181201|Reported after accidental exposure
AL1|3|MA|300916003^Latex^SCT|MO|Skin irritation|20191010|Observed during procedure with latex gloves

Aquí tenemos un sencillo ADT_A08 con información relativa a un paciente y sus alergias, como unos implementadores aplicados que somos, sabemos que este mensaje contendrá (por lo menos) un recurso del tipo Patient y 3 del tipo AllergyIntolerance. 

Introducimos en nuestra producción un business service para capturar ficheros de HL7 y enviarlo a nuestro BPL:

Una vez capturado, transformamos nuestro HL7 a R4 pasando por SDA, echemos un vistazo al recurso AlleryIntolerance generado en R4:

{
                                                              
	"request": {
		"method": "POST",
		"url": "AllergyIntolerance"
	},
	"fullUrl": "urn:uuid:4b9f7b2e-9dd0-11f0-870e-c6b593068383",
	"resource": {
		"resourceType": "AllergyIntolerance",
		"category": [
			"food"
		],
		"clinicalStatus": {
			"coding": [
				{
					"code": "active",
					"system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical"
				}
			]
		},
		"code": {
			"coding": [
				{
					"code": "256349002",
					"display": "Peanut protein",
					"system": "http://snomed.info/sct"
				}
			]
		},
		"extension": [
			{
				"url": "http://intersystems.com/fhir/extn/sda3/lib/allergy-discovery-time",
				"valueDateTime": "2018-12-01T00:00:00+00:00"
			},
			{
				"url": "http://intersystems.com/fhir/extn/sda3/lib/allergy-entered-at",
				"valueReference": {
					"reference": "urn:uuid:4b9e97f6-9dd0-11f0-870e-c6b593068383"
				}
			}
		],
		"onsetDateTime": "2018-12-01T00:00:00+00:00",
		"patient": {
			"reference": "urn:uuid:4b9edb58-9dd0-11f0-870e-c6b593068383"
		},
		"reaction": [
			{
				"extension": [
					{
						"url": "http://intersystems.com/fhir/extn/sda3/lib/allergy-severity",
						"valueCodeableConcept": {
							"coding": [
								{
									"code": "SE"
								}
							]
						}
					}
				],
				"manifestation": [
					{
			
						"coding": [
							{
								"code": "Anaphylaxis"
							}
						]
 
					}
				]
			}
		]
	}
}

Y ahora el mismo recurso pero adaptado a la versión R5:

{
	"fullUrl": "urn:uuid:4b9f7b2e-9dd0-11f0-870e-c6b593068383",
	"request": {
		"method": "POST",
		"url": "AllergyIntolerance"
	},
													  
	"resource": {
		"resourceType": "AllergyIntolerance",
		"category": [
			"food"
		],
		"clinicalStatus": {
			"coding": [
				{
					"code": "active",
					"system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical"
				}
			]
		},
		"code": {
			"coding": [
				{
					"code": "256349002",
					"display": "Peanut protein",
					"system": "http://snomed.info/sct"
				}
			]
		},
		"extension": [
			{
				"url": "http://intersystems.com/fhir/extn/sda3/lib/allergy-discovery-time",
				"valueDateTime": "2018-12-01T00:00:00+00:00"
			},
			{
				"url": "http://intersystems.com/fhir/extn/sda3/lib/allergy-entered-at",
				"valueReference": {
					"reference": "urn:uuid:4b9e97f6-9dd0-11f0-870e-c6b593068383"
				}
			}
		],
		"onsetDateTime": "2018-12-01T00:00:00+00:00",
		"patient": {
			"reference": "urn:uuid:4b9edb58-9dd0-11f0-870e-c6b593068383"
		},
		"reaction": [
			{
				"extension": [
					{
						"url": "http://intersystems.com/fhir/extn/sda3/lib/allergy-severity",
						"valueCodeableConcept": {
							"coding": [
								{
									"code": "SE"
								}
							]
						}
					}
				],
				"manifestation": [
					{
						"concept": {
							"coding": [
								{
									"code": "Anaphylaxis"
								}
							]
						}
					}
				]
			}
		]
	}
}

Como podéis ver, la propiedad reaction ha sido modificada, adaptándose a la definición R5 del mismo:

Conclusión

Como habéis visto, las posibilidades que nos ofrece Health Connect para adaptarnos a los distintos proyectos basados en FHIR que nos puedan surgir son infinitas. Con el motor de interoperabilidad podremos adaptar nuestros datos del modo que necesitemos, teniendo en consideración cualquier requisito que se salga del comportamiento estándar.

¡Espero que os resulte de utilidad!

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