1
0
mirror of https://github.com/pcvolkmer/etl-processor.git synced 2025-07-17 12:52:54 +00:00

refactor: moved consent processing from RequestProcessor.kt to new class ConsentProcessor.kt

This commit is contained in:
Jakub Lidke
2025-07-16 11:02:11 +02:00
parent 600ac84ff3
commit 38eec60189
10 changed files with 288 additions and 252 deletions

View File

@ -1,112 +0,0 @@
package dev.dnpm.etl.processor.consent;
import ca.uhn.fhir.context.FhirContext;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
import dev.pcvolkmer.mv64e.mtb.ConsentProvision;
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose;
import dev.pcvolkmer.mv64e.mtb.Mtb;
import dev.pcvolkmer.mv64e.mtb.Provision;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Consent.ProvisionComponent;
import org.hl7.fhir.r4.model.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class BaseConsentService implements ICheckConsent {
protected final GIcsConfigProperties gIcsConfigProperties;
private final ObjectMapper objectMapper;
protected Logger logger = LoggerFactory.getLogger(BaseConsentService.class);
static FhirContext fhirCtx = FhirContext.forR4();
public BaseConsentService(GIcsConfigProperties gIcsConfigProperties,
ObjectMapper objectMapper) {
this.gIcsConfigProperties = gIcsConfigProperties;
this.objectMapper = objectMapper;
}
public void embedBroadConsentResources(Mtb mtbFile, Bundle broadConsent) {
for (Bundle.BundleEntryComponent entry : broadConsent.getEntry()) {
Resource resource = entry.getResource();
if (resource instanceof Consent) {
// since jackson convertValue does not work here,
// we need another step to back to string, before we convert to object map
var asJsonString = fhirCtx.newJsonParser().encodeResourceToString(resource);
try {
var mapOfJson = objectMapper.readValue(asJsonString,
new TypeReference<HashMap<String, Object>>() {
});
mtbFile.getMetadata().getResearchConsents().add(mapOfJson);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
}
public void addGenomeDbProvisions(Mtb mtbFile, Bundle consentGnomeDe) {
for (Bundle.BundleEntryComponent entry : consentGnomeDe.getEntry()) {
Resource resource = entry.getResource();
if (!(resource instanceof Consent consentFhirResource)) {
continue;
}
// We expect only one provision in collection, therefore get first or none
List<ProvisionComponent> provisions = consentFhirResource.getProvision().getProvision();
if (provisions.isEmpty()) {
continue;
}
var provisionComponent = provisions.getFirst();
String provisionCode = null;
if (provisionComponent.getCode() != null && !provisionComponent.getCode().isEmpty()) {
CodeableConcept codeableConcept = provisionComponent.getCode().getFirst();
if (codeableConcept.getCoding() != null && !codeableConcept.getCoding().isEmpty()) {
provisionCode = codeableConcept.getCoding().getFirst().getCode();
}
}
if (provisionCode != null) {
try {
ModelProjectConsentPurpose modelProjectConsentPurpose =
ModelProjectConsentPurpose.forValue(provisionCode);
if (ModelProjectConsentPurpose.SEQUENCING.equals(modelProjectConsentPurpose)) {
// CONVENTION: wrapping date is date of SEQUENCING consent
mtbFile.getMetadata().getModelProjectConsent()
.setDate(consentFhirResource.getDateTime());
}
Provision provision = Provision.builder()
.type(ConsentProvision.valueOf(provisionComponent.getType().name()))
.date(provisionComponent.getPeriod().getStart())
.purpose(modelProjectConsentPurpose)
.build();
mtbFile.getMetadata().getModelProjectConsent().getProvisions().add(provision);
} catch (IOException ioe) {
logger.error(
"Provision code '" + provisionCode + "' is unknown and cannot be mapped.",
ioe.toString());
}
}
if (!mtbFile.getMetadata().getModelProjectConsent().getProvisions().isEmpty()) {
mtbFile.getMetadata().getModelProjectConsent()
.setVersion(gIcsConfigProperties.getGenomeDeConsentVersion());
}
}
}
}

View File

@ -42,14 +42,4 @@ public class ConsentCheckFileBased implements ICheckConsent {
Date requestDate, ConsentDomain consentDomain) { Date requestDate, ConsentDomain consentDomain) {
return ConsentProvisionType.NULL; return ConsentProvisionType.NULL;
} }
@Override
public void embedBroadConsentResources(Mtb mtbFile, Bundle broadConsent) {
throw new NotImplementedException("not intended to be implemented here!");
}
@Override
public void addGenomeDbProvisions(Mtb mtbFile, Bundle consentGnomeDe) {
throw new NotImplementedException("not intended to be implemented here!");
}
} }

