From 9ad4466d696723398ca25097cd3a65f13dbae35d Mon Sep 17 00:00:00 2001 From: Jakub Lidke Date: Wed, 25 Jun 2025 16:16:35 +0200 Subject: [PATCH] feat: Broad Consent and GenomDE Consent can be embedded into mtb file --- .../processor/consent/GicsConsentService.java | 4 +- .../processor/services/RequestProcessor.kt | 88 ++++++++++++++++++- .../etl/processor/pseudonym/ExtensionsTest.kt | 13 ++- .../services/RequestProcessorTest.kt | 10 ++- 4 files changed, 103 insertions(+), 12 deletions(-) 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 2b6172f..1403c59 100644 --- a/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java +++ b/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java @@ -214,13 +214,11 @@ public class GicsConsentService implements ICheckConsent { } public Bundle getBroadConsent(String personIdentifierValue, Date requestDate) { - String consentDomainName = gIcsConfigProperties.getBroadConsentDomainName(); return currentConsentForPersonAndTemplate(personIdentifierValue, ConsentDomain.BroadConsent, requestDate); } - public Bundle getGnomDeConsent(String personIdentifierValue, Date requestDate) { - + public Bundle getGenomDeConsent(String personIdentifierValue, Date requestDate) { return currentConsentForPersonAndTemplate(personIdentifierValue, ConsentDomain.Modelvorhaben64e, requestDate); } diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt index edc4288..d89bb86 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import de.ukw.ccc.bwhc.dto.MtbFile import dev.dnpm.etl.processor.* import dev.dnpm.etl.processor.config.AppConfigProperties +import dev.dnpm.etl.processor.consent.GicsConsentService import dev.dnpm.etl.processor.consent.TtpConsentStatus import dev.dnpm.etl.processor.monitoring.Report import dev.dnpm.etl.processor.monitoring.Request @@ -32,9 +33,17 @@ import dev.dnpm.etl.processor.output.* import dev.dnpm.etl.processor.pseudonym.PseudonymizeService import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith +import dev.pcvolkmer.mv64e.mtb.ConsentProvision +import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent +import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose import dev.pcvolkmer.mv64e.mtb.Mtb +import dev.pcvolkmer.mv64e.mtb.MvhMetadata +import dev.pcvolkmer.mv64e.mtb.Provision import org.apache.commons.codec.binary.Base32 import org.apache.commons.codec.digest.DigestUtils +import org.hl7.fhir.instance.model.api.IBaseResource +import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.Consent import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Service import java.time.Instant @@ -48,7 +57,8 @@ class RequestProcessor( private val requestService: RequestService, private val objectMapper: ObjectMapper, private val applicationEventPublisher: ApplicationEventPublisher, - private val appConfigProperties: AppConfigProperties + private val appConfigProperties: AppConfigProperties, + private val gicsConsentService: GicsConsentService? ) { fun processMtbFile(mtbFile: MtbFile) { @@ -69,12 +79,84 @@ class RequestProcessor( fun processMtbFile(mtbFile: Mtb, requestId: RequestId) { val pid = PatientId(mtbFile.patient.id) + + addConsentToMtb(mtbFile) mtbFile pseudonymizeWith pseudonymizeService mtbFile anonymizeContentWith pseudonymizeService val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile)) saveAndSend(request, pid) } + fun addConsentToMtb(mtbFile: Mtb) { + if (gicsConsentService == null) return + // init metadata if necessary + if (mtbFile.metadata == null) { + val mvhMetadata = MvhMetadata.builder().build(); + mtbFile.metadata = mvhMetadata + if (mtbFile.metadata.researchConsents == null) { + mtbFile.metadata.researchConsents = mutableListOf() + } + if (mtbFile.metadata.modelProjectConsent == null) { + mtbFile.metadata.modelProjectConsent = ModelProjectConsent() + mtbFile.metadata.modelProjectConsent.provisions = mutableListOf() + } + } + + // fixme Date should be extracted from mtbFile + val consentGnomeDe = + gicsConsentService.getGenomDeConsent(mtbFile.patient.id, Date.from(Instant.now())) + addGenomeDbProvisions(mtbFile, consentGnomeDe) + + // fixme Date should be extracted from mtbFile + val broadConsent = + gicsConsentService.getBroadConsent(mtbFile.patient.id, Date.from(Instant.now())) + embedBroadConsentResources(mtbFile, broadConsent) + } + + fun embedBroadConsentResources( + mtbFile: Mtb, + broadConsent: Bundle + ) { + broadConsent.entry.forEach { it -> + mtbFile.metadata.researchConsents.add(mapOf(it.resource.id to it as IBaseResource)) + } + } + + fun addGenomeDbProvisions( + mtbFile: Mtb, + consentGnomeDe: Bundle + ) { + consentGnomeDe.entry.forEach { it -> + { + val consent = it.resource as Consent + val provisionComponent = consent.provision.provision.firstOrNull() + val provisionCode = + provisionComponent?.code?.firstOrNull()?.coding?.firstOrNull()?.code + var isValidCode = true + if (provisionCode != null) { + var modelProjectConsentPurpose: ModelProjectConsentPurpose = + ModelProjectConsentPurpose.SEQUENCING + if (provisionCode == "Teilnahme") { + modelProjectConsentPurpose = ModelProjectConsentPurpose.SEQUENCING + } else if (provisionCode == "Fallidentifizierung") { + modelProjectConsentPurpose = ModelProjectConsentPurpose.CASE_IDENTIFICATION + } else if (provisionCode == "Rekontaktierung") { + modelProjectConsentPurpose = ModelProjectConsentPurpose.REIDENTIFICATION + } else { + isValidCode = false + } + if (isValidCode) mtbFile.metadata.modelProjectConsent.provisions.add( + Provision.builder().type( + ConsentProvision.forValue(provisionComponent.type.name) + ).date(provisionComponent.period.start).purpose( + modelProjectConsentPurpose + ).build() + ) + } + } + } + } + private fun saveAndSend(request: MtbFileRequest, pid: PatientId) { requestService.save( Request( @@ -126,7 +208,9 @@ class RequestProcessor( return null != lastMtbFileRequestForPatient && !isLastRequestDeletion - && lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFileRequest) + && lastMtbFileRequestForPatient.fingerprint == fingerprint( + pseudonymizedMtbFileRequest + ) } fun processDeletion(patientId: PatientId, isConsented: TtpConsentStatus) { diff --git a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt index e30ee73..7f517b9 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt @@ -22,6 +22,7 @@ package dev.dnpm.etl.processor.pseudonym import com.fasterxml.jackson.databind.ObjectMapper import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.Patient +import dev.dnpm.etl.processor.config.JacksonConfig import dev.pcvolkmer.mv64e.mtb.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested @@ -39,6 +40,9 @@ import java.util.* @ExtendWith(MockitoExtension::class) class ExtensionsTest { + fun getObjectMapper() : ObjectMapper { + return JacksonConfig().objectMapper() + } @Nested inner class UsingBwhcDatamodel { @@ -46,13 +50,14 @@ class ExtensionsTest { val FAKE_MTB_FILE_PATH = "fake_MTBFile.json" val CLEAN_PATIENT_ID = "5dad2f0b-49c6-47d8-a952-7b9e9e0f7549" + private fun fakeMtbFile(): MtbFile { val mtbFile = ClassPathResource(FAKE_MTB_FILE_PATH).inputStream - return ObjectMapper().readValue(mtbFile, MtbFile::class.java) + return getObjectMapper().readValue(mtbFile, MtbFile::class.java) } private fun MtbFile.serialized(): String { - return ObjectMapper().writeValueAsString(this) + return getObjectMapper().writeValueAsString(this) } @Test @@ -211,11 +216,11 @@ class ExtensionsTest { private fun fakeMtbFile(): Mtb { val mtbFile = ClassPathResource(FAKE_MTB_FILE_PATH).inputStream - return ObjectMapper().readValue(mtbFile, Mtb::class.java) + return getObjectMapper().readValue(mtbFile, Mtb::class.java) } private fun Mtb.serialized(): String { - return ObjectMapper().writeValueAsString(this) + return getObjectMapper().writeValueAsString(this) } @Test diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt index f443243..34e6a66 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt @@ -25,6 +25,7 @@ import dev.dnpm.etl.processor.Fingerprint import dev.dnpm.etl.processor.PatientId import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.config.AppConfigProperties +import dev.dnpm.etl.processor.consent.GicsConsentService import dev.dnpm.etl.processor.consent.TtpConsentStatus import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.RequestStatus @@ -59,7 +60,7 @@ class RequestProcessorTest { private lateinit var requestService: RequestService private lateinit var applicationEventPublisher: ApplicationEventPublisher private lateinit var appConfigProperties: AppConfigProperties - + private lateinit var gicsConsentService : GicsConsentService private lateinit var requestProcessor: RequestProcessor @BeforeEach @@ -68,7 +69,8 @@ class RequestProcessorTest { @Mock transformationService: TransformationService, @Mock sender: RestMtbFileSender, @Mock requestService: RequestService, - @Mock applicationEventPublisher: ApplicationEventPublisher + @Mock applicationEventPublisher: ApplicationEventPublisher, + @Mock gicsConsentService: GicsConsentService ) { this.pseudonymizeService = pseudonymizeService this.transformationService = transformationService @@ -76,6 +78,7 @@ class RequestProcessorTest { this.requestService = requestService this.applicationEventPublisher = applicationEventPublisher this.appConfigProperties = AppConfigProperties(null) + this.gicsConsentService = gicsConsentService val objectMapper = ObjectMapper() @@ -86,7 +89,8 @@ class RequestProcessorTest { requestService, objectMapper, applicationEventPublisher, - appConfigProperties + appConfigProperties, + gicsConsentService ) }