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:
@ -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) {
|
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!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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!");
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -81,11 +82,20 @@ class AppConfiguration {
|
|||||||
|
|
||||||
@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()
|
||||||
@ -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
|
||||||
|
@ -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 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))
|
||||||
|
@ -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
|
||||||
@ -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
|
||||||
|
@ -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())
|
||||||
|
Reference in New Issue
Block a user