View File

@ -39,7 +39,7 @@ import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
public class GicsConsentService extends BaseConsentService { public class GicsConsentService implements ICheckConsent {
private final Logger log = LoggerFactory.getLogger(GicsConsentService.class); private final Logger log = LoggerFactory.getLogger(GicsConsentService.class);
@ -49,17 +49,18 @@ public class GicsConsentService extends BaseConsentService {
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
private final FhirContext fhirContext; private final FhirContext fhirContext;
private final HttpHeaders httpHeader; private final HttpHeaders httpHeader;
private final GIcsConfigProperties gIcsConfigProperties;
private String url; private String url;
public GicsConsentService(GIcsConfigProperties gIcsConfigProperties, public GicsConsentService(GIcsConfigProperties gIcsConfigProperties,
RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig, ObjectMapper objectMapper) { RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
super(gIcsConfigProperties,objectMapper);
this.retryTemplate = retryTemplate; this.retryTemplate = retryTemplate;
this.restTemplate = restTemplate; this.restTemplate = restTemplate;
this.fhirContext = appFhirConfig.fhirContext(); this.fhirContext = appFhirConfig.fhirContext();
httpHeader = buildHeader(gIcsConfigProperties.getUsername(), httpHeader = buildHeader(gIcsConfigProperties.getUsername(),
gIcsConfigProperties.getPassword()); gIcsConfigProperties.getPassword());
this.gIcsConfigProperties = gIcsConfigProperties;
log.info("GicsConsentService initialized..."); log.info("GicsConsentService initialized...");
} }
@ -207,7 +208,8 @@ public class GicsConsentService extends BaseConsentService {
String consentDomain; String consentDomain;
switch (targetConsentDomain) { switch (targetConsentDomain) {
case BroadConsent -> consentDomain = gIcsConfigProperties.getBroadConsentDomainName(); case BroadConsent -> consentDomain = gIcsConfigProperties.getBroadConsentDomainName();
case Modelvorhaben64e -> consentDomain = gIcsConfigProperties.getGenomDeConsentDomainName(); case Modelvorhaben64e ->
consentDomain = gIcsConfigProperties.getGenomDeConsentDomainName();
default -> throw new IllegalArgumentException( default -> throw new IllegalArgumentException(
"target ConsentDomain is missing but must be provided!"); "target ConsentDomain is missing but must be provided!");
} }

View File

@ -1,6 +1,5 @@
package dev.dnpm.etl.processor.consent; package dev.dnpm.etl.processor.consent;
import dev.pcvolkmer.mv64e.mtb.Mtb;
import java.util.Date; import java.util.Date;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Consent.ConsentProvisionType; import org.hl7.fhir.r4.model.Consent.ConsentProvisionType;
@ -59,8 +58,4 @@ public interface ICheckConsent {
ConsentProvisionType getProvisionTypeByPolicyCode(Bundle consentBundle, Date requestDate, ConsentProvisionType getProvisionTypeByPolicyCode(Bundle consentBundle, Date requestDate,
ConsentDomain consentDomain); ConsentDomain consentDomain);
void embedBroadConsentResources(Mtb mtbFile, Bundle broadConsent);
void addGenomeDbProvisions(Mtb mtbFile, Bundle consentGnomeDe);
} }

View File

@ -30,6 +30,7 @@ import dev.dnpm.etl.processor.pseudonym.GpasPseudonymGenerator
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
import dev.dnpm.etl.processor.security.TokenRepository import dev.dnpm.etl.processor.security.TokenRepository
import dev.dnpm.etl.processor.security.TokenService import dev.dnpm.etl.processor.security.TokenService
import dev.dnpm.etl.processor.services.ConsentProcessor
import dev.dnpm.etl.processor.services.Transformation import dev.dnpm.etl.processor.services.Transformation
import dev.dnpm.etl.processor.services.TransformationService import dev.dnpm.etl.processor.services.TransformationService
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -75,17 +76,26 @@ class AppConfiguration {
} }
@Bean @Bean
fun appFhirConfig(): AppFhirConfig{ fun appFhirConfig(): AppFhirConfig {
return AppFhirConfig() return AppFhirConfig()
} }
@ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS") @ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
@Bean @Bean
fun gpasPseudonymGenerator(configProperties: GPasConfigProperties, retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig): Generator { fun gpasPseudonymGenerator(
configProperties: GPasConfigProperties,
retryTemplate: RetryTemplate,
restTemplate: RestTemplate,
appFhirConfig: AppFhirConfig
): Generator {
return GpasPseudonymGenerator(configProperties, retryTemplate, restTemplate, appFhirConfig) return GpasPseudonymGenerator(configProperties, retryTemplate, restTemplate, appFhirConfig)
} }
@ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "BUILDIN", matchIfMissing = true) @ConditionalOnProperty(
value = ["app.pseudonymize.generator"],
havingValue = "BUILDIN",
matchIfMissing = true
)
@Bean @Bean
fun buildinPseudonymGenerator(): Generator { fun buildinPseudonymGenerator(): Generator {
return AnonymizingGenerator() return AnonymizingGenerator()
@ -105,7 +115,7 @@ class AppConfiguration {
} }
@Bean @Bean
fun getObjectMapper () : ObjectMapper{ fun getObjectMapper(): ObjectMapper {
return JacksonConfig().objectMapper() return JacksonConfig().objectMapper()
} }
@ -133,7 +143,11 @@ class AppConfiguration {
callback: RetryCallback<T, E>, callback: RetryCallback<T, E>,
throwable: Throwable throwable: Throwable
) { ) {
logger.warn("Error occured: {}. Retrying {}", throwable.message, context.retryCount) logger.warn(
"Error occured: {}. Retrying {}",
throwable.message,
context.retryCount
)
} }
}) })
.build() .build()
@ -141,7 +155,11 @@ class AppConfiguration {
@ConditionalOnProperty(value = ["app.security.enable-tokens"], havingValue = "true") @ConditionalOnProperty(value = ["app.security.enable-tokens"], havingValue = "true")
@Bean @Bean
fun tokenService(userDetailsManager: InMemoryUserDetailsManager, passwordEncoder: PasswordEncoder, tokenRepository: TokenRepository): TokenService { fun tokenService(
userDetailsManager: InMemoryUserDetailsManager,
passwordEncoder: PasswordEncoder,
tokenRepository: TokenRepository
): TokenService {
return TokenService(userDetailsManager, passwordEncoder, tokenRepository) return TokenService(userDetailsManager, passwordEncoder, tokenRepository)
} }
@ -162,7 +180,11 @@ class AppConfiguration {
gPasConfigProperties: GPasConfigProperties, gPasConfigProperties: GPasConfigProperties,
connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult> connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
): ConnectionCheckService { ): ConnectionCheckService {
return GPasConnectionCheckService(restTemplate, gPasConfigProperties, connectionCheckUpdateProducer) return GPasConnectionCheckService(
restTemplate,
gPasConfigProperties,
connectionCheckUpdateProducer
)
} }
@ConditionalOnProperty(value = ["app.pseudonymizer"], havingValue = "GPAS") @ConditionalOnProperty(value = ["app.pseudonymizer"], havingValue = "GPAS")
@ -173,7 +195,11 @@ class AppConfiguration {
gPasConfigProperties: GPasConfigProperties, gPasConfigProperties: GPasConfigProperties,
connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult> connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
): ConnectionCheckService { ): ConnectionCheckService {
return GPasConnectionCheckService(restTemplate, gPasConfigProperties, connectionCheckUpdateProducer) return GPasConnectionCheckService(
restTemplate,
gPasConfigProperties,
connectionCheckUpdateProducer
)
} }
@Bean @Bean
@ -183,14 +209,31 @@ class AppConfiguration {
@Bean @Bean
@ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true") @ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
fun gicsConsentService( gIcsConfigProperties: GIcsConfigProperties, fun gicsConsentService(
retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig, getObjectMapper: ObjectMapper): ICheckConsent { gIcsConfigProperties: GIcsConfigProperties,
retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig
): ICheckConsent {
return GicsConsentService( return GicsConsentService(
gIcsConfigProperties, gIcsConfigProperties,
retryTemplate, retryTemplate,
restTemplate, restTemplate,
appFhirConfig, appFhirConfig
getObjectMapper )
}
@Bean
@ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
fun gicsConsentProcessor(
gIcsConfigProperties: GIcsConfigProperties,
getObjectMapper: ObjectMapper,
appFhirConfig: AppFhirConfig,
gicsConsentService: ICheckConsent
): ConsentProcessor {
return ConsentProcessor(
gIcsConfigProperties,
getObjectMapper,
appFhirConfig.fhirContext(),
gicsConsentService
) )
} }
@ -201,7 +244,11 @@ class AppConfiguration {
gIcsConfigProperties: GIcsConfigProperties, gIcsConfigProperties: GIcsConfigProperties,
connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult> connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
): ConnectionCheckService { ): ConnectionCheckService {
return GIcsConnectionCheckService(restTemplate, gIcsConfigProperties, connectionCheckUpdateProducer) return GIcsConnectionCheckService(
restTemplate,
gIcsConfigProperties,
connectionCheckUpdateProducer
)
} }
@Bean @Bean

