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

refactor: moved consent evaluation to new ConsentProcessor.kt

This commit is contained in:
Jakub Lidke
2025-07-16 15:20:26 +02:00
parent 38eec60189
commit dc6a4c6cb9
13 changed files with 310 additions and 277 deletions

View File

@ -23,9 +23,9 @@ import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.anyValueClass import dev.dnpm.etl.processor.anyValueClass
import dev.dnpm.etl.processor.config.AppSecurityConfiguration import dev.dnpm.etl.processor.config.AppSecurityConfiguration
import dev.dnpm.etl.processor.consent.ConsentCheckFileBased import dev.dnpm.etl.processor.consent.ConsentByMtbFile
import dev.dnpm.etl.processor.consent.TtpConsentStatus import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.consent.ICheckConsent import dev.dnpm.etl.processor.consent.IGetConsent
import dev.dnpm.etl.processor.security.TokenRepository import dev.dnpm.etl.processor.security.TokenRepository
import dev.dnpm.etl.processor.security.UserRoleRepository import dev.dnpm.etl.processor.security.UserRoleRepository
import dev.dnpm.etl.processor.services.RequestProcessor import dev.dnpm.etl.processor.services.RequestProcessor
@ -55,7 +55,7 @@ import org.springframework.test.web.servlet.post
classes = [ classes = [
MtbFileRestController::class, MtbFileRestController::class,
AppSecurityConfiguration::class, AppSecurityConfiguration::class,
ConsentCheckFileBased::class, ICheckConsent::class ConsentByMtbFile::class, IGetConsent::class
] ]
) )
@MockitoBean(types = [TokenRepository::class, RequestProcessor::class]) @MockitoBean(types = [TokenRepository::class, RequestProcessor::class])

View File

@ -1,18 +1,15 @@
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.apache.commons.lang3.NotImplementedException;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Consent.ConsentProvisionType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class ConsentCheckFileBased implements ICheckConsent { public class ConsentByMtbFile implements IGetConsent {
private static final Logger log = LoggerFactory.getLogger(ConsentCheckFileBased.class); private static final Logger log = LoggerFactory.getLogger(ConsentByMtbFile.class);
public ConsentCheckFileBased() { public ConsentByMtbFile() {
log.info("ConsentCheckFileBased initialized..."); log.info("ConsentCheckFileBased initialized...");
} }
@ -23,12 +20,12 @@ public class ConsentCheckFileBased implements ICheckConsent {
@Override @Override
public Bundle getBroadConsent(String personIdentifierValue, Date requestDate) { public Bundle getBroadConsent(String personIdentifierValue, Date requestDate) {
return ICheckConsent.super.getBroadConsent(personIdentifierValue, requestDate); return IGetConsent.super.getBroadConsent(personIdentifierValue, requestDate);
} }
@Override @Override
public Bundle getGenomDeConsent(String personIdentifierValue, Date requestDate) { public Bundle getGenomDeConsent(String personIdentifierValue, Date requestDate) {
return ICheckConsent.super.getGenomDeConsent(personIdentifierValue, requestDate); return IGetConsent.super.getGenomDeConsent(personIdentifierValue, requestDate);
} }
@Override @Override
@ -36,10 +33,4 @@ public class ConsentCheckFileBased implements ICheckConsent {
ConsentDomain targetConsentDomain, Date requestDate) { ConsentDomain targetConsentDomain, Date requestDate) {
return new Bundle(); return new Bundle();
} }
@Override
public ConsentProvisionType getProvisionTypeByPolicyCode(Bundle consentBundle,
Date requestDate, ConsentDomain consentDomain) {
return ConsentProvisionType.NULL;
}
} }

View File

