mirror of
https://github.com/pcvolkmer/mv64e-etl-processor
synced 2025-09-13 17:02:52 +00:00
feat: check consent for DNPM 2.1 requests (#126)
Co-authored-by: Jakub Lidke <jakub.lidke@uni-marburg.de>
This commit is contained in:
@@ -73,8 +73,8 @@ data class GIcsConfigProperties(
|
||||
*
|
||||
*/
|
||||
val uri: String?,
|
||||
val username: String?,
|
||||
val password: String?,
|
||||
val username: String? = null,
|
||||
val password: String? = null,
|
||||
|
||||
/**
|
||||
* gICS specific system
|
||||
|
@@ -20,9 +20,9 @@
|
||||
package dev.dnpm.etl.processor.config
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import dev.dnpm.etl.processor.consent.ConsentByMtbFile
|
||||
import dev.dnpm.etl.processor.consent.MtbFileConsentService
|
||||
import dev.dnpm.etl.processor.consent.GicsConsentService
|
||||
import dev.dnpm.etl.processor.consent.IGetConsent
|
||||
import dev.dnpm.etl.processor.consent.IConsentService
|
||||
import dev.dnpm.etl.processor.monitoring.*
|
||||
import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator
|
||||
import dev.dnpm.etl.processor.pseudonym.Generator
|
||||
@@ -218,7 +218,7 @@ class AppConfiguration {
|
||||
retryTemplate: RetryTemplate,
|
||||
restTemplate: RestTemplate,
|
||||
appFhirConfig: AppFhirConfig
|
||||
): IGetConsent {
|
||||
): IConsentService {
|
||||
return GicsConsentService(
|
||||
gIcsConfigProperties,
|
||||
retryTemplate,
|
||||
@@ -234,7 +234,7 @@ class AppConfiguration {
|
||||
gIcsConfigProperties: GIcsConfigProperties,
|
||||
getObjectMapper: ObjectMapper,
|
||||
appFhirConfig: AppFhirConfig,
|
||||
gicsConsentService: IGetConsent
|
||||
gicsConsentService: IConsentService
|
||||
): ConsentProcessor {
|
||||
return ConsentProcessor(
|
||||
configProperties,
|
||||
@@ -261,8 +261,8 @@ class AppConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun iGetConsentService(): IGetConsent {
|
||||
return ConsentByMtbFile()
|
||||
fun iGetConsentService(): IConsentService {
|
||||
return MtbFileConsentService()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -271,13 +271,9 @@ class GicsEnabledCondition :
|
||||
AnyNestedCondition(ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN) {
|
||||
|
||||
@ConditionalOnProperty(name = ["app.consent.service"], havingValue = "gics")
|
||||
@ConditionalOnProperty(name = ["app.consent.gics.uri"])
|
||||
class OnGicsServiceSelected {
|
||||
// Just for Condition
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
|
||||
class OnGicsEnabled {
|
||||
// Just for Condition
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@
|
||||
package dev.dnpm.etl.processor.config
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import dev.dnpm.etl.processor.consent.ConsentEvaluator
|
||||
import dev.dnpm.etl.processor.input.KafkaInputListener
|
||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
|
||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
|
||||
@@ -100,9 +101,10 @@ class AppKafkaConfiguration {
|
||||
@ConditionalOnProperty(value = ["app.kafka.input-topic"])
|
||||
fun kafkaInputListener(
|
||||
requestProcessor: RequestProcessor,
|
||||
objectMapper: ObjectMapper
|
||||
objectMapper: ObjectMapper,
|
||||
consentEvaluator: ConsentEvaluator
|
||||
): KafkaInputListener {
|
||||
return KafkaInputListener(requestProcessor, objectMapper)
|
||||
return KafkaInputListener(requestProcessor, consentEvaluator, objectMapper)
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -113,4 +115,4 @@ class AppKafkaConfiguration {
|
||||
return KafkaConnectionCheckService(consumerFactory.createConsumer(), connectionCheckUpdateProducer)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of ETL-Processor
|
||||
*
|
||||
* Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package dev.dnpm.etl.processor.consent
|
||||
|
||||
import dev.pcvolkmer.mv64e.mtb.ConsentProvision
|
||||
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose
|
||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
/**
|
||||
* Evaluates consent using provided consent service and file based consent information
|
||||
*/
|
||||
@Service
|
||||
class ConsentEvaluator(
|
||||
private val consentService: IConsentService
|
||||
) {
|
||||
fun check(mtbFile: Mtb): ConsentEvaluation {
|
||||
val ttpConsentStatus = consentService.getTtpBroadConsentStatus(mtbFile.patient.id)
|
||||
val consentGiven = ttpConsentStatus == TtpConsentStatus.BROAD_CONSENT_GIVEN
|
||||
|| ttpConsentStatus == TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT
|
||||
// Aktuell nur Modellvorhaben Consent im File
|
||||
|| ttpConsentStatus == TtpConsentStatus.UNKNOWN_CHECK_FILE && mtbFile.metadata?.modelProjectConsent?.provisions?.any {
|
||||
it.purpose == ModelProjectConsentPurpose.SEQUENCING
|
||||
&& it.type == ConsentProvision.PERMIT
|
||||
} == true
|
||||
|
||||
return ConsentEvaluation(ttpConsentStatus, consentGiven)
|
||||
}
|
||||
}
|
||||
|
||||
data class ConsentEvaluation(private val ttpConsentStatus: TtpConsentStatus, private val consentGiven: Boolean) {
|
||||
/**
|
||||
* Checks if any required consent is present
|
||||
*/
|
||||
fun hasConsent(): Boolean {
|
||||
return consentGiven
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the consent status
|
||||
*/
|
||||
fun getStatus(): TtpConsentStatus {
|
||||
if (ttpConsentStatus == TtpConsentStatus.UNKNOWN_CHECK_FILE) {
|
||||
// in case ttp check is disabled - we propagate rejected status anyway
|
||||
return TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED
|
||||
}
|
||||
return ttpConsentStatus
|
||||
}
|
||||
}
|
@@ -23,9 +23,9 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import dev.dnpm.etl.processor.CustomMediaType
|
||||
import dev.dnpm.etl.processor.PatientId
|
||||
import dev.dnpm.etl.processor.RequestId
|
||||
import dev.dnpm.etl.processor.consent.ConsentEvaluator
|
||||
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||
import dev.pcvolkmer.mv64e.mtb.ConsentProvision
|
||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -34,6 +34,7 @@ import org.springframework.kafka.listener.MessageListener
|
||||
|
||||
class KafkaInputListener(
|
||||
private val requestProcessor: RequestProcessor,
|
||||
private val consentEvaluator: ConsentEvaluator,
|
||||
private val objectMapper: ObjectMapper
|
||||
) : MessageListener<String, String> {
|
||||
private val logger = LoggerFactory.getLogger(KafkaInputListener::class.java)
|
||||
@@ -70,8 +71,7 @@ class KafkaInputListener(
|
||||
RequestId("")
|
||||
}
|
||||
|
||||
// TODO: Use MV Consent for now - needs to be replaced with proper consent evaluation
|
||||
if (mtbFile.metadata.modelProjectConsent.provisions.filter { it.type == ConsentProvision.PERMIT }.isNotEmpty()) {
|
||||
if (consentEvaluator.check(mtbFile).hasConsent()) {
|
||||
logger.debug("Accepted MTB File for processing")
|
||||
if (requestId.isBlank()) {
|
||||
requestProcessor.processMtbFile(mtbFile)
|
||||
|
@@ -21,7 +21,7 @@ package dev.dnpm.etl.processor.input
|
||||
|
||||
import dev.dnpm.etl.processor.CustomMediaType
|
||||
import dev.dnpm.etl.processor.PatientId
|
||||
import dev.dnpm.etl.processor.consent.IGetConsent
|
||||
import dev.dnpm.etl.processor.consent.ConsentEvaluator
|
||||
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||
@@ -34,9 +34,8 @@ import org.springframework.web.bind.annotation.*
|
||||
@RequestMapping(path = ["mtbfile", "mtb"])
|
||||
class MtbFileRestController(
|
||||
private val requestProcessor: RequestProcessor,
|
||||
private val iGetConsent: IGetConsent
|
||||
private val consentEvaluator: ConsentEvaluator
|
||||
) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
|
||||
|
||||
@GetMapping
|
||||
@@ -46,8 +45,15 @@ class MtbFileRestController(
|
||||
|
||||
@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE, CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE])
|
||||
fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
|
||||
logger.debug("Accepted MTB File (DNPM V2) for processing")
|
||||
requestProcessor.processMtbFile(mtbFile)
|
||||
val consentEvaluation = consentEvaluator.check(mtbFile)
|
||||
if (consentEvaluation.hasConsent()) {
|
||||
logger.debug("Accepted MTB File (DNPM V2) for processing")
|
||||
requestProcessor.processMtbFile(mtbFile)
|
||||
} else {
|
||||
logger.debug("Accepted MTB File (DNPM V2) and process deletion")
|
||||
val patientId = PatientId(mtbFile.patient.id)
|
||||
requestProcessor.processDeletion(patientId, consentEvaluation.getStatus())
|
||||
}
|
||||
return ResponseEntity.accepted().build()
|
||||
}
|
||||
|
||||
|
@@ -6,9 +6,9 @@ import com.fasterxml.jackson.core.type.TypeReference
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import dev.dnpm.etl.processor.config.AppConfigProperties
|
||||
import dev.dnpm.etl.processor.config.GIcsConfigProperties
|
||||
import dev.dnpm.etl.processor.consent.ConsentByMtbFile
|
||||
import dev.dnpm.etl.processor.consent.MtbFileConsentService
|
||||
import dev.dnpm.etl.processor.consent.ConsentDomain
|
||||
import dev.dnpm.etl.processor.consent.IGetConsent
|
||||
import dev.dnpm.etl.processor.consent.IConsentService
|
||||
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized
|
||||
import dev.pcvolkmer.mv64e.mtb.*
|
||||
import org.apache.commons.lang3.NotImplementedException
|
||||
@@ -31,7 +31,7 @@ class ConsentProcessor(
|
||||
private val gIcsConfigProperties: GIcsConfigProperties,
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val fhirContext: FhirContext,
|
||||
private val consentService: IGetConsent
|
||||
private val consentService: IConsentService
|
||||
) {
|
||||
private var logger: Logger = LoggerFactory.getLogger("ConsentProcessor")
|
||||
|
||||
@@ -49,7 +49,7 @@ class ConsentProcessor(
|
||||
*
|
||||
*/
|
||||
fun consentGatedCheckAndTryEmbedding(mtbFile: Mtb): Boolean {
|
||||
if (consentService is ConsentByMtbFile) {
|
||||
if (consentService is MtbFileConsentService) {
|
||||
// consent check is disabled
|
||||
return true
|
||||
}
|
||||
@@ -70,7 +70,7 @@ class ConsentProcessor(
|
||||
* broad consent
|
||||
*/
|
||||
val broadConsent = consentService.getConsent(
|
||||
personIdentifierValue, requestDate, ConsentDomain.BroadConsent
|
||||
personIdentifierValue, requestDate, ConsentDomain.BROAD_CONSENT
|
||||
)
|
||||
val broadConsentHasBeenAsked = !broadConsent.entry.isEmpty()
|
||||
|
||||
@@ -78,7 +78,7 @@ class ConsentProcessor(
|
||||
if (!broadConsentHasBeenAsked) return false
|
||||
|
||||
val genomeDeConsent = consentService.getConsent(
|
||||
personIdentifierValue, requestDate, ConsentDomain.Modelvorhaben64e
|
||||
personIdentifierValue, requestDate, ConsentDomain.MODELLVORHABEN_64E
|
||||
)
|
||||
|
||||
addGenomeDbProvisions(mtbFile, genomeDeConsent)
|
||||
@@ -88,11 +88,11 @@ class ConsentProcessor(
|
||||
embedBroadConsentResources(mtbFile, broadConsent)
|
||||
|
||||
val broadConsentStatus = getProvisionTypeByPolicyCode(
|
||||
broadConsent, requestDate, ConsentDomain.BroadConsent
|
||||
broadConsent, requestDate, ConsentDomain.BROAD_CONSENT
|
||||
)
|
||||
|
||||
val genomDeSequencingStatus = getProvisionTypeByPolicyCode(
|
||||
genomeDeConsent, requestDate, ConsentDomain.Modelvorhaben64e
|
||||
genomeDeConsent, requestDate, ConsentDomain.MODELLVORHABEN_64E
|
||||
)
|
||||
|
||||
if (Consent.ConsentProvisionType.NULL == broadConsentStatus) {
|
||||
@@ -204,10 +204,10 @@ class ConsentProcessor(
|
||||
): Consent.ConsentProvisionType {
|
||||
val code: String?
|
||||
val system: String?
|
||||
if (ConsentDomain.BroadConsent == consentDomain) {
|
||||
if (ConsentDomain.BROAD_CONSENT == consentDomain) {
|
||||
code = gIcsConfigProperties.broadConsentPolicyCode
|
||||
system = gIcsConfigProperties.broadConsentPolicySystem
|
||||
} else if (ConsentDomain.Modelvorhaben64e == consentDomain) {
|
||||
} else if (ConsentDomain.MODELLVORHABEN_64E == consentDomain) {
|
||||
code = gIcsConfigProperties.genomeDePolicyCode
|
||||
system = gIcsConfigProperties.genomeDePolicySystem
|
||||
} else {
|
||||
@@ -279,4 +279,4 @@ class ConsentProcessor(
|
||||
return isRequestDateAfterOrEqualStart <= 0 && isRequestDateBeforeOrEqualEnd >= 0
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user