mirror of
https://github.com/pcvolkmer/etl-processor.git
synced 2025-07-16 20:32:54 +00:00
refactor: moved consent processing from RequestProcessor.kt to new class ConsentProcessor.kt
This commit is contained in:
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -42,14 +42,4 @@ public class ConsentCheckFileBased implements ICheckConsent {
|
||||
Date requestDate, ConsentDomain consentDomain) {
|
||||
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!");
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
|
||||
public class GicsConsentService extends BaseConsentService {
|
||||
public class GicsConsentService implements ICheckConsent {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(GicsConsentService.class);
|
||||
|
||||
@ -49,17 +49,18 @@ public class GicsConsentService extends BaseConsentService {
|
||||
private final RestTemplate restTemplate;
|
||||
private final FhirContext fhirContext;
|
||||
private final HttpHeaders httpHeader;
|
||||
private final GIcsConfigProperties gIcsConfigProperties;
|
||||
private String url;
|
||||
|
||||
public GicsConsentService(GIcsConfigProperties gIcsConfigProperties,
|
||||
RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig, ObjectMapper objectMapper) {
|
||||
super(gIcsConfigProperties,objectMapper);
|
||||
RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
|
||||
|
||||
this.retryTemplate = retryTemplate;
|
||||
this.restTemplate = restTemplate;
|
||||
this.fhirContext = appFhirConfig.fhirContext();
|
||||
httpHeader = buildHeader(gIcsConfigProperties.getUsername(),
|
||||
gIcsConfigProperties.getPassword());
|
||||
this.gIcsConfigProperties = gIcsConfigProperties;
|
||||
log.info("GicsConsentService initialized...");
|
||||
}
|
||||
|
||||
@ -207,7 +208,8 @@ public class GicsConsentService extends BaseConsentService {
|
||||
String consentDomain;
|
||||
switch (targetConsentDomain) {
|
||||
case BroadConsent -> consentDomain = gIcsConfigProperties.getBroadConsentDomainName();
|
||||
case Modelvorhaben64e -> consentDomain = gIcsConfigProperties.getGenomDeConsentDomainName();
|
||||
case Modelvorhaben64e ->
|
||||
consentDomain = gIcsConfigProperties.getGenomDeConsentDomainName();
|
||||
default -> throw new IllegalArgumentException(
|
||||
"target ConsentDomain is missing but must be provided!");
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package dev.dnpm.etl.processor.consent;
|
||||
|
||||
import dev.pcvolkmer.mv64e.mtb.Mtb;
|
||||
import java.util.Date;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.Consent.ConsentProvisionType;
|
||||
@ -59,8 +58,4 @@ public interface ICheckConsent {
|
||||
ConsentProvisionType getProvisionTypeByPolicyCode(Bundle consentBundle, Date requestDate,
|
||||
ConsentDomain consentDomain);
|
||||
|
||||
|
||||
void embedBroadConsentResources(Mtb mtbFile, Bundle broadConsent);
|
||||
|
||||
void addGenomeDbProvisions(Mtb mtbFile, Bundle consentGnomeDe);
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ data class GIcsConfigProperties(
|
||||
* Consent version (fixed version)
|
||||
*
|
||||
*/
|
||||
val genomeDeConsentVersion: String = "2.0"
|
||||
val genomeDeConsentVersion: String = "2.0"
|
||||
) {
|
||||
companion object {
|
||||
const val NAME = "app.consent.gics"
|
||||
|
@ -30,6 +30,7 @@ import dev.dnpm.etl.processor.pseudonym.GpasPseudonymGenerator
|
||||
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
|
||||
import dev.dnpm.etl.processor.security.TokenRepository
|
||||
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.TransformationService
|
||||
import org.slf4j.LoggerFactory
|
||||
@ -75,17 +76,26 @@ class AppConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun appFhirConfig(): AppFhirConfig{
|
||||
fun appFhirConfig(): AppFhirConfig {
|
||||
return AppFhirConfig()
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
|
||||
@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)
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "BUILDIN", matchIfMissing = true)
|
||||
@ConditionalOnProperty(
|
||||
value = ["app.pseudonymize.generator"],
|
||||
havingValue = "BUILDIN",
|
||||
matchIfMissing = true
|
||||
)
|
||||
@Bean
|
||||
fun buildinPseudonymGenerator(): Generator {
|
||||
return AnonymizingGenerator()
|
||||
@ -105,7 +115,7 @@ class AppConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun getObjectMapper () : ObjectMapper{
|
||||
fun getObjectMapper(): ObjectMapper {
|
||||
return JacksonConfig().objectMapper()
|
||||
}
|
||||
|
||||
@ -133,7 +143,11 @@ class AppConfiguration {
|
||||
callback: RetryCallback<T, E>,
|
||||
throwable: Throwable
|
||||
) {
|
||||
logger.warn("Error occured: {}. Retrying {}", throwable.message, context.retryCount)
|
||||
logger.warn(
|
||||
"Error occured: {}. Retrying {}",
|
||||
throwable.message,
|
||||
context.retryCount
|
||||
)
|
||||
}
|
||||
})
|
||||
.build()
|
||||
@ -141,7 +155,11 @@ class AppConfiguration {
|
||||
|
||||
@ConditionalOnProperty(value = ["app.security.enable-tokens"], havingValue = "true")
|
||||
@Bean
|
||||
fun tokenService(userDetailsManager: InMemoryUserDetailsManager, passwordEncoder: PasswordEncoder, tokenRepository: TokenRepository): TokenService {
|
||||
fun tokenService(
|
||||
userDetailsManager: InMemoryUserDetailsManager,
|
||||
passwordEncoder: PasswordEncoder,
|
||||
tokenRepository: TokenRepository
|
||||
): TokenService {
|
||||
return TokenService(userDetailsManager, passwordEncoder, tokenRepository)
|
||||
}
|
||||
|
||||
@ -162,7 +180,11 @@ class AppConfiguration {
|
||||
gPasConfigProperties: GPasConfigProperties,
|
||||
connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
|
||||
): ConnectionCheckService {
|
||||
return GPasConnectionCheckService(restTemplate, gPasConfigProperties, connectionCheckUpdateProducer)
|
||||
return GPasConnectionCheckService(
|
||||
restTemplate,
|
||||
gPasConfigProperties,
|
||||
connectionCheckUpdateProducer
|
||||
)
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(value = ["app.pseudonymizer"], havingValue = "GPAS")
|
||||
@ -173,7 +195,11 @@ class AppConfiguration {
|
||||
gPasConfigProperties: GPasConfigProperties,
|
||||
connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
|
||||
): ConnectionCheckService {
|
||||
return GPasConnectionCheckService(restTemplate, gPasConfigProperties, connectionCheckUpdateProducer)
|
||||
return GPasConnectionCheckService(
|
||||
restTemplate,
|
||||
gPasConfigProperties,
|
||||
connectionCheckUpdateProducer
|
||||
)
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -183,14 +209,31 @@ class AppConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
|
||||
fun gicsConsentService( gIcsConfigProperties: GIcsConfigProperties,
|
||||
retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig, getObjectMapper: ObjectMapper): ICheckConsent {
|
||||
fun gicsConsentService(
|
||||
gIcsConfigProperties: GIcsConfigProperties,
|
||||
retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig
|
||||
): ICheckConsent {
|
||||
return GicsConsentService(
|
||||
gIcsConfigProperties,
|
||||
retryTemplate,
|
||||
restTemplate,
|
||||
appFhirConfig,
|
||||
getObjectMapper
|
||||
appFhirConfig
|
||||
)
|
||||
}
|
||||
|
||||
@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
|
||||
)
|
||||
}
|
||||
|
||||
@ -198,10 +241,14 @@ class AppConfiguration {
|
||||
@Bean
|
||||
fun gIcsConnectionCheckService(
|
||||
restTemplate: RestTemplate,
|
||||
gIcsConfigProperties: GIcsConfigProperties,
|
||||
gIcsConfigProperties: GIcsConfigProperties,
|
||||
connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
|
||||
): ConnectionCheckService {
|
||||
return GIcsConnectionCheckService(restTemplate, gIcsConfigProperties, connectionCheckUpdateProducer)
|
||||
return GIcsConnectionCheckService(
|
||||
restTemplate,
|
||||
gIcsConfigProperties,
|
||||
connectionCheckUpdateProducer
|
||||
)
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -58,7 +58,7 @@ class RequestProcessor(
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val applicationEventPublisher: ApplicationEventPublisher,
|
||||
private val appConfigProperties: AppConfigProperties,
|
||||
private val consentService: ICheckConsent?
|
||||
private val consentProcessor: ConsentProcessor?
|
||||
) {
|
||||
|
||||
private var logger: Logger = LoggerFactory.getLogger("RequestProcessor")
|
||||
@ -78,80 +78,13 @@ class RequestProcessor(
|
||||
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) {
|
||||
val pid = PatientId(extractPatientIdentifier(mtbFile))
|
||||
|
||||
if (consentGatedCheckAndTryEmbedding(mtbFile)) {
|
||||
val isConsentOk = consentProcessor != null &&
|
||||
consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) || consentProcessor == null;
|
||||
if (isConsentOk) {
|
||||
mtbFile pseudonymizeWith pseudonymizeService
|
||||
mtbFile anonymizeContentWith pseudonymizeService
|
||||
val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
|
||||
|
@ -19,14 +19,13 @@
|
||||
|
||||
package dev.dnpm.etl.processor.pseudonym
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext
|
||||
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.GIcsConfigProperties
|
||||
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.ConsentProcessor
|
||||
import dev.dnpm.etl.processor.services.TransformationServiceTest
|
||||
import dev.pcvolkmer.mv64e.mtb.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@ -46,7 +45,7 @@ import java.util.*
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class ExtensionsTest {
|
||||
fun getObjectMapper() : ObjectMapper {
|
||||
fun getObjectMapper(): ObjectMapper {
|
||||
return JacksonConfig().objectMapper()
|
||||
}
|
||||
|
||||
@ -251,35 +250,19 @@ class ExtensionsTest {
|
||||
private fun addConsentData(mtbFile: Mtb) {
|
||||
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 dummyConsent = TransformationServiceTest.getDummyConsent()
|
||||
dummyConsent.patient.reference = "Patient/$CLEAN_PATIENT_ID"
|
||||
bundle.addEntry().resource = dummyConsent
|
||||
|
||||
baseConsentService.embedBroadConsentResources(mtbFile, bundle)
|
||||
ConsentProcessor(
|
||||
gIcsConfigProperties,
|
||||
JacksonConfig().objectMapper(),
|
||||
FhirContext.forR4(),
|
||||
null
|
||||
).embedBroadConsentResources(mtbFile, bundle)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -60,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 consentProcessor: ConsentProcessor
|
||||
private lateinit var requestProcessor: RequestProcessor
|
||||
|
||||
@BeforeEach
|
||||
@ -70,7 +70,7 @@ class RequestProcessorTest {
|
||||
@Mock sender: RestMtbFileSender,
|
||||
@Mock requestService: RequestService,
|
||||
@Mock applicationEventPublisher: ApplicationEventPublisher,
|
||||
@Mock gicsConsentService: GicsConsentService
|
||||
@Mock consentProcessor: ConsentProcessor
|
||||
) {
|
||||
this.pseudonymizeService = pseudonymizeService
|
||||
this.transformationService = transformationService
|
||||
@ -78,7 +78,7 @@ class RequestProcessorTest {
|
||||
this.requestService = requestService
|
||||
this.applicationEventPublisher = applicationEventPublisher
|
||||
this.appConfigProperties = AppConfigProperties(null)
|
||||
this.gicsConsentService = gicsConsentService
|
||||
this.consentProcessor = consentProcessor
|
||||
|
||||
val objectMapper = ObjectMapper()
|
||||
|
||||
@ -90,7 +90,7 @@ class RequestProcessorTest {
|
||||
objectMapper,
|
||||
applicationEventPublisher,
|
||||
appConfigProperties,
|
||||
gicsConsentService
|
||||
consentProcessor
|
||||
)
|
||||
}
|
||||
|
||||
@ -348,7 +348,10 @@ class RequestProcessorTest {
|
||||
MtbFileSender.Response(status = RequestStatus.UNKNOWN)
|
||||
}.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>()
|
||||
verify(requestService, times(1)).save(requestCaptor.capture())
|
||||
@ -366,7 +369,10 @@ class RequestProcessorTest {
|
||||
MtbFileSender.Response(status = RequestStatus.SUCCESS)
|
||||
}.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>()
|
||||
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
|
||||
@ -384,7 +390,10 @@ class RequestProcessorTest {
|
||||
MtbFileSender.Response(status = RequestStatus.ERROR)
|
||||
}.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>()
|
||||
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
|
||||
@ -396,7 +405,10 @@ class RequestProcessorTest {
|
||||
fun testShouldSendDeleteRequestWithPseudonymErrorAndSaveErrorRequestStatus() {
|
||||
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>()
|
||||
verify(requestService, times(1)).save(requestCaptor.capture())
|
||||
|
Reference in New Issue
Block a user