Cesar AI es una aplicación web que transforma información de un paciente (de cualquier formato) al recurso Patient de HL7 FHIR.
Es un experimento que construí, que podría ayudar a acelerar procesos de integración entre distintos sistemas informáticos en salud.
A continuación te explico de qué trata el experimento, cómo lo construí y consideraciones a futuro de este tipo de aplicaciones.
Motivación
Luego del evento de la Emprendethon, comencé a pensar en qué tipo de soluciones podrían ayudar a acelerar los proyectos de interoperabilidad en salud.
Si bien aprender a usar los estándares de interoperabilidad como FHIR es importante, a veces no es tan sencillo hacerlo.
Imagina un Centro Médico que quiere interoperar y que tiene su propia base de datos. En esta, tendrá información bajo un cierto esquema, que probablemente es muy diferente al formato de datos en FHIR. Así que para lograrlo, tenemos que escribir código que transforme desde el formato del Centro Médico a FHIR, lo cual toma tiempo y puede ser costoso.
¿Qué pasa si esa transformación se hace automáticamente? ¿Es posible crear un algoritmo que pase de un formato a otro de manera automática?
Pruebas Preliminares
El último tiempo han aparecido varias aplicaciones que procesan texto, que son capaces de analizar sentimientos en un texto e incluso de crear contenido por ti. Muchas de ellas usan herramientas proveídas por una compañía llamada OpenAI.
En redes sociales me topé con comentarios que decían que estas herramientas eran buenas para transformar datos de un formato a otro, que es justamente lo que yo quería hacer. Así que un siguiente paso es probarlas.
Para ver si funciona para mi caso de uso, hay que responder las siguientes preguntas:
- ¿Me sirven estas herramientas para transformar datos de un paciente a FHIR? (Prueba 1)
- ¿Puedo usar las herramientas desde mis propios programas? (Prueba 2)
- ¿Es fácil usarlas en mis propios programas? (Prueba 3)
Primera prueba: ¿Me sirven estas herramientas para transformar datos de un paciente a FHIR?
La forma más rápida de responder a esta pregunta es usando el modelo más fácil de usar y popular: ChatGTP. Si no lo logro con este, difícil que lo logre con los otros.
ChatGTP es un programa creado por OpenAI diseñado para procesar y generar texto en muchos idiomas. Funciona como un asistente al cual le puedes escribir preguntas sobre distintos temas, y este hará lo posible por responder.
Por ejemplo, le puedes preguntar “explica FHIR a un niño de 5 años” y te responderá algo como:
También puedes hacer solicitudes más complejas. Por ejemplo, si le pides que transforme datos no estructurados de un paciente a HL7 FHIR, te dará una respuesta suficientemente buena:
La respuesta fue esta:
{
"resourceType": "Patient",
"identifier": [
{
"system": "http://www.run.gov",
"value": "66.2345.333-1"
}
],
"name": [
{
"family": "Organa de la Rosa",
"given": [
"Jan-Solo"
]
}
],
"gender": "unknown",
"birthDate": "1961-04-03",
"address": [
{
"line": [
"Av Los Caudillos, número 67"
],
"city": "Valparaíso",
"district": "Valparaíso",
"postalCode": "xxxxx"
}
]
}
Así concluyo que estas herramientas sí permiten transformar datos de un paciente a FHIR.
Segunda prueba: ¿puedo usar estas herramientas desde mis propios programas?
En informática, cuando quieres conectar un sistema informático con otro, se usa algo llamado “API”. Por ejemplo, si tengo una aplicación donde quiero usar el valor actual del dólar, en vez de actualizar el valor a mano cada cierto tiempo, puedo usar un servicio externo (”API”) que me entregue el número actualizado.
Las API permiten que nuestros programas usen sistemas externos para lograr distintas tareas. En mi caso, necesito acceder a una API que me permita hacer preguntas como lo hago con ChatGTP.
La buena noticia es que OpenAI ofrece una API sobre la cual desarrolladores y empresas pueden construir aplicaciones. Me atrevo a decir que la base de su negocio es ofrecer este tipo de herramientas y cobrar por uso.
Nota: en OpenAI API puedes ver todas las características y ejemplos de lo que puedes lograr.
Si vas a la página de Playground, puedes jugar con la herramienta sin necesidad de usar la API directamente. Para probar, le entregué el mismo texto que a ChatGTP:
La respuesta es la siguiente:
{
"resourceType": "Patient",
"id": "66.2345.333-1",
"name": [
{
"use": "official",
"family": "Organa de la Rosa",
"given": ["Jan", "Solo"]
}
],
"gender": "male",
"birthDate": "1961-04-03",
"address": [
{
"line": ["Av Los Caudillos, número 67"],
"city": "Valparaíso",
"state": "Valparaíso",
"postalCode": "Región de Valparaíso"
}
],
"extension": [
{
"url": "http://example.org/age",
"valueAge": {
"value": 61
}
},
{
"url": "http://example.org/visit",
"valueDateTime": "2022-07-28T17:30:00"
De aquí hay cosas que no me gustaron. Por ejemplo, el RUT lo dejó como id
, y la edad la colocó como una extensión. Además, el JSON está incompleto.
¿Por qué está incompleto? Pasa que las palabras que ingresas y las que te entregan se contabilizan en “tokens”, donde 1000 tokens equivalen aproximadamente a 750 palabras. En el PlayGround, el límite de tokens por defecto es 256, por eso no me da una respuesta completa.
Para mejorar la calidad de respuesta, tengo que mejorar el texto inicial que dice “Transforma los datos del siguiente paciente a FHIR”. Esta parte se le llama “prompt”, que son las instrucciones que le damos al modelo para así obtener las respuestas que queremos.
Si bien hasta ahora la respuesta deja mucho que desear, la pregunta inicial es si puedo usar la herramienta desde mis propios programas. La buena noticia es que hay una opción que dice “View Code” que te genera el código exacto para usar en tu aplicación, en el lenguaje de programación que desees:
Hasta ahora sé que puedo usar la herramienta en mis propios programas y que necesito mejorar las instrucciones que le doy para obtener mejores resultados.
Tercera prueba: ¿es fácil usarlas en mis propios programas?
El código que genera el Playground se ve bastante simple, así que ahora toca usar este código.
En la documentación hay un Quickstart tutorial, donde te explican lo básico que tienes que saber y te dan una aplicación base de ejemplo. En esta aplicación colocas un animal y te sugiere nombres:
Esta aplicación está construida con Next.js, que si bien no lo he usado anteriormente, conozco React y JavaScript al revés y al derecho.
El código es bastante sencillo. Hay una función llamada generatePromp
que es lo que hay que cambiar para que en vez de solicitar nombres de animales, me entregue información de pacientes.
function generatePrompt(animal) {
const capitalizedAnimal =
animal[0].toUpperCase() + animal.slice(1).toLowerCase();
return `Suggest three names for an animal that is a superhero.
Animal: Cat
Names: Captain Sharpclaw, Agent Fluffball, The Incredible Feline
Animal: Dog
Names: Ruff the Protector, Wonder Canine, Sir Barks-a-Lot
Animal: ${capitalizedAnimal}
Names:`;
}
Cambié la función a lo siguiente:
function generatePrompt(patient) {
return `Transforma los datos del siguiente paciente a un recurso de HL7 FHIR:
${patient}
[INSERT]
`;
}
Nota: el texto [INSERT]
es para indicarle al modelo donde tiene que rellenar la información.
El resultado, si bien estaba truncado, parecía ser un recurso HL7 válido:
Así concluye esta tercera prueba. Hasta ahora sé que es fácil usar la herramienta en mis propios programas, y que incluso tengo una base sobre la cual construir mi aplicación.
Resultado de las pruebas
Las pruebas fueron exitosas. A partir de un texto que tiene información no estructurada de un paciente, fui capaz de generar un recurso FHIR. Además, descubrí que usarlo en programas propios era sencillo.
Sin embargo, también me di cuenta de que no le estoy dando el enfoque correcto. Los costos son proporcionales a la cantidad de texto que entregas y a la cantidad de texto que recibes. El recurso FHIR tiene mucha información repetitiva, hay palabras que me sobran.
Debido a lo anterior, un mejor enfoque es pedir información del paciente en un formato texto. Por ejemplo:
En la imagen, la respuesta del modelo es | 66.2345.333-1 | Jan-Solo | Organa | de la Rosa | Unknown | N/A | N/A | Av Los Caudillos, número 67, Valparaíso, Provincia de Valparaíso, Región de Valparaíso | 3 de abril de 1961
Ya sé que el primer elemento entre los caracteres “|
” es el RUT, el segundo elemento corresponde al nombre, y así. Gracias a esto, la respuesta es más barata porque usa menos “tokens”. Eso sí, ya no estamos hablando de FHIR, sino que es un formato muy parecido a CSV (donde la separación es con el carácter ”|”). Tendremos que pasar de este a FHIR, y para eso, hay que escribir código 😅
Construyendo Cesar AI
La prueba de concepto funcionó bien. Ahora toca armar un prototipo de herramienta que pueda transformar los datos de un paciente a su formato en FHIR, con el formato que usamos en Chile.
Diseñando el MVP: qué funciones debía tener
Fue natural mantener una interfaz parecida a lo que ocurre en el PlayGround de OpenAI: a la izquierda colocar una caja de texto para ingresar la información del paciente, y a la derecha, el recurso FHIR transformado.
Las funciones son 2:
- Permitir ingresar datos a transformar.
- Poder ver esos datos transformados en un recurso FHIR.
Construyendo el MVP: usando el código base de OpenAI
El código base de OpenAI es fácil de modificar.
Por ejemplo, en la siguiente imagen, podemos ver la función que entrega el resultado desde el servidor a la aplicación web. Aquí, cambié generatePrompt(animal)
por una función nueva que creé, y también ajusté algunos parámetros del modelo. En particular, la temperatura la dejé en 0 para que el resultado que me entregara el modelo fuera siempre el mismo para un mismo input:
Notar también que estoy enviando como respuesta el recurso FHIR (variable resource
), que es lo que muestro en la interfaz una vez haces clic en “Generar Recurso FHIR”.
En cuanto al Frontend:
- Utilicé Bootstrap que es la librería para crear interfaces que más fácil se me da ocupar, aunque admito que es un poco mucho para el prototipo 😅.
- Lo que ocurre una vez le das clic a “Generar Recurso FHIR”, es muy parecida a la del tutorial:
Nota: si quieres aprender a hacer data fetching con React, te recomiendo leer el artículo “Data fetching con React usando useState y useEffect” que escribí para Escuela Frontend.
Problemas y limitaciones: Google Maps al rescate
La primera versión funcionaba bien. Solo había un problema: las direcciones.
HL7 Chile definió un formato para guardar direcciones que básicamente tiene 4 componentes:
Por ejemplo, la dirección “C. Arturo Prat 210, San Nicolás, Ñuble” se debe descomponer en:
- Line: Calle Arturo Prat 210
- City: San Nicolás (código 16305)
- District: Punilla (código 163)
- State: Ñuble (código 16)
- Country: Chile (código 152)
Para lograr esto, hay que decirle a OpenAI que necesitamos la dirección con sus 5 componentes: línea, comuna, provincia, estado y país. Si bien el modelo detecta bien la dirección completa, es difícil que haga una buena descomposición. Hice las pruebas y además de que se equivocaba en la descomposición, el resultado entregado no es compatible con el listado de códigos definido por HL7 Chile.
¿Cómo descomponemos una dirección en sus componentes? Hay una API que lo hace muy bien: Google Maps.
De esta forma, Open AI se encarga de extraer la dirección, y luego Google Maps de descomponerla.
El último desafío es que Google Maps no entrega los mismos nombres que están definidos en los códigos de provincias de Chile. Por ejemplo, “Punilla” para Google es “Provincia de Punilla”. Así como este, hay otras provincias con inconsistencias parecidas.
Para mí es obvio que si Google me dice “Provincia de Punilla”, corresponde a “Punilla”. Pero programar eso no es tan sencillo, sobre todo considerando que Google no entrega el listado de provincias. Para solucionarlo, le pasé el listado completo de provincias al modelo de OpenAI, y luego le pregunto algo como: “¿a qué provincia corresponde este texto?”
Otra forma de hacerlo podría ser con una base de datos que permita hacer búsqueda por similitud. De esa forma, nos ahorraríamos llamados a OpenAI que estimo que es más caro.
Resultado final
El resultado final lo puedes ver aquí en vivo.
El diagrama de funcionamiento final:
Consideraciones a futuro
CesarAI es un prototipo que busca transformar datos de un paciente en cualquier formato a uno interoperable (HL7 FHIR), funciona bien, pero faltan cosas que mejorar:
- Modelo de negocio: ¿hay mercado? En Chile, la interoperabilidad viene fuerte, entonces la cantidad de proyectos que necesitarán disponer datos en el formato interoperable van a ir en aumento. Además, una herramienta de este estilo podría ahorrar varias horas de desarrollo, y se puede cobrar en base a ese ahorro.
- Ético y Legal: En Italia restringieron el uso de ChatGTP por razones de privacidad. Además, según este artículo, tampoco cumple con HIPAA, ya que estás entregándole información de paciente personal. Si bien en los términos de uso dicen no usar el contenido que entregas para mejorar sus servicios, también menciona que si quieres procesar datos personales, hay que entregar el aviso legal correspondiente.
- Branding: El nombre se llama “CesarAI”, pero la verdad se me fue dejarlo solo en “Cesar”. “Powered by AI” no es un feature, es simplemente una característica interna.
Siguientes pasos
Debido a las consideraciones éticas y legales, creo que por ahora no es bueno usar una herramienta de este estilo para datos de paciente. Sin embargo, puede servir como una herramienta de estudio del estándar HL7 FHIR.
Alternativamente, habría que explorar si existen otros modelos que cumplan regulaciones estrictas como HIPAA y GDPR, y así actualizar la herramienta para que los use.
Finalmente, es posible explorar otras ideas que buscan solucionar la misma problemática. ¿Qué pasa si en vez de entregar datos de paciente, entregas el “formato” de tus datos? Cesar podría generar una función en cualquier lenguaje de programación que transforme tus datos a HL7 FHIR. Así, es posible contribuir a disminuir costos de implementación sin comprometer datos personales.