View File

@ -0,0 +1,186 @@
package dev.dnpm.etl.processor.services
import ca.uhn.fhir.context.FhirContext
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.config.GIcsConfigProperties
import dev.dnpm.etl.processor.consent.ConsentDomain
import dev.dnpm.etl.processor.consent.ICheckConsent
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized
import dev.pcvolkmer.mv64e.mtb.*
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.CodeableConcept
import org.hl7.fhir.r4.model.Consent
import org.hl7.fhir.r4.model.Consent.ProvisionComponent
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.io.IOException
import java.time.Clock
import java.time.Instant
import java.util.*
@Service
class ConsentProcessor(
private val gIcsConfigProperties: GIcsConfigProperties, private val objectMapper: ObjectMapper,
private val fhirContext: FhirContext,
private val consentService: ICheckConsent?
) {
private var logger: Logger = LoggerFactory.getLogger("ConsentProcessor")
/**
* In case an instance of {@link ICheckConsent} is active, consent will be embedded and checked.
*
* Logik:
* * <c>true</c> IF consent check is disabled.
* * <c>true</c> IF broad consent (BC) has been given.
* * <c>true</c> BC has been asked AND declined but genomDe consent has been consented.
* * ELSE <c>false</c> is returned.
*
* @param mtbFile File v2 (will be enriched with consent data)
* @return true if consent is given
*
*/
fun consentGatedCheckAndTryEmbedding(mtbFile: Mtb): Boolean {
if (consentService == null) {
// consent check seems to be disabled
return true
}
mtbFile.ensureMetaDataIsInitialized()
val personIdentifierValue = mtbFile.patient.id
val requestDate = Date.from(Instant.now(Clock.systemUTC()))
// 1. Broad consent Entry exists?
// 1.1. -> yes and research consent is given -> send mtb file
// 1.2. -> no -> return status error - consent has not been asked
// 2. -> Broad consent found but rejected -> is GenomDe consent provision 'sequencing' given?
// 2.1 -> yes -> send mtb file
// 2.2 -> no -> warn/info no consent given
/*
* broad consent
*/
val broadConsent = consentService.getBroadConsent(personIdentifierValue, requestDate)
val broadConsentHasBeenAsked = !broadConsent.entry.isEmpty()
// fast exit - if patient has not been asked, we can skip and exit
if (!broadConsentHasBeenAsked) return false
val genomeDeConsent = consentService.getGenomDeConsent(
personIdentifierValue, requestDate
)
addGenomeDbProvisions(mtbFile, genomeDeConsent)
if (!genomeDeConsent.entry.isEmpty()) setGenomDeSubmissionType(mtbFile)
embedBroadConsentResources(mtbFile, broadConsent)
val broadConsentStatus = consentService.getProvisionTypeByPolicyCode(
broadConsent, requestDate, ConsentDomain.BroadConsent
)
val genomDeSequencingStatus = consentService.getProvisionTypeByPolicyCode(
genomeDeConsent, requestDate, ConsentDomain.Modelvorhaben64e
)
if (Consent.ConsentProvisionType.NULL == broadConsentStatus) {
// bc not asked
return false
}
if (Consent.ConsentProvisionType.PERMIT == broadConsentStatus ||
Consent.ConsentProvisionType.PERMIT == genomDeSequencingStatus
) return true
return false
}
public fun embedBroadConsentResources(mtbFile: Mtb, broadConsent: Bundle) {
for (entry in broadConsent.getEntry()) {
val resource = entry.getResource()
if (resource is Consent) {
// since jackson convertValue does not work here,
// we need another step to back to string, before we convert to object map
val asJsonString =
fhirContext.newJsonParser().encodeResourceToString(resource)
try {
val mapOfJson: HashMap<String?, Any?>? =
objectMapper.readValue<HashMap<String?, Any?>?>(
asJsonString,
object : TypeReference<HashMap<String?, Any?>?>() {
})
mtbFile.metadata.researchConsents.add(mapOfJson)
} catch (e: JsonProcessingException) {
throw RuntimeException(e)
}
}
}
}
public fun addGenomeDbProvisions(mtbFile: Mtb, consentGnomeDe: Bundle) {
for (entry in consentGnomeDe.getEntry()) {
val resource = entry.getResource()
if (resource !is Consent) {
continue
}
// We expect only one provision in collection, therefore get first or none
val provisions = resource.getProvision().getProvision()
if (provisions.isEmpty()) {
continue
}
val provisionComponent: ProvisionComponent = provisions.first()
var provisionCode: String? = null
if (provisionComponent.getCode() != null && !provisionComponent.getCode().isEmpty()) {
val codableConcept: CodeableConcept = provisionComponent.getCode().first()
if (codableConcept.getCoding() != null && !codableConcept.getCoding().isEmpty()) {
provisionCode = codableConcept.getCoding().first().getCode()
}
}
if (provisionCode != null) {
try {
val modelProjectConsentPurpose =
ModelProjectConsentPurpose.forValue(provisionCode)
if (ModelProjectConsentPurpose.SEQUENCING == modelProjectConsentPurpose) {
// CONVENTION: wrapping date is date of SEQUENCING consent
mtbFile.metadata.modelProjectConsent.date = resource.getDateTime()
}
val provision = Provision.builder()
.type(ConsentProvision.valueOf(provisionComponent.getType().name))
.date(provisionComponent.getPeriod().getStart())
.purpose(modelProjectConsentPurpose)
.build()
mtbFile.metadata.modelProjectConsent.provisions.add(provision)
} catch (ioe: IOException) {
logger.error(
"Provision code '$provisionCode' is unknown and cannot be mapped.",
ioe.toString()
)
}
}
if (!mtbFile.metadata.modelProjectConsent.provisions.isEmpty()) {
mtbFile.metadata.modelProjectConsent.version =
gIcsConfigProperties.genomeDeConsentVersion
}
}
}
/**
* fixme: currently we do not have information about submission type
*/
private fun setGenomDeSubmissionType(mtbFile: Mtb) {
mtbFile.metadata.type = MvhSubmissionType.INITIAL
}
}

