|
8 min de lectura
|

Validación FHIR: Recursión o sin Recursión

Resumir este artículo con:
ChatGPTPerplexityClaudeGrok

Cómo empezó todo

Mientras trabajábamos en el validador FHIR de Aidbox, recibimos una pregunta de un cliente: ¿por qué falla la validación en los elementos Questionnaire.item anidados cuando la restricción solo está definida en el item de nivel superior?

Estábamos aplicando las restricciones del perfil de forma recursiva: si una restricción se definía sobre Questionnaire.item, nuestro validador la aplicaba también a item.item, item.item.item, y así sucesivamente. Nos parecía lo correcto. Al fin y al cabo, la especificación R5 dice:

Cuando un perfil define restricciones sobre dichos elementos, las restricciones se aplican también a las referencias recursivas a esos elementos.

Pero el caso del cliente nos llevó a analizarlo más de cerca. Y lo que encontramos fue una contradicción genuina en la especificación.

El caso SDC

El perfil SDC Questionnaire define el invariante sdc-1 sobre Questionnaire.item:

Un elemento no puede tener una answerExpression si ya existe answerOption o answerValueSet.

Considere este Questionnaire en el que tanto un elemento de nivel superior como un elemento anidado violan sdc-1:

{
  "resourceType": "Questionnaire",
  "meta": {
    "profile": [
      "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire"
    ]
  },
  "url": "http://example.org/test",
  "status": "active",
  "item": [
    {
      "linkId": "1",
      "type": "decimal",
      "item": [
        {
          "linkId": "1.1",
          "type": "decimal",
          "answerValueSet": "http://example.org/vs",
          "extension": [
            {
              "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-answerExpression",
              "valueExpression": {
                "language": "text/fhirpath",
                "expression": "1 + 1"
              }
            }
          ]
        }
      ]
    },
    {
      "linkId": "2",
      "type": "decimal",
      "answerValueSet": "http://example.org/vs",
      "extension": [
        {
          "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-answerExpression",
          "valueExpression": {
            "language": "text/fhirpath",
            "expression": "1 + 1"
          }
        }
      ]
    }
  ]
}

Nuestro validador de Aidbox marcó ambos elementos. El validador de referencia de HL7 en validator.fhir.org marcó únicamente el de nivel superior. ¿Quién tiene razón?

Analizando el perfil en profundidad

Al examinar el StructureDefinition del perfil SDC, la respuesta se vuelve menos evidente. El differential menciona explícitamente Questionnaire.item.item, pero únicamente establece mustSupport: true en él; sin restricciones, sin invariantes:

{
  "id": "Questionnaire.item.item",
  "path": "Questionnaire.item.item",
  "mustSupport": true
}

No queda claro si el autor del perfil pretendía que las restricciones se aplicaran únicamente en el nivel superior o si esperaba que se propagaran en cascada a los elementos anidados; es perfectamente posible que asumiera que sdc-1 se aplicaría automáticamente a los elementos anidados. En cualquier caso, el differential no restringe explícitamente Questionnaire.item.item, y el validador de HL7 solo aplica lo que el differential indica.

No obstante, desde la perspectiva de la especificación, nuestro comportamiento también era correcto: las restricciones deben aplicarse de forma recursiva.

Por qué la recursión automática rompe los perfiles reales

Trasladé el problema a Grahame Grieve en chat.fhir.org, y su reacción fue inmediata.

Considere el perfil IPS Composition. Restringe Composition.section con una cardinalidad mínima de 3..*. Si aplicamos esto de forma recursiva:

  • Composition.section – debe tener 3 o más secciones
  • Composition.section.section – también debe tener 3 o más secciones
  • Composition.section.section.section – y así sucesivamente, de forma infinita

Ningún documento IPS válido podría existir jamás. La reacción de Grahame ante el texto de la especificación:

«Eso es totalmente incorrecto en lo que respecta a los perfiles de Composition. Absolutamente imposible de usar. No sé qué estábamos pensando cuando lo escribimos.»

El slicing lo complica aún más

El perfil Subscriptions Backport define slices sobre Parameters.parameter:

  • parameter:subscription (obligatorio)
  • parameter:topic (obligatorio)
  • parameter:type (obligatorio)

El recurso Parameters es recursivo: parameter contiene part, que comparte la misma estructura mediante contentReference. Si aplicamos el slicing de forma recursiva, cada .part necesitaría sus propios slices subscription, topic y type. Evidentemente, no era la intención del autor.

Estos perfiles están ampliamente implementados. Aplicar restricciones recursivas los rompería a todos.

Recursión frente a reutilización de tipos

La discusión puso de manifiesto un problema más profundo. FHIR utiliza contentReference para dos propósitos distintos:

Recursión verdadera: un elemento que se referencia a sí mismo:

  • Questionnaire.item -> Questionnaire.item.item
  • Composition.section -> Composition.section.section
  • Consent.provision -> Consent.provision.provision

Reutilización de tipos: distintos elementos que comparten una estructura:

  • ValueSet.compose.include y ValueSet.compose.exclude
  • Parameters.parameter y Parameters.parameter.part

Ambos utilizan contentReference internamente, pero la semántica es fundamentalmente diferente. Como señaló Gino Canessa:

«Ambos se implementan mediante el mecanismo contentReference, y la recursión inicial no es el mismo elemento. En este caso, sucede que tienen el mismo nombre de elemento, pero eso no es lo que lo hace recursivo.»

