Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.

Commit 44293c2

Browse files
committed
adds support for references in blood-gas-panel resource
This is not a fix for the more general problem described in #37, but blood-gas-panel is now fully supported.
1 parent 77dc628 commit 44293c2

9 files changed

Lines changed: 913 additions & 101 deletions

File tree

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractComplexFhirClient.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,18 +185,18 @@ protected Optional<Patient> findPatientInLocalFhirStore(String pseudonym)
185185
{
186186
logger.warn("Error while searching for Patient with pseudonym {}|{}, message: {}, status: {}",
187187
NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM, pseudonym, e.getMessage(), e.getStatusCode());
188-
188+
189189
IBaseOperationOutcome outcome = e.getOperationOutcome();
190190

191191
if (outcome != null && outcome instanceof OperationOutcome)
192192
outcomeLogger.logOutcome((OperationOutcome) outcome);
193-
193+
194194
throw e;
195195
}
196196
catch (Exception e)
197197
{
198-
logger.warn("Error while searching for Patient with pseudonym " + NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM + "|"
199-
+ pseudonym, e);
198+
logger.warn("Error while searching for Patient with pseudonym " + NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM
199+
+ "|" + pseudonym, e);
200200
throw e;
201201
}
202202
}
@@ -227,10 +227,10 @@ public Stream<DomainResource> getNewData(String pseudonym, DateWithPrecision exp
227227
logger.debug("Search-Bundle result: {}",
228228
geccoClient.getFhirContext().newJsonParser().encodeResourceToString(resultBundle));
229229

230-
return Stream.concat(Stream.of(localPatient.get()),
230+
return distinctById(Stream.concat(Stream.of(localPatient.get()),
231231
resultBundle.getEntry().stream().filter(BundleEntryComponent::hasResource)
232232
.map(BundleEntryComponent::getResource).filter(r -> r instanceof Bundle).map(r -> (Bundle) r)
233-
.flatMap(this::getDomainResources));
233+
.flatMap(this::getDomainResources)));
234234
}
235235

236236
private Optional<Patient> findPatientInLocalFhirStore(String system, String pseudonym)

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/AbstractFhirClient.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,54 @@ public abstract class AbstractFhirClient implements GeccoFhirClient
5959
private static final Logger logger = LoggerFactory.getLogger(AbstractFhirClient.class);
6060
private static final OutcomeLogger outcomeLogger = new OutcomeLogger(logger);
6161

62+
private static final class DomainResourceUniqueByUnqualifiedVersionlessId
63+
{
64+
private final DomainResource resource;
65+
private final String unqualifiedVersionlessIdValue;
66+
67+
public DomainResourceUniqueByUnqualifiedVersionlessId(DomainResource resource)
68+
{
69+
this.resource = resource;
70+
71+
unqualifiedVersionlessIdValue = resource.getIdElement().toUnqualifiedVersionless().getValue();
72+
}
73+
74+
public DomainResource getResource()
75+
{
76+
return resource;
77+
}
78+
79+
@Override
80+
public int hashCode()
81+
{
82+
final int prime = 31;
83+
int result = 1;
84+
result = prime * result
85+
+ ((unqualifiedVersionlessIdValue == null) ? 0 : unqualifiedVersionlessIdValue.hashCode());
86+
return result;
87+
}
88+
89+
@Override
90+
public boolean equals(Object obj)
91+
{
92+
if (this == obj)
93+
return true;
94+
if (obj == null)
95+
return false;
96+
if (getClass() != obj.getClass())
97+
return false;
98+
DomainResourceUniqueByUnqualifiedVersionlessId other = (DomainResourceUniqueByUnqualifiedVersionlessId) obj;
99+
if (unqualifiedVersionlessIdValue == null)
100+
{
101+
if (other.unqualifiedVersionlessIdValue != null)
102+
return false;
103+
}
104+
else if (!unqualifiedVersionlessIdValue.equals(other.unqualifiedVersionlessIdValue))
105+
return false;
106+
return true;
107+
}
108+
}
109+
62110
private static final List<String> RESOURCES_WITH_PATIENT_REF = Arrays.asList("AllergyIntolerance", "CarePlan",
63111
"CareTeam", "ClinicalImpression", "Composition", "Condition", "Consent", "DetectedIssue", "DeviceRequest",
64112
"DeviceUseStatement", "DiagnosticReport", "DocumentManifest", "DocumentReference", "Encounter",
@@ -624,4 +672,10 @@ public void updatePatient(Patient patient)
624672
throw e;
625673
}
626674
}
675+
676+
protected Stream<DomainResource> distinctById(Stream<DomainResource> resources)
677+
{
678+
return resources.map(DomainResourceUniqueByUnqualifiedVersionlessId::new).distinct()
679+
.map(DomainResourceUniqueByUnqualifiedVersionlessId::getResource);
680+
}
627681
}

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/FhirBridgeClient.java

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,22 @@
22