View File

@ -58,7 +58,7 @@ class RequestProcessor(
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
private val applicationEventPublisher: ApplicationEventPublisher, private val applicationEventPublisher: ApplicationEventPublisher,
private val appConfigProperties: AppConfigProperties, private val appConfigProperties: AppConfigProperties,
private val consentService: ICheckConsent? private val consentProcessor: ConsentProcessor?
) { ) {
private var logger: Logger = LoggerFactory.getLogger("RequestProcessor") private var logger: Logger = LoggerFactory.getLogger("RequestProcessor")
@ -78,80 +78,13 @@ class RequestProcessor(
processMtbFile(mtbFile, randomRequestId()) processMtbFile(mtbFile, randomRequestId())
} }
/**
* In case an instance of {@link ICheckConsent} is active, consent will be embedded and checked.
*
* Logik:
* * <c>true</c> IF consent check is disabled.
* * <c>true</c> IF broad consent (BC) has been given.
* * <c>true</c> BC has been asked AND declined but genomDe consent has been consented.
* * ELSE <c>false</c> is returned.
*
* @param mtbFile File v2 (will be enriched with consent data)
* @return true if consent is given
*
*/
fun consentGatedCheckAndTryEmbedding(mtbFile: Mtb): Boolean {
if (consentService == null) {
// consent check seems to be disabled
return true
}
mtbFile.ensureMetaDataIsInitialized()
val personIdentifierValue = extractPatientIdentifier(mtbFile)
val requestDate = Date.from(Instant.now(Clock.systemUTC()))
// 1. Broad consent Entry exists?
// 1.1. -> yes and research consent is given -> send mtb file
// 1.2. -> no -> return status error - consent has not been asked
// 2. -> Broad consent found but rejected -> is GenomDe consent provision 'sequencing' given?
// 2.1 -> yes -> send mtb file
// 2.2 -> no -> warn/info no consent given
/*
* broad consent
*/
val broadConsent = consentService.getBroadConsent(personIdentifierValue, requestDate)
val broadConsentHasBeenAsked = !broadConsent.entry.isEmpty()
// fast exit - if patient has not been asked, we can skip and exit
if (!broadConsentHasBeenAsked) return false
val genomeDeConsent = consentService.getGenomDeConsent(
personIdentifierValue, requestDate
)
consentService.addGenomeDbProvisions(mtbFile, genomeDeConsent)
// fixme: currently we do not have information about submission type
if (!genomeDeConsent.entry.isEmpty()) mtbFile.metadata.type = MvhSubmissionType.INITIAL
consentService.embedBroadConsentResources(mtbFile, broadConsent)
val broadConsentStatus = consentService.getProvisionTypeByPolicyCode(
broadConsent, requestDate, ConsentDomain.BroadConsent
)
val genomDeSequencingStatus = consentService.getProvisionTypeByPolicyCode(
genomeDeConsent, requestDate, ConsentDomain.Modelvorhaben64e
)
if (Consent.ConsentProvisionType.NULL == broadConsentStatus) {
// bc not asked
return false
}
if (Consent.ConsentProvisionType.PERMIT == broadConsentStatus ||
Consent.ConsentProvisionType.PERMIT == genomDeSequencingStatus
) return true
return false
}
fun processMtbFile(mtbFile: Mtb, requestId: RequestId) { fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
val pid = PatientId(extractPatientIdentifier(mtbFile)) val pid = PatientId(extractPatientIdentifier(mtbFile))
if (consentGatedCheckAndTryEmbedding(mtbFile)) { val isConsentOk = consentProcessor != null &&
consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) || consentProcessor == null;
if (isConsentOk) {
mtbFile pseudonymizeWith pseudonymizeService mtbFile pseudonymizeWith pseudonymizeService
mtbFile anonymizeContentWith pseudonymizeService mtbFile anonymizeContentWith pseudonymizeService
val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile)) val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))