@ -2,27 +2,18 @@ package dev.dnpm.etl.processor.consent;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.dnpm.etl.processor.config.AppFhirConfig; import dev.dnpm.etl.processor.config.AppFhirConfig;
import dev.dnpm.etl.processor.config.GIcsConfigProperties; import dev.dnpm.etl.processor.config.GIcsConfigProperties;
import java.util.Date; import java.util.Date;
import java.util.Optional;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Consent.ConsentProvisionType;
import org.hl7.fhir.r4.model.Consent.ConsentState;
import org.hl7.fhir.r4.model.Consent.ProvisionComponent;
import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r4.model.Period;
import org.hl7.fhir.r4.model.ResourceType;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -39,7 +30,7 @@ import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
public class GicsConsentService implements ICheckConsent { public class GicsConsentService implements IGetConsent {
private final Logger log = LoggerFactory.getLogger(GicsConsentService.class); private final Logger log = LoggerFactory.getLogger(GicsConsentService.class);
@ -272,83 +263,4 @@ public class GicsConsentService implements ICheckConsent {
return TtpConsentStatus.FAILED_TO_ASK; return TtpConsentStatus.FAILED_TO_ASK;
} }
/**
* @param consentBundle consent resource
* @param requestDate date which must be within validation period of provision
* @return type of provision, will be {@link ConsentProvisionType#NULL} if none is found.
*/
public ConsentProvisionType getProvisionTypeByPolicyCode(Bundle consentBundle,
Date requestDate, ConsentDomain consentDomain) {
String code;
String system;
if (ConsentDomain.BroadConsent == consentDomain) {
code = gIcsConfigProperties.getBroadConsentPolicyCode();
system = gIcsConfigProperties.getBroadConsentPolicySystem();
} else if (ConsentDomain.Modelvorhaben64e == consentDomain) {
code = gIcsConfigProperties.getGenomeDePolicyCode();
system = gIcsConfigProperties.getGenomeDePolicySystem();
} else {
throw new NotImplementedException("unknown consent domain " + consentDomain.name());
}
Optional<ConsentProvisionType> provisionTypeByPolicyCode = getProvisionTypeByPolicyCode(
consentBundle, code,
system, requestDate);
return provisionTypeByPolicyCode.orElse(ConsentProvisionType.NULL);
}
/**
* @param consentBundle consent resource
* @param policyAndProvisionCode policyRule and provision code value
* @param policyAndProvisionSystem policyRule and provision system value
* @param requestDate date which must be within validation period of provision
* @return type of provision, will be {@link ConsentProvisionType#NULL} if none is found.
*/
public Optional<ConsentProvisionType> getProvisionTypeByPolicyCode(Bundle consentBundle,
String policyAndProvisionCode, String policyAndProvisionSystem, Date requestDate) {
return consentBundle.getEntry().stream().filter(entry -> {
if (entry.getResource().getResourceType() != ResourceType.Consent) {
// no consent in bundle
return false;
}
Consent consent = (Consent) entry.getResource();
// consent ist active and its policy rule must fits search criteria
return consent.getStatus() == ConsentState.ACTIVE && checkCoding(
policyAndProvisionCode, policyAndProvisionSystem,
consent.getPolicyRule().getCodingFirstRep()) && isIsRequestDateInRange(requestDate,
consent.getProvision().getPeriod());
}).map(consentWithTargetPolicy -> {
ProvisionComponent provision = ((Consent) consentWithTargetPolicy.getResource()).getProvision();
var provisionComponentByCode = provision.getProvision().stream().filter(prov ->
checkCoding(policyAndProvisionCode, policyAndProvisionSystem,
prov.getCodeFirstRep().getCodingFirstRep()) && isIsRequestDateInRange(
requestDate, prov.getPeriod())
).findFirst();
if (provisionComponentByCode.isPresent()) {
// actual provision we search for
return provisionComponentByCode.get().getType();
}
// no fitting nested provision found - fall back to wrapping provision with default value
return provision.getType();
}).findFirst().or(() -> Optional.of(ConsentProvisionType.NULL));
}
protected static boolean checkCoding(String researchAllowedPolicyOid,
String researchAllowedPolicySystem, Coding coding) {
return coding.getSystem().equals(researchAllowedPolicySystem) && coding.getCode()
.equals(researchAllowedPolicyOid);
}
protected static boolean isIsRequestDateInRange(Date requestdate, Period provPeriod) {
var isRequestDateAfterOrEqualStart = provPeriod.getStart().compareTo(requestdate);
var isRequestDateBeforeOrEqualEnd = provPeriod.getEnd().compareTo(requestdate);
return isRequestDateAfterOrEqualStart <= 0 && isRequestDateBeforeOrEqualEnd >= 0;
}
} }

View File

@ -2,9 +2,8 @@ package dev.dnpm.etl.processor.consent;
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;
public interface ICheckConsent { public interface IGetConsent {
/** /**
* Get broad consent status for a patient identifier * Get broad consent status for a patient identifier
@ -50,12 +49,4 @@ public interface ICheckConsent {
Bundle currentConsentForPersonAndTemplate(String personIdentifierValue, Bundle currentConsentForPersonAndTemplate(String personIdentifierValue,
ConsentDomain targetConsentDomain, Date requestDate); ConsentDomain targetConsentDomain, Date requestDate);
/**
* @param consentBundle consent resource
* @param requestDate date which must be within validation period of provision
* @return type of provision, will be {@link ConsentProvisionType#NULL} if none is found.
*/
ConsentProvisionType getProvisionTypeByPolicyCode(Bundle consentBundle, Date requestDate,
ConsentDomain consentDomain);
} }

View File