33
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM;
44

5+
import java.util.HashMap;
6+
import java.util.List;
7+
import java.util.Map;
58
import java.util.Optional;
69
import java.util.function.Predicate;
710

811
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
912
import org.hl7.fhir.r4.model.Bundle;
1013
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
1114
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
15+
import org.hl7.fhir.r4.model.CanonicalType;
16+
import org.hl7.fhir.r4.model.IdType;
17+
import org.hl7.fhir.r4.model.Observation;
1218
import org.hl7.fhir.r4.model.OperationOutcome;
1319
import org.hl7.fhir.r4.model.Patient;
20+
import org.hl7.fhir.r4.model.Reference;
1421
import org.hl7.fhir.r4.model.Resource;
1522
import org.slf4j.Logger;
1623
import org.slf4j.LoggerFactory;
@@ -28,6 +35,8 @@ public class FhirBridgeClient extends AbstractComplexFhirClient
2835
private static final Logger logger = LoggerFactory.getLogger(FhirBridgeClient.class);
2936
private static final OutcomeLogger outcomeLogger = new OutcomeLogger(logger);
3037

38+
private static final String NUM_CODEX_BLOOD_GAS_PANEL = "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/blood-gas-panel";
39+
3140
/**
3241
* @param geccoClient
3342
* not <code>null</code>
@@ -43,14 +52,16 @@ public void storeBundle(Bundle bundle)
4352
// either bundle has a patient, or patient should already exists
4453
Patient patient = createOrUpdatePatient(bundle).orElseGet(() -> getExistingPatientOrThrow(bundle));
4554

55+
Map<String, IdType> resourceIdsByUuid = new HashMap<>();
4656
for (int i = 0; i < bundle.getEntry().size(); i++)
4757
{
4858
BundleEntryComponent entry = bundle.getEntry().get(i);
4959

5060
if (isEntrySupported(entry, e -> !(e.getResource() instanceof Patient)))
51-
createOrUpdateEntry(i, entry, patient);
61+
createOrUpdateEntry(i, entry, patient, resourceIdsByUuid);
62+
63+
// only log for non Patients
5264
else if (!entry.hasResource() || !(entry.getResource() instanceof Patient))
53-
// only log for non Patients
5465
logger.warn("Bundle entry at index {} not supported, ignoring entry", i);
5566
}
5667
}
@@ -220,14 +231,18 @@ private Optional<Patient> create(Patient newPatient, String pseudonym, String bu
220231
}
221232
}
222233

223-
private void createOrUpdateEntry(int index, BundleEntryComponent entry, Patient patient)
234+
private void createOrUpdateEntry(int index, BundleEntryComponent entry, Patient patient,
235+
Map<String, IdType> resourceIdsByUuid)
224236
{
225237
Resource resource = entry.getResource();
226238
String url = entry.getRequest().getUrl();
227239

228240
Optional<Resource> existingResource = findResourceInLocalFhirStore(url, resource.getClass());
229-
existingResource.ifPresentOrElse(existing -> update(existing, resource, entry.getFullUrl()),
230-
() -> create(resource, entry.getFullUrl()));
241+
IdType resourceId = existingResource.map(
242+
existing -> update(existing, fixTemporaryReferences(resource, resourceIdsByUuid), entry.getFullUrl()))
243+
.orElseGet(() -> create(fixTemporaryReferences(resource, resourceIdsByUuid), entry.getFullUrl()));
244+
245+
resourceIdsByUuid.put(entry.getFullUrl(), resourceId);
231246
}
232247

233248
private Optional<Resource> findResourceInLocalFhirStore(String url, Class<? extends Resource> resourceType)
@@ -302,7 +317,43 @@ private Optional<Resource> findResourceInLocalFhirStore(String url, Class<? exte
302317
}
303318
}
304319

305-
private void update(Resource existingResource, Resource newResource, String bundleFullUrl)
320+
private Resource fixTemporaryReferences(Resource resource, Map<String, IdType> resourceIdsByUuid)
321+
{
322+
if (resource == null)
323+
return null;
324+
325+
else if (resource instanceof Observation)
326+
{
327+
if (resource.getMeta().getProfile().stream().map(CanonicalType::getValue)
328+
.anyMatch(url -> NUM_CODEX_BLOOD_GAS_PANEL.equals(url)
329+
|| (url != null && url.startsWith(NUM_CODEX_BLOOD_GAS_PANEL + "|"))))
330+
{
331+
Observation observation = (Observation) resource;
332+
List<Reference> members = observation.getHasMember();
333+
for (int i = 0; i < members.size(); i++)
334+
{
335+
Reference member = members.get(i);
336+
if (member.hasReference())
337+
{
338+
String uuid = member.getReference();
339+
IdType resourceId = resourceIdsByUuid.get(uuid);
340+
341+
if (resourceId != null)
342+
{
343+
logger.debug(
344+
"Replacing reference at Observation.hasMember[{}] from bundle resource {} with existing resource id",
345+
i, resource.getIdElement().getValue());
346+
member.setReferenceElement(resourceId);
347+
}
348+
}
349+
}
350+
}
351+
}
352+
353+
return resource;
354+
}
355+
356+
private IdType update(Resource existingResource, Resource newResource, String bundleFullUrl)
306357
{
307358
logger.debug("Updating {}", newResource.getResourceType().name());
308359

@@ -315,7 +366,7 @@ private void update(Resource existingResource, Resource newResource, String bund
315366

316367
if (outcome.getId() == null)
317368
{
318-
logger.warn("Could not update {} {}", newResource.getResourceType().name(),
369+
logger.warn("Could not update {} {}: unknown reason", newResource.getResourceType().name(),
319370
newResource.getIdElement().toString());
320371
if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome() instanceof OperationOutcome)
321372
outcomeLogger.logOutcome((OperationOutcome) outcome.getOperationOutcome());
@@ -324,7 +375,15 @@ private void update(Resource existingResource, Resource newResource, String bund
324375
+ newResource.getIdElement().toString());
325376
}
326377
else if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome() instanceof OperationOutcome)
378+
{
327379
outcomeLogger.logOutcome((OperationOutcome) outcome.getOperationOutcome());
380+
logger.warn("Could not update {} {}: unknown reason", newResource.getResourceType().name(),
381+
newResource.getIdElement().toString());
382+
throw new RuntimeException("Could not create " + newResource.getResourceType().name() + " "
383+
+ newResource.getIdElement().toString() + ": unknown reason");
384+
}
385+
else
386+
return (IdType) outcome.getId();
328387
}
329388
catch (UnprocessableEntityException e)
330389
{
@@ -367,7 +426,7 @@ else if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome()
367426
}
368427
}
369428

370-
private void create(Resource newResource, String bundleFullUrl)
429+
private IdType create(Resource newResource, String bundleFullUrl)
371430
{
372431
logger.debug("Creating {}", newResource.getResourceType().name());
373432

@@ -387,7 +446,15 @@ private void create(Resource newResource, String bundleFullUrl)
387446
+ newResource.getIdElement().toString());
388447
}
389448
else if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome() instanceof OperationOutcome)
449+
{
390450
outcomeLogger.logOutcome((OperationOutcome) outcome.getOperationOutcome());
451+
logger.warn("Could not create {} {}: unknown reason", newResource.getResourceType().name(),
452+
newResource.getIdElement().toString());
453+
throw new RuntimeException("Could not create " + newResource.getResourceType().name() + " "
454+
+ newResource.getIdElement().toString() + ": unknown reason");
455+
}
456+
else
457+
return (IdType) outcome.getId();
391458
}
392459
catch (UnprocessableEntityException e)
393460
{

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/fhir/SimpleFhirClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ public Stream<DomainResource> getNewData(String pseudonym, DateWithPrecision exp
4949
logger.debug("Search-Bundle result: {}",
5050
geccoClient.getFhirContext().newJsonParser().encodeResourceToString(resultBundle));
5151

52-
return resultBundle.getEntry().stream().filter(BundleEntryComponent::hasResource)
52+
return distinctById(resultBundle.getEntry().stream().filter(BundleEntryComponent::hasResource)
5353
.map(BundleEntryComponent::getResource).filter(r -> r instanceof Bundle).map(r -> (Bundle) r)
54-
.flatMap(this::getDomainResources);
54+
.flatMap(this::getDomainResources));
5555
}
5656
}

0 commit comments

Comments
 (0)