|
8 Min. Lesezeit
|

Über Patient/$merge hinaus: Ein ressourcenagnostisches, clientgesteuertes Merge fĂŒr FHIR

Zusammenfassung

Wir haben FHIR $merge erweitert, sodass es ein clientseitig bereitgestelltes Transaction-Bundle als plan-Parameter akzeptiert. Der Client entscheidet, was aktualisiert, erstellt oder gelöscht wird – der Server gewĂ€hrleistet AtomaritĂ€t und erstellt Task- und Provenance-Audit-Ressourcen innerhalb derselben Transaktion. In Kombination mit einer neuen $referencing-Operation fĂŒr generisches Reverse-Include ergibt sich ein vollstĂ€ndig prĂŒfbares, ressourcenagnostisches Merge, das fĂŒr Patient, Organization, Practitioner oder beliebige andere Ressourcentypen funktioniert.

Diesen Artikel zusammenfassen mit:
ChatGPTPerplexityClaudeGrok

Wir betreiben Master Data Management in MDMbox seit zwei Jahren in Produktivumgebungen – wir deduplizieren Patienten, fĂŒhren Organisationen zusammen und gleichen Leistungserbringer ĂŒber Einrichtungen hinweg ab. Dabei haben wir gelernt, dass jede Organisation anders zusammenfĂŒhrt und kein einzelner serverseitiger Algorithmus die Vielfalt realer Merge-Richtlinien abdecken kann.

FHIR R5 hat Patient/$merge eingefĂŒhrt – eine lang erwartete Operation, mit der Sie doppelte PatientendatensĂ€tze zusammenfĂŒhren können. Sie definiert Eingabeparameter (source-patient, target-patient, result-patient), erwartet, dass der Server die Referenzaktualisierungen ĂŒbernimmt, und gibt das zusammengefĂŒhrte Ergebnis zurĂŒck. Die Operation befindet sich derzeit auf Reifegrad 0 und wurde seit ihrer EinfĂŒhrung nicht weiterentwickelt.

Das ist ein guter Anfang. Nach der Implementierung von Merge in der Produktion haben wir jedoch festgestellt, dass die Spezifikation nicht weit genug geht.

Das Problem mit servergesteuertem Merge

Die FHIR-$merge-Spezifikation setzt voraus, dass der Server weiß, wie zusammenzufĂŒhren ist. Der Server deaktiviert die Quelle, kopiert Identifikatoren und aktualisiert Referenzen. Die Merge-Logik variiert jedoch erheblich zwischen Organisationen:

  • Krankenhaus A löscht den Quellpatienten vollstĂ€ndig und schreibt alle Referenzen neu
  • Krankenhaus B behĂ€lt die Quelle inaktiv und erstellt eine Linkage-Ressource fĂŒr die Provenienz
  • Krankenhaus C fĂŒhrt Encounters und Observations in das Ziel zusammen, bewahrt aber separate AllergyIntolerance-DatensĂ€tze zur manuellen ÜberprĂŒfung
  • Gesundheitsinformationsverbund D muss Organizations und Practitioners zusammenfĂŒhren, nicht nur Patients

Die Spezifikation deckt nichts davon ab – sie beschrĂ€nkt sich auf Patient, und der Schritt „alle Referenzen aktualisieren" wird nur vage beschrieben.

Merge flow

Was wir benötigten, war eine Merge-Operation, bei der:

  • Der Client entscheidet, was geschieht (welche Ressourcen aktualisiert, erstellt oder gelöscht werden)
  • Der Server AtomaritĂ€t gewĂ€hrleistet, einen Audit-Trail erstellt und SicherheitsprĂŒfungen durchsetzt
  • Sie fĂŒr beliebige Ressourcentypen funktioniert, nicht nur fĂŒr Patient

Unser Ansatz: der plan-Parameter

Wir haben ein FHIR-konformes $merge entwickelt, das ein Standard-Transaction-Bundle als zusÀtzlichen plan-Parameter akzeptiert:

