|
8 Min. Lesezeit
|

FHIR-Validierung: Rekursion oder keine Rekursion

Diesen Artikel zusammenfassen mit:
ChatGPTPerplexityClaudeGrok

Wie alles begann

Während der Arbeit am Aidbox FHIR-Validator erhielten wir eine Frage von einem Kunden: Warum schlägt die Validierung bei verschachtelten Questionnaire.item-Elementen fehl, wenn die Einschränkung nur auf dem obersten item definiert ist?

Wir wendeten Profilbeschränkungen rekursiv an – wenn eine Einschränkung auf Questionnaire.item definiert war, erzwang unser Validator diese auch für item.item, item.item.item und so weiter. Das schien das Richtige zu sein. Schließlich heißt es in der R5-Spezifikation:

Wenn ein Profil Einschränkungen für solche Elemente definiert, gelten diese Einschränkungen auch für die rekursiven Verweise auf diese Elemente.

Der Fall des Kunden veranlasste uns jedoch, genauer hinzusehen. Und was wir fanden, war ein echter Widerspruch in der Spezifikation.

Der SDC-Fall

Das SDC-Questionnaire-Profil definiert die Invariante sdc-1 auf Questionnaire.item:

Ein Element darf keine answerExpression haben, wenn bereits answerOption oder answerValueSet vorhanden ist.

Betrachten Sie diesen Questionnaire, bei dem sowohl ein Element der obersten Ebene als auch ein verschachteltes Element gegen sdc-1 verstoßen:

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

Unser Aidbox-Validator meldete Fehler bei beiden Elementen. Der HL7-Referenzvalidator auf validator.fhir.org meldete nur das Element der obersten Ebene. Wer hat recht?

Analyse des Profils

Ein Blick auf die StructureDefinition des SDC-Profils macht die Antwort weniger eindeutig. Das Differential erwähnt Questionnaire.item.item explizit, setzt dort jedoch nur mustSupport: true – keine Einschränkungen, keine Invarianten:

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

Ob der Profilautor beabsichtigte, dass Einschränkungen nur auf der obersten Ebene gelten, oder davon ausging, dass sdc-1 automatisch auf verschachtelte Elemente angewendet wird, ist unklar – es ist durchaus möglich, dass er annahm, sdc-1 würde automatisch auf verschachtelte Elemente kaskadieren. In jedem Fall schränkt das Differential Questionnaire.item.item nicht explizit ein, und der HL7-Validator erzwingt nur das, was das Differential vorgibt.

Aus Sicht der Spezifikation war unser Verhalten jedoch ebenfalls korrekt – Einschränkungen sollten rekursiv angewendet werden.

Warum automatische Rekursion reale Profile bricht

Ich habe dies mit Grahame Grieve auf chat.fhir.org besprochen, und seine Reaktion war unmittelbar.

Betrachten Sie das IPS-Composition-Profil. Es schränkt Composition.section mit einer Mindest-Kardinalität von 3..* ein. Wenn wir dies rekursiv anwenden:

  • Composition.section – muss 3 oder mehr Abschnitte haben
  • Composition.section.section – muss ebenfalls 3 oder mehr Abschnitte haben
  • Composition.section.section.section – und so weiter, unendlich

Kein gültiges IPS-Dokument könnte jemals existieren. Grahames Reaktion auf den Spezifikationstext:

„Das ist völlig falsch, wenn es um Composition-Profile geht. Damit ist es absolut unmöglich zu arbeiten. Ich weiß nicht, was wir uns dabei gedacht haben, als wir das geschrieben haben."

Slicing macht es noch schlimmer

Das Subscriptions-Backport-Profil definiert Slices auf Parameters.parameter:

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

Die Parameters-Ressource ist rekursiv – parameter enthält part, das über contentReference dieselbe Struktur teilt. Wenn wir Slicing rekursiv anwenden, würde jedes .part seine eigenen subscription-, topic- und type-Slices benötigen. Das ist offensichtlich nicht die Absicht der Autoren.

Diese Profile sind weit verbreitet implementiert. Die Erzwingung rekursiver Einschränkungen würde sie alle brechen.

Rekursion vs. Typwiederverwendung

Die Diskussion offenbarte ein tieferliegendes Problem. FHIR verwendet contentReference für zwei unterschiedliche Zwecke:

Echte Rekursion – ein Element, das auf sich selbst verweist:

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

Typwiederverwendung – verschiedene Elemente, die eine Struktur teilen:

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

Beide verwenden intern contentReference, aber die Semantik ist grundlegend verschieden. Wie Gino Canessa darauf hinwies:

„Beide werden über den contentReference-Mechanismus realisiert, und die anfängliche Rekursion ist nicht dasselbe Element. In diesem Fall haben sie zufällig denselben Elementnamen, aber das ist nicht das, was es rekursiv macht."

Jede Lösung muss diese Fälle unterscheiden – und weder die Spezifikation noch die aktuellen Tools tun dies.

Das ist nicht das erste Mal

Grahame verwies auf einen früheren Thread auf chat.fhir.org, in dem genau dieses Problem bereits 2020 diskutiert wurde. Chris Moesel von MITRE fragte, ob Einschränkungen auf Questionnaire.item über contentReference auf item.item übertragen werden sollten. Die Community kam zu dem Schluss, dass dies nicht der Fall sein sollte – und Grahame verwies auf eine bestehende Extension, elementdefinition-profile-element, als Mechanismus für explizite rekursive Wiederverwendung. Die Extension wird auf das untergeordnete Element gesetzt und verweist zurück auf das übergeordnete Profil, sodass Autoren explizit in die Vererbung von Einschränkungen einwilligen können.

