From 004e1021c8156bf81f85ac5ad1ef6d260392dc6f Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 22 Oct 2025 13:09:50 +0200 Subject: Merge pull request #154 --- .../etl/processor/consent/GicsConsentService.java | 226 +++++---- .../processor/consent/GicsConsentServiceTest.java | 177 ++++--- .../etl/processor/services/ConsentProcessorTest.kt | 2 +- .../fake_broadConsent_mii_response_permit.json | 513 +++++++++++++++++++++ 4 files changed, 760 insertions(+), 158 deletions(-) create mode 100644 src/test/resources/fake_broadConsent_mii_response_permit.json diff --git a/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java b/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java index 95e8e8f..de722e6 100644 --- a/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java +++ b/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java @@ -4,6 +4,8 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.DataFormatException; import dev.dnpm.etl.processor.config.AppFhirConfig; import dev.dnpm.etl.processor.config.GIcsConfigProperties; +import kotlin.random.Random; +import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; @@ -34,6 +36,8 @@ public class GicsConsentService implements IConsentService { public static final String IS_CONSENTED_ENDPOINT = "/$isConsented"; public static final String IS_POLICY_STATES_FOR_PERSON_ENDPOINT = "/$currentPolicyStatesForPerson"; + private static final String BROAD_CONSENT_PROFILE_URI = "https://www.medizininformatik-initiative.de/fhir/modul-consent/StructureDefinition/mii-pr-consent-einwilligung"; + private static final String BROAD_CONSENT_POLICY = "urn:oid:2.16.840.1.113883.3.1937.777.24.2.1791"; private final RetryTemplate retryTemplate; private final RestTemplate restTemplate; @@ -41,10 +45,10 @@ public class GicsConsentService implements IConsentService { private final GIcsConfigProperties gIcsConfigProperties; public GicsConsentService( - GIcsConfigProperties gIcsConfigProperties, - RetryTemplate retryTemplate, - RestTemplate restTemplate, - AppFhirConfig appFhirConfig + GIcsConfigProperties gIcsConfigProperties, + RetryTemplate retryTemplate, + RestTemplate restTemplate, + AppFhirConfig appFhirConfig ) { this.retryTemplate = retryTemplate; this.restTemplate = restTemplate; @@ -54,34 +58,34 @@ public class GicsConsentService implements IConsentService { } protected Parameters getFhirRequestParameters( - String personIdentifierValue + String personIdentifierValue ) { var result = new Parameters(); result.addParameter( - new ParametersParameterComponent() - .setName("personIdentifier") - .setValue( - new Identifier() - .setValue(personIdentifierValue) - .setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem()) - ) + new ParametersParameterComponent() + .setName("personIdentifier") + .setValue( + new Identifier() + .setValue(personIdentifierValue) + .setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem()) + ) ); result.addParameter( - new ParametersParameterComponent() - .setName("domain") - .setValue( - new StringType() - .setValue(this.gIcsConfigProperties.getBroadConsentDomainName()) - ) + new ParametersParameterComponent() + .setName("domain") + .setValue( + new StringType() + .setValue(this.gIcsConfigProperties.getBroadConsentDomainName()) + ) ); result.addParameter( - new ParametersParameterComponent() - .setName("policy") - .setValue( - new Coding() - .setCode(this.gIcsConfigProperties.getBroadConsentPolicyCode()) - .setSystem(this.gIcsConfigProperties.getBroadConsentPolicySystem()) - ) + new ParametersParameterComponent() + .setName("policy") + .setValue( + new Coding() + .setCode(this.gIcsConfigProperties.getBroadConsentPolicyCode()) + .setSystem(this.gIcsConfigProperties.getBroadConsentPolicySystem()) + ) ); /* @@ -89,10 +93,10 @@ public class GicsConsentService implements IConsentService { * 'ignoreVersionNumber'. */ result.addParameter( - new ParametersParameterComponent() - .setName("version") - .setValue(new StringType().setValue("1.1") - ) + new ParametersParameterComponent() + .setName("version") + .setValue(new StringType().setValue("1.1") + ) ); /* add config parameter with: @@ -101,17 +105,17 @@ public class GicsConsentService implements IConsentService { * unknownStateIsConsideredAsDecline -> true */ var config = new ParametersParameterComponent() - .setName("config") - .addPart( - new ParametersParameterComponent() - .setName("ignoreVersionNumber") - .setValue(new BooleanType().setValue(true)) - ) - .addPart( - new ParametersParameterComponent() - .setName("unknownStateIsConsideredAsDecline") - .setValue(new BooleanType().setValue(false)) - ); + .setName("config") + .addPart( + new ParametersParameterComponent() + .setName("ignoreVersionNumber") + .setValue(new BooleanType().setValue(true)) + ) + .addPart( + new ParametersParameterComponent() + .setName("unknownStateIsConsideredAsDecline") + .setValue(new BooleanType().setValue(false)) + ); result.addParameter(config); @@ -130,8 +134,8 @@ public class GicsConsentService implements IConsentService { headers.setContentType(MediaType.APPLICATION_XML); if ( - StringUtils.isBlank(this.gIcsConfigProperties.getUsername()) - || StringUtils.isBlank(this.gIcsConfigProperties.getPassword()) + StringUtils.isBlank(this.gIcsConfigProperties.getUsername()) + || StringUtils.isBlank(this.gIcsConfigProperties.getPassword()) ) { return headers; } @@ -145,28 +149,28 @@ public class GicsConsentService implements IConsentService { HttpEntity requestEntity = new HttpEntity<>(parameterAsXml, this.headersWithHttpBasicAuth()); try { var responseEntity = retryTemplate.execute( - ctx -> restTemplate.exchange(endpointUri(endpoint), HttpMethod.POST, requestEntity, String.class) + ctx -> restTemplate.exchange(endpointUri(endpoint), HttpMethod.POST, requestEntity, String.class) ); if (responseEntity.getStatusCode().is2xxSuccessful()) { return responseEntity.getBody(); } else { var msg = String.format( - "Trusted party system reached but request failed! code: '%s' response: '%s'", - responseEntity.getStatusCode(), responseEntity.getBody()); + "Trusted party system reached but request failed! code: '%s' response: '%s'", + responseEntity.getStatusCode(), responseEntity.getBody()); log.error(msg); return null; } } catch (RestClientException e) { var msg = String.format("Get consents status request failed reason: '%s", - e.getMessage()); + e.getMessage()); log.error(msg); return null; } catch (TerminatedRetryException terminatedRetryException) { var msg = String.format( - "Get consents status process has been terminated. termination reason: '%s", - terminatedRetryException.getMessage()); + "Get consents status process has been terminated. termination reason: '%s", + terminatedRetryException.getMessage()); log.error(msg); return null; } @@ -175,45 +179,45 @@ public class GicsConsentService implements IConsentService { @Override public TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue) { var consentStatusResponse = callGicsApi( - getFhirRequestParameters(personIdentifierValue), - GicsConsentService.IS_CONSENTED_ENDPOINT + getFhirRequestParameters(personIdentifierValue), + GicsConsentService.IS_CONSENTED_ENDPOINT ); return evaluateConsentResponse(consentStatusResponse); } protected Bundle currentConsentForPersonAndTemplate( - String personIdentifierValue, - ConsentDomain consentDomain, - Date requestDate + String personIdentifierValue, + ConsentDomain consentDomain, + Date requestDate ) { var requestParameter = buildRequestParameterCurrentPolicyStatesForPerson( - personIdentifierValue, - requestDate, - consentDomain + personIdentifierValue, + requestDate, + consentDomain ); var consentDataSerialized = callGicsApi(requestParameter, - GicsConsentService.IS_POLICY_STATES_FOR_PERSON_ENDPOINT); + GicsConsentService.IS_POLICY_STATES_FOR_PERSON_ENDPOINT); if (consentDataSerialized == null) { // error occurred - should not process further! throw new IllegalStateException( - "consent data request failed - stopping processing! - try again or fix other problems first."); + "consent data request failed - stopping processing! - try again or fix other problems first."); } var iBaseResource = fhirContext.newJsonParser() - .parseResource(consentDataSerialized); + .parseResource(consentDataSerialized); if (iBaseResource instanceof OperationOutcome) { // log error - very likely a configuration error String errorMessage = - "Consent request failed! Check outcome:\n " + consentDataSerialized; + "Consent request failed! Check outcome:\n " + consentDataSerialized; log.error(errorMessage); throw new IllegalStateException(errorMessage); } else if (iBaseResource instanceof Bundle bundle) { return bundle; } else { String errorMessage = "Consent request failed! Unexpected response received! -> " - + consentDataSerialized; + + consentDataSerialized; log.error(errorMessage); throw new IllegalStateException(errorMessage); } @@ -228,43 +232,43 @@ public class GicsConsentService implements IConsentService { } protected Parameters buildRequestParameterCurrentPolicyStatesForPerson( - String personIdentifierValue, - Date requestDate, - ConsentDomain consentDomain + String personIdentifierValue, + Date requestDate, + ConsentDomain consentDomain ) { var requestParameter = new Parameters(); requestParameter.addParameter( - new ParametersParameterComponent() - .setName("personIdentifier") - .setValue( - new Identifier() - .setValue(personIdentifierValue) - .setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem()) - ) + new ParametersParameterComponent() + .setName("personIdentifier") + .setValue( + new Identifier() + .setValue(personIdentifierValue) + .setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem()) + ) ); requestParameter.addParameter( - new ParametersParameterComponent() - .setName("domain") - .setValue(new StringType().setValue(getConsentDomainName(consentDomain))) + new ParametersParameterComponent() + .setName("domain") + .setValue(new StringType().setValue(getConsentDomainName(consentDomain))) ); Parameters nestedConfigParameters = new Parameters(); nestedConfigParameters - .addParameter( - new ParametersParameterComponent() - .setName("idMatchingType") - .setValue(new Coding() - .setSystem("https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType") - .setCode("AT_LEAST_ONE") - ) - ) - .addParameter("ignoreVersionNumber", false) - .addParameter("unknownStateIsConsideredAsDecline", false) - .addParameter("requestDate", new DateType().setValue(requestDate)); + .addParameter( + new ParametersParameterComponent() + .setName("idMatchingType") + .setValue(new Coding() + .setSystem("https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType") + .setCode("AT_LEAST_ONE") + ) + ) + .addParameter("ignoreVersionNumber", false) + .addParameter("unknownStateIsConsideredAsDecline", false) + .addParameter("requestDate", new DateType().setValue(requestDate)); requestParameter.addParameter( - new ParametersParameterComponent().setName("config").addPart().setResource(nestedConfigParameters) + new ParametersParameterComponent().setName("config").addPart().setResource(nestedConfigParameters) ); return requestParameter; @@ -291,7 +295,7 @@ public class GicsConsentService implements IConsentService { } } else if (response instanceof OperationOutcome outcome) { log.error("failed to get consent status from ttp. probably configuration error. " - + "outcome: '{}'", fhirContext.newJsonParser().encodeToString(outcome)); + + "outcome: '{}'", fhirContext.newJsonParser().encodeToString(outcome)); } } catch (DataFormatException dfe) { @@ -302,6 +306,52 @@ public class GicsConsentService implements IConsentService { @Override public Bundle getConsent(String patientId, Date requestDate, ConsentDomain consentDomain) { - return currentConsentForPersonAndTemplate(patientId, consentDomain, requestDate); + Bundle gIcsResultBundle = currentConsentForPersonAndTemplate(patientId, consentDomain, requestDate); + if (ConsentDomain.BROAD_CONSENT == consentDomain) { + return anonymizeBroadConsent(convertGicsResultToMiiBroadConsent(gIcsResultBundle)); + } + return gIcsResultBundle; + } + + protected Bundle convertGicsResultToMiiBroadConsent(Bundle gIcsResultBundle) { + if (gIcsResultBundle == null + || gIcsResultBundle.getEntry().isEmpty() + || !(gIcsResultBundle.getEntry().getFirst().getResource() instanceof Consent)) + return gIcsResultBundle; + + Bundle.BundleEntryComponent bundleEntryComponent = gIcsResultBundle.getEntry().getFirst(); + + var consentAsOne = (Consent) bundleEntryComponent.getResource(); + if (consentAsOne.getPolicy().stream().noneMatch(p -> p.getUri().equals(BROAD_CONSENT_POLICY))) { + consentAsOne.addPolicy(new Consent.ConsentPolicyComponent().setUri(BROAD_CONSENT_POLICY)); + } + + if (consentAsOne.getMeta().getProfile().stream().noneMatch(p -> p.getValue().equals(BROAD_CONSENT_PROFILE_URI))) { + consentAsOne.getMeta().addProfile(BROAD_CONSENT_PROFILE_URI); + } + + consentAsOne.setPolicyRule(null); + + gIcsResultBundle.getEntry().stream().skip(1).forEach(c -> consentAsOne.getProvision().addProvision(((Consent) c.getResource()).getProvision().getProvisionFirstRep())); + + gIcsResultBundle.getEntry().clear(); + gIcsResultBundle.addEntry(bundleEntryComponent); + return gIcsResultBundle; + } + + protected Bundle anonymizeBroadConsent(Bundle bundle) { + Bundle.BundleEntryComponent bundleEntryComponent = bundle.getEntry().getFirst(); + hashBundleEntry(bundleEntryComponent); + return bundle; + } + + private static void hashBundleEntry(Bundle.BundleEntryComponent entry) { + String id = entry.getResource().getIdPart(); + var hash = DigestUtils.sha256Hex("%s_%s".formatted(Random.Default.toString(), id)); + + entry.getResource().setId(hash); + entry.setFullUrl(entry.getFullUrl().replace(id, hash)); + var consent = (Consent) entry.getResource(); + consent.getSource().setProperty("reference", new StringType("QuestionnaireResponse/%s".formatted(hash))); } } diff --git a/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java b/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java index 6fa8f08..02b4d37 100644 --- a/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java +++ b/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java @@ -1,9 +1,11 @@ package dev.dnpm.etl.processor.consent; +import ca.uhn.fhir.context.FhirContext; import com.fasterxml.jackson.databind.ObjectMapper; import dev.dnpm.etl.processor.config.AppConfiguration; import dev.dnpm.etl.processor.config.AppFhirConfig; import dev.dnpm.etl.processor.config.GIcsConfigProperties; +import org.apache.commons.io.IOUtils; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.r4.model.OperationOutcome.IssueType; @@ -20,18 +22,21 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; +import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Date; +import java.util.Objects; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; @ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class}) @TestPropertySource(properties = { - "app.consent.service=gics", - "app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics" + "app.consent.service=gics", + "app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics" }) @RestClientTest class GicsConsentServiceTest { @@ -46,8 +51,8 @@ class GicsConsentServiceTest { @BeforeEach void setUp( - @Autowired AppFhirConfig appFhirConfig, - @Autowired GIcsConfigProperties gIcsConfigProperties + @Autowired AppFhirConfig appFhirConfig, + @Autowired GIcsConfigProperties gIcsConfigProperties ) { this.appFhirConfig = appFhirConfig; this.gIcsConfigProperties = gIcsConfigProperties; @@ -56,33 +61,33 @@ class GicsConsentServiceTest { this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate); this.gicsConsentService = new GicsConsentService( - this.gIcsConfigProperties, - RetryTemplate.builder().maxAttempts(1).build(), - restTemplate, - this.appFhirConfig + this.gIcsConfigProperties, + RetryTemplate.builder().maxAttempts(1).build(), + restTemplate, + this.appFhirConfig ); } @Test void shouldReturnTtpBroadConsentStatus() { final Parameters consentedResponse = new Parameters() - .addParameter( - new ParametersParameterComponent() - .setName("consented") - .setValue(new BooleanType().setValue(true)) - ); + .addParameter( + new ParametersParameterComponent() + .setName("consented") + .setValue(new BooleanType().setValue(true)) + ); mockRestServiceServer - .expect( - requestTo( - "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT) - ) - .andRespond( - withSuccess( - appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(consentedResponse), - MediaType.APPLICATION_JSON + .expect( + requestTo( + "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT) ) - ); + .andRespond( + withSuccess( + appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(consentedResponse), + MediaType.APPLICATION_JSON + ) + ); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN); @@ -91,22 +96,22 @@ class GicsConsentServiceTest { @Test void shouldReturnRevokedConsent() { final Parameters revokedResponse = new Parameters() - .addParameter( - new ParametersParameterComponent() - .setName("consented") - .setValue(new BooleanType().setValue(false)) - ); + .addParameter( + new ParametersParameterComponent() + .setName("consented") + .setValue(new BooleanType().setValue(false)) + ); mockRestServiceServer - .expect( - requestTo( - "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT) - ) - .andRespond( - withSuccess( - appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(revokedResponse), - MediaType.APPLICATION_JSON) - ); + .expect( + requestTo( + "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT) + ) + .andRespond( + withSuccess( + appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(revokedResponse), + MediaType.APPLICATION_JSON) + ); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED); @@ -116,23 +121,23 @@ class GicsConsentServiceTest { @Test void shouldReturnInvalidParameterResponse() { final OperationOutcome responseWithErrorOutcome = new OperationOutcome() - .addIssue( - new OperationOutcomeIssueComponent() - .setSeverity(IssueSeverity.ERROR) - .setCode(IssueType.PROCESSING) - .setDiagnostics("Invalid policy parameter...") - ); + .addIssue( + new OperationOutcomeIssueComponent() + .setSeverity(IssueSeverity.ERROR) + .setCode(IssueType.PROCESSING) + .setDiagnostics("Invalid policy parameter...") + ); mockRestServiceServer - .expect( - requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT) - ) - .andRespond( - withSuccess( - appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseWithErrorOutcome), - MediaType.APPLICATION_JSON + .expect( + requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT) ) - ); + .andRespond( + withSuccess( + appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseWithErrorOutcome), + MediaType.APPLICATION_JSON + ) + ); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK); @@ -141,12 +146,12 @@ class GicsConsentServiceTest { @Test void shouldReturnRequestError() { mockRestServiceServer - .expect( - requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT) - ) - .andRespond( - withServerError() - ); + .expect( + requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT) + ) + .andRespond( + withServerError() + ); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK); @@ -156,26 +161,60 @@ class GicsConsentServiceTest { void buildRequestParameterCurrentPolicyStatesForPersonTest() { String pid = "12345678"; var result = gicsConsentService - .buildRequestParameterCurrentPolicyStatesForPerson( - pid, - Date.from(Instant.now()), - ConsentDomain.MODELLVORHABEN_64E - ); + .buildRequestParameterCurrentPolicyStatesForPerson( + pid, + Date.from(Instant.now()), + ConsentDomain.MODELLVORHABEN_64E + ); assertThat(result.getParameter()) - .as("should contain 3 parameter resources") - .hasSize(3); + .as("should contain 3 parameter resources") + .hasSize(3); assertThat(((StringType) result.getParameter("domain").getValue()).getValue()) - .isEqualTo( - gIcsConfigProperties.getGenomDeConsentDomainName() - ); + .isEqualTo( + gIcsConfigProperties.getGenomDeConsentDomainName() + ); assertThat(((Identifier) result.getParameter("personIdentifier").getValue()).getValue()) - .isEqualTo( - pid - ); + .isEqualTo( + pid + ); + } + + @Test + void convertGicsResultToMiiBroadConsent() throws Exception { + var fhirJsonParser = FhirContext.forR4().newJsonParser(); + fhirJsonParser.setPrettyPrint(true); + + var gicsInputStream = Objects.requireNonNull( + this.getClass().getClassLoader().getResourceAsStream("fake_broadConsent_gics_response_permit.json") + ); + var gicsConsentBundle = (Bundle) fhirJsonParser.parseResource(IOUtils.toString(gicsInputStream, StandardCharsets.UTF_8)); + + var miiInputStream = Objects.requireNonNull( + this.getClass().getClassLoader().getResourceAsStream("fake_broadConsent_mii_response_permit.json") + ); + var miiConsent = IOUtils.toString(miiInputStream, StandardCharsets.UTF_8); + + var actual = gicsConsentService.convertGicsResultToMiiBroadConsent(gicsConsentBundle); + + assertThat(fhirJsonParser.encodeToString(actual)).isEqualTo(miiConsent); } + @Test + void convertedMiiBroadConsentShouldNotContainPatientId() throws Exception { + var fhirJsonParser = FhirContext.forR4().newJsonParser(); + fhirJsonParser.setPrettyPrint(true); + + var miiInputStream = Objects.requireNonNull( + this.getClass().getClassLoader().getResourceAsStream("fake_broadConsent_mii_response_permit.json") + ); + var miiConsentBundle = (Bundle) fhirJsonParser.parseResource(IOUtils.toString(miiInputStream, StandardCharsets.UTF_8)); + + var currentPatientId = miiConsentBundle.getEntry().getFirst().getResource().getIdPart(); + var actual = gicsConsentService.anonymizeBroadConsent(miiConsentBundle); + assertThat(fhirJsonParser.encodeToString(actual)).doesNotContain(currentPatientId); + } } diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt index 4d414c5..bbc8b1a 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt @@ -80,7 +80,7 @@ class ConsentProcessorTest { val checkResult = consentProcessor.consentGatedCheckAndTryEmbedding(inputMtb) assertThat(checkResult).isTrue - assertThat(inputMtb.metadata.researchConsents).hasSize(26) + assertThat(inputMtb.metadata.researchConsents).isNotEmpty } companion object { diff --git a/src/test/resources/fake_broadConsent_mii_response_permit.json b/src/test/resources/fake_broadConsent_mii_response_permit.json new file mode 100644 index 0000000..e7bea5a --- /dev/null +++ b/src/test/resources/fake_broadConsent_mii_response_permit.json @@ -0,0 +1,513 @@ +{ + "resourceType": "Bundle", + "type": "collection", + "total": 26, + "entry": [ { + "fullUrl": "http://localhost:8080/ttp-fhir/fhir/gics/Consent/7d3456c2-79b1-11f0-ab27-6ed0ed82d0fd", + "resource": { + "resourceType": "Consent", + "id": "7d3456c2-79b1-11f0-ab27-6ed0ed82d0fd", + "meta": { + "lastUpdated": "2025-08-15T11:13:59.143+02:00", + "profile": [ "http://fhir.de/ConsentManagement/StructureDefinition/Consent", "https://www.medizininformatik-initiative.de/fhir/modul-consent/StructureDefinition/mii-pr-consent-einwilligung" ] + }, + "extension": [ { + "url": "http://fhir.de/ConsentManagement/StructureDefinition/DomainReference", + "extension": [ { + "url": "domain", + "valueReference": { + "reference": "ResearchStudy/3c3ffec5-79b1-11f0-ab27-6ed0ed82d0fd" + } + }, { + "url": "status", + "valueCoding": { + "system": "http://hl7.org/fhir/publication-status", + "code": "active" + } + } ] + } ], + "status": "active", + "scope": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/consentscope", + "code": "research" + } ] + }, + "category": [ { + "coding": [ { + "system": "http://loinc.org", + "code": "57016-8" + } ] + }, { + "coding": [ { + "system": "http://fhir.de/ConsentManagement/CodeSystem/ResultType", + "code": "policy" + } ] + } ], + "patient": { + "reference": "Patient/7d2da57f-79b1-11f0-ab27-6ed0ed82d0fd", + "display": "Patienten-ID 644bae7a-56f6-4ee8-b02f-c532e65af5b1" + }, + "dateTime": "2025-08-15T00:00:00+02:00", + "organization": [ { + "display": "MII" + } ], + "sourceReference": { + "reference": "QuestionnaireResponse/7d314bc5-79b1-11f0-ab27-6ed0ed82d0fd" + }, + "policy": [ { + "uri": "urn:oid:2.16.840.1.113883.3.1937.777.24.2.1791" + } ], + "provision": { + "type": "deny", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "provision": [ { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "IDAT_erheben", + "display": "Erfassung neuer identifizierender Daten (IDAT)" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.2", + "display": "IDAT erheben" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "KKDAT_5J_retro_speichern_verarbeiten", + "display": "Retrospektive Krankenkassendaten (KKDAT) aus fünf Jahren vor Einwilligung speichern und codiert verarbeiten zu Zwecken med. Forschung in der verantwortlichen Stelle" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.12", + "display": "KKDAT 5J retrospektiv speichern verarbeiten" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "BIOMAT_wissenschaftlich_nutzen_EU_DSGVO_konform", + "display": "Bereitstellung umcodierter Biomaterialien (BIOMAT) für wissenschaftliche Nutzung und Analysen zu Zwecken med. Forschung an ext. Forscher" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.22", + "display": "BIOMAT wissenschaftlich nutzen EU DSGVO NIVEAU" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "KKDAT_5J_retro_wissenschaftlich_nutzen", + "display": "Bereitstellung umcodierter retrospektiver Krankenkassendaten (KKDAT) für wissenschaftliche Nutzung zu Zwecken med. Forschung an externe Forscher" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.13", + "display": "KKDAT 5J retrospektiv wissenschaftlich nutzen" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "BIOMAT_Analysedaten_zusammenfuehren_Dritte", + "display": "Zusammenführen von auf Biomaterialien (BIOMAT) basierenden Analysedaten mit Analysedaten Dritter, sofern dort ebenfalls eine Einwilligung vorliegt" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.23", + "display": "BIOMAT Analysedaten zusammenfuehren Dritte" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "Rekontaktierung_Zusatzbefund", + "display": "Rekontaktierung bezüglich Zusatzbefund im Rahmen der am Standort dafür entwickelten Prozesse und der im Nutzungsantrag angegebenen Bedingungen" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.31", + "display": "Rekontaktierung Zusatzbefund" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "KKDAT_5J_pro_wissenschaftlich_nutzen", + "display": "Bereitstellung umcodierter prospektiver Krankenkassendaten (KKDAT) aus fünf Jahren ab Einwilligung zu Zwecken med. Forschung an ext. Forscher" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.17", + "display": "KKDAT 5J prospektiv wissenschaftlich nutzen" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "BIOMAT_lagern_verarbeiten", + "display": "Lagerung und Verarbeitung von Biomaterialien innerhalb der verantwortlichen Stelle (BIOMAT)" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.20", + "display": "BIOMAT lagern verarbeiten" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "KKDAT_5J_retro_uebertragen", + "display": "Krankenkassendaten (KKDAT) der letzten fünf Kalenderjahre vor Datum Unterschrift übertragen" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.11", + "display": "KKDAT 5J retrospektiv uebertragen" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "KKDAT_KVNR_5J_retro_uebertragen", + "display": "Erlaubnis zur retrospektiven Übermittlung der KVNr., MII-Pseudonym und Zeitraum Datenübermittlung (von:5 Jahre vor Datum Unterschrift; bis: Datum Unterschrift) an zuständige Stelle" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.38", + "display": "KKDAT 5J retrospektiv uebertragen KVNR" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2030-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "MDAT_erheben", + "display": "Erfassung medizinischer Daten (MDAT)" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.6", + "display": "MDAT erheben" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "KKDAT_5J_pro_speichern_verarbeiten", + "display": "Prospektive Krankenkassendaten (KKDAT) aus fünf Jahren ab Einwilligung speichern und codiert verarbeiten zu Zwecken der med. Forschung in der verantwortlichen Stelle" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.16", + "display": "KKDAT 5J prospektiv speichern verarbeiten" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "MDAT_wissenschaftlich_nutzen_EU_DSGVO_konform", + "display": "Bereitstellung umcodierter medizinischer Daten (MDAT) für wissenschaftliche Nutzung zu Zwecken med. Forschung an externe Forscher" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.8", + "display": "MDAT wissenschaftlich nutzen EU DSGVO NIVEAU" + } ] + } ] + }, { + "type": "deny", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "Rekontaktierung_Verknuepfung_Datenbanken", + "display": "Rekontaktierung zur Verknüpfung von Patientendaten mit Daten anderer Datenbanken" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.27", + "display": "Rekontaktierung Verknüpfung Datenbanken" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "Rekontaktierung_weitere_Studien", + "display": "Rekontaktierung bezüglich Information zu neuen Forschungsvorhaben oder Studien" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.29", + "display": "Rekontaktierung weitere Studien" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "IDAT_bereitstellen_EU_DSGVO_konform", + "display": "Herausgabe identifizierender Daten (IDAT) an unabhängige Treuhandstelle zur weiteren Verarbeitung" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.5", + "display": "IDAT bereitstellen EU DSGVO NIVEAU" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "IDAT_speichern_verarbeiten", + "display": "Speicherung und Verarbeitung identifizierender Daten (IDAT) zu Zwecken med. Forschung in der verantwortlichen Stelle" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.3", + "display": "IDAT speichern, verarbeiten" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "MDAT_speichern_verarbeiten", + "display": "Speicherung und Verarbeitung von medizinischen codierten Daten zu Zwecken med. Forschung innerhalb der verantwortlichen Stelle (MDAT)" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.7", + "display": "MDAT speichern, verarbeiten" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2030-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "BIOMAT_erheben", + "display": "Gewinnung von Biomaterialien (BIOMAT)" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.19", + "display": "BIOMAT erheben" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "Rekontaktierung_Ergebnisse_erheblicher_Bedeutung", + "display": "Rekontaktierung des Betroffenen bei Ergebnissen von erheblicher Bedeutung" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.37", + "display": "Rekontaktierung Ergebnisse erheblicher Bedeutung" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2030-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "KKDAT_KVNR_5J_pro_uebertragen", + "display": "Erlaubnis zur prospektiven Übermittlung der KVNr., MII-Pseudonym und Zeitraum Datenübermittlung (von: Datum Unterschrift; bis: max. 5 Kalenderjahre nach Unterschrift) an zuständige Stelle" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.39", + "display": "KKDAT 5J prospektiv uebertragen KVNR" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2030-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "BIOMAT_Zusatzmengen_entnehmen", + "display": "Entnahme zusätzlicher Mengen von Biomaterialien (BIOMAT) in den in der Einwilligung beschriebenen Grenzen" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.25", + "display": "BIOMAT Zusatzmengen entnehmen" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "Rekontaktierung_weitere_Erhebung", + "display": "Rekontaktierung bezüglich Erhebung zusätzlicher Daten" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.28", + "display": "Rekontaktierung weitere Erhebung" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "IDAT_zusammenfuehren_Dritte", + "display": "Zusammenführung identifizierender Daten (IDAT) über die unabhängige Treuhandstelle mit Dritten Forschungspartnern, sofern dort eine Einwilligung vorliegt" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.4", + "display": "IDAT zusammenfuehren Dritte" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2055-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy", + "code": "MDAT_zusammenfuehren_Dritte", + "display": "Zusammenführung medizinischer Daten (MDAT) mit Dritten Forschungspartnern, sofern dort eine Einwilligung vorliegt" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.9", + "display": "MDAT zusammenfuehren Dritte" + } ] + } ] + }, { + "type": "permit", + "period": { + "start": "2025-08-15T00:00:00+02:00", + "end": "2030-08-15T00:00:00+02:00" + }, + "code": [ { + "coding": [ { + "system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/MII", + "code": "KKDAT_5J_pro_uebertragen", + "display": "Prospektive Krankenkassendaten (KKDAT) für fünf Kalenderjahre nach Datum Unterschrift übertragen" + }, { + "system": "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3", + "code": "2.16.840.1.113883.3.1937.777.24.5.3.15", + "display": "KKDAT 5J prospektiv uebertragen" + } ] + } ] + } ] + } + } + } ] +} \ No newline at end of file -- cgit v1.2.3