Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modifications for Risk Principe #124

Merged
merged 21 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
abd709a
1.0.0.0 release
hhund Oct 11, 2023
4262d48
Merge remote-tracking branch 'origin/release/1.0.0.0' into main
hhund Oct 11, 2023
a8e1ea3
updated version to 1.0.0.1-SNAPSHOT
schwzr Mar 16, 2024
40f5b9b
replaced default SearchBundle to match up required searches for Risk …
schwzr Mar 16, 2024
19c8909
added basic support for Specimen resource
schwzr Mar 16, 2024
cea20b5
disabled tests, needs to be modified to use the tested SearchBundle a…
schwzr Mar 16, 2024
56a883a
allow the usage of local pseudonyms
schwzr Mar 18, 2024
e21f00f
re-enabled tests
schwzr Mar 25, 2024
833765b
add support for Specimen and contained resources
schwzr Mar 25, 2024
b89b225
changed default packages to those required by risk principe
schwzr Mar 25, 2024
812c267
implement fix for invalid xhtml content until https://github.com/medi…
schwzr Mar 25, 2024
e3d0774
add workarounds until fixed kds mikrobiologie packages are released
schwzr Mar 25, 2024
6ca78a4
added fixes for local pseudonyms and ensure compatibility with curren…
schwzr Mar 25, 2024
aafb7ec
added missing valuesets required for KdsMikrobiologieBugFixer
schwzr Mar 25, 2024
63b5def
updated docker image versions
schwzr Mar 25, 2024
eadce42
add bundle with demo data for risk principe
schwzr Mar 25, 2024
725f013
implemented suggestions provided by @hhund
schwzr Mar 25, 2024
7e42a75
javadoc formatting fix
schwzr Mar 25, 2024
3384d0c
javadoc formatting fix to please format check
schwzr Mar 25, 2024
f3bacda
add comment explaining the versioned id issue with hapi client
schwzr Mar 25, 2024
4e5a6ff
improved logging by @hhund
schwzr Mar 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion codex-process-data-transfer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<parent>
<groupId>de.netzwerk-universitaetsmedizin.codex</groupId>
<artifactId>codex-processes-ap1</artifactId>
<version>1.0.0.0-SNAPSHOT</version>
<version>1.1.0.0-SNAPSHOT</version>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public interface ConstantsDataTransfer
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_BAD_PATIENT_REFERENCE = "bad-patient-reference";
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_FTTP_NOT_REACHABLE = "fttp-not-reachable";
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_NO_DIC_PSEUDONYM_FOR_BLOOMFILTER = "no-dic-pseudonym-for-bloomfilter";
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_NO_DIC_PSEUDONYM_FOR_LOCAL_PSEUDONYM = "no-dic-pseudonym-for-local-pseudonym";
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_VALIDATION_FAILED = "validation-failed";
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_ECRYPTION_OF_DATA_FOR_CRR_FAILED = "ecryption-of-data-for-crr-failed";
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_UNABLE_TO_STORE_ECRYPTED_DATA = "unable-to-store-ecrypted-data";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

public class DataTransferProcessPluginDefinition implements ProcessPluginDefinition
{
public static final String VERSION = "1.0.0.0";
public static final LocalDate DATE = LocalDate.of(2023, 9, 25);
public static final String VERSION = "1.1.0.0";
public static final LocalDate DATE = LocalDate.of(2024, 3, 18);

@Override
public String getName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ public interface FttpClient
*/
Optional<String> getDicPseudonym(String bloomFilter);

Optional<String> getDicPseudonymForLocalPseudonym(String localPseudonym);

void testConnection();
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ public Optional<String> getDicPseudonym(String bloomFilter)
return pseudonym;
}

@Override
public Optional<String> getDicPseudonymForLocalPseudonym(String localPseudonym)
{
Optional<String> pseudonym = sha256(localPseudonym).map(p -> "dic_test/" + p);

logger.warn(
"Returning simulated DIC pseudonym '{}' for local pseudonym '{}', fTTP connection not configured.",
pseudonym.orElseThrow(), localPseudonym);

return pseudonym;
}

