El problema de ejecutar eCQMs
Las medidas electrónicas de calidad clínica (eCQMs) son el mecanismo con el que los programas miden la calidad asistencial: si los pacientes elegibles recibieron su cribado colorrectal, si su hipertensión está controlada. Las medidas de CMS y HEDIS que reporta cada programa de atención basada en valor se elaboran en CQL (Clinical Quality Language), y la forma habitual de ejecutarlas es un motor CQL dedicado: un runtime independiente que analiza la medida, consulta su servidor FHIR para obtener terminología y datos de pacientes, y devuelve un MeasureReport.
Esto funciona bien, pero tiene un coste:
- Un nivel de cómputo separado. Un motor CQL es su propio runtime — hay que desplegarlo y escalarlo junto al servidor FHIR, y realiza el cálculo poblacional fuera de la base de datos. Se trata de una infraestructura real y un problema de escalado para poblaciones grandes.
- Difícil de explicar. Cuando un paciente aparece con una brecha asistencial, ¿puede mostrarle a un clínico exactamente por qué? Eso implica rastrear los internos del motor y las expansiones de value sets — no leer una consulta.
Esta es la pregunta que responde esta entrada: ¿y si la lógica de la medida fuera simplemente SQL, ejecutándose donde ya viven los datos?
Resulta que puede serlo — y el resultado es más que un truco de rendimiento: con SQL on FHIR, todo el conocimiento sobre cómo calcular una medida se convierte en artefactos FHIR portátiles y estándar que pueden empaquetarse en un paquete FHIR e instalarse en otro servidor. Este es un recorrido técnico de cómo lograrlo, con un ejemplo completamente funcional que puede clonar y ejecutar.
La idea: las medidas son lógica de conjuntos, y SQL es un lenguaje de conjuntos
Una medida de calidad es fundamentalmente aritmética de conjuntos sobre una población de pacientes:
- Población inicial — quién es elegible (edad, encuentros, una condición).
- Denominador / Exclusiones — quién se cuenta, menos quién queda excluido (cuidados paliativos, hospicio, fragilidad…).
- Numerador — quién cumplió la medida (un cribado, una lectura controlada).
- Puntuación —
numerador / (denominador − exclusiones).
Cada uno de esos elementos es un conjunto de pacientes. SQL trabaja muy bien con conjuntos. Lo único que se interpone entre los datos FHIR y una consulta SQL es que los recursos FHIR son JSON profundamente anidado, y la pertenencia a una terminología («¿está este código en el value set?») no es una columna por la que filtrar. SQL on FHIR resuelve ambos problemas.
Capa 1: aplanar FHIR en tablas con ViewDefinitions
Una ViewDefinition es un recurso FHIR que describe cómo aplanar un tipo de recurso en una tabla plana. Apúntela a Encounter, materialícela, y obtendrá una tabla sof.encounter_flat bien organizada con patient_id, type_system, type_code, status, period_start, etc. — la lógica de la medida lee columnas simples en lugar de explorar estructuras FHIR anidadas.
El ejemplo incluye una ViewDefinition por tipo de recurso que utilizan las medidas — patient, encounter, condition, observation, procedure y algunos más — cada una materializada en el esquema sof.* y expuesta a través de una vista envolvente ligera. Esa capa plana compartida es reutilizada por cada medida.
Capa 2: terminología como JOIN, no como $expand
La otra parte difícil de una medida es la pertenencia a conjuntos de códigos: «¿es este encuentro uno de los siete tipos de visita cualificante?». En un motor CQL esto se resuelve típicamente en tiempo de ejecución contra un servicio de terminología. Con SQL on FHIR se aplanan las expansiones de ValueSet una sola vez en una tabla concepts — una fila por (valueset_url, system, code) — y la pertenencia se convierte en un join ordinario:
JOIN concepts c
ON c.system = e.type_system
AND c.code = e.type_code
AND c.valueset_url = 'http://cts.nlm.nih.gov/fhir/ValueSet/…'
Sin llamadas de red, sin expansión por paciente. El value set se expande de forma offline y se indexa, de modo que la comprobación «¿cuenta el código de este paciente?» está basada en conjuntos y es rápida.
Capa 3: la medida es una sola consulta SQL
Con tablas planas y un join a concepts disponibles, una medida completa se convierte en una única consulta compuesta de CTEs — y su lectura se aproxima notablemente a la definición en lenguaje natural de la medida. Aquí está la Población Inicial de CMS130 (Cribado de Cáncer Colorrectal) — pacientes de entre 46 y 75 años con un encuentro cualificante durante el período de medición:
WITH mp AS (
SELECT '2026-01-01'::timestamptz AS mp_start,
'2026-12-31'::timestamptz AS mp_end
),
qualifying_encounters AS (
SELECT DISTINCT e.patient_id
FROM encounter_flat e
JOIN concepts c
ON c.system = e.type_system
AND c.code = e.type_code
AND c.valueset_url IN (
-- ValueSet URLs shortened for readability; full URLs in the example repo
'http://cts.nlm.nih.gov/fhir/ValueSet/…', -- Office Visit
'http://cts.nlm.nih.gov/fhir/ValueSet/…', -- Annual Wellness Visit
'…' -- + 5 more
)
CROSS JOIN mp
WHERE e.status = 'finished'
AND e.period_start BETWEEN mp.mp_start AND mp.mp_end
),
initial_population AS (
SELECT p.id AS patient_id
FROM patient_flat p
CROSS JOIN mp
WHERE EXTRACT(YEAR FROM AGE(mp.mp_end, p.birth_date::date)) BETWEEN 46 AND 75
AND p.id IN (SELECT patient_id FROM qualifying_encounters)
)
-- … denominator exclusions, numerator, and the final score follow as more CTEs
El numerador añade algunos CTEs más (colonoscopia en los últimos 9 años, FOBT durante el período, etc.), las exclusiones unen hospicio/cuidados paliativos/fragilidad, y un SELECT final calcula la puntuación. La clave es que la medida es legible. Puede leerla, compararla con la especificación y ejecutar SELECT * FROM initial_population para ver exactamente quién calificó.
La lógica compartida permanece compartida
Exclusiones como hospicio, cuidados paliativos y enfermedad avanzada con fragilidad se repiten en muchas medidas. En el ejemplo se extraen en piezas reutilizables en lugar de copiarse y pegarse, de modo que corregir la lógica de hospicio la corrige en todos los lugares a la vez.
¿Pero quién escribe todo este SQL?
La objeción obvia: traducir manualmente el CQL de una medida a SQL suena como un trabajo minucioso y propenso a errores — y hay docenas de eCQMs. En la práctica, este es exactamente el tipo de tarea que los asistentes de codificación con IA manejan bien. CQL y SQL son ambos lenguajes estructurados y bien especificados, y la lógica de una medida se mapea limpiamente sobre el patrón de CTEs mostrado arriba (población inicial → exclusiones → numerador → puntuación).
Esto no es una esperanza — es cómo se construyó el propio ejemplo. Cada medida se tradujo de su CQL publicado a SQL con un asistente de IA, y después se verificó contra los fixtures de MeasureReport de referencia de CMS paciente a paciente hasta que los números coincidieron exactamente. La IA realiza la traducción mecánica; los fixtures la mantienen honesta.
Los fixtures de referencia son los que hacen confiable la traducción asistida por IA: los resultados esperados de MeasureReport para los pacientes de prueba se publican junto al contenido de la medida en el repositorio dqm-content-qicore-2025, por lo que cada medida traducida puede verificarse contra la verdad de referencia — no se acepta por fe.
Hacerlo conforme a los estándares: SQLQuery + Measure/$evaluate-measure
Ejecutar SQL es correcto internamente, pero el objetivo es un servicio FHIR conforme, no un script de base de datos. Dos elementos de SQL on FHIR cierran esa brecha:
- El SQL de cada medida se almacena en Aidbox como un recurso SQLQuery
Library(un perfil SQL on FHIR sobreLibrary) y se invoca con la operación$sqlquery-run. La lógica de cálculo vive en el servidor FHIR, como recursos de primera clase — no en el código de la aplicación. - El servicio responde a la operación estándar FHIR R4
Measure/$evaluate-measurey devuelve unMeasureReportapropiado — de modo que cualquier cliente FHIR lo consume de la misma manera que consumiría el resultado de un motor CQL.
Cada SQLQuery Library también declara linaje depends-on hacia las ViewDefinitions que lee, lo que le proporciona un grafo consultable: medida → vistas → recursos. Todo lo que necesita una medida — las vistas planas, la terminología, el cálculo — está ahora expresado como recursos FHIR estándar que viven en el servidor FHIR. Lo que prepara el terreno para el verdadero beneficio.
La aplicación evaluate-measure intermedia no contiene lógica de medida alguna: resuelve la SQLQuery Library correcta, invoca $sqlquery-run y da forma a las filas devueltas en un MeasureReport. El cómputo se ejecuta en la base de datos.
El beneficio: toda la suite de medidas es un paquete FHIR portátil
Dado que cada parte de una medida es ahora un recurso FHIR estándar, toda la suite se empaqueta en un solo paquete FHIR NPM: terminología (CodeSystems + ValueSets), las ViewDefinitions que aplanan los datos y las SQLQuery Libraries que contienen la lógica de cálculo. En el ejemplo, ese paquete alberga 170 recursos distribuidos en una docena de medidas — 10 CodeSystems, 107 ValueSets, 10 ViewDefinitions y 43 SQLQuery Libraries.
El formato del paquete es estándar — el mismo formato FHIR NPM con el que se distribuyen las Implementation Guides — por lo que cualquier servidor FHIR puede cargarlo con su propio mecanismo de instalación de paquetes; en Aidbox eso es una llamada $fhir-package-install al arrancar el servidor, y las definiciones simplemente están ahí. Y aquí está la parte que importa: ese paquete no está ligado a Aidbox. Son bloques de construcción SQL on FHIR estándar — ViewDefinitions y SQLQuery Libraries definidas por la especificación SQL on FHIR. Traslade el paquete a cualquier servidor FHIR que implemente estos bloques de construcción, instálelo, y las mismas medidas se calculan de la misma manera. Cómo las ejecuta cada servidor es un detalle de implementación — algunos ejecutan un motor SQL on FHIR separado, otros lo ejecutan dentro de la base de datos.
Aidbox toma la vía dentro de la base de datos: las ViewDefinitions se materializan en tablas o vistas y las SQLQuery Libraries se ejecutan como SQL nativo, directamente en su PostgreSQL — justo donde ya viven los datos. De este modo, el paquete se instala y ejecuta sin nada adicional que desplegar para el cómputo en sí: no hay motor de ejecución que desplegar, escalar o mantener sincronizado. La lógica de la medida se distribuye de la misma manera que una Implementation Guide — como artefactos FHIR estándar y compartibles.
Investigar un resultado
¿Recuerda el problema de «difícil de explicar»? Aquí recibe una respuesta directa. Al solicitar un paciente individual, el MeasureReport incluye un array evaluatedResource: referencias reales a recursos FHIR, cada una etiquetada con la población que satisface mediante la extensión estándar cqf-criteriaReference.
"evaluatedResource": [
{
"reference": "Encounter/abc",
"extension": [
{ "url": ".../cqf-criteriaReference", "valueString": "initial-population" },
{ "url": ".../cqf-criteriaReference", "valueString": "denominator" }
]
},
{
"reference": "Procedure/xyz",
"extension": [
{ "url": ".../cqf-criteriaReference", "valueString": "numerator" }
]
}
]
«¿Por qué está este paciente en el numerador?» — este Procedure. «¿Por qué está excluido?» — este encuentro de hospicio. Cada referencia se resuelve a un recurso que existe en el servidor, de modo que el siguiente paso de la investigación es simplemente un GET.
Para la investigación a escala poblacional, cada medida también incluye una SQLQuery Library -evidence: una fila por paciente con la cadena de decisión completa — qué vía satisfizo el numerador (colonoscopia vs. FOBT vs. ninguna), el recurso desencadenante con su código y fecha, y qué exclusión se activó. Eso es una lista de trabajo de brechas asistenciales — quién tiene pendiente el cribado y qué exactamente le falta — como una sola consulta. La aplicación de demostración del ejemplo convierte estas consultas en elementos clicables: una lista de trabajo de medidas cruzadas, una vista de 360° por paciente con desglose de evidencia, y listas de alcance exportables.

