---
{
  "title": "FHIR braucht Datenqualitätsprüfungen",
  "description": "Ein grüner Validator sagt Ihnen nichts darüber aus, ob ein Datensatz verwendbar ist. OMOP hat dieses Problem vor einem Jahrzehnt mit dem Data Quality Dashboard gelöst. FHIR hat nun alle notwendigen Bausteine, um sein eigenes zu entwickeln – auf Basis von SQLQuery plus einiger Erweiterungen.",
  "date": "2026-07-15",
  "author": "Nikolai Ryzhikov",
  "reading-time": "16 min read",
  "tags": [
    "SQL on FHIR",
    "Data Quality",
    "Analytics"
  ],
  "tldr": "Der FHIR-Validator beantwortet die Frage: „Ist diese Ressource wohlgeformt?“ Er kann nicht beantworten: „Ist dieser Datensatz verwendbar?“ – denn diese Frage betrifft Zählwerte, Raten und Verteilungen. FHIR profiliert die Instanz, kennt jedoch kein Datensatzprofil. Die OMOP-Welt setzt seit einem Jahrzehnt auf das Data Quality Dashboard, und die gesamte Analytics-Branche testet Daten auf dieselbe Weise: Eine Prüfung ist schlicht eine Abfrage, die die fehlerhaften Zeilen zurückgibt. Ein Datensatzprofil für FHIR benötigt daher weder eine neue Ressource noch eine neue Operation – lediglich einen SQLQuery plus drei Erweiterungen mit Kategorie, Schwellenwert und Schweregrad, die jede SQL on FHIR-Engine ausführen kann.",
  "utm-campaign": "analytics",
  "utm-content": "fhir-data-quality"
}
---

## Garbage in, garbage out

Jemand möchte eine klinische Qualitätskennzahl über Ihre FHIR-Daten berechnen – etwa eine HEDIS-ähnliche Kennzahl in CQL, ein Diabetes-Kontroll-Dashboard oder eine Kohorte für eine Studie. Die Person lädt einige Millionen Ressourcen über einen Bulk FHIR-Export, wendet ihre Logik darauf an und erhält eine Zahl zurück.

Sollte sie dieser Zahl vertrauen?

Alles wurde gegen US Core validiert. Jede Referenz wurde aufgelöst. Der Validator war von oben bis unten grün. Und dennoch: Möglicherweise enthalten 40 % der Laborbeobachtungen keinen numerischen Wert. Vielleicht haben die Hälfte der Patienten überhaupt keine Encounters. Vielleicht ist ein Stapel von Körpergewichtsmessungen in Pfund angeliefert worden, obwohl das Profil Kilogramm erwartet – eine vollkommen konforme `Quantity` mit einer vollkommen falschen Zahl. Vielleicht fehlt bei jedem Patienten, der ein Diabetes-Medikament erhält, eine Diabetes-Diagnose, weil das Quellsystem diese in einer Tabelle gespeichert hatte, die niemand gemappt hat.

Nichts davon verletzt eine einzige Profilregel. Alles fließt direkt in die Kennzahl ein und verschiebt das Ergebnis unbemerkt. Garbage in, garbage out – nur ist der Müll hier unsichtbar, weil jedes Byte davon wohlgeformtes FHIR ist. Und die ehrliche Antwort auf die Frage „Wie viele dieser Daten sind falsch?“ ist heute ein Achselzucken.

Dabei setzt das noch voraus, dass die Daten überhaupt dort sind, wo Sie gesucht haben. FHIR bietet mehr als eine gültige Möglichkeit, dieselbe klinische Tatsache zu erfassen. Der Diabetes eines Patienten kann in einer `Condition` gespeichert sein, als `Observation` mit einem diagnostischen Code, oder nur implizit durch einen `MedicationRequest` für Metformin oder einen `Procedure`-Eintrag. Eine Kennzahl, die nur `Condition` abfragt und nichts anderes, ist nicht *falsch* – sie übersieht lediglich stillschweigend jeden Patienten, dessen Diabetes auf andere Weise modelliert wurde. Die Daten sind gültig und konform, nur nicht dort, wo die Logik gesucht hat, und kein Validator wird Sie darauf hinweisen.