POST $merge

{
  "resourceType": "Parameters",
  "parameter": [
    {"name": "source", "valueReference": {"reference": "Patient/123"}},
    {"name": "target", "valueReference": {"reference": "Patient/456"}},
    {"name": "preview", "valueBoolean": false},
    {
      "name": "plan",
      "resource": {
        "resourceType": "Bundle",
        "type": "transaction",
        "entry": [
          {
            "resource": {"resourceType": "Patient", "id": "456", "...": "merged state"},
            "request": {"method": "PUT", "url": "Patient/456", "ifMatch": "W/\"3\""}
          },
          {
            "resource": {"resourceType": "Encounter", "id": "789",
                         "subject": {"reference": "Patient/456"}},
            "request": {"method": "PUT", "url": "Encounter/789", "ifMatch": "W/\"1\""}
          },
          {
            "request": {"method": "DELETE", "url": "Patient/123", "ifMatch": "W/\"2\""}
          }
        ]
      }
    }
  ]
}

Der Client sendet jeden PUT, POST und DELETE explizit. Der Server ergĂ€nzt den Plan um Audit-Ressourcen (Task + Provenance) und fĂŒhrt alles als einzelne FHIR-Transaktion aus. Entweder gelingt alles – einschließlich des Audit-Trails – oder nichts.

Beachten Sie die ifMatch-Header bei jedem Eintrag – dies ist optimistisches Locking. Wenn eine Ressource zwischen dem Zeitpunkt, zu dem der Client den Plan erstellt hat, und dem Zeitpunkt der AusfĂŒhrung geĂ€ndert wurde, schlĂ€gt die Transaktion fehl. Keine stillen Überschreibungen, keine verlorenen Aktualisierungen.

Eine Obermenge, keine Abspaltung

Sie werden bemerken, dass wir source, target und result anstelle der spezifikationsgemĂ€ĂŸen Bezeichnungen source-patient, target-patient und result-patient verwenden. Das ist beabsichtigt – die Operation ist ressourcenagnostisch, daher wĂ€re eine patientenspezifische Benennung irrefĂŒhrend. Doppelte Organizations zusammenfĂŒhren? Practitioners? Dieselbe Operation, derselbe Audit-Trail.

Lassen Sie den plan-Parameter weg und ĂŒbergeben Sie stattdessen result – Sie erhalten das Standardverhalten von FHIR $merge. Die identifikatorbasierte Suche nach Quelle und Ziel wird unterstĂŒtzt. Der plan ist das, was die Operation streng leistungsfĂ€higer macht: Wenn er vorhanden ist, ĂŒbernimmt der Client die vollstĂ€ndige Kontrolle ĂŒber die Merge-Logik.

Das fehlende Element: $referencing

Um einen guten Merge-Plan zu erstellen, muss der Client eine Frage beantworten: „Welche Ressourcen referenzieren Patient/123?"

FHIR bietet _revinclude fĂŒr Suchergebnisse und $everything fĂŒr Patient, aber keine generische Operation, die besagt: „Gib mir jede Ressource in der Datenbank, die auf diese Referenz verweist." Wir haben eine solche entwickelt.

Before and after merge

Intern handelt es sich um eine PostgreSQL-Abfrage, die JSONPath verwendet, um ĂŒber Ressourcentabellen zu suchen:

SELECT id, resource_type, resource
FROM <resource_table>
WHERE jsonb_path_query_array(resource, '$.** ? (@.reference == "Patient/123")')
      != '[]'::jsonb

Die obige Abfrage ist zur Verdeutlichung vereinfacht, aber der Kerngedanke bleibt: $.** durchsucht rekursiv die gesamte Ressourcenstruktur und findet jedes verschachtelte Objekt, bei dem reference dem Ziel entspricht – unabhĂ€ngig davon, wo es im Ressourcenbaum vorkommt. Keine fest kodierten Pfade, keine ressourcentypspezifische Konfiguration. Eine einzige Abfrage findet Referenzen in Encounter.subject, Observation.performer, Claim.provider oder jedem anderen Referenzfeld.