View File

@ -19,14 +19,13 @@
package dev.dnpm.etl.processor.pseudonym package dev.dnpm.etl.processor.pseudonym
import ca.uhn.fhir.context.FhirContext
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.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.services.ConsentProcessor
import dev.dnpm.etl.processor.consent.ConsentDomain
import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.services.TransformationServiceTest 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
@ -46,7 +45,7 @@ import java.util.*
@ExtendWith(MockitoExtension::class) @ExtendWith(MockitoExtension::class)
class ExtensionsTest { class ExtensionsTest {
fun getObjectMapper() : ObjectMapper { fun getObjectMapper(): ObjectMapper {
return JacksonConfig().objectMapper() return JacksonConfig().objectMapper()
} }
@ -251,35 +250,19 @@ class ExtensionsTest {
private fun addConsentData(mtbFile: Mtb) { private fun addConsentData(mtbFile: Mtb) {
val gIcsConfigProperties = GIcsConfigProperties("", "", "", true) val gIcsConfigProperties = GIcsConfigProperties("", "", "", true)
val baseConsentService = object : BaseConsentService(gIcsConfigProperties,
JacksonConfig().objectMapper()) {
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 bundle = Bundle()
val dummyConsent = TransformationServiceTest.getDummyConsent() val dummyConsent = TransformationServiceTest.getDummyConsent()
dummyConsent.patient.reference = "Patient/$CLEAN_PATIENT_ID" dummyConsent.patient.reference = "Patient/$CLEAN_PATIENT_ID"
bundle.addEntry().resource = dummyConsent bundle.addEntry().resource = dummyConsent
baseConsentService.embedBroadConsentResources(mtbFile, bundle) ConsentProcessor(
gIcsConfigProperties,
JacksonConfig().objectMapper(),
FhirContext.forR4(),
null
).embedBroadConsentResources(mtbFile, bundle)
} }
@Test @Test

View File

@ -60,7 +60,7 @@ class RequestProcessorTest {
private lateinit var requestService: RequestService private lateinit var requestService: RequestService
private lateinit var applicationEventPublisher: ApplicationEventPublisher private lateinit var applicationEventPublisher: ApplicationEventPublisher
private lateinit var appConfigProperties: AppConfigProperties private lateinit var appConfigProperties: AppConfigProperties
private lateinit var gicsConsentService : GicsConsentService private lateinit var consentProcessor: ConsentProcessor
private lateinit var requestProcessor: RequestProcessor private lateinit var requestProcessor: RequestProcessor
@BeforeEach @BeforeEach
@ -70,7 +70,7 @@ class RequestProcessorTest {
@Mock sender: RestMtbFileSender, @Mock sender: RestMtbFileSender,
@Mock requestService: RequestService, @Mock requestService: RequestService,
@Mock applicationEventPublisher: ApplicationEventPublisher, @Mock applicationEventPublisher: ApplicationEventPublisher,
@Mock gicsConsentService: GicsConsentService @Mock consentProcessor: ConsentProcessor
) { ) {
this.pseudonymizeService = pseudonymizeService this.pseudonymizeService = pseudonymizeService
this.transformationService = transformationService this.transformationService = transformationService
@ -78,7 +78,7 @@ class RequestProcessorTest {
this.requestService = requestService this.requestService = requestService
this.applicationEventPublisher = applicationEventPublisher this.applicationEventPublisher = applicationEventPublisher
this.appConfigProperties = AppConfigProperties(null) this.appConfigProperties = AppConfigProperties(null)
this.gicsConsentService = gicsConsentService this.consentProcessor = consentProcessor
val objectMapper = ObjectMapper() val objectMapper = ObjectMapper()
@ -90,7 +90,7 @@ class RequestProcessorTest {
objectMapper, objectMapper,
applicationEventPublisher, applicationEventPublisher,
appConfigProperties, appConfigProperties,
gicsConsentService consentProcessor
) )
} }
@ -348,7 +348,10 @@ class RequestProcessorTest {
MtbFileSender.Response(status = RequestStatus.UNKNOWN) MtbFileSender.Response(status = RequestStatus.UNKNOWN)
}.whenever(sender).send(any<DeleteRequest>()) }.whenever(sender).send(any<DeleteRequest>())
this.requestProcessor.processDeletion(TEST_PATIENT_ID, isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE) this.requestProcessor.processDeletion(
TEST_PATIENT_ID,
isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE
)
val requestCaptor = argumentCaptor<Request>() val requestCaptor = argumentCaptor<Request>()
verify(requestService, times(1)).save(requestCaptor.capture()) verify(requestService, times(1)).save(requestCaptor.capture())
@ -366,7 +369,10 @@ class RequestProcessorTest {
MtbFileSender.Response(status = RequestStatus.SUCCESS) MtbFileSender.Response(status = RequestStatus.SUCCESS)
}.whenever(sender).send(any<DeleteRequest>()) }.whenever(sender).send(any<DeleteRequest>())
this.requestProcessor.processDeletion(TEST_PATIENT_ID, isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE) this.requestProcessor.processDeletion(
TEST_PATIENT_ID,
isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE
)
val eventCaptor = argumentCaptor<ResponseEvent>() val eventCaptor = argumentCaptor<ResponseEvent>()
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
@ -384,7 +390,10 @@ class RequestProcessorTest {
MtbFileSender.Response(status = RequestStatus.ERROR) MtbFileSender.Response(status = RequestStatus.ERROR)
}.whenever(sender).send(any<DeleteRequest>()) }.whenever(sender).send(any<DeleteRequest>())
this.requestProcessor.processDeletion(TEST_PATIENT_ID, isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE) this.requestProcessor.processDeletion(
TEST_PATIENT_ID,
isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE
)
val eventCaptor = argumentCaptor<ResponseEvent>() val eventCaptor = argumentCaptor<ResponseEvent>()
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
@ -396,7 +405,10 @@ class RequestProcessorTest {
fun testShouldSendDeleteRequestWithPseudonymErrorAndSaveErrorRequestStatus() { fun testShouldSendDeleteRequestWithPseudonymErrorAndSaveErrorRequestStatus() {
doThrow(RuntimeException()).whenever(pseudonymizeService).patientPseudonym(anyValueClass()) doThrow(RuntimeException()).whenever(pseudonymizeService).patientPseudonym(anyValueClass())
this.requestProcessor.processDeletion(TEST_PATIENT_ID, isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE) this.requestProcessor.processDeletion(
TEST_PATIENT_ID,
isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE
)
val requestCaptor = argumentCaptor<Request>() val requestCaptor = argumentCaptor<Request>()
verify(requestService, times(1)).save(requestCaptor.capture()) verify(requestService, times(1)).save(requestCaptor.capture())