private Optional<String> sha256(String original)
{
try
Expand Down Expand Up @@ -142,7 +154,7 @@ public void testConnection()
{
logger.info(
"Testing connection to fTTP with {trustStorePath: {}, certificatePath: {}, privateKeyPath: {}, privateKeyPassword: {},"
+ " basicAuthUsername {}, basicAuthPassword {}, serverBase: {}, apiKey: {}, study: {}, target: {}, proxyUrl {}, proxyUsername, proxyPassword {}}",
+ " basicAuthUsername: {}, basicAuthPassword: {}, serverBase: {}, apiKey: {}, study: {}, target: {}, proxyUrl: {}, proxyUsername: {}, proxyPassword: {}}",
trustStorePath, certificatePath, privateKeyPath, privateKeyPassword != null ? "***" : "null",
fttpBasicAuthUsername, fttpBasicAuthPassword != null ? "***" : "null", fttpServerBase,
fttpApiKey != null ? "***" : "null", fttpStudy, fttpTarget, proxyUrl, proxyUsername,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,18 @@ protected Parameters createParametersForPsnWorkflow(String dicSourceAndPseudonym
return p;
}

protected Parameters createParametersForPsnWorkflowLocalPseudonym(String localPseudonym)
{
Parameters p = new Parameters();
p.addParameter("study", fttpStudy);
p.addParameter("original", localPseudonym);
p.addParameter("source", "local");
p.addParameter("target", fttpTarget);
p.addParameter("apikey", fttpApiKey);

return p;
}

@Override
public Optional<String> getDicPseudonym(String bloomFilter)
{
Expand All @@ -178,6 +190,30 @@ public Optional<String> getDicPseudonym(String bloomFilter)
}
}

@Override
public Optional<String> getDicPseudonymForLocalPseudonym(String localPseudonym)
{
Objects.requireNonNull(localPseudonym, "localPseudonym");

logger.info("Requesting DIC Pseudonym for local Pseudonym {} ...", localPseudonym);

try
{
IGenericClient client = createGenericClient();

Parameters parameters = client.operation().onServer().named("requestPsnWorkflow")
.withParameters(createParametersForPsnWorkflowLocalPseudonym(localPseudonym))
.accept(Constants.CT_FHIR_XML_NEW).encoded(EncodingEnum.XML).execute();

return getPseudonym(parameters).map(p -> fttpTarget + "/" + p);
}
catch (Exception e)
{
logger.error("Error while retrieving DIC pseudonym: {} - {}", e.getClass().getName(), e.getMessage());
throw new BpmnError(CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_FTTP_NOT_REACHABLE, e.getMessage());
}
}

protected Parameters createParametersForBfWorkflow(String bloomFilter)
{
Parameters p = new Parameters();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,8 @@ public void updatePatient(Patient patient)
Objects.requireNonNull(patient, "patient");

String id = patient.getIdElement().toVersionless().getValue();
// set the patient id to versionless id to workaround a `If-Match`-header bug in hapi fhir client
patient.setId(id);
schwzr marked this conversation as resolved.
Show resolved Hide resolved
logger.info("Updating patient {}", id);

try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.uhn.fhir.context.FhirContext;
import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.logging.DataLogger;
import dev.dsf.bpe.v1.ProcessPluginApi;
import dev.dsf.bpe.v1.activity.AbstractServiceDelegate;
import dev.dsf.bpe.v1.constants.NamingSystems;
Expand All @@ -26,12 +26,14 @@ public class StoreValidationErrorForDts extends AbstractServiceDelegate
private static final Logger logger = LoggerFactory.getLogger(StoreValidationErrorForDts.class);

private final String dtsIdentifierValue;
private final DataLogger dataLogger;

public StoreValidationErrorForDts(ProcessPluginApi api, String dtsIdentifierValue)
public StoreValidationErrorForDts(ProcessPluginApi api, String dtsIdentifierValue, DataLogger dataLogger)
{
super(api);

this.dtsIdentifierValue = dtsIdentifierValue;
this.dataLogger = dataLogger;
}

@Override
Expand All @@ -40,6 +42,7 @@ public void afterPropertiesSet() throws Exception
super.afterPropertiesSet();

Objects.requireNonNull(dtsIdentifierValue, "dtsIdentifierValue");
Objects.requireNonNull(dataLogger, "dataLogger");
}

@Override
Expand Down Expand Up @@ -72,7 +75,7 @@ private IdType createBinaryResource(Binary binary)
}
catch (Exception e)
{
logger.debug("Binary to create {}", FhirContext.forR4().newJsonParser().encodeResourceToString(binary));
dataLogger.logData("Binary to create", binary);
logger.warn("Error while creating Binary resource: " + e.getMessage(), e);

throw new BpmnError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import org.camunda.bpm.engine.delegate.BpmnError;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleType;
Expand Down Expand Up @@ -69,6 +70,7 @@
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.Specimen;
import org.hl7.fhir.r4.model.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -176,6 +178,7 @@ protected Bundle toBundle(String pseudonym, Stream<DomainResource> resourcesStre
.collect(Collectors.toMap(r -> r.getIdElement().toUnqualifiedVersionless().getValue(),
r -> "urn:uuid:" + UUID.randomUUID().toString()));


List<DomainResource> resourcesWithTemporaryReferences = fixReferences(resources, resourcesById, uuidsById);
List<BundleEntryComponent> entries = resourcesWithTemporaryReferences.stream().map(r ->
{
Expand Down Expand Up @@ -240,12 +243,20 @@ else if (resource instanceof Observation o)
Observation::setEncounter);
fixReferences(o, uuidsById, "Observation.hasMember", Observation::hasHasMember, Observation::getHasMember,
Observation::setHasMember);
fixReference(o, uuidsById, "Observation.specimen", Observation::hasSpecimen, Observation::getSpecimen,
Observation::setSpecimen);
}
else if (resource instanceof Procedure p)
{
fixReference(p, uuidsById, "Procedure.encounter", Procedure::hasEncounter, Procedure::getEncounter,
Procedure::setEncounter);
}
else if (resource instanceof Specimen s)
{
fixReferences(s, uuidsById, "Specimen.parent", Specimen::hasParent, Specimen::getParent,
Specimen::setParent);
}


return resource;
}
Expand All @@ -269,6 +280,18 @@ else if (oldReference.hasReference() && oldReference.getReference() == null && o
{
logger.debug("Not removing empty reference at {} with data-absent-reason extension", path);
}
else if (!oldReference.getResource().isEmpty())
{
String internalId = "#" + UUID.randomUUID();
Reference fixedReference = new Reference(internalId);
IBaseResource oldContainedResource = clean((DomainResource) oldReference.getResource());
oldContainedResource.setId(internalId);
fixedReference.setResource(oldContainedResource);
setReference.apply(resource, fixedReference);
logger.debug(
"Replacing reference to contained resource at {} from resource {} with bundle temporary id in transport bundle",
path, getAbsoluteId(resource).getValue());
}
else
{
logger.warn("Removing reference at {} from resource {} in transport bundle", path,
Expand Down Expand Up @@ -307,6 +330,18 @@ else if (oldReference.hasReference() && oldReference.getReference() == null
logger.debug("Not removing empty reference at {}[{}] with data-absent-reason extension", path, i);
fixedReferences.add(oldReference);
}
else if (!oldReference.getResource().isEmpty())
{
String internalId = "#" + UUID.randomUUID();
Reference fixedReference = new Reference(internalId);
IBaseResource oldContainedResource = clean((DomainResource) oldReference.getResource());
oldContainedResource.setId(internalId);
fixedReference.setResource(oldContainedResource);
fixedReferences.add(fixedReference);
logger.debug(
"Replacing reference to contained resource at {}[{}] from resource {} with bundle temporary id in transport bundle",
path, i, getAbsoluteId(resource).getValue());
}
else
{
logger.warn("Removing reference at {}[{}] from resource {} in transport bundle", path, i,
Expand Down Expand Up @@ -342,9 +377,10 @@ private <R extends DomainResource, C> R fixReferenceFromComponents(R resource, M
i, getAbsoluteId(resource).getValue());
setReference.apply(component, new Reference(uuidsById.get(oldReference.getReference())));
}
else if (oldReference.hasReference() && oldReference.getReference() == null
else if ((oldReference.hasReference() && oldReference.getReference() == null
&& oldReference.getReferenceElement_()
.hasExtension("http://hl7.org/fhir/StructureDefinition/data-absent-reason"))
|| oldReference.hasExtension("http://hl7.org/fhir/StructureDefinition/data-absent-reason"))
{
logger.debug("Not removing empty reference at " + path + " with data-absent-reason extension", i);
}
Expand Down Expand Up @@ -645,7 +681,6 @@ else if (resource instanceof Observation o)
cleanUnsupportedReferences(o, "Observation.focus", Observation::hasFocus, Observation::setFocus);
cleanUnsupportedReferences(o, "Observation.performer", Observation::hasPerformer,
Observation::setPerformer);
cleanUnsupportedReference(o, "Observation.specimen", Observation::hasSpecimen, Observation::setSpecimen);
cleanUnsupportedReference(o, "Observation.device", Observation::hasDevice, Observation::setDevice);
cleanUnsupportedReferences(o, "Observation.derivedFrom", Observation::hasDerivedFrom,
Observation::setDerivedFrom);
Expand Down Expand Up @@ -674,6 +709,20 @@ else if (resource instanceof Procedure p)
cleanUnsupportedReferences(p, "Procedure.usedReference", Procedure::hasUsedReference,
Procedure::setUsedReference);
}
else if (resource instanceof Specimen s)
{
cleanUnsupportedReferences(s, "Specimen.request", Specimen::hasRequest, Specimen::setRequest);
cleanUnsupportedReferenceFromComponent(s, "Specimen.collection.collector", Specimen::hasCollection,
Specimen::getCollection, Specimen.SpecimenCollectionComponent::hasCollector,
Specimen.SpecimenCollectionComponent::setCollector);
cleanUnsupportedReferencesFromComponents(s, "Specimen.processing[{}].additive", Specimen::hasProcessing,
Specimen::getProcessing, Specimen.SpecimenProcessingComponent::hasAdditive,
Specimen.SpecimenProcessingComponent::setAdditive);
cleanUnsupportedReferenceFromComponents(s, "Specimen.container[{}].additiveReference",
Specimen::hasContainer, Specimen::getContainer,
Specimen.SpecimenContainerComponent::hasAdditiveReference,
Specimen.SpecimenContainerComponent::setAdditive);
}
else
throw new RuntimeException("Resource of type " + resource.getResourceType().name() + " not supported");
}
Expand Down Expand Up @@ -741,10 +790,21 @@ else if (resource instanceof Procedure p)
p.setIdentifier(Collections.emptyList());
p.setSubject(patientRef);
}
else if (resource instanceof Specimen s)
{
s.setIdentifier(Collections.emptyList());
s.setAccessionIdentifier(null);
s.setSubject(patientRef);
}
else
throw new RuntimeException("Resource of type " + resource.getResourceType().name() + " not supported");
}

if (resource instanceof DomainResource d)
d.getContained().forEach(r -> setSubjectOrIdentifier(r, pseudonym));
else
throw new RuntimeException("Resource of type " + resource.getResourceType().name() + " not supported");

return resource;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

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_ERROR_VALUE_NO_DIC_PSEUDONYM_FOR_BLOOMFILTER;
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_NO_DIC_PSEUDONYM_FOR_LOCAL_PSEUDONYM;
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_PATIENT_NOT_FOUND;
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.IDENTIFIER_NUM_CODEX_DIC_PSEUDONYM_TYPE_CODE;
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_BLOOM_FILTER;
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM;
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.RFC_4122_SYSTEM;

import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -94,8 +96,33 @@ private Optional<String> getPseudonym(Patient patient)

private String resolvePseudonymAndUpdatePatient(Patient patient)
{
String bloomFilter = getBloomFilter(patient);
String pseudonym = resolveBloomFilter(bloomFilter);
String pseudonym;
// first try to find a bloom filter
Optional<String> bloomFilter = getBloomFilter(patient);
if (bloomFilter.isPresent())
{
pseudonym = resolveBloomFilter(bloomFilter.get());
}
else
{
// otherwise try to find a local pseudonym
// --> no record linkage
logger.info(
"No bloom filter present for patient {}. Try to use the local pseudonym for data transfer without record linkage",
patient.getIdElement().getValue());
Optional<String> localPseudonym = getLocalPseudonym(patient);
if (localPseudonym.isPresent())
{
pseudonym = resolveLocalPseudonym(localPseudonym.get());
}
else
{
logger.info("No local pseudonym present for patient {}. Aborted", patient.getIdElement().getValue());
throw new RuntimeException("Could not find pseudonym");
}


}

patient.getIdentifier().removeIf(i -> NAMING_SYSTEM_NUM_CODEX_BLOOM_FILTER.equals(i.getSystem()));
patient.addIdentifier().setSystem(NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM).setValue(pseudonym).getType()
Expand All @@ -107,12 +134,18 @@ private String resolvePseudonymAndUpdatePatient(Patient patient)
return pseudonym;
}

private String getBloomFilter(Patient patient)
private Optional<String> getBloomFilter(Patient patient)
{
return patient.getIdentifier().stream().filter(Identifier::hasSystem)
.filter(i -> NAMING_SYSTEM_NUM_CODEX_BLOOM_FILTER.equals(i.getSystem())).filter(Identifier::hasValue)
.findFirst().map(Identifier::getValue).orElseThrow(() -> new RuntimeException(
"No bloom filter present in patient " + patient.getIdElement().getValue()));
.findFirst().map(Identifier::getValue);
}

private Optional<String> getLocalPseudonym(Patient patient)
{
return patient.getIdentifier().stream().filter(Identifier::hasSystem)
.filter(i -> RFC_4122_SYSTEM.equals(i.getSystem())).filter(Identifier::hasValue).findFirst()
schwzr marked this conversation as resolved.
Show resolved Hide resolved
.map(Identifier::getValue);
}

private String resolveBloomFilter(String bloomFilter)
Expand All @@ -123,6 +156,15 @@ private String resolveBloomFilter(String bloomFilter)
"Unable to get DIC pseudonym for given BloomFilter"));
}

private String resolveLocalPseudonym(String localPseudonym)
{
return fttpClientFactory.getFttpClient().getDicPseudonymForLocalPseudonym(localPseudonym)
.orElseThrow(() -> new BpmnError(
CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_NO_DIC_PSEUDONYM_FOR_LOCAL_PSEUDONYM,
"Unable to get DIC pseudonym for given localPseudonym"));
}


private void updatePatient(Patient patient)
{
dataStoreClientFactory.getDataStoreClient().getFhirClient().updatePatient(patient);
Expand Down
Loading
Loading