Cualquier solución debe distinguir estos casos, y ni la especificación ni las herramientas actuales lo hacen.

No es la primera vez

Grahame señaló un hilo anterior en chat.fhir.org donde este mismo problema se debatió en 2020. Chris Moesel, de MITRE, preguntó si las restricciones sobre Questionnaire.item deberían propagarse a item.item mediante contentReference. La comunidad concluyó que no debería ser así, y Grahame señaló una extensión existente, elementdefinition-profile-element, como mecanismo para la reutilización recursiva explícita. La extensión se coloca en el elemento hijo y apunta de vuelta al perfil padre, permitiendo a los autores optar por la herencia de restricciones de forma voluntaria.

Después, en 2023, se publicó R5 con una nueva sección de Elementos Recursivos que decía lo contrario: las restricciones se aplican de forma recursiva por defecto. Chris Moesel señaló la contradicción de inmediato. Como dijo Grahame: «No sé qué estábamos pensando cuando lo escribimos.»

Así pues, la comunidad ya había identificado el problema, había creado una extensión para resolverlo, y luego la especificación tomó la dirección opuesta.

Una posible solución: una extensión sobre ElementDefinition

Durante la discusión, propuse una solución: una extensión que controle explícitamente si las restricciones se heredan de forma recursiva. La idea es sencilla: añadir un indicador en ElementDefinition que informe a los validadores de que deben «aplicar (o no aplicar) las restricciones del elemento padre a esta referencia recursiva».

Gino Canessa lo desarrolló con un enfoque más flexible: una extensión en el elemento hijo que apunta de vuelta al elemento del que debe heredar las restricciones. Por ejemplo, Questionnaire.item.item podría indicar explícitamente «aplicar las restricciones de Questionnaire.item»:

Questionnaire.item          – restricciones definidas aquí
Questionnaire.item.item     – extensión: "inherit-constraints-from: Questionnaire.item"

Este enfoque resuelve ambos casos:

  • Recursión verdadera (Questionnaire.item.item): el autor del perfil añade la extensión cuando desea que las restricciones se propaguen en cascada — opt-in
  • Reutilización de tipos (ValueSet.compose.include / .exclude): cada elemento permanece independiente por defecto — sin filtración accidental de restricciones

La pregunta clave de diseño es el comportamiento predeterminado. La comunidad se inclina por no recursivo por defecto con opt-in explícito:

PosiciónDefensoresJustificación
No recursivo por defecto, opt-in mediante extensiónGino Canessa, Grahame GrieveMás seguro para los perfiles existentes; evita cambios disruptivos
Recursivo por defecto, opt-out mediante extensiónBrian PostlethwaiteMás intuitivo para los casos de uso habituales
Prefiere recursivo, acepta no recursivoLloyd McKenzieCambiar el comportamiento predeterminado supondría un cambio disruptivo

Lloyd McKenzie expresó la tensión de forma precisa:

«Debe existir alguna forma de establecer restricciones que se apliquen de forma recursiva, porque es sin duda un caso de uso necesario. Pero dado que el comportamiento actual no es ese, realizar ese cambio podría ser disruptivo.»

Qué estamos haciendo al respecto

En FHIR Schema —el formato de validación en forma de árbol que desarrollamos en Health Samurai— este problema se presenta como una decisión arquitectónica explícita. Cuando transpilamos StructureDefinitions a un árbol de esquema, contentReference se convierte en un puntero a otro nodo, y debemos decidir: ¿copiar las restricciones del padre o no?

Por ahora, hemos alineado el validador de Aidbox con el validador de referencia de HL7: las restricciones no se aplican de forma recursiva a menos que el perfil las defina explícitamente en los elementos anidados. Esto coincide con la forma en que los perfiles se escriben realmente en la práctica.

El tema está señalado para su discusión en la HL7 Working Group Meeting de Róterdam. Esperamos que de ella surjan propuestas concretas para un mecanismo de extensión y un lenguaje de especificación más claro.

Qué significa esto para los implementadores

Si trabaja con elementos recursivos (Questionnaire.item, Composition.section, Consent.provision, Parameters.parameter):

  • Comportamiento actual en los validadores: las restricciones no se aplican de forma recursiva, independientemente de lo que diga la especificación R5
  • Si necesita restricciones en todos los niveles de anidamiento: defínalas explícitamente en el differential de su perfil para cada nivel, o utilice invariantes FHIRPath que recorran el árbol
  • Si está desarrollando un validador: no aplique restricciones de forma recursiva por defecto — romperá perfiles ampliamente utilizados

Esto es una laguna en la especificación, no un error. La especificación dice una cosa, los perfiles del mundo real necesitan otra, y los validadores han optado pragmáticamente por el comportamiento que no rompe las implementaciones existentes.

Próximo paso: la WGM de Róterdam

Planeamos llevar esto a la HL7 Working Group Meeting de Róterdam con una propuesta concreta para el mecanismo de extensión descrito anteriormente. El objetivo es lograr el consenso del grupo de trabajo de FHIR Infrastructure y convertir esto en un cambio formal de la especificación.

Si se ha encontrado con este problema en sus perfiles o en su validador, nos encantaría conocer sus casos de uso antes de la reunión. Únase a la discusión en chat.fhir.org o póngase en contacto con nosotros directamente.

Véase también: Cómo crear una guía de implementación FHIR y FHIR Additional Bindings.

Compartir este artículo
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

Get the latest articles on FHIR, interoperability, and healthcare IT.