Dies ist kein Hygieneproblem, das man später bereinigen kann. Es ist genau das, was zwischen FHIR-Daten und jedem Anwendungsfall steht, der die Erhebung dieser Daten motiviert hat – Analytics, Qualitätsmessung, Forschung, ein Modell.

## FHIR profiliert die Instanz, nicht den Datensatz

Der Instinkt ist, nach Profilen zu greifen – und Profile sind tatsächlich ein Teil der Antwort, nur nicht der Teil, den man gemeinhin annimmt. Ein Profil ist der Ort, an dem die Community sich auf eine *Darstellung* einigt: dass Diabetes in `Condition` gehört, aus diesem ValueSet kodiert, mit diesen Elementen vorhanden. Das ist genau die Mehrdeutigkeit aus der Einleitung, festgeschrieben – ohne ein Profil hat jeder Datensatz eine andere Form, und keine gemeinsame Prüfung lässt sich überhaupt formulieren. Profile sind das Fundament, auf dem dieser gesamte Ansatz aufbaut.

Aber ein Profil ist eine *Vereinbarung*, kein Audit – und FHIR-Profile sind bewusst permissiv gestaltet. Die meisten Elemente bleiben optional; Must-Support verlangt von einem System, ein Feld *bereitstellen zu können*, ohne jemals einen Wert zu fordern; Bindings sind häufig erweiterbar; Ausstiegsmechanismen wie `data-absent-reason` sind eingebaut. Diese Lockerheit ist bewusst gewählt, damit reale Daten fließen können. Sie ist auch der Grund, warum ein Profil die *Form* guter Daten beschreibt, ohne anzugeben, wie viele Ihrer Daten diese Form tatsächlich ausfüllen – ein Vertrag, keine Messung.

Und selbst die Regeln, die ein Profil *durchsetzt*, setzt seine Engine **eine Ressource nach der anderen** durch. Der Validator kennt keinen Begriff für eine zweite Ressource, geschweige denn für zehn Millionen. Genau die Fragen, die hier am wichtigsten sind, kann er also nicht stellen:

- Welcher Anteil von `Observation.value` ist null? *(eine Rate, keine Regel)*
- Wiederholt sich ein Identifier über zwei Patienten hinweg? *(Eindeutigkeit erstreckt sich über Datensätze)*
- Liegt unsere Diabetesprävalenz bei 0,1 %, obwohl sie nahe 10 % sein sollte? *(eine Verteilung)*
- Haben Patienten mit Metformin eine passende Diagnose? *(ein Join)*

Ein Validator kann per Konstruktion nicht zählen. Kein Profil wird jemals ausdrücken können, dass „Nullwerte unter 5 % akzeptabel sind“, weil ein Profil kein Konzept von *Anzahl* kennt.

![Left: an instance profile validates one Observation — status, code, value, subject all structurally valid. Right: a dataset profile, the checks that run across millions of records — null rate, unique keys, distribution, cross-resource joins.](dq-two-engines.svg "FHIR profiliert die Instanz – eine Ressource gegen eine StructureDefinition. Was es nicht benennt, ist das Datensatzprofil: die Raten, Joins und Schwellenwerte, die darüber entscheiden, ob die Daten als Ganzes verwendbar sind.")

Diese Asymmetrie ist das gesamte Argument in einem Bild. FHIR bietet Ihnen ein reichhaltiges **Instanzprofil** – eine StructureDefinition, die beschreibt, wie eine wohlgeformte Ressource aussieht – und nichts für das **Datensatzprofil**: keinen Standardweg, um festzulegen oder zu prüfen, wie eine gute *Sammlung* dieser Ressourcen aussieht. Alles Folgende dreht sich darum, diese fehlende Hälfte aufzubauen.

