diff --git a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractComplexFhirClient.java b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractComplexFhirClient.java index ed098083..9d80b473 100644 --- a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractComplexFhirClient.java +++ b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractComplexFhirClient.java @@ -185,18 +185,18 @@ protected Optional findPatientInLocalFhirStore(String pseudonym) { logger.warn("Error while searching for Patient with pseudonym {}|{}, message: {}, status: {}", NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM, pseudonym, e.getMessage(), e.getStatusCode()); - + IBaseOperationOutcome outcome = e.getOperationOutcome(); if (outcome != null && outcome instanceof OperationOutcome) outcomeLogger.logOutcome((OperationOutcome) outcome); - + throw e; } catch (Exception e) { - logger.warn("Error while searching for Patient with pseudonym " + NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM + "|" - + pseudonym, e); + logger.warn("Error while searching for Patient with pseudonym " + NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM + + "|" + pseudonym, e); throw e; } } @@ -227,10 +227,10 @@ public Stream getNewData(String pseudonym, DateWithPrecision exp logger.debug("Search-Bundle result: {}", geccoClient.getFhirContext().newJsonParser().encodeResourceToString(resultBundle)); - return Stream.concat(Stream.of(localPatient.get()), + return distinctById(Stream.concat(Stream.of(localPatient.get()), resultBundle.getEntry().stream().filter(BundleEntryComponent::hasResource) .map(BundleEntryComponent::getResource).filter(r -> r instanceof Bundle).map(r -> (Bundle) r) - .flatMap(this::getDomainResources)); + .flatMap(this::getDomainResources))); } private Optional findPatientInLocalFhirStore(String system, String pseudonym) diff --git a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractFhirClient.java b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractFhirClient.java index e0a4fa6c..bcba607f 100644 --- a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractFhirClient.java +++ b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractFhirClient.java @@ -59,6 +59,54 @@ public abstract class AbstractFhirClient implements GeccoFhirClient private static final Logger logger = LoggerFactory.getLogger(AbstractFhirClient.class); private static final OutcomeLogger outcomeLogger = new OutcomeLogger(logger); + private static final class DomainResourceUniqueByUnqualifiedVersionlessId + { + private final DomainResource resource; + private final String unqualifiedVersionlessIdValue; + + public DomainResourceUniqueByUnqualifiedVersionlessId(DomainResource resource) + { + this.resource = resource; + + unqualifiedVersionlessIdValue = resource.getIdElement().toUnqualifiedVersionless().getValue(); + } + + public DomainResource getResource() + { + return resource; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + + ((unqualifiedVersionlessIdValue == null) ? 0 : unqualifiedVersionlessIdValue.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DomainResourceUniqueByUnqualifiedVersionlessId other = (DomainResourceUniqueByUnqualifiedVersionlessId) obj; + if (unqualifiedVersionlessIdValue == null) + { + if (other.unqualifiedVersionlessIdValue != null) + return false; + } + else if (!unqualifiedVersionlessIdValue.equals(other.unqualifiedVersionlessIdValue)) + return false; + return true; + } + } + private static final List RESOURCES_WITH_PATIENT_REF = Arrays.asList("AllergyIntolerance", "CarePlan", "CareTeam", "ClinicalImpression", "Composition", "Condition", "Consent", "DetectedIssue", "DeviceRequest", "DeviceUseStatement", "DiagnosticReport", "DocumentManifest", "DocumentReference", "Encounter", @@ -624,4 +672,10 @@ public void updatePatient(Patient patient) throw e; } } + + protected Stream distinctById(Stream resources) + { + return resources.map(DomainResourceUniqueByUnqualifiedVersionlessId::new).distinct() + .map(DomainResourceUniqueByUnqualifiedVersionlessId::getResource); + } } diff --git a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/FhirBridgeClient.java b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/FhirBridgeClient.java index 469e4a29..75e38362 100644 --- a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/FhirBridgeClient.java +++ b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/FhirBridgeClient.java @@ -2,6 +2,9 @@ import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Predicate; @@ -9,8 +12,12 @@ import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.HTTPVerb; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,6 +35,8 @@ public class FhirBridgeClient extends AbstractComplexFhirClient private static final Logger logger = LoggerFactory.getLogger(FhirBridgeClient.class); private static final OutcomeLogger outcomeLogger = new OutcomeLogger(logger); + private static final String NUM_CODEX_BLOOD_GAS_PANEL = "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/blood-gas-panel"; + /** * @param geccoClient * not null @@ -43,14 +52,16 @@ public void storeBundle(Bundle bundle) // either bundle has a patient, or patient should already exists Patient patient = createOrUpdatePatient(bundle).orElseGet(() -> getExistingPatientOrThrow(bundle)); + Map resourceIdsByUuid = new HashMap<>(); for (int i = 0; i < bundle.getEntry().size(); i++) { BundleEntryComponent entry = bundle.getEntry().get(i); if (isEntrySupported(entry, e -> !(e.getResource() instanceof Patient))) - createOrUpdateEntry(i, entry, patient); + createOrUpdateEntry(i, entry, patient, resourceIdsByUuid); + + // only log for non Patients else if (!entry.hasResource() || !(entry.getResource() instanceof Patient)) - // only log for non Patients logger.warn("Bundle entry at index {} not supported, ignoring entry", i); } } @@ -220,14 +231,18 @@ private Optional create(Patient newPatient, String pseudonym, String bu } } - private void createOrUpdateEntry(int index, BundleEntryComponent entry, Patient patient) + private void createOrUpdateEntry(int index, BundleEntryComponent entry, Patient patient, + Map resourceIdsByUuid) { Resource resource = entry.getResource(); String url = entry.getRequest().getUrl(); Optional existingResource = findResourceInLocalFhirStore(url, resource.getClass()); - existingResource.ifPresentOrElse(existing -> update(existing, resource, entry.getFullUrl()), - () -> create(resource, entry.getFullUrl())); + IdType resourceId = existingResource.map( + existing -> update(existing, fixTemporaryReferences(resource, resourceIdsByUuid), entry.getFullUrl())) + .orElseGet(() -> create(fixTemporaryReferences(resource, resourceIdsByUuid), entry.getFullUrl())); + + resourceIdsByUuid.put(entry.getFullUrl(), resourceId); } private Optional findResourceInLocalFhirStore(String url, Class resourceType) @@ -302,7 +317,43 @@ private Optional findResourceInLocalFhirStore(String url, Class resourceIdsByUuid) + { + if (resource == null) + return null; + + else if (resource instanceof Observation) + { + if (resource.getMeta().getProfile().stream().map(CanonicalType::getValue) + .anyMatch(url -> NUM_CODEX_BLOOD_GAS_PANEL.equals(url) + || (url != null && url.startsWith(NUM_CODEX_BLOOD_GAS_PANEL + "|")))) + { + Observation observation = (Observation) resource; + List members = observation.getHasMember(); + for (int i = 0; i < members.size(); i++) + { + Reference member = members.get(i); + if (member.hasReference()) + { + String uuid = member.getReference(); + IdType resourceId = resourceIdsByUuid.get(uuid); + + if (resourceId != null) + { + logger.debug( + "Replacing reference at Observation.hasMember[{}] from bundle resource {} with existing resource id", + i, resource.getIdElement().getValue()); + member.setReferenceElement(resourceId); + } + } + } + } + } + + return resource; + } + + private IdType update(Resource existingResource, Resource newResource, String bundleFullUrl) { logger.debug("Updating {}", newResource.getResourceType().name()); @@ -315,7 +366,7 @@ private void update(Resource existingResource, Resource newResource, String bund if (outcome.getId() == null) { - logger.warn("Could not update {} {}", newResource.getResourceType().name(), + logger.warn("Could not update {} {}: unknown reason", newResource.getResourceType().name(), newResource.getIdElement().toString()); if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome() instanceof OperationOutcome) outcomeLogger.logOutcome((OperationOutcome) outcome.getOperationOutcome()); @@ -324,7 +375,15 @@ private void update(Resource existingResource, Resource newResource, String bund + newResource.getIdElement().toString()); } else if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome() instanceof OperationOutcome) + { outcomeLogger.logOutcome((OperationOutcome) outcome.getOperationOutcome()); + logger.warn("Could not update {} {}: unknown reason", newResource.getResourceType().name(), + newResource.getIdElement().toString()); + throw new RuntimeException("Could not create " + newResource.getResourceType().name() + " " + + newResource.getIdElement().toString() + ": unknown reason"); + } + else + return (IdType) outcome.getId(); } catch (UnprocessableEntityException e) { @@ -367,7 +426,7 @@ else if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome() } } - private void create(Resource newResource, String bundleFullUrl) + private IdType create(Resource newResource, String bundleFullUrl) { logger.debug("Creating {}", newResource.getResourceType().name()); @@ -387,7 +446,15 @@ private void create(Resource newResource, String bundleFullUrl) + newResource.getIdElement().toString()); } else if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome() instanceof OperationOutcome) + { outcomeLogger.logOutcome((OperationOutcome) outcome.getOperationOutcome()); + logger.warn("Could not create {} {}: unknown reason", newResource.getResourceType().name(), + newResource.getIdElement().toString()); + throw new RuntimeException("Could not create " + newResource.getResourceType().name() + " " + + newResource.getIdElement().toString() + ": unknown reason"); + } + else + return (IdType) outcome.getId(); } catch (UnprocessableEntityException e) { diff --git a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/SimpleFhirClient.java b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/SimpleFhirClient.java index 09cc71be..6670d4d9 100644 --- a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/SimpleFhirClient.java +++ b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/SimpleFhirClient.java @@ -49,8 +49,8 @@ public Stream getNewData(String pseudonym, DateWithPrecision exp logger.debug("Search-Bundle result: {}", geccoClient.getFhirContext().newJsonParser().encodeResourceToString(resultBundle)); - return resultBundle.getEntry().stream().filter(BundleEntryComponent::hasResource) + return distinctById(resultBundle.getEntry().stream().filter(BundleEntryComponent::hasResource) .map(BundleEntryComponent::getResource).filter(r -> r instanceof Bundle).map(r -> (Bundle) r) - .flatMap(this::getDomainResources); + .flatMap(this::getDomainResources)); } } diff --git a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/ReadData.java b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/ReadData.java index ab76a1d0..32c9b607 100644 --- a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/ReadData.java +++ b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/ReadData.java @@ -10,11 +10,15 @@ import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.IDENTIFIER_NUM_CODEX_DIC_PSEUDONYM_TYPE_SYSTEM; import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -33,20 +37,33 @@ import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Condition; +import org.hl7.fhir.r4.model.Condition.ConditionEvidenceComponent; +import org.hl7.fhir.r4.model.Condition.ConditionStageComponent; import org.hl7.fhir.r4.model.Consent; +import org.hl7.fhir.r4.model.Consent.ConsentVerificationComponent; +import org.hl7.fhir.r4.model.Consent.provisionActorComponent; import org.hl7.fhir.r4.model.Consent.provisionComponent; +import org.hl7.fhir.r4.model.Consent.provisionDataComponent; import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.DiagnosticReport; +import org.hl7.fhir.r4.model.DiagnosticReport.DiagnosticReportMediaComponent; import org.hl7.fhir.r4.model.DomainResource; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Immunization; +import org.hl7.fhir.r4.model.Immunization.ImmunizationPerformerComponent; +import org.hl7.fhir.r4.model.Immunization.ImmunizationProtocolAppliedComponent; +import org.hl7.fhir.r4.model.Immunization.ImmunizationReactionComponent; import org.hl7.fhir.r4.model.InstantType; import org.hl7.fhir.r4.model.MedicationStatement; import org.hl7.fhir.r4.model.Meta; import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Patient.ContactComponent; +import org.hl7.fhir.r4.model.Patient.PatientLinkComponent; import org.hl7.fhir.r4.model.Procedure; +import org.hl7.fhir.r4.model.Procedure.ProcedureFocalDeviceComponent; +import org.hl7.fhir.r4.model.Procedure.ProcedurePerformerComponent; import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.ResourceType; @@ -66,6 +83,7 @@ public class ReadData extends AbstractServiceDelegate private static final String NUM_CODEX_STRUCTURE_DEFINITION_PREFIX = "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition"; private static final String MII_LAB_STRUCTURED_DEFINITION = "https://www.medizininformatik-initiative.de/fhir/core/modul-labor/StructureDefinition/ObservationLab"; private static final String NUM_CODEX_DO_NOT_RESUSCITAT_ORDER = "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/do-not-resuscitate-order"; + private static final String NUM_CODEX_BLOOD_GAS_PANEL = "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/blood-gas-panel"; private static final Logger logger = LoggerFactory.getLogger(ReadData.class); @@ -151,19 +169,28 @@ private Stream getInputParameterValues(Task task, String sys .map(c -> type.cast(c.getValue())); } - protected Bundle toBundle(String pseudonym, Stream resources) + protected Bundle toBundle(String pseudonym, Stream resourcesStream) { Bundle b = new Bundle(); b.setType(BundleType.TRANSACTION); - List entries = resources.map(r -> + List resources = resourcesStream.collect(Collectors.toList()); + + Map resourcesById = resources.stream().collect( + Collectors.toMap(r -> r.getIdElement().toUnqualifiedVersionless().getValue(), Function.identity())); + Map uuidsById = resources.stream().collect( + Collectors.toMap(r -> r.getIdElement().toUnqualifiedVersionless().getValue(), r -> UUID.randomUUID())); + + List resourcesWithTemporaryReferences = fixReferences(resources, resourcesById, uuidsById); + List entries = resourcesWithTemporaryReferences.stream().map(r -> { BundleEntryComponent entry = b.addEntry(); // storing original resource reference for validation error tracking entry.setUserData(HAPI_USER_DATA_SOURCE_ID_ELEMENT, getAbsoluteId(r)); - entry.setFullUrl("urn:uuid:" + UUID.randomUUID()); + entry.setFullUrl( + "urn:uuid:" + uuidsById.get(r.getIdElement().toUnqualifiedVersionless().getValue()).toString()); entry.getRequest().setMethod(HTTPVerb.PUT).setUrl(getConditionalUpdateUrl(pseudonym, r)); entry.setResource(setSubjectOrIdentifier(clean(r), pseudonym)); @@ -174,6 +201,45 @@ protected Bundle toBundle(String pseudonym, Stream resources) return b; } + private List fixReferences(List resources, + Map resourcesById, Map uuidsById) + { + return resources.stream().map(r -> fixReferences(r, resourcesById, uuidsById)).collect(Collectors.toList()); + } + + private DomainResource fixReferences(DomainResource resource, Map resourcesById, + Map uuidsById) + { + if (resource instanceof Observation && resource.getMeta().getProfile().stream().map(CanonicalType::getValue) + .anyMatch(url -> NUM_CODEX_BLOOD_GAS_PANEL.equals(url) + || (url != null && url.startsWith(NUM_CODEX_BLOOD_GAS_PANEL + "|")))) + { + Observation observation = (Observation) resource; + List oldReferences = observation.getHasMember(); + List fixedReferences = new ArrayList<>(); + + for (int i = 0; i < oldReferences.size(); i++) + { + if (uuidsById.containsKey(oldReferences.get(i).getReference())) + { + logger.debug( + "Replacing reference at Observation.hasMember[{}] from resource {} with bundle temporary id", + i, resource.getIdElement().getValue()); + fixedReferences.add(oldReferences.get(i).copy() + .setReference("urn:uuid:" + uuidsById.get(oldReferences.get(i).getReference()).toString())); + } + else + logger.warn("Removing reference at Observation.hasMember[{}] from resource {}", i, + resource.getIdElement().getValue()); + } + + observation.setHasMember(fixedReferences); + return observation; + } + + return resource; + } + private IdType getAbsoluteId(DomainResource r) { if (r == null) @@ -198,122 +264,261 @@ private DomainResource clean(DomainResource r) return r; } + private void cleanUnsupportedReference(R resource, String path, + Function hasReference, BiFunction setReference) + { + if (hasReference.apply(resource)) + { + logger.warn("Removing reference at {} from resource {}", path, resource.getIdElement().getValue()); + setReference.apply(resource, (Reference) null); + } + } + + private void cleanUnsupportedReferences(R resource, String path, + Function hasReferences, BiFunction, R> setReferences) + { + if (hasReferences.apply(resource)) + { + logger.warn("Removing reference at {} from resource {}", path, resource.getIdElement().getValue()); + setReferences.apply(resource, (List) null); + } + } + + private void cleanUnsupportedReferenceFromComponents(R resource, String path, + Function hasComponents, Function> getComponents, Function hasReference, + BiFunction setReference) + { + if (hasComponents.apply(resource)) + { + List components = getComponents.apply(resource); + for (int i = 0; i < components.size(); i++) + { + C component = components.get(i); + if (hasReference.apply(component)) + { + logger.warn("Removing reference at " + path + " from resource {}", i, + resource.getIdElement().getValue()); + setReference.apply(component, null); + } + } + } + } + + private void cleanUnsupportedReferencesFromComponents(R resource, String path, + Function hasComponents, Function> getComponents, Function hasReferences, + BiFunction, C> setReferences) + { + if (hasComponents.apply(resource)) + { + List components = getComponents.apply(resource); + for (int i = 0; i < components.size(); i++) + { + C component = components.get(i); + if (hasReferences.apply(component)) + { + logger.warn("Removing references at " + path + " from resource {}", i, + resource.getIdElement().getValue()); + setReferences.apply(component, null); + } + } + } + } + + private void cleanUnsupportedReferencesFromComponentComponents(R resource, + String path, Function hasComponent1, Function getComponent1, + Function hasComponents2, Function> getComponents2, + Function hasReference, BiFunction setReference) + { + if (hasComponent1.apply(resource)) + { + C1 component1 = getComponent1.apply(resource); + if (hasComponents2.apply(component1)) + { + List components2 = getComponents2.apply(component1); + for (int i = 0; i < components2.size(); i++) + { + C2 component2 = components2.get(i); + if (hasReference.apply(component2)) + { + logger.warn("Removing reference at " + path + " from resource {}", i, + resource.getIdElement().getValue()); + setReference.apply(component2, null); + } + } + } + } + } + private void cleanUnsupportedReferences(DomainResource resource) { if (resource == null) { return; } + else if (resource instanceof Patient) + { + Patient patient = (Patient) resource; + cleanUnsupportedReferenceFromComponents(patient, "Patient.contact[{}].organization", Patient::hasContact, + Patient::getContact, ContactComponent::hasOrganization, ContactComponent::setOrganization); + cleanUnsupportedReferences(patient, "Patient.generalPractitioner", Patient::hasGeneralPractitioner, + Patient::setGeneralPractitioner); + cleanUnsupportedReference(patient, "Patient.managingOrganization", Patient::hasManagingOrganization, + Patient::setManagingOrganization); + cleanUnsupportedReferenceFromComponents(patient, "Patient.link[{}].other", Patient::hasLink, + Patient::getLink, PatientLinkComponent::hasOther, PatientLinkComponent::setOther); + } else if (resource instanceof Condition) { Condition condition = (Condition) resource; - condition.setEncounter(null); - condition.setRecorder(null); - condition.setAsserter(null); - if (condition.hasStage()) - condition.getStage().forEach(s -> s.setAssessment(null)); - if (condition.hasEvidence()) - condition.getEvidence().forEach(e -> e.setDetail(null)); + cleanUnsupportedReference(condition, "Condition.encounter", Condition::hasEncounter, + Condition::setEncounter); + cleanUnsupportedReference(condition, "Condition.recorder", Condition::hasRecorder, Condition::setRecorder); + cleanUnsupportedReference(condition, "Condition.asserter", Condition::hasAsserter, Condition::setAsserter); + cleanUnsupportedReferencesFromComponents(condition, "Condition.stage[{}].assessment", Condition::hasStage, + Condition::getStage, ConditionStageComponent::hasAssessment, + ConditionStageComponent::setAssessment); + cleanUnsupportedReferencesFromComponents(condition, "Condition.evidence[{}].detail", Condition::hasEvidence, + Condition::getEvidence, ConditionEvidenceComponent::hasDetail, + ConditionEvidenceComponent::setDetail); } else if (resource instanceof Consent) { Consent consent = (Consent) resource; - consent.setPerformer(null); - consent.setOrganization(null); - if (consent.hasVerification()) - consent.getVerification().forEach(v -> v.setVerifiedWith(null)); - if (consent.hasProvision()) - { - // class name starts with lower case p - provisionComponent provision = consent.getProvision(); - if (provision.hasActor()) - provision.getActor().forEach(a -> a.setReference(null)); - if (provision.hasData()) - provision.getData().forEach(d -> d.setReference(null)); - } + cleanUnsupportedReferences(consent, "Consent.performer", Consent::hasPerformer, Consent::setPerformer); + cleanUnsupportedReferences(consent, "Consent.organization", Consent::hasOrganization, + Consent::setOrganization); + cleanUnsupportedReferenceFromComponents(consent, "Consent.verification.verifiedWith", + Consent::hasVerification, Consent::getVerification, ConsentVerificationComponent::hasVerifiedWith, + ConsentVerificationComponent::setVerifiedWith); + // provisionComponent and provisionActorComponent class names starts with lower case p + cleanUnsupportedReferencesFromComponentComponents(consent, "Consent.provision.actor[{}].reference", + Consent::hasProvision, Consent::getProvision, provisionComponent::hasActor, + provisionComponent::getActor, provisionActorComponent::hasReference, + provisionActorComponent::setReference); + // provisionComponent and provisionDataComponent class names starts with lower case p + cleanUnsupportedReferencesFromComponentComponents(consent, "Consent.provision.data[{}].reference", + Consent::hasProvision, Consent::getProvision, provisionComponent::hasData, + provisionComponent::getData, provisionDataComponent::hasReference, + provisionDataComponent::setReference); } else if (resource instanceof DiagnosticReport) { DiagnosticReport report = (DiagnosticReport) resource; - report.setBasedOn(null); - report.setEncounter(null); - report.setPerformer(null); - report.setResultsInterpreter(null); - report.setSpecimen(null); - report.setImagingStudy(null); - if (report.hasMedia()) - report.getMedia().forEach(m -> m.setLink(null)); + + cleanUnsupportedReferences(report, "DiagnosticReport.basedOn", DiagnosticReport::hasBasedOn, + DiagnosticReport::setBasedOn); + cleanUnsupportedReference(report, "DiagnosticReport.encounter", DiagnosticReport::hasEncounter, + DiagnosticReport::setEncounter); + cleanUnsupportedReferences(report, "DiagnosticReport.performer", DiagnosticReport::hasPerformer, + DiagnosticReport::setPerformer); + cleanUnsupportedReferences(report, "DiagnosticReport.resultsInterpreter", + DiagnosticReport::hasResultsInterpreter, DiagnosticReport::setResultsInterpreter); + cleanUnsupportedReferences(report, "DiagnosticReport.specimen", DiagnosticReport::hasSpecimen, + DiagnosticReport::setSpecimen); + cleanUnsupportedReferences(report, "DiagnosticReport.result", DiagnosticReport::hasResult, + DiagnosticReport::setResult); + cleanUnsupportedReferences(report, "DiagnosticReport.imagingStudy", DiagnosticReport::hasImagingStudy, + DiagnosticReport::setImagingStudy); + cleanUnsupportedReferenceFromComponents(report, "DiagnosticReport.media[{}].link", + DiagnosticReport::hasMedia, DiagnosticReport::getMedia, DiagnosticReportMediaComponent::hasLink, + DiagnosticReportMediaComponent::setLink); } else if (resource instanceof Immunization) { Immunization immunization = (Immunization) resource; - immunization.setEncounter(null); - immunization.setLocation(null); - immunization.setManufacturer(null); - if (immunization.hasPerformer()) - immunization.getPerformer().forEach(p -> p.setActor(null)); - immunization.setReasonReference(null); - if (immunization.hasReaction()) - immunization.getReaction().forEach(r -> r.setDetail(null)); - if (immunization.hasProtocolApplied()) - immunization.getProtocolApplied().forEach(p -> p.setAuthority(null)); + + cleanUnsupportedReference(immunization, "Immunization.encounter", Immunization::hasEncounter, + Immunization::setEncounter); + cleanUnsupportedReference(immunization, "Immunization.location", Immunization::hasLocation, + Immunization::setLocation); + cleanUnsupportedReference(immunization, "Immunization.manufacturer", Immunization::hasManufacturer, + Immunization::setManufacturer); + cleanUnsupportedReferenceFromComponents(immunization, "Immunization.performer.actor", + Immunization::hasPerformer, Immunization::getPerformer, ImmunizationPerformerComponent::hasActor, + ImmunizationPerformerComponent::setActor); + cleanUnsupportedReferences(immunization, "Immunization.reasonReference", Immunization::hasReasonReference, + Immunization::setReasonReference); + cleanUnsupportedReferenceFromComponents(immunization, "Immunization.reaction.detail", + Immunization::hasReaction, Immunization::getReaction, ImmunizationReactionComponent::hasDetail, + ImmunizationReactionComponent::setDetail); + cleanUnsupportedReferenceFromComponents(immunization, "Immunization.protocolApplied.authority", + Immunization::hasProtocolApplied, Immunization::getProtocolApplied, + ImmunizationProtocolAppliedComponent::hasAuthority, + ImmunizationProtocolAppliedComponent::setAuthority); } else if (resource instanceof MedicationStatement) { MedicationStatement medication = (MedicationStatement) resource; - medication.setBasedOn(null); - medication.setPartOf(null); - if (medication.hasMedicationReference()) - medication.setMedication(null); - medication.setContext(null); - medication.setInformationSource(null); - medication.setDerivedFrom(null); - medication.setReasonReference(null); + cleanUnsupportedReferences(medication, "MedicationStatement.basedOn", MedicationStatement::hasBasedOn, + MedicationStatement::setBasedOn); + cleanUnsupportedReferences(medication, "MedicationStatement.partOf", MedicationStatement::hasPartOf, + MedicationStatement::setPartOf); + cleanUnsupportedReference(medication, "MedicationStatement.medicationReference", + MedicationStatement::hasMedicationReference, MedicationStatement::setMedication); + cleanUnsupportedReference(medication, "MedicationStatement.context", MedicationStatement::hasContext, + MedicationStatement::setContext); + cleanUnsupportedReference(medication, "MedicationStatement.informationSource", + MedicationStatement::hasInformationSource, MedicationStatement::setInformationSource); + cleanUnsupportedReferences(medication, "MedicationStatement.derivedFrom", + MedicationStatement::hasDerivedFrom, MedicationStatement::setDerivedFrom); + cleanUnsupportedReferences(medication, "MedicationStatement.reasonReference", + MedicationStatement::hasReasonReference, MedicationStatement::setReasonReference); } else if (resource instanceof Observation) { Observation observation = (Observation) resource; - observation.setBasedOn(null); - observation.setPartOf(null); - observation.setFocus(null); - observation.setEncounter(null); - observation.setPerformer(null); - observation.setSpecimen(null); - observation.setDevice(null); + + cleanUnsupportedReferences(observation, "Observation.basedOn", Observation::hasBasedOn, + Observation::setBasedOn); + cleanUnsupportedReferences(observation, "Observation.partOf", Observation::hasPartOf, + Observation::setPartOf); + cleanUnsupportedReferences(observation, "Observation.focus", Observation::hasFocus, Observation::setFocus); + cleanUnsupportedReference(observation, "Observation.encounter", Observation::hasEncounter, + Observation::setEncounter); + cleanUnsupportedReferences(observation, "Observation.performer", Observation::hasPerformer, + Observation::setPerformer); + cleanUnsupportedReference(observation, "Observation.specimen", Observation::hasSpecimen, + Observation::setSpecimen); + cleanUnsupportedReference(observation, "Observation.device", Observation::hasDevice, + Observation::setDevice); // Do not remove blood-gas-panel member references - // TODO fix blood-gas-panel member references, to bundle internal temporary urn:uuid:... IDs - if (!resource.getMeta().getProfile().stream().map(CanonicalType::getValue).anyMatch( - url -> "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/blood-gas-panel" - .equals(url))) + if (!resource.getMeta().getProfile().stream().map(CanonicalType::getValue) + .anyMatch(url -> NUM_CODEX_BLOOD_GAS_PANEL.equals(url) + || (url != null && url.startsWith(NUM_CODEX_BLOOD_GAS_PANEL + "|")))) { - observation.setHasMember(null); + cleanUnsupportedReferences(observation, "Observation.hasMember", Observation::hasHasMember, + Observation::setHasMember); } - observation.setDerivedFrom(null); + cleanUnsupportedReferences(observation, "Observation.derivedFrom", Observation::hasDerivedFrom, + Observation::setDerivedFrom); } else if (resource instanceof Procedure) { Procedure procedure = (Procedure) resource; - procedure.setInstantiatesCanonical(null); - procedure.setBasedOn(null); - procedure.setPartOf(null); - procedure.setEncounter(null); - procedure.setRecorder(null); - procedure.setAsserter(null); - if (procedure.hasPerformer()) - procedure.getPerformer().forEach(p -> - { - p.setActor(null); - p.setOnBehalfOf(null); - }); - procedure.setLocation(null); - procedure.setReasonReference(null); - procedure.setReport(null); - procedure.setComplicationDetail(null); - if (procedure.hasFocalDevice()) - procedure.getFocalDevice().forEach(d -> d.setManipulated(null)); - procedure.setUsedReference(null); + + cleanUnsupportedReferences(procedure, "Procedure.basedOn", Procedure::hasBasedOn, Procedure::setBasedOn); + cleanUnsupportedReferences(procedure, "Procedure.partOf", Procedure::hasPartOf, Procedure::setPartOf); + cleanUnsupportedReference(procedure, "Procedure.encounter", Procedure::hasEncounter, + Procedure::setEncounter); + cleanUnsupportedReference(procedure, "Procedure.recorder", Procedure::hasRecorder, Procedure::setRecorder); + cleanUnsupportedReference(procedure, "Procedure.asserter", Procedure::hasAsserter, Procedure::setAsserter); + cleanUnsupportedReferenceFromComponents(procedure, "", Procedure::hasPerformer, Procedure::getPerformer, + ProcedurePerformerComponent::hasActor, ProcedurePerformerComponent::setActor); + cleanUnsupportedReferenceFromComponents(procedure, "", Procedure::hasPerformer, Procedure::getPerformer, + ProcedurePerformerComponent::hasOnBehalfOf, ProcedurePerformerComponent::setOnBehalfOf); + cleanUnsupportedReference(procedure, "Procedure.location", Procedure::hasLocation, Procedure::setLocation); + cleanUnsupportedReferences(procedure, "Procedure.reasonReference", Procedure::hasReasonReference, + Procedure::setReasonReference); + cleanUnsupportedReferences(procedure, "Procedure.report", Procedure::hasReport, Procedure::setReport); + cleanUnsupportedReferences(procedure, "Procedure.complicationDetail", Procedure::hasComplicationDetail, + Procedure::setComplicationDetail); + cleanUnsupportedReferenceFromComponents(procedure, "Procedure.focalDevice.manipulated", + Procedure::hasFocalDevice, Procedure::getFocalDevice, ProcedureFocalDeviceComponent::hasManipulated, + ProcedureFocalDeviceComponent::setManipulated); + cleanUnsupportedReferences(procedure, "Procedure.usedReference", Procedure::hasUsedReference, + Procedure::setUsedReference); } else throw new RuntimeException("Resource of type " + resource.getResourceType().name() + " not supported"); diff --git a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/spring/config/TransferDataConfig.java b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/spring/config/TransferDataConfig.java index f952e756..d650473e 100644 --- a/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/spring/config/TransferDataConfig.java +++ b/codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/spring/config/TransferDataConfig.java @@ -226,7 +226,8 @@ public class TransferDataConfig @Value("${de.netzwerk.universitaetsmedizin.codex.fttp.basicauth.password:#{null}}") private String fttpBasicAuthPassword; - @ProcessDocumentation(description = "The base URL of the fTTP server", processNames = { "wwwnetzwerk-universitaetsmedizinde_dataSend", + @ProcessDocumentation(description = "The base URL of the fTTP server", processNames = { + "wwwnetzwerk-universitaetsmedizinde_dataSend", "wwwnetzwerk-universitaetsmedizinde_dataTranslate" }, recommendation = "Specify if you are using the send process to request pseudonyms from the fTTP. Caution: The fTTP client is unable to follow redirects, specify the final url if the server redirects requests") @Value("${de.netzwerk.universitaetsmedizin.codex.fttp.server.base.url:#{null}}") private String fttpServerBase; diff --git a/codex-process-data-transfer/src/main/resources/fhir/Bundle/SearchBundle.xml b/codex-process-data-transfer/src/main/resources/fhir/Bundle/SearchBundle.xml index 2e6e0033..edf4153d 100644 --- a/codex-process-data-transfer/src/main/resources/fhir/Bundle/SearchBundle.xml +++ b/codex-process-data-transfer/src/main/resources/fhir/Bundle/SearchBundle.xml @@ -147,7 +147,7 @@ - + @@ -237,13 +237,13 @@ - + - + diff --git a/codex-process-data-transfer/src/test/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/ReadDataTest.java b/codex-process-data-transfer/src/test/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/ReadDataTest.java new file mode 100644 index 00000000..18458153 --- /dev/null +++ b/codex-process-data-transfer/src/test/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/ReadDataTest.java @@ -0,0 +1,138 @@ +package de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.service.send; + +import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_BUNDLE; +import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_PATIENT_REFERENCE; +import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER; +import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_EXPORT_TO; +import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM; +import static org.highmed.dsf.bpe.ConstantsBase.BPMN_EXECUTION_VARIABLE_TASK; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.verify; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; + +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.highmed.dsf.fhir.authorization.read.ReadAccessHelper; +import org.highmed.dsf.fhir.client.FhirWebserviceClientProvider; +import org.highmed.dsf.fhir.task.TaskHelper; +import org.highmed.dsf.fhir.variables.FhirResourceValues.FhirResourceValue; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.DomainResource; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.InstantType; +import org.hl7.fhir.r4.model.Task; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import ca.uhn.fhir.context.FhirContext; +import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.client.GeccoClient; +import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.client.GeccoClientFactory; +import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.client.fhir.GeccoFhirClient; +import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.variables.PatientReference; + +public class ReadDataTest +{ + @Test + public void testExecuteWithBgaData() throws Exception + { + FhirContext fhirContext = FhirContext.forR4(); + + FhirWebserviceClientProvider clientProvider = Mockito.mock(FhirWebserviceClientProvider.class); + TaskHelper taskHelper = Mockito.mock(TaskHelper.class); + ReadAccessHelper readAccessHelper = Mockito.mock(ReadAccessHelper.class); + GeccoClientFactory geccoClientFactory = Mockito.mock(GeccoClientFactory.class); + GeccoClient geccoClient = Mockito.mock(GeccoClient.class); + GeccoFhirClient fhirClient = Mockito.mock(GeccoFhirClient.class); + Mockito.when(geccoClientFactory.getGeccoClient()).thenReturn(geccoClient); + Mockito.when(geccoClient.getFhirClient()).thenReturn(fhirClient); + Mockito.when(fhirClient.getNewData(Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(readBundle(fhirContext)); + + ReadData readData = new ReadData(clientProvider, taskHelper, readAccessHelper, fhirContext, geccoClientFactory); + DelegateExecution execution = Mockito.mock(DelegateExecution.class); + Mockito.when(execution.getCurrentActivityId()).thenReturn(UUID.randomUUID().toString()); + Mockito.when(execution.getVariable(BPMN_EXECUTION_VARIABLE_PATIENT_REFERENCE)).thenReturn(PatientReference + .from(new Identifier().setSystem(NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM).setValue("source/original"))); + Task task = createTask(); + Mockito.when(execution.getVariable(BPMN_EXECUTION_VARIABLE_TASK)).thenReturn(task); + + readData.execute(execution); + + ArgumentCaptor keyCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(FhirResourceValue.class); + verify(execution).setVariable(keyCaptor.capture(), bundleCaptor.capture()); + + assertEquals(BPMN_EXECUTION_VARIABLE_BUNDLE, keyCaptor.getValue()); + + Bundle bundle = (Bundle) bundleCaptor.getValue().getValue(); + assertNotNull(bundle.getEntry()); + assertEquals(7, bundle.getEntry().size()); + + assertTrue(bundle.getEntry().get(0).hasResource()); + assertTrue(bundle.getEntry().get(0).getResource().hasMeta()); + assertTrue(bundle.getEntry().get(0).getResource().getMeta().hasProfile()); + assertEquals("https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/Patient", + bundle.getEntry().get(0).getResource().getMeta().getProfile().get(0).getValue()); + + List bgaProfiles = new ArrayList<>(Arrays.asList( + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/pH", + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/carbon-dioxide-partial-pressure", + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/oxygen-partial-pressure", + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/oxygen-saturation", + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/inhaled-oxygen-concentration")); + + for (int i = 1; i < bundle.getEntry().size() - 1; i++) + { + assertTrue(bundle.getEntry().get(i).hasResource()); + assertTrue(bundle.getEntry().get(i).getResource().hasMeta()); + assertTrue(bundle.getEntry().get(i).getResource().getMeta().hasProfile()); + assertTrue(bgaProfiles + .contains(bundle.getEntry().get(i).getResource().getMeta().getProfile().get(0).getValue())); + + // removing profile from expected profiles to check every expected profile only appears once + bgaProfiles.remove(bundle.getEntry().get(i).getResource().getMeta().getProfile().get(0).getValue()); + } + + assertTrue(bundle.getEntry().get(bundle.getEntry().size() - 1).hasResource()); + assertTrue(bundle.getEntry().get(bundle.getEntry().size() - 1).getResource().hasMeta()); + assertTrue(bundle.getEntry().get(bundle.getEntry().size() - 1).getResource().getMeta().hasProfile()); + assertEquals("https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/blood-gas-panel", bundle + .getEntry().get(bundle.getEntry().size() - 1).getResource().getMeta().getProfile().get(0).getValue()); + } + + private Stream readBundle(FhirContext fhirContext) throws FileNotFoundException, IOException + { + try (InputStream in = Files + .newInputStream(Paths.get("src/test/resources/fhir/Bundle/dic_fhir_store_demo_psn_bga.json"))) + { + Bundle bundle = fhirContext.newJsonParser().parseResource(Bundle.class, in); + return bundle.getEntry().stream().filter(BundleEntryComponent::hasResource) + .map(BundleEntryComponent::getResource).filter(r -> r instanceof DomainResource) + .map(r -> (DomainResource) r); + } + } + + private Task createTask() + { + Task task = new Task(); + task.addInput().setValue(new InstantType(new Date())).getType().getCodingFirstRep() + .setSystem(CODESYSTEM_NUM_CODEX_DATA_TRANSFER) + .setCode(CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_EXPORT_TO); + + return task; + } +} diff --git a/codex-process-data-transfer/src/test/resources/fhir/Bundle/dic_fhir_store_demo_psn_bga.json b/codex-process-data-transfer/src/test/resources/fhir/Bundle/dic_fhir_store_demo_psn_bga.json new file mode 100644 index 00000000..d861ae39 --- /dev/null +++ b/codex-process-data-transfer/src/test/resources/fhir/Bundle/dic_fhir_store_demo_psn_bga.json @@ -0,0 +1,347 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [{ + "fullUrl": "urn:uuid:6e6304b9-f6cf-4bac-97ef-6ab5b4d45610", + "resource": { + "resourceType": "Patient", + "meta": { + "profile": [ + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/Patient" + ] + }, + "extension": [{ + "url": "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/ethnic-group", + "valueCoding": { + "system": "http://snomed.info/sct", + "code": "26242008", + "display": "Mixed (qualifier value)" + } + }, { + "extension": [{ + "url": "dateTimeOfDocumentation", + "valueDateTime": "2020-10-01" + }, { + "url": "age", + "valueAge": { + "value": 67, + "unit": "years", + "system": "http://unitsofmeasure.org", + "code": "a" + } + } + ], + "url": "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/age" + } + ], + "identifier": [ + { + "system": "http://www.netzwerk-universitaetsmedizin.de/sid/dic-pseudonym", + "value": "source/original", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "ANON" + } + ] + } + } + ], + "birthDate": "1953-09-30" + }, + "request": { + "method": "PUT", + "url": "Patient?identifier=http://www.netzwerk-universitaetsmedizin.de/sid/dic-pseudonym|source/original" + } + }, { + "fullUrl": "urn:uuid:342946b6-77bc-4e3c-901a-f92fe6a9dc45", + "resource": { + "resourceType": "Observation", + "meta": { + "profile": [ + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/pH" + ] + }, + "status": "final", + "category": [{ + "coding": [{ + "system": "http://loinc.org", + "code": "26436-6" + }, { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "laboratory" + }, { + "system": "http://loinc.org", + "code": "18767-4" + } + ] + } + ], + "code": { + "coding": [{ + "system": "http://loinc.org", + "code": "2744-1", + "display": "pH of Arterial blood" + } + ], + "text": "pH of Arterial blood" + }, + "subject": { + "reference": "urn:uuid:6e6304b9-f6cf-4bac-97ef-6ab5b4d45610" + }, + "effectiveDateTime": "2020-09-21", + "valueQuantity": { + "value": 7.4, + "unit": "pH", + "system": "http://unitsofmeasure.org", + "code": "[pH]" + } + }, + "request": { + "method": "PUT", + "url": "Observation?_profile=https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/pH&date=2020-09-21" + } + }, { + "fullUrl": "urn:uuid:06659981-4625-4a8f-a190-0f2f7a3ac35c", + "resource": { + "resourceType": "Observation", + "meta": { + "profile": [ + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/carbon-dioxide-partial-pressure" + ] + }, + "status": "final", + "category": [{ + "coding": [{ + "system": "http://loinc.org", + "code": "26436-6" + }, { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "laboratory" + }, { + "system": "http://loinc.org", + "code": "18767-4" + } + ] + } + ], + "code": { + "coding": [{ + "system": "http://loinc.org", + "code": "2019-8", + "display": "Carbon dioxide [Partial pressure] in Arterial blood" + } + ], + "text": "Carbon dioxide [Partial pressure] in Arterial blood" + }, + "subject": { + "reference": "urn:uuid:6e6304b9-f6cf-4bac-97ef-6ab5b4d45610" + }, + "effectiveDateTime": "2020-09-21", + "valueQuantity": { + "value": 44, + "unit": "mmHg", + "system": "http://unitsofmeasure.org", + "code": "mm[Hg]" + } + }, + "request": { + "method": "PUT", + "url": "Observation?_profile=https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/carbon-dioxide-partial-pressure&date=2020-09-21" + } + }, { + "fullUrl": "urn:uuid:1ba15fa6-7c32-403b-92bf-2aa505a70849", + "resource": { + "resourceType": "Observation", + "meta": { + "profile": [ + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/oxygen-partial-pressure" + ] + }, + "status": "final", + "category": [{ + "coding": [{ + "system": "http://loinc.org", + "code": "26436-6" + }, { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "laboratory" + }, { + "system": "http://loinc.org", + "code": "18767-4" + } + ] + } + ], + "code": { + "coding": [{ + "system": "http://loinc.org", + "code": "2703-7", + "display": "Oxygen [Partial pressure] in Arterial blood" + } + ], + "text": "Oxygen [Partial pressure] in Arterial blood" + }, + "subject": { + "reference": "urn:uuid:6e6304b9-f6cf-4bac-97ef-6ab5b4d45610" + }, + "effectiveDateTime": "2020-09-21", + "valueQuantity": { + "value": 67, + "unit": "mmHg", + "system": "http://unitsofmeasure.org", + "code": "mm[Hg]" + } + }, + "request": { + "method": "PUT", + "url": "Observation?_profile=https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/oxygen-partial-pressure&date=2020-09-21" + } + }, { + "fullUrl": "urn:uuid:a83114c8-cb20-4645-95be-94a3fd308df8", + "resource": { + "resourceType": "Observation", + "meta": { + "profile": [ + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/oxygen-saturation" + ] + }, + "status": "final", + "category": [{ + "coding": [{ + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "vital-signs" + } + ] + } + ], + "code": { + "coding": [{ + "system": "http://loinc.org", + "code": "2708-6", + "display": "Oxygen saturation in Arterial blood" + } + ], + "text": "Oxygen saturation in Arterial blood" + }, + "subject": { + "reference": "urn:uuid:6e6304b9-f6cf-4bac-97ef-6ab5b4d45610" + }, + "effectiveDateTime": "2020-09-21", + "valueQuantity": { + "value": 98, + "unit": "%", + "system": "http://unitsofmeasure.org", + "code": "%" + } + }, + "request": { + "method": "PUT", + "url": "Observation?_profile=https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/oxygen-saturation&date=2020-09-21" + } + }, { + "fullUrl": "urn:uuid:6c6817e2-69d3-48e7-834b-5adbf3b5adae", + "resource": { + "resourceType": "Observation", + "meta": { + "profile": [ + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/inhaled-oxygen-concentration" + ] + }, + "status": "final", + "category": [{ + "coding": [{ + "system": "http://loinc.org", + "code": "26436-6" + }, { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "laboratory" + }, { + "system": "http://loinc.org", + "code": "18767-4" + } + ] + } + ], + "code": { + "coding": [{ + "system": "http://loinc.org", + "code": "3150-0", + "display": "Inhaled oxygen concentration" + } + ], + "text": "Inhaled oxygen concentration" + }, + "subject": { + "reference": "urn:uuid:6e6304b9-f6cf-4bac-97ef-6ab5b4d45610" + }, + "effectiveDateTime": "2020-09-21", + "valueQuantity": { + "value": 21, + "unit": "%", + "system": "http://unitsofmeasure.org", + "code": "%" + } + }, + "request": { + "method": "PUT", + "url": "Observation?_profile=https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/inhaled-oxygen-concentration&date=2020-09-21" + } + }, { + "fullUrl": "urn:uuid:e90f9fcd-e6c3-4304-8082-546bf0aba855", + "resource": { + "resourceType": "Observation", + "meta": { + "profile": [ + "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/blood-gas-panel" + ] + }, + "status": "final", + "category": [{ + "coding": [{ + "system": "http://loinc.org", + "code": "26436-6" + }, { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "laboratory" + }, { + "system": "http://loinc.org", + "code": "18767-4" + } + ] + } + ], + "code": { + "coding": [{ + "system": "http://loinc.org", + "code": "24336-0", + "display": "Gas panel - Arterial blood" + } + ], + "text": "Gas panel - Arterial blood" + }, + "subject": { + "reference": "urn:uuid:6e6304b9-f6cf-4bac-97ef-6ab5b4d45610" + }, + "effectiveDateTime": "2020-09-21", + "hasMember": [{ + "reference": "urn:uuid:342946b6-77bc-4e3c-901a-f92fe6a9dc45" + }, { + "reference": "urn:uuid:06659981-4625-4a8f-a190-0f2f7a3ac35c" + }, { + "reference": "urn:uuid:1ba15fa6-7c32-403b-92bf-2aa505a70849" + }, { + "reference": "urn:uuid:a83114c8-cb20-4645-95be-94a3fd308df8" + }, { + "reference": "urn:uuid:6c6817e2-69d3-48e7-834b-5adbf3b5adae" + } + ] + }, + "request": { + "method": "PUT", + "url": "Observation?_profile=https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/blood-gas-panel&date=2020-09-21" + } + } + ] +}