@ -20,8 +20,8 @@
package dev.dnpm.etl.processor.config package dev.dnpm.etl.processor.config
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.consent.ConsentCheckFileBased import dev.dnpm.etl.processor.consent.ConsentByMtbFile
import dev.dnpm.etl.processor.consent.ICheckConsent import dev.dnpm.etl.processor.consent.IGetConsent
import dev.dnpm.etl.processor.consent.GicsConsentService import dev.dnpm.etl.processor.consent.GicsConsentService
import dev.dnpm.etl.processor.monitoring.* import dev.dnpm.etl.processor.monitoring.*
import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator
@ -212,7 +212,7 @@ class AppConfiguration {
fun gicsConsentService( fun gicsConsentService(
gIcsConfigProperties: GIcsConfigProperties, gIcsConfigProperties: GIcsConfigProperties,
retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig
): ICheckConsent { ): IGetConsent {
return GicsConsentService( return GicsConsentService(
gIcsConfigProperties, gIcsConfigProperties,
retryTemplate, retryTemplate,
@ -227,7 +227,7 @@ class AppConfiguration {
gIcsConfigProperties: GIcsConfigProperties, gIcsConfigProperties: GIcsConfigProperties,
getObjectMapper: ObjectMapper, getObjectMapper: ObjectMapper,
appFhirConfig: AppFhirConfig, appFhirConfig: AppFhirConfig,
gicsConsentService: ICheckConsent gicsConsentService: IGetConsent
): ConsentProcessor { ): ConsentProcessor {
return ConsentProcessor( return ConsentProcessor(
gIcsConfigProperties, gIcsConfigProperties,
@ -253,8 +253,8 @@ class AppConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
fun constService(): ICheckConsent { fun iGetConsentService(): IGetConsent {
return ConsentCheckFileBased() return ConsentByMtbFile()
} }
} }

View File

@ -23,7 +23,7 @@ import de.ukw.ccc.bwhc.dto.Consent
import de.ukw.ccc.bwhc.dto.MtbFile import de.ukw.ccc.bwhc.dto.MtbFile
import dev.dnpm.etl.processor.CustomMediaType import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.PatientId import dev.dnpm.etl.processor.PatientId
import dev.dnpm.etl.processor.consent.ICheckConsent import dev.dnpm.etl.processor.consent.IGetConsent
import dev.dnpm.etl.processor.consent.TtpConsentStatus import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.services.RequestProcessor import dev.dnpm.etl.processor.services.RequestProcessor
import dev.pcvolkmer.mv64e.mtb.Mtb import dev.pcvolkmer.mv64e.mtb.Mtb
@ -35,7 +35,7 @@ import org.springframework.web.bind.annotation.*
@RestController @RestController
@RequestMapping(path = ["mtbfile", "mtb"]) @RequestMapping(path = ["mtbfile", "mtb"])
class MtbFileRestController( class MtbFileRestController(
private val requestProcessor: RequestProcessor, private val iCheckConsent: ICheckConsent private val requestProcessor: RequestProcessor, private val iGetConsent: IGetConsent
) { ) {
private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java) private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
@ -63,7 +63,7 @@ class MtbFileRestController(
} }
private fun checkConsentStatus(mtbFile: MtbFile): Pair<TtpConsentStatus, Boolean> { private fun checkConsentStatus(mtbFile: MtbFile): Pair<TtpConsentStatus, Boolean> {
var ttpConsentStatus = iCheckConsent.getTtpBroadConsentStatus(mtbFile.patient.id) var ttpConsentStatus = iGetConsent.getTtpBroadConsentStatus(mtbFile.patient.id)
val isConsentOK = val isConsentOK =
(ttpConsentStatus.equals(TtpConsentStatus.UNKNOWN_CHECK_FILE) && mtbFile.consent.status == Consent.Status.ACTIVE) || (ttpConsentStatus.equals(TtpConsentStatus.UNKNOWN_CHECK_FILE) && mtbFile.consent.status == Consent.Status.ACTIVE) ||

View File

@ -6,12 +6,14 @@ import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.config.GIcsConfigProperties import dev.dnpm.etl.processor.config.GIcsConfigProperties
import dev.dnpm.etl.processor.consent.ConsentDomain import dev.dnpm.etl.processor.consent.ConsentDomain
import dev.dnpm.etl.processor.consent.ICheckConsent import dev.dnpm.etl.processor.consent.IGetConsent
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized
import dev.pcvolkmer.mv64e.mtb.* import dev.pcvolkmer.mv64e.mtb.*
import org.hl7.fhir.r4.model.Bundle import org.apache.commons.lang3.NotImplementedException
import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.*
import org.hl7.fhir.r4.model.Consent import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Consent.ConsentState
import org.hl7.fhir.r4.model.Consent.ProvisionComponent import org.hl7.fhir.r4.model.Consent.ProvisionComponent
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -23,9 +25,10 @@ import java.util.*
@Service @Service
class ConsentProcessor( class ConsentProcessor(
private val gIcsConfigProperties: GIcsConfigProperties, private val objectMapper: ObjectMapper, private val gIcsConfigProperties: GIcsConfigProperties,
private val objectMapper: ObjectMapper,
private val fhirContext: FhirContext, private val fhirContext: FhirContext,
private val consentService: ICheckConsent? private val consentService: IGetConsent?
) { ) {
private var logger: Logger = LoggerFactory.getLogger("ConsentProcessor") private var logger: Logger = LoggerFactory.getLogger("ConsentProcessor")
@ -80,11 +83,11 @@ class ConsentProcessor(
embedBroadConsentResources(mtbFile, broadConsent) embedBroadConsentResources(mtbFile, broadConsent)
val broadConsentStatus = consentService.getProvisionTypeByPolicyCode( val broadConsentStatus = getProvisionTypeByPolicyCode(
broadConsent, requestDate, ConsentDomain.BroadConsent broadConsent, requestDate, ConsentDomain.BroadConsent
) )
val genomDeSequencingStatus = consentService.getProvisionTypeByPolicyCode( val genomDeSequencingStatus = getProvisionTypeByPolicyCode(
genomeDeConsent, requestDate, ConsentDomain.Modelvorhaben64e genomeDeConsent, requestDate, ConsentDomain.Modelvorhaben64e
) )
@ -92,27 +95,22 @@ class ConsentProcessor(
// bc not asked // bc not asked
return false return false
} }
if (Consent.ConsentProvisionType.PERMIT == broadConsentStatus || if (Consent.ConsentProvisionType.PERMIT == broadConsentStatus || Consent.ConsentProvisionType.PERMIT == genomDeSequencingStatus) return true
Consent.ConsentProvisionType.PERMIT == genomDeSequencingStatus
) return true
return false return false
} }
public fun embedBroadConsentResources(mtbFile: Mtb, broadConsent: Bundle) { fun embedBroadConsentResources(mtbFile: Mtb, broadConsent: Bundle) {
for (entry in broadConsent.getEntry()) { for (entry in broadConsent.getEntry()) {
val resource = entry.getResource() val resource = entry.getResource()
if (resource is Consent) { if (resource is Consent) {
// since jackson convertValue does not work here, // since jackson convertValue does not work here,
// we need another step to back to string, before we convert to object map // we need another step to back to string, before we convert to object map
val asJsonString = val asJsonString = fhirContext.newJsonParser().encodeResourceToString(resource)
fhirContext.newJsonParser().encodeResourceToString(resource)
try { try {
val mapOfJson: HashMap<String?, Any?>? = val mapOfJson: HashMap<String?, Any?>? =
objectMapper.readValue<HashMap<String?, Any?>?>( objectMapper.readValue<HashMap<String?, Any?>?>(
asJsonString, asJsonString, object : TypeReference<HashMap<String?, Any?>?>() {})
object : TypeReference<HashMap<String?, Any?>?>() {
})
mtbFile.metadata.researchConsents.add(mapOfJson) mtbFile.metadata.researchConsents.add(mapOfJson)
} catch (e: JsonProcessingException) { } catch (e: JsonProcessingException) {
throw RuntimeException(e) throw RuntimeException(e)
@ -121,7 +119,7 @@ class ConsentProcessor(
} }
} }
public fun addGenomeDbProvisions(mtbFile: Mtb, consentGnomeDe: Bundle) { fun addGenomeDbProvisions(mtbFile: Mtb, consentGnomeDe: Bundle) {
for (entry in consentGnomeDe.getEntry()) { for (entry in consentGnomeDe.getEntry()) {
val resource = entry.getResource() val resource = entry.getResource()
if (resource !is Consent) { if (resource !is Consent) {
@ -157,8 +155,7 @@ class ConsentProcessor(
val provision = Provision.builder() val provision = Provision.builder()
.type(ConsentProvision.valueOf(provisionComponent.getType().name)) .type(ConsentProvision.valueOf(provisionComponent.getType().name))
.date(provisionComponent.getPeriod().getStart()) .date(provisionComponent.getPeriod().getStart())
.purpose(modelProjectConsentPurpose) .purpose(modelProjectConsentPurpose).build()
.build()
mtbFile.metadata.modelProjectConsent.provisions.add(provision) mtbFile.metadata.modelProjectConsent.provisions.add(provision)
} catch (ioe: IOException) { } catch (ioe: IOException) {
@ -183,4 +180,89 @@ class ConsentProcessor(
mtbFile.metadata.type = MvhSubmissionType.INITIAL mtbFile.metadata.type = MvhSubmissionType.INITIAL
} }
/**
* @param consentBundle consent resource
* @param requestDate date which must be within validation period of provision
* @return type of provision, will be [org.hl7.fhir.r4.model.Consent.ConsentProvisionType.NULL] if none is found.
*/
fun getProvisionTypeByPolicyCode(
consentBundle: Bundle, requestDate: Date?, consentDomain: ConsentDomain
): Consent.ConsentProvisionType {
val code: String?
val system: String?
if (ConsentDomain.BroadConsent == consentDomain) {
code = gIcsConfigProperties.broadConsentPolicyCode
system = gIcsConfigProperties.broadConsentPolicySystem
} else if (ConsentDomain.Modelvorhaben64e == consentDomain) {
code = gIcsConfigProperties.genomeDePolicyCode
system = gIcsConfigProperties.genomeDePolicySystem
} else {
throw NotImplementedException("unknown consent domain " + consentDomain.name)
}
val provisionTypeByPolicyCode = getProvisionTypeByPolicyCode(
consentBundle, code, system, requestDate
)
return provisionTypeByPolicyCode
}
/**
* @param consentBundle consent resource
* @param policyAndProvisionCode policyRule and provision code value
* @param policyAndProvisionSystem policyRule and provision system value
* @param requestDate date which must be within validation period of provision
* @return type of provision, will be [org.hl7.fhir.r4.model.Consent.ConsentProvisionType.NULL] if none is found.
*/
fun getProvisionTypeByPolicyCode(
consentBundle: Bundle,
policyAndProvisionCode: String?,
policyAndProvisionSystem: String?,
requestDate: Date?
): Consent.ConsentProvisionType {
val entriesOfInterest = consentBundle.entry.filter { entry ->
entry.resource.isResource && entry.resource.resourceType == ResourceType.Consent && (entry.resource as Consent).status == ConsentState.ACTIVE && checkCoding(
policyAndProvisionCode,
policyAndProvisionSystem,
(entry.resource as Consent).policyRule.codingFirstRep
) && isIsRequestDateInRange(
requestDate, (entry.resource as Consent).provision.period
)
}.map { consentWithTargetPolicy: BundleEntryComponent ->
val provision = (consentWithTargetPolicy.getResource() as Consent).getProvision()
val provisionComponentByCode =
provision.getProvision().stream().filter { prov: ProvisionComponent? ->
checkCoding(
policyAndProvisionCode,
policyAndProvisionSystem,
prov!!.getCodeFirstRep().getCodingFirstRep()
) && isIsRequestDateInRange(
requestDate, prov.getPeriod()
)
}.findFirst()
if (provisionComponentByCode.isPresent) {
// actual provision we search for
return@map provisionComponentByCode.get().getType()
} else {
if (provision.type != null) return provision.type
}
return Consent.ConsentProvisionType.NULL
}.firstOrNull()
if (entriesOfInterest == null) return Consent.ConsentProvisionType.NULL
return entriesOfInterest
}
fun checkCoding(
researchAllowedPolicyOid: String?, researchAllowedPolicySystem: String?, coding: Coding
): Boolean {
return coding.getSystem() == researchAllowedPolicySystem && (coding.getCode() == researchAllowedPolicyOid)
}
fun isIsRequestDateInRange(requestdate: Date?, provPeriod: Period): Boolean {
val isRequestDateAfterOrEqualStart = provPeriod.getStart().compareTo(requestdate)
val isRequestDateBeforeOrEqualEnd = provPeriod.getEnd().compareTo(requestdate)
return isRequestDateAfterOrEqualStart <= 0 && isRequestDateBeforeOrEqualEnd >= 0
}
} }

View File

@ -23,8 +23,6 @@ import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.MtbFile import de.ukw.ccc.bwhc.dto.MtbFile
import dev.dnpm.etl.processor.* import dev.dnpm.etl.processor.*
import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.config.AppConfigProperties
import dev.dnpm.etl.processor.consent.ConsentDomain
import dev.dnpm.etl.processor.consent.ICheckConsent
import dev.dnpm.etl.processor.consent.TtpConsentStatus import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.monitoring.Report import dev.dnpm.etl.processor.monitoring.Report
import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.Request
@ -33,19 +31,15 @@ import dev.dnpm.etl.processor.monitoring.RequestType
import dev.dnpm.etl.processor.output.* import dev.dnpm.etl.processor.output.*
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized
import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
import dev.pcvolkmer.mv64e.mtb.Mtb import dev.pcvolkmer.mv64e.mtb.Mtb
import dev.pcvolkmer.mv64e.mtb.MvhSubmissionType
import org.apache.commons.codec.binary.Base32 import org.apache.commons.codec.binary.Base32
import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.codec.digest.DigestUtils
import org.hl7.fhir.r4.model.Consent
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationEventPublisher import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.lang.RuntimeException import java.lang.RuntimeException
import java.time.Clock
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
@ -83,7 +77,7 @@ class RequestProcessor(
val pid = PatientId(extractPatientIdentifier(mtbFile)) val pid = PatientId(extractPatientIdentifier(mtbFile))
val isConsentOk = consentProcessor != null && val isConsentOk = consentProcessor != null &&
consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) || consentProcessor == null; consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) || consentProcessor == null
if (isConsentOk) { if (isConsentOk) {
mtbFile pseudonymizeWith pseudonymizeService mtbFile pseudonymizeWith pseudonymizeService
mtbFile anonymizeContentWith pseudonymizeService mtbFile anonymizeContentWith pseudonymizeService
@ -190,7 +184,7 @@ class RequestProcessor(
) )
) )
} catch (e: Exception) { } catch (_: Exception) {
requestService.save( requestService.save(
Request( Request(
uuid = requestId, uuid = requestId,

View File

@ -3,20 +3,13 @@ package dev.dnpm.etl.processor.consent;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import ca.uhn.fhir.context.FhirContext;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import dev.dnpm.etl.processor.config.AppConfiguration; import dev.dnpm.etl.processor.config.AppConfiguration;
import dev.dnpm.etl.processor.config.AppFhirConfig; import dev.dnpm.etl.processor.config.AppFhirConfig;
import dev.dnpm.etl.processor.config.GIcsConfigProperties; import dev.dnpm.etl.processor.config.GIcsConfigProperties;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime;
import java.util.Date; import java.util.Date;
import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Consent.ConsentProvisionType;
import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
@ -27,17 +20,13 @@ import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.test.web.client.MockRestServiceServer;
@ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class}) @ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class})
@TestPropertySource(properties = {"app.consent.gics.enabled=true", @TestPropertySource(properties = {"app.consent.gics.enabled=true",
"app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics"}) "app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics"})
@ -63,7 +52,6 @@ public class GicsConsentServiceTest {
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
mockRestServiceServer = MockRestServiceServer.createServer(appConfiguration.restTemplate()); mockRestServiceServer = MockRestServiceServer.createServer(appConfiguration.restTemplate());
} }
@Test @Test
@ -72,12 +60,10 @@ public class GicsConsentServiceTest {
new ParametersParameterComponent().setName("consented") new ParametersParameterComponent().setName("consented")
.setValue(new BooleanType().setValue(true))); .setValue(new BooleanType().setValue(true)));
mockRestServiceServer.expect( mockRestServiceServer.expect(requestTo(
requestTo("http://localhost:8090/ttp-fhir/fhir/gics" "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT))
+ GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond( .andRespond(withSuccess(appFhirConfig.fhirContext().newJsonParser()
withSuccess(appFhirConfig.fhirContext().newJsonParser() .encodeResourceToString(responseConsented), MediaType.APPLICATION_JSON));
.encodeResourceToString(responseConsented),
MediaType.APPLICATION_JSON));
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN); assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN);
@ -89,11 +75,10 @@ public class GicsConsentServiceTest {
new ParametersParameterComponent().setName("consented") new ParametersParameterComponent().setName("consented")
.setValue(new BooleanType().setValue(false))); .setValue(new BooleanType().setValue(false)));
mockRestServiceServer.expect( mockRestServiceServer.expect(requestTo(
requestTo("http://localhost:8090/ttp-fhir/fhir/gics" "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT))
+ GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond( .andRespond(withSuccess(
withSuccess(appFhirConfig.fhirContext().newJsonParser() appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseRevoced),
.encodeResourceToString(responseRevoced),
MediaType.APPLICATION_JSON)); MediaType.APPLICATION_JSON));
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
@ -104,15 +89,13 @@ public class GicsConsentServiceTest {
@Test @Test
void gicsParameterInvalid() { void gicsParameterInvalid() {
final OperationOutcome responseErrorOutcome = new OperationOutcome().addIssue( final OperationOutcome responseErrorOutcome = new OperationOutcome().addIssue(
new OperationOutcomeIssueComponent().setSeverity( new OperationOutcomeIssueComponent().setSeverity(IssueSeverity.ERROR)
IssueSeverity.ERROR).setCode(IssueType.PROCESSING) .setCode(IssueType.PROCESSING).setDiagnostics("Invalid policy parameter..."));
.setDiagnostics("Invalid policy parameter..."));
mockRestServiceServer.expect( mockRestServiceServer.expect(
requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond( requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond(
withSuccess(appFhirConfig.fhirContext().newJsonParser() withSuccess(appFhirConfig.fhirContext().newJsonParser()
.encodeResourceToString(responseErrorOutcome), .encodeResourceToString(responseErrorOutcome), MediaType.APPLICATION_JSON));
MediaType.APPLICATION_JSON));
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK); assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
@ -122,51 +105,19 @@ public class GicsConsentServiceTest {
void buildRequestParameterCurrentPolicyStatesForPersonTest() { void buildRequestParameterCurrentPolicyStatesForPersonTest() {
String pid = "12345678"; String pid = "12345678";
var result = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(gIcsConfigProperties, var result = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(
pid, Date.from(Instant.now()),gIcsConfigProperties.getGenomDeConsentDomainName()); gIcsConfigProperties, pid, Date.from(Instant.now()),
gIcsConfigProperties.getGenomDeConsentDomainName());
assertThat(result.getParameter().size()).as("should contain 3 parameter resources").isEqualTo(3); assertThat(result.getParameter().size()).as("should contain 3 parameter resources")
.isEqualTo(3);
assertThat(((StringType)result.getParameter("domain").getValue()).getValue()).isEqualTo(gIcsConfigProperties.getGenomDeConsentDomainName()); assertThat(((StringType) result.getParameter("domain").getValue()).getValue()).isEqualTo(
assertThat(((Identifier)result.getParameter("personIdentifier").getValue()).getValue()).isEqualTo(pid); gIcsConfigProperties.getGenomDeConsentDomainName());
assertThat(
((Identifier) result.getParameter("personIdentifier").getValue()).getValue()).isEqualTo(
pid);
} }
@ParameterizedTest
@CsvSource({
"2.16.840.1.113883.3.1937.777.24.5.3.8,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2025-07-23T00:00:00+02:00,PERMIT,expect permit",
"2.16.840.1.113883.3.1937.777.24.5.3.8,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2025-06-23T00:00:00+02:00,PERMIT,expect permit date is exactly on start",
"2.16.840.1.113883.3.1937.777.24.5.3.8,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2055-06-23T00:00:00+02:00,PERMIT,expect permit date is exactly on end",
"2.16.840.1.113883.3.1937.777.24.5.3.8,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2021-06-23T00:00:00+02:00,NULL,date is before start",
"2.16.840.1.113883.3.1937.777.24.5.3.8,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2060-06-23T00:00:00+02:00,NULL,date is after end",
"2.16.840.1.113883.3.1937.777.24.5.3.8,XXXX,2025-07-23T00:00:00+02:00,NULL,system not found - therefore expect NULL",
"2.16.840.1.113883.3.1937.777.24.5.3.27,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2025-07-23T00:00:00+02:00,DENY,provision is denied"})
void getProvisionTypeByPolicyCode(String code, String system, String timeStamp, String expected,
String desc) {
var testData = getDummyBroadConsent();
Date requestDate = Date.from(OffsetDateTime.parse(timeStamp).toInstant());
var result = gicsConsentService.getProvisionTypeByPolicyCode(testData, code, system, requestDate);
assertThat(result).isNotNull();
assertThat(result).isNotEmpty();
assertThat(result.get()).as(desc).isEqualTo(ConsentProvisionType.valueOf(expected));
}
private Bundle getDummyBroadConsent() {
InputStream bundle;
try {
bundle = new ClassPathResource(
"fake_broadConsent_gics_response_permit.json").getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
return FhirContext.forR4().newJsonParser().parseResource(Bundle.class, bundle);
}
} }

View File

@ -23,7 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import de.ukw.ccc.bwhc.dto.Consent.Status import de.ukw.ccc.bwhc.dto.Consent.Status
import dev.dnpm.etl.processor.CustomMediaType import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.consent.ConsentCheckFileBased import dev.dnpm.etl.processor.consent.ConsentByMtbFile
import dev.dnpm.etl.processor.consent.GicsConsentService import dev.dnpm.etl.processor.consent.GicsConsentService
import dev.dnpm.etl.processor.consent.TtpConsentStatus import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.services.RequestProcessor import dev.dnpm.etl.processor.services.RequestProcessor
@ -68,7 +68,7 @@ class MtbFileRestControllerTest {
) { ) {
this.requestProcessor = requestProcessor this.requestProcessor = requestProcessor
val controller = MtbFileRestController(requestProcessor, val controller = MtbFileRestController(requestProcessor,
ConsentCheckFileBased() ConsentByMtbFile()
) )
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build() this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
} }
@ -220,7 +220,7 @@ class MtbFileRestControllerTest {
) { ) {
this.requestProcessor = requestProcessor this.requestProcessor = requestProcessor
val controller = MtbFileRestController(requestProcessor, val controller = MtbFileRestController(requestProcessor,
ConsentCheckFileBased() ConsentByMtbFile()
) )
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build() this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
} }

View File

@ -26,7 +26,7 @@ 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.services.ConsentProcessor import dev.dnpm.etl.processor.services.ConsentProcessor
import dev.dnpm.etl.processor.services.TransformationServiceTest import dev.dnpm.etl.processor.services.ConsentProcessorTest
import dev.pcvolkmer.mv64e.mtb.* import dev.pcvolkmer.mv64e.mtb.*
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.Bundle
@ -252,7 +252,7 @@ class ExtensionsTest {
val bundle = Bundle() val bundle = Bundle()
val dummyConsent = TransformationServiceTest.getDummyConsent() val dummyConsent = ConsentProcessorTest.getDummyGenomDeConsent()
dummyConsent.patient.reference = "Patient/$CLEAN_PATIENT_ID" dummyConsent.patient.reference = "Patient/$CLEAN_PATIENT_ID"
bundle.addEntry().resource = dummyConsent bundle.addEntry().resource = dummyConsent

View File

@ -0,0 +1,160 @@
package dev.dnpm.etl.processor.services
import ca.uhn.fhir.context.FhirContext
import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.config.GIcsConfigProperties
import dev.dnpm.etl.processor.config.JacksonConfig
import dev.dnpm.etl.processor.consent.GicsConsentService
import dev.pcvolkmer.mv64e.mtb.*
import org.assertj.core.api.Assertions.assertThat
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.CodeableConcept
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Consent
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.whenever
import org.springframework.core.io.ClassPathResource
import java.io.IOException
import java.io.InputStream
import java.time.Instant
import java.time.OffsetDateTime
import java.util.*
@ExtendWith(MockitoExtension::class)
class ConsentProcessorTest {
private lateinit var gicsConsentService: GicsConsentService
private lateinit var objectMapper: ObjectMapper
private lateinit var gIcsConfigProperties: GIcsConfigProperties
private lateinit var fhirContext: FhirContext
private lateinit var consentProcessor: ConsentProcessor
@BeforeEach
fun setups(
@Mock gicsConsentService: GicsConsentService,
) {
this.gIcsConfigProperties = GIcsConfigProperties(null, null, null, true)
val jacksonConfig = JacksonConfig()
this.objectMapper = jacksonConfig.objectMapper()
this.fhirContext = JacksonConfig.fhirContext()
this.gicsConsentService = gicsConsentService
this.consentProcessor =
ConsentProcessor(gIcsConfigProperties, objectMapper, fhirContext, gicsConsentService)
}
@Test
fun consentOk() {
assertThat(consentProcessor.toString()).isNotNull
// prep gICS response
doAnswer { getDummyBroadConsentBundle() }.whenever(gicsConsentService)
.getBroadConsent(any(), any())
doAnswer { Bundle() }.whenever(gicsConsentService)
.getGenomDeConsent(any(), any())
val inputMtb = Mtb.builder()
.patient(Patient.builder().id("d611d429-5003-11f0-a144-661e92ac9503").build()).build()
val checkResult = consentProcessor.consentGatedCheckAndTryEmbedding(inputMtb)
assertThat(checkResult).isTrue
assertThat(inputMtb.metadata.researchConsents).hasSize(13)
}
companion object {
fun getDummyGenomDeConsent(): Consent {
val consent = Consent()
consent.id = "consent 1 id"
consent.patient.reference = "Patient/1234-pat1"
consent.provision.setType(
Consent.ConsentProvisionType.fromCode(
"deny"
)
)
consent.provision.period.start =
Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
consent.provision.period.end =
Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
val addProvision1 = consent.provision.addProvision()
addProvision1.setType(Consent.ConsentProvisionType.fromCode("permit"))
addProvision1.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
addProvision1.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
addProvision1.code.addLast(
CodeableConcept(
Coding(
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
"Teilnahme",
"Teilnahme am Modellvorhaben und Einwilligung zur Genomsequenzierung"
)
)
)
val addProvision2 = consent.provision.addProvision()
addProvision2.setType(Consent.ConsentProvisionType.fromCode("deny"))
addProvision2.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
addProvision2.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
addProvision2.code.addLast(
CodeableConcept(
Coding(
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
"Rekontaktierung",
"Re-Identifizierung meiner Daten über die Vertrauensstelle beim Robert Koch-Institut und in die erneute Kontaktaufnahme durch meine behandelnde Ärztin oder meinen behandelnden Arzt"
)
)
)
return consent
}
}
@ParameterizedTest
@CsvSource(
"2.16.840.1.113883.3.1937.777.24.5.3.8,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2025-07-23T00:00:00+02:00,PERMIT,expect permit",
"2.16.840.1.113883.3.1937.777.24.5.3.8,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2025-06-23T00:00:00+02:00,PERMIT,expect permit date is exactly on start",
"2.16.840.1.113883.3.1937.777.24.5.3.8,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2055-06-23T00:00:00+02:00,PERMIT,expect permit date is exactly on end",
"2.16.840.1.113883.3.1937.777.24.5.3.8,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2021-06-23T00:00:00+02:00,NULL,date is before start",
"2.16.840.1.113883.3.1937.777.24.5.3.8,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2060-06-23T00:00:00+02:00,NULL,date is after end",
"2.16.840.1.113883.3.1937.777.24.5.3.8,XXXX,2025-07-23T00:00:00+02:00,NULL,system not found - therefore expect NULL",
"2.16.840.1.113883.3.1937.777.24.5.3.27,urn:oid:2.16.840.1.113883.3.1937.777.24.5.3,2025-07-23T00:00:00+02:00,DENY,provision is denied"
)
fun getProvisionTypeByPolicyCode(
code: String?, system: String?, timeStamp: String, expected: String?,
desc: String?
) {
val testData = getDummyBroadConsentBundle()
val requestDate = Date.from(OffsetDateTime.parse(timeStamp).toInstant())
val result: Consent.ConsentProvisionType =
consentProcessor.getProvisionTypeByPolicyCode(testData, code, system, requestDate)
assertThat(result).isNotNull()
assertThat(result).`as`(desc)
.isEqualTo(Consent.ConsentProvisionType.valueOf(expected!!))
}
fun getDummyBroadConsentBundle(): Bundle {
val bundle: InputStream?
try {
bundle = ClassPathResource(
"fake_broadConsent_gics_response_permit.json"
).getInputStream()
} catch (e: IOException) {
throw RuntimeException(e)
}
return FhirContext.forR4().newJsonParser()
.parseResource<Bundle>(Bundle::class.java, bundle)
}
}

View File

@ -34,8 +34,6 @@ import dev.pcvolkmer.mv64e.mtb.Mtb
import dev.pcvolkmer.mv64e.mtb.MvhMetadata import dev.pcvolkmer.mv64e.mtb.MvhMetadata
import dev.pcvolkmer.mv64e.mtb.Provision import dev.pcvolkmer.mv64e.mtb.Provision
import org.hl7.fhir.instance.model.api.IBaseResource import org.hl7.fhir.instance.model.api.IBaseResource
import org.hl7.fhir.r4.model.CodeableConcept
import org.hl7.fhir.r4.model.Coding
import java.time.Instant import java.time.Instant
import java.util.Date import java.util.Date
@ -145,7 +143,7 @@ class TransformationServiceTest {
.date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build() .date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build()
) )
).build() ).build()
val consent = getDummyConsent() val consent = ConsentProcessorTest.getDummyGenomDeConsent()
mvhMetadata.researchConsents = mutableListOf() mvhMetadata.researchConsents = mutableListOf()
mvhMetadata.researchConsents.add(mapOf(consent.id to consent as IBaseResource)) mvhMetadata.researchConsents.add(mapOf(consent.id to consent as IBaseResource))
@ -157,51 +155,5 @@ class TransformationServiceTest {
} }
companion object {
fun getDummyConsent(): org.hl7.fhir.r4.model.Consent {
val modelVorhabenConsent = org.hl7.fhir.r4.model.Consent()
modelVorhabenConsent.id = "consent 1 id"
modelVorhabenConsent.patient.reference = "Patient/1234-pat1"
modelVorhabenConsent.provision.setType(
org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode(
"deny"
)
)
modelVorhabenConsent.provision.period.start =
Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
modelVorhabenConsent.provision.period.end =
Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
val addProvision1 = modelVorhabenConsent.provision.addProvision()
addProvision1.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("permit"))
addProvision1.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
addProvision1.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
addProvision1.code.addLast(
CodeableConcept(
Coding(
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
"Teilnahme",
"Teilnahme am Modellvorhaben und Einwilligung zur Genomsequenzierung"
)
)
)
val addProvision2 = modelVorhabenConsent.provision.addProvision()
addProvision2.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("deny"))
addProvision2.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
addProvision2.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
addProvision2.code.addLast(
CodeableConcept(
Coding(
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
"Rekontaktierung",
"Re-Identifizierung meiner Daten über die Vertrauensstelle beim Robert Koch-Institut und in die erneute Kontaktaufnahme durch meine behandelnde Ärztin oder meinen behandelnden Arzt"
)
)
)
return modelVorhabenConsent
}
}
} }