Y cuando un número sigue pareciendo incorrecto, cada población es un CTE con nombre: SELECT * FROM initial_population y reduzca el ámbito paso a paso — sin rastrear internos del motor.
Pruébelo usted mismo
Todo lo anterior es un ejemplo funcional de código abierto en el repositorio de ejemplos de Aidbox — una docena de medidas de CMS (CMS130, CMS165, CMS125, CMS131 y más) con datos de pacientes de muestra y una aplicación de demostración interactiva.
→ github.com/Aidbox/examples · aidbox-custom-operations/measure-evaluate
Siga las instrucciones del README para ejecutar la pila completa en local: inicie Aidbox con el paquete de medidas instalado al arrancar, cargue el conjunto de datos de muestra, calcule las medidas mediante la operación estándar Measure/$evaluate-measure y explore la evidencia de cada paciente en la interfaz de la aplicación de demostración. Lo que se devuelve es un MeasureReport FHIR estándar con los recuentos de población y la puntuación — calculado por SQL, dentro de Aidbox, sin ningún motor CQL en ningún punto de la pila.

Conclusión
CQL es un buen lenguaje de autoría para medidas de calidad. Pero no tiene que ser su motor de ejecución. Cuando aplana FHIR con ViewDefinitions, convierte la terminología en un join y expresa cada medida como una SQLQuery Library detrás de Measure/$evaluate-measure, la lógica de la medida deja de estar encerrada en un motor y se convierte en lo que SQL on FHIR promete: artefactos FHIR estándar que puede empaquetar una vez y ejecutar en cualquier lugar. Distribuya toda la suite como un paquete FHIR, instálelo en cualquier servidor SQL on FHIR, y las mismas medidas se calculan de la misma manera — explicables hasta el recurso individual. En Aidbox se ejecutan de forma nativa en PostgreSQL, por lo que no hay ningún motor de ejecución separado que operar. Y llegar hasta aquí es más accesible de lo que parece: los asistentes de IA traducen el CQL a SQL, y los propios fixtures de referencia de CMS verifican cada medida contra los resultados esperados.
¿Le interesa probar este enfoque de cálculo de medidas con sus propios datos? Contáctenos — estaremos encantados de guiarle.