Dann wurde 2023 R5 mit einem neuen Abschnitt zu Rekursiven Elementen veröffentlicht, der das Gegenteil besagte: Einschränkungen werden standardmäßig rekursiv angewendet. Chris Moesel machte sofort auf den Widerspruch aufmerksam. Wie Grahame es formulierte: „Ich weiß nicht, was wir uns dabei gedacht haben, als wir das geschrieben haben."

Die Community hatte das Problem also bereits identifiziert, eine Extension zur Lösung entwickelt – und dann ging die Spezifikation in die entgegengesetzte Richtung.

Ein möglicher Lösungsansatz: eine Extension auf ElementDefinition

Im Laufe der Diskussion schlug ich eine Lösung vor: eine Extension, die explizit steuert, ob Einschränkungen rekursiv vererbt werden. Die Idee ist einfach – ein Flag auf ElementDefinition, das Validatoren mitteilt: „Wende Einschränkungen des übergeordneten Elements auf diesen rekursiven Verweis an (oder nicht)."

Gino Canessa entwickelte dies weiter zu einem flexibleren Ansatz: eine Extension auf dem untergeordneten Element, die auf das Element verweist, von dem es Einschränkungen erben soll. Beispielsweise könnte Questionnaire.item.item explizit angeben: „Übernimm Einschränkungen von Questionnaire.item":

Questionnaire.item          – Einschränkungen hier definiert
Questionnaire.item.item     – Extension: "inherit-constraints-from: Questionnaire.item"

Dieser Ansatz löst beide Fälle:

  • Echte Rekursion (Questionnaire.item.item): Der Profilautor fügt die Extension hinzu, wenn er möchte, dass Einschränkungen kaskadieren – Opt-in
  • Typwiederverwendung (ValueSet.compose.include / .exclude): Jedes Element bleibt standardmäßig unabhängig – keine versehentliche Übertragung von Einschränkungen

Die zentrale Designfrage ist der Standard. Die Community tendiert zu nicht-rekursiv als Standard mit explizitem Opt-in:

PositionBefürworterBegründung
Nicht-rekursiv als Standard, Opt-in über ExtensionGino Canessa, Grahame GrieveSicherste Option für bestehende Profile; vermeidet Breaking Changes
Rekursiv als Standard, Opt-out über ExtensionBrian PostlethwaiteIntuitiver für gängige Anwendungsfälle
Bevorzugt rekursiv, akzeptiert nicht-rekursivLloyd McKenzieEine Änderung des Standards wäre ein Breaking Change

Lloyd McKenzie fasste die Spannung treffend zusammen:

„Es muss eine Möglichkeit geben, Einschränkungen zu definieren, die rekursiv gelten, denn das ist definitiv ein benötigter Anwendungsfall. Da das aktuelle Verhalten jedoch nicht so ist, könnte eine Änderung des Standards einen Breaking Change darstellen."

Was wir dagegen unternehmen

In FHIR Schema – dem baumförmigen Validierungsformat, das wir bei Health Samurai entwickeln – tritt dieses Problem als explizite architektonische Entscheidung auf. Wenn wir StructureDefinitions in einen Schema-Baum transpilieren, wird contentReference zu einem Zeiger auf einen anderen Knoten, und wir müssen entscheiden: Die Einschränkungen des übergeordneten Elements kopieren oder nicht?

Vorerst haben wir den Aidbox-Validator am HL7-Referenzvalidator ausgerichtet – Einschränkungen werden nicht rekursiv angewendet, es sei denn, das Profil definiert sie explizit auf verschachtelten Elementen. Das entspricht der Art und Weise, wie Profile in der Praxis tatsächlich geschrieben werden.

Das Thema ist für die Diskussion beim HL7 Working Group Meeting in Rotterdam vorgemerkt. Wir erwarten, dass dort konkrete Vorschläge für einen Extension-Mechanismus und eine klarere Spezifikationssprache erarbeitet werden.

Was das für Implementierer bedeutet

Wenn Sie mit rekursiven Elementen arbeiten (Questionnaire.item, Composition.section, Consent.provision, Parameters.parameter):

  • Aktuelles Verhalten verschiedener Validatoren: Einschränkungen werden nicht rekursiv angewendet, unabhängig davon, was die R5-Spezifikation besagt
  • Wenn Sie Einschränkungen auf allen Verschachtelungsebenen benötigen: Definieren Sie diese explizit im Differential Ihres Profils für jede Ebene, oder verwenden Sie FHIRPath-Invarianten, die den Baum traversieren
  • Wenn Sie einen Validator entwickeln: Wenden Sie Einschränkungen nicht standardmäßig rekursiv an – das wird weit verbreitete Profile brechen

Dies ist eine Lücke in der Spezifikation, kein Fehler. Die Spezifikation sagt eine Sache, reale Profile benötigen eine andere, und Validatoren haben pragmatisch das Verhalten gewählt, das bestehende Implementierungen nicht bricht.

Nächster Schritt: WGM Rotterdam

Wir planen, dieses Thema beim HL7 Working Group Meeting in Rotterdam mit einem konkreten Vorschlag für den oben beschriebenen Extension-Mechanismus einzubringen. Das Ziel ist es, eine Einigung mit der FHIR Infrastructure Working Group zu erzielen und dies in eine formelle Spezifikationsänderung zu überführen.

Wenn Sie dieses Problem in Ihren Profilen oder Ihrem Validator bereits erlebt haben – wir würden Ihre Anwendungsfälle gerne vor dem Meeting hören. Beteiligen Sie sich an der Diskussion auf chat.fhir.org oder wenden Sie sich direkt an uns.

Siehe auch: So erstellen Sie einen FHIR Implementation Guide und FHIR Additional Bindings.

Diesen Artikel teilen
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

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