mirror of
https://github.com/pcvolkmer/etl-processor.git
synced 2025-07-17 12:52:54 +00:00
fix: pseudonymize patient reference at embedded consent resources
This commit is contained in:
@ -21,7 +21,9 @@ package dev.dnpm.etl.processor.pseudonym
|
|||||||
|
|
||||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||||
import dev.dnpm.etl.processor.PatientId
|
import dev.dnpm.etl.processor.PatientId
|
||||||
|
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent
|
||||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||||
|
import dev.pcvolkmer.mv64e.mtb.MvhMetadata
|
||||||
import org.apache.commons.codec.digest.DigestUtils
|
import org.apache.commons.codec.digest.DigestUtils
|
||||||
import org.hl7.fhir.r4.model.Consent
|
import org.hl7.fhir.r4.model.Consent
|
||||||
|
|
||||||
@ -291,12 +293,13 @@ infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
|
|||||||
it.patient.id = patientPseudonym
|
it.patient.id = patientPseudonym
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: MUST CREATE TESTCASE - NEEDS TESTING!!
|
this.metadata?.researchConsents?.forEach { it ->
|
||||||
this.metadata?.researchConsents?.forEach { it -> {
|
val entry = it ?: return@forEach
|
||||||
val consent = it as? Consent
|
val key = entry.keys.first()
|
||||||
consent?.patient?.reference = "Patient/$patientPseudonym"
|
val consent = entry[key] as? Consent ?: return@forEach
|
||||||
consent?.patient?.display = null
|
val patRef= "Patient/$patientPseudonym"
|
||||||
}
|
consent.patient?.setReference(patRef)
|
||||||
|
consent.patient?.display = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,3 +329,23 @@ infix fun Mtb.anonymizeContentWith(pseudonymizeService: PseudonymizeService) {
|
|||||||
|
|
||||||
// TODO all other properties
|
// TODO all other properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Mtb.ensureMetaDataIsInitialized() {
|
||||||
|
// init metadata if necessary
|
||||||
|
if (this.metadata == null) {
|
||||||
|
val mvhMetadata = MvhMetadata.builder().build()
|
||||||
|
this.metadata = mvhMetadata
|
||||||
|
}
|
||||||
|
if (this.metadata.researchConsents == null) {
|
||||||
|
this.metadata.researchConsents = mutableListOf()
|
||||||
|
}
|
||||||
|
if (this.metadata.modelProjectConsent == null) {
|
||||||
|
this.metadata.modelProjectConsent = ModelProjectConsent()
|
||||||
|
this.metadata.modelProjectConsent.provisions = mutableListOf()
|
||||||
|
} else
|
||||||
|
if (this.metadata.modelProjectConsent.provisions != null) {
|
||||||
|
// make sure list can be changed
|
||||||
|
this.metadata.modelProjectConsent.provisions =
|
||||||
|
this.metadata.modelProjectConsent.provisions.toMutableList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -33,10 +33,9 @@ import dev.dnpm.etl.processor.monitoring.RequestType
|
|||||||
import dev.dnpm.etl.processor.output.*
|
import dev.dnpm.etl.processor.output.*
|
||||||
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
|
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
|
||||||
import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith
|
import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith
|
||||||
|
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized
|
||||||
import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
|
import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
|
||||||
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent
|
|
||||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||||
import dev.pcvolkmer.mv64e.mtb.MvhMetadata
|
|
||||||
import org.apache.commons.codec.binary.Base32
|
import org.apache.commons.codec.binary.Base32
|
||||||
import org.apache.commons.codec.digest.DigestUtils
|
import org.apache.commons.codec.digest.DigestUtils
|
||||||
import org.hl7.fhir.r4.model.Consent
|
import org.hl7.fhir.r4.model.Consent
|
||||||
@ -97,7 +96,7 @@ class RequestProcessor(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
initMetaDataAtMtbFile(mtbFile)
|
mtbFile.ensureMetaDataIsInitialized()
|
||||||
|
|
||||||
val personIdentifierValue = extractPatientIdentifier(mtbFile)
|
val personIdentifierValue = extractPatientIdentifier(mtbFile)
|
||||||
val requestDate = Date.from(Instant.now(Clock.systemUTC()))
|
val requestDate = Date.from(Instant.now(Clock.systemUTC()))
|
||||||
@ -146,27 +145,6 @@ class RequestProcessor(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun initMetaDataAtMtbFile(mtbFile: Mtb) {
|
|
||||||
// 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()
|
|
||||||
} else
|
|
||||||
if (mtbFile.metadata.modelProjectConsent.provisions != null) {
|
|
||||||
// make sure list can be changed
|
|
||||||
mtbFile.metadata.modelProjectConsent.provisions =
|
|
||||||
mtbFile.metadata.modelProjectConsent.provisions.toMutableList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
|
fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
|
||||||
val pid = PatientId(extractPatientIdentifier(mtbFile))
|
val pid = PatientId(extractPatientIdentifier(mtbFile))
|
||||||
|
|
||||||
|
@ -22,9 +22,15 @@ package dev.dnpm.etl.processor.pseudonym
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import de.ukw.ccc.bwhc.dto.*
|
import de.ukw.ccc.bwhc.dto.*
|
||||||
import de.ukw.ccc.bwhc.dto.Patient
|
import de.ukw.ccc.bwhc.dto.Patient
|
||||||
|
import dev.dnpm.etl.processor.config.GIcsConfigProperties
|
||||||
import dev.dnpm.etl.processor.config.JacksonConfig
|
import dev.dnpm.etl.processor.config.JacksonConfig
|
||||||
|
import dev.dnpm.etl.processor.consent.BaseConsentService
|
||||||
|
import dev.dnpm.etl.processor.consent.ConsentDomain
|
||||||
|
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
||||||
|
import dev.dnpm.etl.processor.services.TransformationServiceTest
|
||||||
import dev.pcvolkmer.mv64e.mtb.*
|
import dev.pcvolkmer.mv64e.mtb.*
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.hl7.fhir.r4.model.Bundle
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
@ -231,6 +237,8 @@ class ExtensionsTest {
|
|||||||
}.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
|
}.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
|
||||||
|
|
||||||
val mtbFile = fakeMtbFile()
|
val mtbFile = fakeMtbFile()
|
||||||
|
mtbFile.ensureMetaDataIsInitialized()
|
||||||
|
addConsentData(CLEAN_PATIENT_ID,mtbFile)
|
||||||
|
|
||||||
mtbFile.pseudonymizeWith(pseudonymizeService)
|
mtbFile.pseudonymizeWith(pseudonymizeService)
|
||||||
|
|
||||||
@ -238,6 +246,39 @@ class ExtensionsTest {
|
|||||||
assertThat(mtbFile.serialized()).doesNotContain(CLEAN_PATIENT_ID)
|
assertThat(mtbFile.serialized()).doesNotContain(CLEAN_PATIENT_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addConsentData(cleanPatientId: String, mtbFile: Mtb) {
|
||||||
|
val gIcsConfigProperties = GIcsConfigProperties("","","", true)
|
||||||
|
|
||||||
|
val baseConsentService = object: BaseConsentService(gIcsConfigProperties){
|
||||||
|
override fun getTtpBroadConsentStatus(personIdentifierValue: String?): TtpConsentStatus? {
|
||||||
|
throw NotImplementedError("dummy")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun currentConsentForPersonAndTemplate(
|
||||||
|
personIdentifierValue: String?,
|
||||||
|
targetConsentDomain: ConsentDomain?,
|
||||||
|
requestDate: Date?
|
||||||
|
): Bundle? {
|
||||||
|
throw NotImplementedError("dummy")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProvisionTypeByPolicyCode(
|
||||||
|
consentBundle: Bundle?,
|
||||||
|
requestDate: Date?,
|
||||||
|
consentDomain: ConsentDomain?
|
||||||
|
): org.hl7.fhir.r4.model.Consent.ConsentProvisionType? {
|
||||||
|
throw NotImplementedError("dummy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bundle = Bundle()
|
||||||
|
val dummyConsent = TransformationServiceTest.getDummyConsent()
|
||||||
|
dummyConsent.patient.reference = "Patient/$cleanPatientId"
|
||||||
|
bundle.addEntry().resource= dummyConsent
|
||||||
|
|
||||||
|
baseConsentService.embedBroadConsentResources(mtbFile,bundle)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldNotThrowExceptionOnNullValues(@Mock pseudonymizeService: PseudonymizeService) {
|
fun shouldNotThrowExceptionOnNullValues(@Mock pseudonymizeService: PseudonymizeService) {
|
||||||
doAnswer {
|
doAnswer {
|
||||||
|
@ -133,18 +133,18 @@ class TransformationServiceTest {
|
|||||||
mvhMetadata.modelProjectConsent =
|
mvhMetadata.modelProjectConsent =
|
||||||
ModelProjectConsent.builder().date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z")))
|
ModelProjectConsent.builder().date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z")))
|
||||||
.version("1").provisions(
|
.version("1").provisions(
|
||||||
listOf(
|
listOf(
|
||||||
Provision.builder().type(ConsentProvision.PERMIT)
|
Provision.builder().type(ConsentProvision.PERMIT)
|
||||||
.purpose(ModelProjectConsentPurpose.SEQUENCING)
|
.purpose(ModelProjectConsentPurpose.SEQUENCING)
|
||||||
.date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build(),
|
.date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build(),
|
||||||
Provision.builder().type(ConsentProvision.PERMIT)
|
Provision.builder().type(ConsentProvision.PERMIT)
|
||||||
.purpose(ModelProjectConsentPurpose.REIDENTIFICATION)
|
.purpose(ModelProjectConsentPurpose.REIDENTIFICATION)
|
||||||
.date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build(),
|
.date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build(),
|
||||||
Provision.builder().type(ConsentProvision.DENY)
|
Provision.builder().type(ConsentProvision.DENY)
|
||||||
.purpose(ModelProjectConsentPurpose.CASE_IDENTIFICATION)
|
.purpose(ModelProjectConsentPurpose.CASE_IDENTIFICATION)
|
||||||
.date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build()
|
.date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build()
|
||||||
)
|
)
|
||||||
).build()
|
).build()
|
||||||
val consent = getDummyConsent()
|
val consent = getDummyConsent()
|
||||||
|
|
||||||
mvhMetadata.researchConsents = mutableListOf()
|
mvhMetadata.researchConsents = mutableListOf()
|
||||||
@ -156,49 +156,52 @@ class TransformationServiceTest {
|
|||||||
assertThat(transformed.metadata.modelProjectConsent.date).isNotNull
|
assertThat(transformed.metadata.modelProjectConsent.date).isNotNull
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun getDummyConsent(): org.hl7.fhir.r4.model.Consent {
|
companion object {
|
||||||
val modelVorhabenConsent = org.hl7.fhir.r4.model.Consent()
|
fun getDummyConsent(): org.hl7.fhir.r4.model.Consent {
|
||||||
modelVorhabenConsent.id = "consent 1 id"
|
val modelVorhabenConsent = org.hl7.fhir.r4.model.Consent()
|
||||||
modelVorhabenConsent.patient.reference = "Patient/1234-pat1"
|
modelVorhabenConsent.id = "consent 1 id"
|
||||||
|
modelVorhabenConsent.patient.reference = "Patient/1234-pat1"
|
||||||
|
|
||||||
modelVorhabenConsent.provision.setType(
|
modelVorhabenConsent.provision.setType(
|
||||||
org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode(
|
org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode(
|
||||||
"deny"
|
"deny"
|
||||||
)
|
)
|
||||||
)
|
|
||||||
modelVorhabenConsent.provision.period.start =
|
|
||||||
Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
|
||||||
modelVorhabenConsent.provision.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
|
||||||
|
|
||||||
|
|
||||||
val addProvision1 = modelVorhabenConsent.provision.addProvision()
|
|
||||||
addProvision1.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("permit"))
|
|
||||||
addProvision1.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
|
||||||
addProvision1.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
|
||||||
addProvision1.code.addLast(
|
|
||||||
CodeableConcept(
|
|
||||||
Coding(
|
|
||||||
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"Teilnahme",
|
|
||||||
"Teilnahme am Modellvorhaben und Einwilligung zur Genomsequenzierung"
|
|
||||||
)
|
)
|
||||||
)
|
modelVorhabenConsent.provision.period.start =
|
||||||
)
|
Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
||||||
|
modelVorhabenConsent.provision.period.end =
|
||||||
|
Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
||||||
|
|
||||||
val addProvision2 = modelVorhabenConsent.provision.addProvision()
|
|
||||||
addProvision2.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("deny"))
|
val addProvision1 = modelVorhabenConsent.provision.addProvision()
|
||||||
addProvision2.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
addProvision1.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("permit"))
|
||||||
addProvision2.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
addProvision1.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
||||||
addProvision2.code.addLast(
|
addProvision1.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
||||||
CodeableConcept(
|
addProvision1.code.addLast(
|
||||||
Coding(
|
CodeableConcept(
|
||||||
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
Coding(
|
||||||
"Rekontaktierung",
|
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
||||||
"Re-Identifizierung meiner Daten über die Vertrauensstelle beim Robert Koch-Institut und in die erneute Kontaktaufnahme durch meine behandelnde Ärztin oder meinen behandelnden Arzt"
|
"Teilnahme",
|
||||||
|
"Teilnahme am Modellvorhaben und Einwilligung zur Genomsequenzierung"
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
)
|
val addProvision2 = modelVorhabenConsent.provision.addProvision()
|
||||||
return modelVorhabenConsent
|
addProvision2.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("deny"))
|
||||||
|
addProvision2.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
||||||
|
addProvision2.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
||||||
|
addProvision2.code.addLast(
|
||||||
|
CodeableConcept(
|
||||||
|
Coding(
|
||||||
|
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
||||||
|
"Rekontaktierung",
|
||||||
|
"Re-Identifizierung meiner Daten über die Vertrauensstelle beim Robert Koch-Institut und in die erneute Kontaktaufnahme durch meine behandelnde Ärztin oder meinen behandelnden Arzt"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return modelVorhabenConsent
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user