Zur Performance: Diese Abfrage verwendet einen GIN-Index auf der JSONB-Spalte, sodass die rekursive Traversierung gegen den Index erfolgt – nicht als vollstĂ€ndiger Tabellen-Scan. Bei einem typischen Merge schließt die Operation in Millisekunden ab. Bei Ressourcentypen mit Millionen von Zeilen parallelisieren wir die Suche ĂŒber Tabellen hinweg und streamen die Ergebnisse an den Client zurĂŒck.

Wir haben dies als eigenstĂ€ndige $referencing-Operation in MDMbox bereitgestellt – ein Baustein, den jeder Merge- (oder Impact-Analysis-)Workflow nutzen kann.

Task + Provenance: mehr als nur Audit

Jeder Merge erstellt einen Task und eine Provenance innerhalb derselben Transaktion. Das geht ĂŒber bloße Compliance hinaus:

Audit trail

Subscriptions. Externe Systeme können die Erstellung von Merge-Tasks abonnieren und sofort reagieren – ihre Caches aktualisieren, nachgelagerte Workflows auslösen, Benutzer benachrichtigen. Standard-FHIR-Subscriptions, ohne benutzerdefinierte Infrastruktur.

Versionierte Snapshots. Die Provenance erfasst fĂŒr jede durch den Merge geĂ€nderte oder gelöschte Ressource eine versionierte Referenz (z. B. Patient/456/_history/3). In Kombination mit der FHIR History API ist der vollstĂ€ndige Zustand vor dem Merge jederzeit wiederherstellbar.

Lifecycle-Tracking. Der Task protokolliert den Merge-Status (requested → in-progress → completed oder failed), wer ihn initiiert hat, wann er stattfand, und verknĂŒpft ihn mit Quelle und Ziel. Die Abfrage „Zeig mir alle Merges, die diesen Patienten betroffen haben" ist eine einzelne FHIR-Suche auf Task.

Ausblick: $unmerge

FHIR definiert noch keine Unmerge-Operation. Betrachten Sie folgendes Szenario: Ein Merge wird am Montag ausgefĂŒhrt, und am Mittwoch stellt jemand fest, dass es sich um zwei tatsĂ€chlich verschiedene Patienten handelte. Zu diesem Zeitpunkt wurden 50 Ressourcen geĂ€ndert – Encounters wurden umgezeigt, Observations neu zugewiesen, der Quellpatient gelöscht.

Da Task den Merge-Lifecycle verfolgt, Provenance jede betroffene Ressource mit ihrer Version vor dem Merge erfasst und die History API alle vorherigen ZustĂ€nde bewahrt – haben wir alles, was nötig ist, um einen Merge zuverlĂ€ssig rĂŒckgĂ€ngig zu machen. Die $unmerge-Operation liest die Provenance, ruft die Versionen vor dem Merge aus der History ab und erstellt ein umgekehrtes Transaction-Bundle, das jede Ressource in ihren vorherigen Zustand zurĂŒckversetzt.

Wir werden $unmerge im nĂ€chsten Beitrag behandeln – jetzt veröffentlicht: Designing $unmerge: Reversing the FHIR Patient Merge. Der zweite Beitrag der Serie, MPI Vendors Couldn't Agree. We Solved Patient Merge, erlĂ€utert, warum ein einzelnes servergesteuertes $merge nicht alle Produktionsrichtlinien abdecken kann. Hintergrundinformationen zu den zugrundeliegenden Master Patient Index-Grundlagen finden Sie unter Master Patient Index (MPI): How It Works + Examples.


Möchten Sie clientgesteuertes Merge in Ihrem Projekt ausprobieren? MDMbox ist heute verfĂŒgbar.

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

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