Man kann beobachten, wie sich dies in der Community niederschlägt. Es gibt einen [Thread mit 109 Nachrichten auf chat.fhir.org](https://chat.fhir.org/#narrow/stream/implementers/topic/Exchanging%20non-conformant%20data) – neunzehn Teilnehmende, darunter einige der erfahrensten Personen im Ökosystem – die diskutieren, was ein System mit einem `Period` tun soll, dessen Ende vor seinem Anfang liegt. Echte Daten, aus einer echten EHR-Konvertierung. Die Debatte umfasst die Fragen, ob man sie senden, verwerfen, in eine Extension verschieben oder als unzuverlässig markieren soll. Es ist eine sorgfältige, durchdachte Diskussion.

Und jedes Wort davon dreht sich um **eine fehlerhafte Period**. Niemand fragt auch nur einmal, welcher Anteil der Periods im Datensatz invertiert ist, weil es keine Möglichkeit gibt, diese Frage zu stellen.

Schlimmer noch: Die Schlussfolgerung, zu der die Community immer wieder gelangt, vergrößert die Lücke. Wenn die akzeptierte Praxis für fehlerhafte Daten darin besteht, sie aus dem berechenbaren Element in eine Extension zu verschieben oder die Ressource mit einem [Integritätssicherheits-Tag](https://terminology.hl7.org/ValueSet-v3-SecurityIntegrityObservationValue.html) zu kennzeichnen, dann kann ein vollständig konformer Datensatz **by design** mit unbrauchbaren Daten gefüllt sein. Konformität deckt das Problem nicht auf. Sie absorbiert es.

### Pflichtfeld, vorhanden und leer

Die schärfste Version davon ist die [data-absent-reason-Extension](https://hl7.org/fhir/extensions/StructureDefinition-data-absent-reason.html). Wenn Sie ein Element mit `1..1` markieren, könnte man meinen, Sie hätten einen Wert garantiert. Das haben Sie nicht. Eine minimale Kardinalität ist erfüllt, wenn das Element lediglich *vorhanden* ist – und ein Element, das nichts außer einem leeren `data-absent-reason` enthält, ist vorhanden. Der Validator zählt es, die Ressource besteht die Prüfung, und es wurde nie ein Wert geliefert.

Dies ist keine Lücke, die jemand vergessen hat zu schließen; sie ist absichtlich aus US Core übernommen worden, als Ausstiegsmechanismus für Legacy-, externe und schwärzungsbedingte Daten. Implementierer sind [in der Praxis darauf gestoßen](https://chat.fhir.org/#narrow/stream/Da.20Vinci.20CRD/topic/data-absent-reason%20on%20mandatory%20elements%20in%20CRD%20profiles) – ein Pflichtfeld, das durch einen Absent-Reason und nichts anderes erfüllt wird – und das Verständnis der Community ist, dass dies den Zweck der Pflichtmarkierung des Feldes still unterläuft. Das vorgeschlagene Gegenmittel ist eine weitere Invariante auf Instanzebene, pro IG geschrieben und durchgesetzt, die die Extension dort verbietet, wo ein echter Wert erwartet wird.

Beachten Sie, was das kostet. Um zu wissen, ob Ihre `1..1`-Felder tatsächlich Daten enthalten, können Sie dem grünen Häkchen nicht vertrauen – Sie müssen fragen, *welcher Anteil davon anstelle eines Wertes durch einen Absent-Reason ersetzt wurde.* Das ist eine Rate über den Datensatz. Ein Profil kann sie nicht berechnen. Eine Abfrage kann es.

Die Lücke zeigt sich sogar dort, wo man am ehesten eine Lösung erwarten würde. Da Vinci DEQM – das IG für den Austausch von Qualitätskennzahlen-Daten – hat einen Abschnitt mit dem Titel [Data Quality](https://hl7.org/fhir/us/davinci-deqm/guidance.html#data-quality). Sein Inhalt, vollständig wiedergegeben: Kennzahlen sollten definierte Profile wie US Core oder QI-Core verwenden, damit ausgetauschte Daten standardisiert und für die Auswertung geeignet sind. Keine Schwellenwerte. Keine Raten. Keine Aggregate. Kein einziger Mechanismus zur Beurteilung der Qualität – nur wieder Profile, dasselbe Werkzeug, das die Frage nicht beantworten kann.

Dies ist also kein Argument gegen Profile – es ist ein Argument für eine zweite Art von Profilen. Das Instanzprofil bleibt die Quelle der Wahrheit dafür, was eine *gültige Ressource* ist; ein Datensatzprofil misst, wie viel Ihrer Daten diesem Anspruch tatsächlich gerecht wird. **Eines besitzt die Instanz, das andere den Datensatz.**

## Jeder andere Data-Stack testet seine Daten bereits

Betrachten Sie etwas außerhalb des Gesundheitswesens, und dieses Problem ist nicht nur gelöst – es ist selbstverständlich. Das Testen von Daten ist eine Standardstufe in jeder ernsthaften Analytics-Pipeline, und der Mechanismus ist immer derselbe und immer so einfach: **Eine Prüfung ist eine Abfrage, die die Zeilen zurückgibt, die eine Regel verletzen. Null Zeilen – die Daten bestehen die Prüfung. Beliebige Zeilen – diese Zeilen sind das Problem.**

Dieselbe Idee wird unter verschiedenen Namen in jedem wichtigen Tool umgesetzt:

| Tool | Was eine Prüfung ist |
|---|---|
| [**dbt**](https://docs.getdbt.com/docs/build/data-tests) | ein `SELECT`, der fehlgeschlagene Zeilen zurückgibt – mit generischen Vorlagen wie `not_null`, `unique`, `accepted_values`, `relationships` |
| [**SQLMesh**](https://sqlmesh.readthedocs.io/en/stable/concepts/audits/) | ein *Audit*: eine Abfrage, die null Zeilen zurückgeben muss, sonst hält die Pipeline an |
| [**Amazon Deequ**](https://github.com/awslabs/deequ) | „Unit-Tests für Daten“ auf Spark – Vollständigkeit, Eindeutigkeit, Verteilung, über Milliarden von Zeilen |
| [**Great Expectations**](https://greatexpectations.io/) · [**Soda**](https://www.soda.io/) | Validation-as-Code: menschenlesbare *Expectations*, die in CI und in der Produktion ausgeführt werden |

Schauen Sie, was sie alle prüfen: Werte vorhanden, Schlüssel eindeutig, Zahlen in einem akzeptierten Bereich, auflösbare Referenzen, plausible Verteilungen. Dieselbe kurze Liste überall – weil Daten domänenunabhängig auf dieselbe Handvoll von Arten fehlerhaft werden. Dies ist ein ausgereifter, tragfähiger Teil des Data Engineering, keine Randpraxis.

## OMOP hat dies vor einem Jahrzehnt auf Gesundheitsdaten angewendet

Healthcare Analytics hat diesen Sprung bereits vollzogen. OHDSIs [Data Quality Dashboard](https://ohdsi.github.io/DataQualityDashboard/) wendet dasselbe Muster einer Abfrage pro Prüfung auf klinische Daten an: Richten Sie es auf eine OMOP CDM-Datenbank, es führt Tausende von Prüfungen durch und liefert einen bewerteten Bericht zurück. In dieser Welt würde niemand einen Datensatz ohne eine solche Prüfung veröffentlichen.

Was OMOP hinzufügt, ist eine Taxonomie der Wege, auf denen Gesundheitsdaten spezifisch fehlerhaft werden – das [Kahn-Framework](https://pmc.ncbi.nlm.nih.gov/articles/PMC5051581/), das jede Prüfung in drei Fragen einteilt:

| Kategorie | Die Frage | Beispiel |
|---|---|---|
| **Conformance** | Liegen die Daten in der richtigen Form vor? | `status` enthält einen Wert außerhalb des erlaubten Satzes |
| **Completeness** | Sind die Daten überhaupt vorhanden? | 40 % der Beobachtungen haben keinen Wert |
| **Plausibility** | Können die Daten geglaubt werden? | Ein Körpergewicht von 1000 kg |

Zwei Mechanismen machen es funktionsfähig. Erstens ist ein Prüftyp eine **Vorlage**, keine Abfrage – eine `not_null`-Vorlage wird über jedes Pflichtfeld jeder Tabelle expandiert, weshalb aus etwa zwei Dutzend Vorlagen Tausende von konkreten Prüfungen werden. Zweitens trägt jede Prüfung einen **Schwellenwert**: Fehlerhafte Zeilen unter 5 % bestehen, über 5 % scheitern. Das macht diese Prüfungen *fuzzy* auf eine Weise, wie es eine Invariante nie sein kann. Eine Invariante ist binär. Eine Datenqualitätsprüfung ist statistisch – und die Realität ist statistisch.

Diese Taxonomie ist auch keine OMOP-Eigenheit. Die [NCQA Bulk FHIR Quality Coalition](https://www.ncqa.org/bulk-fhir-api-quality-coalition/) bewertet Bulk FHIR-Daten mit genau diesen drei Kategorien. Die deutsche [Medizininformatik-Initiative](https://doi.org/10.3233/SHTI230117) bewertet die FHIR-Datenqualität mit Kahn. PhUSE hat [FHIR API-Daten für FDA-Einreichungen evaluiert](https://www.lexjansen.com/phuse-us/2024/ic/PAP_IC12.pdf) auf demselben Framework. Das Vokabular ist etabliert – FHIR hat es bisher nur nicht aufgegriffen.

## FHIR hat jetzt die Bausteine: SQLQuery + Extensions

![FHIR resources flatten into a table via a ViewDefinition, then a SQL query with extensions turns that table into a data quality dashboard.](dq-pipeline.svg "Die gesamte Pipeline besteht aus Standardartefakten: Eine ViewDefinition flacht FHIR ab, eine SQL-Abfrage mit Extensions wandelt das Ergebnis in ein Dashboard um.")

Lesen Sie das Diagramm von links nach rechts, und Sie haben die gesamte Idee. Eine `ViewDefinition` flacht FHIR in eine Tabelle ab. Eine SQL-Abfrage über diese Tabelle gibt die Zeilen zurück, die eine Regel verletzen – dieselbe dbt-artige Prüfung, die jeder andere Stack ausführt. Einige **Extensions** für diese Abfrage – Kahn-Kategorie, Schwellenwert, Schweregrad – verwandeln eine einfache Abfrage in eine bewertete Prüfung, die auf einem Dashboard dargestellt werden kann.

Das ist der gesamte Vorschlag: **Eine Datenqualitätsprüfung ist ein `SQLQuery` plus drei Extensions.** Keine neue Ressource, keine neue Operation, keine neue Engine – eine Prüfung ist strukturell identisch mit jeder anderen Abfrage, und die Extensions sind das Einzige, was sie zu einer Prüfung macht.

Keiner der Teile ist neu – jeder Baustein, den ein Data Quality Dashboard benötigt, existiert bereits in der Spezifikation:

| Ein DQD braucht… | FHIR hat bereits |
|---|---|
| Eine flache Tabelle zur Prüfung | **ViewDefinition** – flacht FHIR in Spalten ab |
| Eine Prüfung | **SQLQuery `Library`** – eine Abfrage über diese View |
| Eine Möglichkeit, sie auszuführen | **`$sqlquery-run`** – die bestehende Operation |
| Komposition, Zusammenfassungen | **`relatedArtifact: depends-on`** – Abhängigkeiten zwischen Abfragen |
| Schemaregeln | **Profile** – bereits die Quelle der Wahrheit |

Das ist, was sich verändert hat. Den Aufbau eines DQD war früher ein Infrastrukturprojekt – OHDSI benötigte eine eigene SQL-Engine, ein eigenes flaches Datenmodell, jahrelange Arbeit. [SQL on FHIR](/blog/aidbox-becomes-the-first-fhir-server-to-pass-all-sql-on-fhir-tests) standardisiert diese Schicht, sodass es in FHIR kein Infrastrukturproblem mehr ist. Es ist nur noch Inhalt: Schreiben Sie die Abfragen.

Und weil eine Prüfung nichts anderes als SQL über eine standardisierte flache View ist, ist sie eine *Spezifikation*, keine Implementierung. Derselbe `SQLQuery` läuft auf Postgres, DuckDB oder Spark – oder wird auf die Engines kompiliert, die jedes Analytics-Team bereits betreibt: ein dbt-Test, eine Deequ-Einschränkung, eine Great Expectations-Suite. Das ist der gesamte Grund für die Standardisierung. Nicht um eine weitere Datenqualitäts-Engine zu bauen – FHIR braucht keine – sondern um dem Ökosystem **eine portable, herstellerneutrale Möglichkeit zu geben, festzulegen, wie ein guter FHIR-Datensatz aussieht**, einmal verfasst und überall ausgeführt.

Dies ist kein Gedankenexperiment. Beim jüngsten HL7 Vulcan Connectathon haben wir es durchgeführt: eine FHIR-zu-OMOP-Transformation, die ausschließlich aus diesen Primitiven aufgebaut wurde, plus **258 DQD-Prüfungen – jede eine `Library(type=sqlquery)` mit den drei oben genannten Extensions** – nicht das Mockup aus dem vorherigen Abschnitt. Die Transformation bestand alle 172 Golden Cases und den 23-Fall-Antwortschlüssel mit **null Konformanzfehlern**. Die Prüfungen meldeten 5 Fehler in unserer eigenen Ausgabe und 20 in den Gold-Tabellen der Arbeitsgruppe – jeder davon ein Vollständigkeits- oder Plausibilitätssignal, das die AG absichtlich eingebaut hatte, zugeordnet zur jeweiligen Zeile (unsere `plausibleGender`-Prüfung fand genau die 6 Fälle mit benigner Prostatahyperplasie und 4 Prostatakrebs-Befunde bei weiblichen Patienten, die die AG eingebaut hatte).

Zwei Dinge fielen auf. Das Portieren eines Jahrzehnts angesammelter Datenqualitätsprüfungen kostete **praktisch nichts** – eine DQD-Prüfung ist eine SQL-Abfrage, die fehlerhafte Zeilen zurückgibt, und SQL on FHIR führt genau das aus. Und die Prüfungen bewährten sich sofort: `plausibleStartBeforeEnd` fand einen Besuch, der drei Tage vor seinem Beginn endete – in den *eigenen Gold-Tabellen* der Arbeitsgruppe, nicht in den 130 Quell-Encounters, nicht in den Vorhersagen von jemandem, ein Artefakt, das kein Mensch entdeckt hatte. Die [Community diskutiert eine invertierte `Period` per Hand](https://chat.fhir.org/#narrow/stream/implementers/topic/Exchanging%20non-conformant%20data); die Prüfung findet sie automatisch über den gesamten Datensatz.

## Wie es aussieht

Alles Folgende teilt eine gemeinsame Quelltabelle – eine ViewDefinition, die `Observation` abflacht:

```json
{ "resourceType": "ViewDefinition", "name": "obs_flat", "resource": "Observation",
  "select": [{ "column": [
    { "name": "id",         "path": "getResourceKey()" },
    { "name": "status",     "path": "status" },
    { "name": "loinc",      "path": "code.coding.where(system='http://loinc.org').code.first()" },
    { "name": "patient_id", "path": "subject.getReferenceKey(Patient)" },
    { "name": "value",      "path": "value.ofType(Quantity).value" },
    { "name": "unit",       "path": "value.ofType(Quantity).code" },
    { "name": "effective",  "path": "effective.ofType(dateTime)" }]}]}
```

Eine Prüfung ist ein SQLQuery über diese View. Die Extensions tragen die Semantik – diese hier sagt *Completeness, bei über 5 % fehlenden Werten warnen*:

```json
{ "resourceType": "Library", "id": "dqc-obs-value-complete",
  "type": { "coding": [{ "code": "sql-query" }] },
  "extension": [
    { "url": ".../dq-category",  "valueCode": "completeness" },
    { "url": ".../dq-threshold", "valueDecimal": 0.05 },
    { "url": ".../dq-severity",  "valueCode": "warning" }],
  "relatedArtifact": [
    { "type": "depends-on", "resource": "ViewDefinition/obs_flat", "label": "obs" }],
  "content": [{ "contentType": "application/sql", "data": "<base64>" }]}
```

Das SQL darin ist bewusst unspektakulär – und das ist das Feature:

```sql
-- completeness: rows where the measurement is missing
SELECT id FROM obs WHERE value IS NULL
```

**Referentielle Integrität** fügt lediglich eine zweite View und eine zweite Abhängigkeit hinzu, `patient_flat` mit dem Label `pat`:

```sql
SELECT o.id, o.patient_id
FROM obs o LEFT JOIN pat ON o.patient_id = pat.id
WHERE o.patient_id IS NOT NULL AND pat.id IS NULL
```

**Plausibility** ist der Bereich, wo sich das Konzept besonders bewährt – kein Profil kann irgendetwas davon ausdrücken. Das DQD von OMOP hat eine ganze Familie von Plausibilitätsprüfungen, und sie lassen sich direkt auf LOINC-kodierte `Observation`s übertragen. Drei der nützlichsten.

*Wert außerhalb des physiologischen Bereichs für seinen Code* – DQDs `plausibleValueLow` / `plausibleValueHigh`. Die Grenzen befinden sich in einer kleinen Referenztabelle, eine Zeile pro LOINC-Code, was genau dem Vorlagenmuster von früher entspricht: eine Prüfung, Tausende von konkreten Grenzen.

```sql
-- 29463-7 body weight (kg)  0–650   |  8480-6 systolic BP (mm[Hg])  0–300
-- 8867-4  heart rate (/min) 0–300   |  4548-4 HbA1c (%)             0–20
SELECT o.id, o.loinc, o.value, o.unit
FROM obs o JOIN obs_range r ON o.loinc = r.loinc
WHERE o.value < r.low OR o.value > r.high
```

*Falsche Einheit für die Messung* – DQDs `plausibleUnitConceptIds`. Ein Körpergewicht, das in einer anderen als einer Masseeinheit aufgezeichnet ist, ist verdächtig, egal wie plausibel die Zahl aussieht:

```sql
SELECT id, value, unit FROM obs
WHERE loinc = '29463-7' AND unit NOT IN ('kg', 'g', '[lb_av]')
```

*Ein Befund, der dem Geschlecht des Patienten widerspricht* – DQDs `plausibleGender`. Ein Prostata-spezifisches Antigen-Ergebnis bei einer weiblichen Patientin (`patient_flat` enthält `gender`):

```sql
SELECT o.id, o.patient_id
FROM obs o JOIN pat ON o.patient_id = pat.id
WHERE o.loinc = '2857-1' AND pat.gender = 'female'
```

Ressourcenübergreifende Regeln gehören ebenfalls hierher. „Ein Patient mit Diabetes-Medikation sollte eine Diabetes-Diagnose haben“ ist ein Join – in SQL routinemäßig, in FHIRPath umständlich bis unmöglich.

**Profilierungsmetriken** sind überhaupt kein Pass/Fail, sondern einfach die Zahlen, die ein Dashboard benötigt:

```sql
SELECT count(*)                              AS "rowCount",
       count(*) FILTER (WHERE value IS NULL) AS "nullCount_value",
       count(DISTINCT patient_id)            AS "distinctCount_patient",
       min(value) AS "min_value", max(value) AS "max_value"
FROM obs
```

Und Zusammenfassungen werden durch denselben Abhängigkeitsmechanismus komponiert, der auf andere Prüfungen statt auf Views zeigt:

```sql
SELECT category, count(*) AS checks, sum(failed) AS failed
FROM ( SELECT 'conformance'  category, (SELECT count(*) FROM c1) > 0 failed
       UNION ALL SELECT 'conformance',  (SELECT count(*) FROM c2) > 0
       UNION ALL SELECT 'completeness', (SELECT count(*) FROM c3) > 0 ) t
GROUP BY category
```

Der Nutzen entsteht dort, wo FHIR bereits seine Arbeit verrichtet: im Implementation Guide. Ein IG-Autor liefert heute ein **Instanzprofil** – die Vereinbarung darüber, was wohin gehört und wie es kodiert ist. Mit diesem Ansatz trägt derselbe IG seine andere Hälfte, ein **Datensatzprofil**, im selben Bundle:

- die **ViewDefinitions**, die mit diesen Profilen konforme Daten in Tabellen abflachen, und
- die **Qualitätsprüfungen** – SQLQuery-Prüfungen über diese Tabellen, jede mit ihrer Kahn-Kategorie und ihrem Schwellenwert versehen.

Nun sagt ein IG mehr als nur *„Hier ist die Form, die Ihre Daten haben sollten.“* Er sagt: *„Hier ist die Form, hier ist die Abfragemöglichkeit, und hier ist, wie Sie feststellen können, ob Ihre Daten ihr gerecht werden.“* Ein Nutzer richtet das Paket auf einen Bulk-Export und erhält ein Datenqualitäts-Dashboard zurück – *dieser Datensatz besteht 94 von 100 Prüfungen aus diesem IG* – ohne eine Zeile benutzerdefiniertem Validierungscode zu schreiben. Profile, Views und Prüfungen reisen gemeinsam, verfasst von den Personen, die die Domäne verstehen.

## Wohin das führt

Fügt man die Teile zusammen, ergibt sich ein klares Bild. Heute liefert ein Implementation Guide ein **Instanzprofil** und zunehmend auch **ViewDefinitions**. Mit diesem Ansatz liefert er die fehlende Hälfte – ein **Datensatzprofil**: einen kuratierten Satz von Datenqualitätsprüfungen, der beschreibt, wie ein guter Datensatz *für dieses IG* tatsächlich aussieht. Veröffentlichen Sie beides zusammen, und jede konforme SQL on FHIR-Engine führt die Prüfungen direkt aus. Der Autor schreibt sie einmal; jeder Server bewertet Daten gegen sie auf dieselbe Weise – ohne benutzerdefiniertes Tooling, ohne herstellerspezifische Einrichtung.

Eine ehrliche Einschränkung: Diese Prüfungen können nicht automatisch aus den Invarianten eines Profils abgeleitet werden, weil die FHIRPath-Teilmenge von ViewDefinition kleiner ist als das, was diese Invarianten verwenden. Der Basiskatalog wird von Hand geschrieben – eine einmalige Arbeit, die die Community teilt.

Und das ist die Einladung. Dies ist kein hypothetisches Konzept – es ist aktive Arbeit in der [SQL on FHIR-Arbeitsgruppe](https://github.com/HL7/sql-on-fhir), mit den Extension-Definitionen und einem Starterset von Prüfungen in [Issue #375](https://github.com/HL7/sql-on-fhir/issues/375), vorangetrieben in den Calls der Gruppe. Die Taxonomie ist etabliert und die Maschinerie existiert; was noch aussteht, ist der Aufbau des Katalogs, in aller Offenheit. Wenn Sie Datenqualitäts-Tooling über FHIR aufgebaut haben – oder sich jemals gewünscht haben, FHIR hätte es – kommen Sie und helfen Sie bei der Gestaltung: Bringen Sie Ihre Prüfungen in den Thread und nehmen Sie an einem Call teil.