mirror of
https://github.com/pcvolkmer/etl-processor.git
synced 2025-07-17 12:52:54 +00:00
fix: reimplement broad consent and genomDE consent check and embedding them into mtb data
This commit is contained in:
@ -1,5 +1,8 @@
|
|||||||
package dev.dnpm.etl.processor.consent;
|
package dev.dnpm.etl.processor.consent;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
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;
|
||||||
|
|
||||||
@ -12,7 +15,29 @@ public class ConsentCheckFileBased implements ICheckConsent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TtpConsentStatus getTtpConsentStatus(String personIdentifierValue) {
|
public TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue) {
|
||||||
return TtpConsentStatus.UNKNOWN_CHECK_FILE;
|
return TtpConsentStatus.UNKNOWN_CHECK_FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle getBroadConsent(String personIdentifierValue, Date requestDate) {
|
||||||
|
return ICheckConsent.super.getBroadConsent(personIdentifierValue, requestDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle getGenomDeConsent(String personIdentifierValue, Date requestDate) {
|
||||||
|
return ICheckConsent.super.getGenomDeConsent(personIdentifierValue, requestDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle currentConsentForPersonAndTemplate(String personIdentifierValue,
|
||||||
|
ConsentDomain targetConsentDomain, Date requestDate) {
|
||||||
|
return new Bundle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConsentProvisionType getProvisionTypeByPolicyCode(Bundle consentBundle,
|
||||||
|
Date requestDate, ConsentDomain consentDomain) {
|
||||||
|
return ConsentProvisionType.NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
package dev.dnpm.etl.processor.consent;
|
package dev.dnpm.etl.processor.consent;
|
||||||
|
|
||||||
public enum ConsentDomain {
|
public enum ConsentDomain {
|
||||||
|
/**
|
||||||
|
* MII Broad consent
|
||||||
|
*/
|
||||||
BroadConsent,
|
BroadConsent,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GenomDe Modelvohaben §64e
|
||||||
|
*/
|
||||||
Modelvorhaben64e
|
Modelvorhaben64e
|
||||||
}
|
}
|
||||||
|
@ -5,16 +5,24 @@ import ca.uhn.fhir.parser.DataFormatException;
|
|||||||
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.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
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;
|
||||||
@ -63,7 +71,7 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"gICS base URL is empty - should call gICS with false configuration.");
|
"gICS base URL is empty - should call gICS with false configuration.");
|
||||||
}
|
}
|
||||||
url = UriComponentsBuilder.fromUriString(gIcsBaseUri).path(IS_CONSENTED_ENDPOINT)
|
url = UriComponentsBuilder.fromUriString(gIcsBaseUri).path(endpoint)
|
||||||
.toUriString();
|
.toUriString();
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
@ -91,8 +99,8 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
result.addParameter(new ParametersParameterComponent().setName("domain")
|
result.addParameter(new ParametersParameterComponent().setName("domain")
|
||||||
.setValue(new StringType().setValue(configProperties.getBroadConsentDomainName())));
|
.setValue(new StringType().setValue(configProperties.getBroadConsentDomainName())));
|
||||||
result.addParameter(new ParametersParameterComponent().setName("policy").setValue(
|
result.addParameter(new ParametersParameterComponent().setName("policy").setValue(
|
||||||
new Coding().setCode(configProperties.getPolicyCode())
|
new Coding().setCode(configProperties.getBroadConsentPolicyCode())
|
||||||
.setSystem(configProperties.getPolicySystem())));
|
.setSystem(configProperties.getBroadConsentPolicySystem())));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* is mandatory parameter, but we ignore it via additional configuration parameter
|
* is mandatory parameter, but we ignore it via additional configuration parameter
|
||||||
@ -152,7 +160,7 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TtpConsentStatus getTtpConsentStatus(String personIdentifierValue) {
|
public TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue) {
|
||||||
var parameter = GicsConsentService.getIsConsentedRequestParam(gIcsConfigProperties,
|
var parameter = GicsConsentService.getIsConsentedRequestParam(gIcsConfigProperties,
|
||||||
personIdentifierValue);
|
personIdentifierValue);
|
||||||
|
|
||||||
@ -199,30 +207,14 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
private String getConsentDomain(ConsentDomain targetConsentDomain) {
|
private String getConsentDomain(ConsentDomain targetConsentDomain) {
|
||||||
String consentDomain;
|
String consentDomain;
|
||||||
switch (targetConsentDomain) {
|
switch (targetConsentDomain) {
|
||||||
case BroadConsent -> {
|
case BroadConsent -> consentDomain = gIcsConfigProperties.getBroadConsentDomainName();
|
||||||
consentDomain = gIcsConfigProperties.getBroadConsentDomainName();
|
case Modelvorhaben64e -> consentDomain = gIcsConfigProperties.getGenomDeConsentDomainName();
|
||||||
}
|
default -> throw new IllegalArgumentException(
|
||||||
case Modelvorhaben64e -> {
|
|
||||||
consentDomain = gIcsConfigProperties.getGnomDeConsentDomainName();
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"target ConsentDomain is missing but must be provided!");
|
"target ConsentDomain is missing but must be provided!");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return consentDomain;
|
return consentDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bundle getBroadConsent(String personIdentifierValue, Date requestDate) {
|
|
||||||
return currentConsentForPersonAndTemplate(personIdentifierValue, ConsentDomain.BroadConsent,
|
|
||||||
requestDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bundle getGenomDeConsent(String personIdentifierValue, Date requestDate) {
|
|
||||||
return currentConsentForPersonAndTemplate(personIdentifierValue,
|
|
||||||
ConsentDomain.Modelvorhaben64e, requestDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static Parameters buildRequestParameterCurrentPolicyStatesForPerson(
|
protected static Parameters buildRequestParameterCurrentPolicyStatesForPerson(
|
||||||
GIcsConfigProperties gIcsConfigProperties, String personIdentifierValue, Date requestDate,
|
GIcsConfigProperties gIcsConfigProperties, String personIdentifierValue, Date requestDate,
|
||||||
String targetDomain) {
|
String targetDomain) {
|
||||||
@ -264,9 +256,9 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
return TtpConsentStatus.FAILED_TO_ASK;
|
return TtpConsentStatus.FAILED_TO_ASK;
|
||||||
}
|
}
|
||||||
if (isConsented.booleanValue()) {
|
if (isConsented.booleanValue()) {
|
||||||
return TtpConsentStatus.CONSENTED;
|
return TtpConsentStatus.BROAD_CONSENT_GIVEN;
|
||||||
} else {
|
} else {
|
||||||
return TtpConsentStatus.CONSENT_MISSING_OR_REJECTED;
|
return TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED;
|
||||||
}
|
}
|
||||||
} else if (response instanceof OperationOutcome outcome) {
|
} else if (response instanceof OperationOutcome outcome) {
|
||||||
log.error("failed to get consent status from ttp. probably configuration error. "
|
log.error("failed to get consent status from ttp. probably configuration error. "
|
||||||
@ -278,4 +270,84 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,61 @@
|
|||||||
package dev.dnpm.etl.processor.consent;
|
package dev.dnpm.etl.processor.consent;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.Consent.ConsentProvisionType;
|
||||||
|
|
||||||
public interface ICheckConsent {
|
public interface ICheckConsent {
|
||||||
|
|
||||||
TtpConsentStatus getTtpConsentStatus(String personIdentifierValue);
|
/**
|
||||||
|
* Get broad consent status for a patient identifier
|
||||||
|
*
|
||||||
|
* @param personIdentifierValue patient identifier used for consent data
|
||||||
|
* @return status of broad consent
|
||||||
|
* @apiNote cannot not differ between not asked and rejected
|
||||||
|
*/
|
||||||
|
TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get broad consent policies with respect to a request date
|
||||||
|
*
|
||||||
|
* @param personIdentifierValue patient identifier used for consent data
|
||||||
|
* @param requestDate target date until consent data should be considered
|
||||||
|
* @return consent policies as bundle; <p>if empty patient has not been asked, yet.</p>
|
||||||
|
*/
|
||||||
|
default Bundle getBroadConsent(String personIdentifierValue, Date requestDate) {
|
||||||
|
return currentConsentForPersonAndTemplate(personIdentifierValue, ConsentDomain.BroadConsent,
|
||||||
|
requestDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get 'GenomDe Modelvorhaben §64e' consent policies with respect to a request date
|
||||||
|
*
|
||||||
|
* @param personIdentifierValue patient identifier used for consent data
|
||||||
|
* @param requestDate target date until consent data should be considered
|
||||||
|
* @return consent policies as bundle; <p>if empty patient has not been asked, yet.</p>
|
||||||
|
*/
|
||||||
|
default Bundle getGenomDeConsent(String personIdentifierValue, Date requestDate) {
|
||||||
|
return currentConsentForPersonAndTemplate(personIdentifierValue, ConsentDomain.BroadConsent,
|
||||||
|
requestDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get consent policies with respect to a request date
|
||||||
|
*
|
||||||
|
* @param personIdentifierValue patient identifier used for consent data
|
||||||
|
* @param targetConsentDomain domain which should be used to request consent
|
||||||
|
* @param requestDate target date until consent data should be considered
|
||||||
|
* @return consent policies as bundle; <p>if empty patient has not been asked, yet.</p>
|
||||||
|
*/
|
||||||
|
Bundle currentConsentForPersonAndTemplate(String personIdentifierValue,
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
@ -4,17 +4,35 @@ public enum TtpConsentStatus {
|
|||||||
/**
|
/**
|
||||||
* Valid consent found
|
* Valid consent found
|
||||||
*/
|
*/
|
||||||
CONSENTED,
|
BROAD_CONSENT_GIVEN,
|
||||||
|
|
||||||
CONSENT_MISSING_OR_REJECTED,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Due technical problems consent status is unknown
|
* Missing or rejected...actually unknown
|
||||||
*/
|
*/
|
||||||
FAILED_TO_ASK,
|
BROAD_CONSENT_MISSING_OR_REJECTED,
|
||||||
|
/**
|
||||||
|
* No Broad consent policy found
|
||||||
|
*/
|
||||||
|
BROAD_CONSENT_MISSING,
|
||||||
|
/**
|
||||||
|
* Research policy has been rejected
|
||||||
|
*/
|
||||||
|
BROAD_CONSENT_REJECTED,
|
||||||
|
|
||||||
|
GENOM_DE_CONSENT_SEQUENCING_PERMIT,
|
||||||
|
/**
|
||||||
|
* No GenomDE consent policy found
|
||||||
|
*/
|
||||||
|
GENOM_DE_CONSENT_MISSING,
|
||||||
|
/**
|
||||||
|
* GenomDE consent policy found, but has been rejected
|
||||||
|
*/
|
||||||
|
GENOM_DE_SEQUENCING_REJECTED,
|
||||||
/**
|
/**
|
||||||
* Consent status is validate via file property 'consent.status'
|
* Consent status is validate via file property 'consent.status'
|
||||||
*/
|
*/
|
||||||
UNKNOWN_CHECK_FILE
|
UNKNOWN_CHECK_FILE,
|
||||||
|
/**
|
||||||
|
* Due technical problems consent status is unknown
|
||||||
|
*/
|
||||||
|
FAILED_TO_ASK
|
||||||
}
|
}
|
||||||
|
@ -87,17 +87,27 @@ data class GIcsConfigProperties(
|
|||||||
/**
|
/**
|
||||||
* Domain of Modelvorhaben 64e consent resources
|
* Domain of Modelvorhaben 64e consent resources
|
||||||
**/
|
**/
|
||||||
val gnomDeConsentDomainName: String = "GenomDE_MV",
|
val genomDeConsentDomainName: String = "GenomDE_MV",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value to expect in case of positiv consent
|
* Value to expect in case of positiv consent
|
||||||
*/
|
*/
|
||||||
val policyCode: String = "2.16.840.1.113883.3.1937.777.24.5.3.6",
|
val broadConsentPolicyCode: String = "2.16.840.1.113883.3.1937.777.24.5.3.6",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consent Policy which should be used for consent check
|
* Consent Policy which should be used for consent check
|
||||||
*/
|
*/
|
||||||
val policySystem: String = "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3"
|
val broadConsentPolicySystem: String = "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value to expect in case of positiv consent
|
||||||
|
*/
|
||||||
|
val genomeDePolicyCode: String = "sequencing",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consent Policy which should be used for consent check
|
||||||
|
*/
|
||||||
|
val genomeDePolicySystem: String = "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV"
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val NAME = "app.consent.gics"
|
const val NAME = "app.consent.gics"
|
||||||
|
@ -63,16 +63,16 @@ class MtbFileRestController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkConsentStatus(mtbFile: MtbFile): Pair<TtpConsentStatus, Boolean> {
|
private fun checkConsentStatus(mtbFile: MtbFile): Pair<TtpConsentStatus, Boolean> {
|
||||||
var ttpConsentStatus = iCheckConsent.getTtpConsentStatus(mtbFile.patient.id)
|
var ttpConsentStatus = iCheckConsent.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) ||
|
||||||
ttpConsentStatus.equals(
|
ttpConsentStatus.equals(
|
||||||
TtpConsentStatus.CONSENTED
|
TtpConsentStatus.BROAD_CONSENT_GIVEN
|
||||||
)
|
)
|
||||||
if (ttpConsentStatus.equals(TtpConsentStatus.UNKNOWN_CHECK_FILE) && mtbFile.consent.status == Consent.Status.REJECTED) {
|
if (ttpConsentStatus.equals(TtpConsentStatus.UNKNOWN_CHECK_FILE) && mtbFile.consent.status == Consent.Status.REJECTED) {
|
||||||
// in case ttp check is disabled - we propagate rejected status anyway
|
// in case ttp check is disabled - we propagate rejected status anyway
|
||||||
ttpConsentStatus = TtpConsentStatus.CONSENT_MISSING_OR_REJECTED
|
ttpConsentStatus = TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED
|
||||||
}
|
}
|
||||||
return Pair(ttpConsentStatus, isConsentOK)
|
return Pair(ttpConsentStatus, isConsentOK)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,8 @@ 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.GicsConsentService
|
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
|
||||||
@ -44,9 +45,15 @@ import org.apache.commons.codec.digest.DigestUtils
|
|||||||
import org.hl7.fhir.instance.model.api.IBaseResource
|
import org.hl7.fhir.instance.model.api.IBaseResource
|
||||||
import org.hl7.fhir.r4.model.Bundle
|
import org.hl7.fhir.r4.model.Bundle
|
||||||
import org.hl7.fhir.r4.model.Consent
|
import org.hl7.fhir.r4.model.Consent
|
||||||
|
import org.slf4j.Logger
|
||||||
|
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.io.IOException
|
||||||
|
import java.lang.RuntimeException
|
||||||
|
import java.time.Clock
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import java.time.ZoneId
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -58,9 +65,10 @@ 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 gicsConsentService: GicsConsentService?
|
private val consentService: ICheckConsent?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private var logger: Logger = LoggerFactory.getLogger("RequestProcessor")
|
||||||
fun processMtbFile(mtbFile: MtbFile) {
|
fun processMtbFile(mtbFile: MtbFile) {
|
||||||
processMtbFile(mtbFile, randomRequestId())
|
processMtbFile(mtbFile, randomRequestId())
|
||||||
}
|
}
|
||||||
@ -77,21 +85,79 @@ class RequestProcessor(
|
|||||||
processMtbFile(mtbFile, randomRequestId())
|
processMtbFile(mtbFile, randomRequestId())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
|
/**
|
||||||
val pid = PatientId(mtbFile.patient.id)
|
* In case an instance of {@link ICheckConsent} is active, consent will be embedded and checked.
|
||||||
|
*
|
||||||
addConsentToMtb(mtbFile)
|
* Logik:
|
||||||
mtbFile pseudonymizeWith pseudonymizeService
|
* * <c>true</c> IF consent check is disabled.
|
||||||
mtbFile anonymizeContentWith pseudonymizeService
|
* * <c>true</c> IF broad consent (BC) has been given.
|
||||||
val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
|
* * <c>true</c> BC has been asked AND declined but genomDe consent has been consented.
|
||||||
saveAndSend(request, pid)
|
* * ELSE <c>false</c> is returned.
|
||||||
|
*
|
||||||
|
* @param mtbFile File v2 (will be enriched with consent data)
|
||||||
|
* @return true if consent is given
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun consentGatedCheck(mtbFile: Mtb): Boolean {
|
||||||
|
if (consentService == null) {
|
||||||
|
// consent check seems to be disabled
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addConsentToMtb(mtbFile: Mtb) {
|
initMetaDataAtMtbFile(mtbFile)
|
||||||
if (gicsConsentService == null) return
|
|
||||||
|
val personIdentifierValue = extractPatientIdentifier(mtbFile)
|
||||||
|
val requestDate = Date.from(Instant.now(Clock.system(ZoneId.of("ECT"))))
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
embedBroadConsentResources(mtbFile, broadConsent)
|
||||||
|
|
||||||
|
val broadConsentStatus = consentService.getProvisionTypeByPolicyCode(
|
||||||
|
broadConsent,
|
||||||
|
requestDate,
|
||||||
|
ConsentDomain.BroadConsent
|
||||||
|
)
|
||||||
|
|
||||||
|
val genomDeSequencingStatus = consentService.getProvisionTypeByPolicyCode(
|
||||||
|
genomeDeConsent, requestDate,
|
||||||
|
ConsentDomain.Modelvorhaben64e
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Consent.ConsentProvisionType.PERMIT == broadConsentStatus) return true
|
||||||
|
if (Consent.ConsentProvisionType.DENY == broadConsentStatus && Consent.ConsentProvisionType.PERMIT == genomDeSequencingStatus) return true
|
||||||
|
if (Consent.ConsentProvisionType.NULL == broadConsentStatus) {
|
||||||
|
// bc not asked
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun initMetaDataAtMtbFile(mtbFile: Mtb) {
|
||||||
// init metadata if necessary
|
// init metadata if necessary
|
||||||
if (mtbFile.metadata == null) {
|
if (mtbFile.metadata == null) {
|
||||||
val mvhMetadata = MvhMetadata.builder().build();
|
val mvhMetadata = MvhMetadata.builder().build()
|
||||||
mtbFile.metadata = mvhMetadata
|
mtbFile.metadata = mvhMetadata
|
||||||
if (mtbFile.metadata.researchConsents == null) {
|
if (mtbFile.metadata.researchConsents == null) {
|
||||||
mtbFile.metadata.researchConsents = mutableListOf()
|
mtbFile.metadata.researchConsents = mutableListOf()
|
||||||
@ -101,21 +167,29 @@ class RequestProcessor(
|
|||||||
mtbFile.metadata.modelProjectConsent.provisions = mutableListOf()
|
mtbFile.metadata.modelProjectConsent.provisions = mutableListOf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixme Date should be extracted from mtbFile
|
|
||||||
val consentGnomeDe =
|
|
||||||
gicsConsentService.getGenomDeConsent(mtbFile.patient.id, Date.from(Instant.now()))
|
|
||||||
addGenomeDbProvisions(mtbFile, consentGnomeDe)
|
|
||||||
|
|
||||||
// fixme Date should be extracted from mtbFile
|
|
||||||
val broadConsent =
|
|
||||||
gicsConsentService.getBroadConsent(mtbFile.patient.id, Date.from(Instant.now()))
|
|
||||||
embedBroadConsentResources(mtbFile, broadConsent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
|
||||||
|
val pid = PatientId(extractPatientIdentifier(mtbFile))
|
||||||
|
|
||||||
|
if (consentGatedCheck(mtbFile)) {
|
||||||
|
mtbFile pseudonymizeWith pseudonymizeService
|
||||||
|
mtbFile anonymizeContentWith pseudonymizeService
|
||||||
|
val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
|
||||||
|
saveAndSend(request, pid)
|
||||||
|
} else {
|
||||||
|
logger.warn("consent check failed file will not be processed further!")
|
||||||
|
applicationEventPublisher.publishEvent(
|
||||||
|
ResponseEvent(
|
||||||
|
requestId, Instant.now(), RequestStatus.NO_CONSENT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun embedBroadConsentResources(
|
fun embedBroadConsentResources(
|
||||||
mtbFile: Mtb,
|
mtbFile: Mtb, broadConsent: Bundle
|
||||||
broadConsent: Bundle
|
|
||||||
) {
|
) {
|
||||||
broadConsent.entry.forEach { it ->
|
broadConsent.entry.forEach { it ->
|
||||||
mtbFile.metadata.researchConsents.add(mapOf(it.resource.id to it as IBaseResource))
|
mtbFile.metadata.researchConsents.add(mapOf(it.resource.id to it as IBaseResource))
|
||||||
@ -123,37 +197,37 @@ class RequestProcessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addGenomeDbProvisions(
|
fun addGenomeDbProvisions(
|
||||||
mtbFile: Mtb,
|
mtbFile: Mtb, consentGnomeDe: Bundle
|
||||||
consentGnomeDe: Bundle
|
|
||||||
) {
|
) {
|
||||||
consentGnomeDe.entry.forEach { it ->
|
consentGnomeDe.entry.forEach { it ->
|
||||||
{
|
{
|
||||||
val consent = it.resource as Consent
|
val consentFhirResource = it.resource as Consent
|
||||||
val provisionComponent = consent.provision.provision.firstOrNull()
|
|
||||||
|
// we expect only one provision in collection, therefore get first or none
|
||||||
|
val provisionComponent = consentFhirResource.provision.provision.firstOrNull()
|
||||||
val provisionCode =
|
val provisionCode =
|
||||||
provisionComponent?.code?.firstOrNull()?.coding?.firstOrNull()?.code
|
provisionComponent?.code?.firstOrNull()?.coding?.firstOrNull()?.code
|
||||||
var isValidCode = true
|
|
||||||
if (provisionCode != null) {
|
if (provisionCode != null) {
|
||||||
var modelProjectConsentPurpose: ModelProjectConsentPurpose =
|
try {
|
||||||
ModelProjectConsentPurpose.SEQUENCING
|
val modelProjectConsentPurpose: ModelProjectConsentPurpose =
|
||||||
if (provisionCode == "Teilnahme") {
|
ModelProjectConsentPurpose.valueOf(provisionCode)
|
||||||
modelProjectConsentPurpose = ModelProjectConsentPurpose.SEQUENCING
|
mtbFile.metadata.modelProjectConsent.provisions.add(
|
||||||
} else if (provisionCode == "Fallidentifizierung") {
|
|
||||||
modelProjectConsentPurpose = ModelProjectConsentPurpose.CASE_IDENTIFICATION
|
|
||||||
} else if (provisionCode == "Rekontaktierung") {
|
|
||||||
modelProjectConsentPurpose = ModelProjectConsentPurpose.REIDENTIFICATION
|
|
||||||
} else {
|
|
||||||
isValidCode = false
|
|
||||||
}
|
|
||||||
if (isValidCode) mtbFile.metadata.modelProjectConsent.provisions.add(
|
|
||||||
Provision.builder().type(
|
Provision.builder().type(
|
||||||
ConsentProvision.forValue(provisionComponent.type.name)
|
ConsentProvision.forValue(provisionComponent.type.name)
|
||||||
).date(provisionComponent.period.start).purpose(
|
).date(provisionComponent.period.start).purpose(
|
||||||
modelProjectConsentPurpose
|
modelProjectConsentPurpose
|
||||||
).build()
|
).build()
|
||||||
)
|
)
|
||||||
|
} catch (ioe: IOException) {
|
||||||
|
logger.error(
|
||||||
|
"provision code '$provisionCode' is unknown and cannot be mapped.",
|
||||||
|
ioe.toString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,9 +246,7 @@ class RequestProcessor(
|
|||||||
if (appConfigProperties.duplicationDetection && isDuplication(request)) {
|
if (appConfigProperties.duplicationDetection && isDuplication(request)) {
|
||||||
applicationEventPublisher.publishEvent(
|
applicationEventPublisher.publishEvent(
|
||||||
ResponseEvent(
|
ResponseEvent(
|
||||||
request.requestId,
|
request.requestId, Instant.now(), RequestStatus.DUPLICATION
|
||||||
Instant.now(),
|
|
||||||
RequestStatus.DUPLICATION
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -206,9 +278,7 @@ class RequestProcessor(
|
|||||||
val isLastRequestDeletion =
|
val isLastRequestDeletion =
|
||||||
requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
|
requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
|
||||||
|
|
||||||
return null != lastMtbFileRequestForPatient
|
return null != lastMtbFileRequestForPatient && !isLastRequestDeletion && lastMtbFileRequestForPatient.fingerprint == fingerprint(
|
||||||
&& !isLastRequestDeletion
|
|
||||||
&& lastMtbFileRequestForPatient.fingerprint == fingerprint(
|
|
||||||
pseudonymizedMtbFileRequest
|
pseudonymizedMtbFileRequest
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -222,9 +292,12 @@ class RequestProcessor(
|
|||||||
val patientPseudonym = pseudonymizeService.patientPseudonym(patientId)
|
val patientPseudonym = pseudonymizeService.patientPseudonym(patientId)
|
||||||
|
|
||||||
val requestStatus: RequestStatus = when (isConsented) {
|
val requestStatus: RequestStatus = when (isConsented) {
|
||||||
TtpConsentStatus.CONSENT_MISSING_OR_REJECTED -> RequestStatus.NO_CONSENT
|
TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED, TtpConsentStatus.BROAD_CONSENT_MISSING, TtpConsentStatus.BROAD_CONSENT_REJECTED -> RequestStatus.NO_CONSENT
|
||||||
TtpConsentStatus.FAILED_TO_ASK -> RequestStatus.ERROR
|
TtpConsentStatus.FAILED_TO_ASK -> RequestStatus.ERROR
|
||||||
TtpConsentStatus.CONSENTED, TtpConsentStatus.UNKNOWN_CHECK_FILE -> RequestStatus.UNKNOWN
|
TtpConsentStatus.BROAD_CONSENT_GIVEN, TtpConsentStatus.UNKNOWN_CHECK_FILE -> RequestStatus.UNKNOWN
|
||||||
|
TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT, TtpConsentStatus.GENOM_DE_CONSENT_MISSING, TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED -> {
|
||||||
|
throw RuntimeException("processDelete should never deal with '" + isConsented.name + "' consent status. This is a bug and need to be fixed!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestService.save(
|
requestService.save(
|
||||||
@ -242,10 +315,7 @@ class RequestProcessor(
|
|||||||
|
|
||||||
applicationEventPublisher.publishEvent(
|
applicationEventPublisher.publishEvent(
|
||||||
ResponseEvent(
|
ResponseEvent(
|
||||||
requestId,
|
requestId, Instant.now(), responseStatus.status, when (responseStatus.status) {
|
||||||
Instant.now(),
|
|
||||||
responseStatus.status,
|
|
||||||
when (responseStatus.status) {
|
|
||||||
RequestStatus.WARNING, RequestStatus.ERROR -> Optional.of(responseStatus.body)
|
RequestStatus.WARNING, RequestStatus.ERROR -> Optional.of(responseStatus.body)
|
||||||
else -> Optional.empty()
|
else -> Optional.empty()
|
||||||
}
|
}
|
||||||
@ -276,10 +346,10 @@ class RequestProcessor(
|
|||||||
|
|
||||||
private fun fingerprint(s: String): Fingerprint {
|
private fun fingerprint(s: String): Fingerprint {
|
||||||
return Fingerprint(
|
return Fingerprint(
|
||||||
Base32().encodeAsString(DigestUtils.sha256(s))
|
Base32().encodeAsString(DigestUtils.sha256(s)).replace("=", "").lowercase()
|
||||||
.replace("=", "")
|
|
||||||
.lowercase()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun extractPatientIdentifier(mtbFile: Mtb): String = mtbFile.patient.id
|
||||||
|
@ -4,13 +4,19 @@ 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;
|
||||||
@ -21,8 +27,11 @@ 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;
|
||||||
@ -58,7 +67,7 @@ public class GicsConsentServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getTtpConsentStatus() {
|
void getTtpBroadConsentStatus() {
|
||||||
final Parameters responseConsented = new Parameters().addParameter(
|
final Parameters responseConsented = new Parameters().addParameter(
|
||||||
new ParametersParameterComponent().setName("consented")
|
new ParametersParameterComponent().setName("consented")
|
||||||
.setValue(new BooleanType().setValue(true)));
|
.setValue(new BooleanType().setValue(true)));
|
||||||
@ -70,8 +79,8 @@ public class GicsConsentServiceTest {
|
|||||||
.encodeResourceToString(responseConsented),
|
.encodeResourceToString(responseConsented),
|
||||||
MediaType.APPLICATION_JSON));
|
MediaType.APPLICATION_JSON));
|
||||||
|
|
||||||
var consentStatus = gicsConsentService.getTtpConsentStatus("123456");
|
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
|
||||||
assertThat(consentStatus).isEqualTo(TtpConsentStatus.CONSENTED);
|
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -87,8 +96,8 @@ public class GicsConsentServiceTest {
|
|||||||
.encodeResourceToString(responseRevoced),
|
.encodeResourceToString(responseRevoced),
|
||||||
MediaType.APPLICATION_JSON));
|
MediaType.APPLICATION_JSON));
|
||||||
|
|
||||||
var consentStatus = gicsConsentService.getTtpConsentStatus("123456");
|
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
|
||||||
assertThat(consentStatus).isEqualTo(TtpConsentStatus.CONSENT_MISSING_OR_REJECTED);
|
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -105,7 +114,7 @@ public class GicsConsentServiceTest {
|
|||||||
.encodeResourceToString(responseErrorOutcome),
|
.encodeResourceToString(responseErrorOutcome),
|
||||||
MediaType.APPLICATION_JSON));
|
MediaType.APPLICATION_JSON));
|
||||||
|
|
||||||
var consentStatus = gicsConsentService.getTtpConsentStatus("123456");
|
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
|
||||||
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
|
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,11 +123,50 @@ public class GicsConsentServiceTest {
|
|||||||
|
|
||||||
String pid = "12345678";
|
String pid = "12345678";
|
||||||
var result = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(gIcsConfigProperties,
|
var result = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(gIcsConfigProperties,
|
||||||
pid, Date.from(Instant.now()),gIcsConfigProperties.getGnomDeConsentDomainName());
|
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.getGnomDeConsentDomainName());
|
assertThat(((StringType)result.getParameter("domain").getValue()).getValue()).isEqualTo(gIcsConfigProperties.getGenomDeConsentDomainName());
|
||||||
assertThat(((Identifier)result.getParameter("personIdentifier").getValue()).getValue()).isEqualTo(pid);
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ class MtbFileRestControllerTest {
|
|||||||
|
|
||||||
verify(requestProcessor, times(1)).processDeletion(
|
verify(requestProcessor, times(1)).processDeletion(
|
||||||
anyValueClass(),
|
anyValueClass(),
|
||||||
org.mockito.kotlin.eq(TtpConsentStatus.CONSENT_MISSING_OR_REJECTED)
|
org.mockito.kotlin.eq(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ class MtbFileRestControllerTest {
|
|||||||
@ValueSource(strings = ["ACTIVE", "REJECTED"])
|
@ValueSource(strings = ["ACTIVE", "REJECTED"])
|
||||||
fun shouldProcessPostRequest(status: String) {
|
fun shouldProcessPostRequest(status: String) {
|
||||||
|
|
||||||
whenever(gicsConsentService.getTtpConsentStatus(any())).thenReturn(TtpConsentStatus.CONSENTED)
|
whenever(gicsConsentService.getTtpBroadConsentStatus(any())).thenReturn(TtpConsentStatus.BROAD_CONSENT_GIVEN)
|
||||||
|
|
||||||
mockMvc.post("/mtbfile") {
|
mockMvc.post("/mtbfile") {
|
||||||
content =
|
content =
|
||||||
@ -169,7 +169,7 @@ class MtbFileRestControllerTest {
|
|||||||
@ValueSource(strings = ["ACTIVE", "REJECTED"])
|
@ValueSource(strings = ["ACTIVE", "REJECTED"])
|
||||||
fun shouldProcessPostRequestWithRejectedConsent(status: String) {
|
fun shouldProcessPostRequestWithRejectedConsent(status: String) {
|
||||||
|
|
||||||
whenever(gicsConsentService.getTtpConsentStatus(any())).thenReturn(TtpConsentStatus.CONSENT_MISSING_OR_REJECTED)
|
whenever(gicsConsentService.getTtpBroadConsentStatus(any())).thenReturn(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED)
|
||||||
|
|
||||||
mockMvc.post("/mtbfile") {
|
mockMvc.post("/mtbfile") {
|
||||||
content =
|
content =
|
||||||
@ -184,7 +184,7 @@ class MtbFileRestControllerTest {
|
|||||||
// consent status from ttp should override file consent value
|
// consent status from ttp should override file consent value
|
||||||
verify(requestProcessor, times(1)).processDeletion(
|
verify(requestProcessor, times(1)).processDeletion(
|
||||||
anyValueClass(),
|
anyValueClass(),
|
||||||
org.mockito.kotlin.eq(TtpConsentStatus.CONSENT_MISSING_OR_REJECTED)
|
org.mockito.kotlin.eq(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ class MtbFileRestControllerTest {
|
|||||||
anyValueClass(),
|
anyValueClass(),
|
||||||
org.mockito.kotlin.eq(TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
org.mockito.kotlin.eq(TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
||||||
)
|
)
|
||||||
verify(gicsConsentService, times(0)).getTtpConsentStatus(any())
|
verify(gicsConsentService, times(0)).getTtpBroadConsentStatus(any())
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,7 +253,7 @@ class MtbFileRestControllerTest {
|
|||||||
|
|
||||||
verify(requestProcessor, times(1)).processDeletion(
|
verify(requestProcessor, times(1)).processDeletion(
|
||||||
anyValueClass(), org.mockito.kotlin.eq(
|
anyValueClass(), org.mockito.kotlin.eq(
|
||||||
TtpConsentStatus.CONSENT_MISSING_OR_REJECTED
|
TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -283,11 +283,12 @@ class MtbFileRestControllerTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup(
|
fun setup(
|
||||||
@Mock requestProcessor: RequestProcessor
|
@Mock requestProcessor: RequestProcessor,
|
||||||
|
@Mock gicsConsentService: GicsConsentService
|
||||||
) {
|
) {
|
||||||
this.requestProcessor = requestProcessor
|
this.requestProcessor = requestProcessor
|
||||||
val controller = MtbFileRestController(requestProcessor,
|
val controller = MtbFileRestController(requestProcessor,
|
||||||
ConsentCheckFileBased()
|
gicsConsentService
|
||||||
)
|
)
|
||||||
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,15 @@ import de.ukw.ccc.bwhc.dto.Diagnosis
|
|||||||
import de.ukw.ccc.bwhc.dto.Icd10
|
import de.ukw.ccc.bwhc.dto.Icd10
|
||||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||||
import dev.dnpm.etl.processor.config.JacksonConfig
|
import dev.dnpm.etl.processor.config.JacksonConfig
|
||||||
|
import dev.pcvolkmer.mv64e.mtb.ConsentProvision
|
||||||
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent
|
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent
|
||||||
|
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
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 dev.pcvolkmer.mv64e.mtb.Mtb
|
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 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.CodeableConcept
|
||||||
import org.hl7.fhir.r4.model.Coding
|
import org.hl7.fhir.r4.model.Coding
|
||||||
@ -124,21 +127,53 @@ class TransformationServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldTransformConsent() {
|
fun shouldTransformConsent() {
|
||||||
val mvhMetadata = MvhMetadata.builder().transferTan("transfertan12345").build();
|
val mvhMetadata = MvhMetadata.builder().transferTan("transfertan12345").build()
|
||||||
|
|
||||||
assertThat(mvhMetadata).isNotNull
|
assertThat(mvhMetadata).isNotNull
|
||||||
mvhMetadata.modelProjectConsent =
|
mvhMetadata.modelProjectConsent =
|
||||||
ModelProjectConsent.builder().date(Date.from(Instant.now())).version("1").build()
|
ModelProjectConsent.builder().date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z")))
|
||||||
val consent1 = org.hl7.fhir.r4.model.Consent()
|
.version("1").provisions(
|
||||||
consent1.id = "consent 1 id"
|
listOf(
|
||||||
consent1.patient.reference = "Patient/1234-pat1"
|
Provision.builder().type(ConsentProvision.PERMIT)
|
||||||
|
.purpose(ModelProjectConsentPurpose.SEQUENCING)
|
||||||
|
.date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build(),
|
||||||
|
Provision.builder().type(ConsentProvision.PERMIT)
|
||||||
|
.purpose(ModelProjectConsentPurpose.REIDENTIFICATION)
|
||||||
|
.date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build(),
|
||||||
|
Provision.builder().type(ConsentProvision.DENY)
|
||||||
|
.purpose(ModelProjectConsentPurpose.CASE_IDENTIFICATION)
|
||||||
|
.date(Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))).build()
|
||||||
|
)
|
||||||
|
).build()
|
||||||
|
val consent = getDummyConsent()
|
||||||
|
|
||||||
consent1.provision.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("deny"))
|
mvhMetadata.researchConsents = mutableListOf()
|
||||||
consent1.provision.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
mvhMetadata.researchConsents.add(mapOf(consent.id to consent as IBaseResource))
|
||||||
consent1.provision.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
|
||||||
|
val mtbFile = Mtb.builder().metadata(mvhMetadata).build()
|
||||||
|
|
||||||
|
val transformed = service.transform(mtbFile)
|
||||||
|
assertThat(transformed.metadata.modelProjectConsent.date).isNotNull
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = consent1.provision.addProvision()
|
val addProvision1 = modelVorhabenConsent.provision.addProvision()
|
||||||
addProvision1.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("permit"))
|
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.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.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
||||||
@ -152,7 +187,7 @@ class TransformationServiceTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val addProvision2 = consent1.provision.addProvision()
|
val addProvision2 = modelVorhabenConsent.provision.addProvision()
|
||||||
addProvision2.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("deny"))
|
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.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.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
||||||
@ -165,14 +200,5 @@ class TransformationServiceTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
return modelVorhabenConsent
|
||||||
mvhMetadata.researchConsents = mutableListOf()
|
|
||||||
mvhMetadata.researchConsents.add(mapOf(consent1.id to consent1 as IBaseResource))
|
|
||||||
|
|
||||||
val mtbFile = Mtb.builder().metadata(mvhMetadata).build()
|
|
||||||
|
|
||||||
val transformed = service.transform(mtbFile)
|
|
||||||
assertThat(transformed.metadata.modelProjectConsent.date).isNotNull
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -606,7 +606,7 @@
|
|||||||
},
|
},
|
||||||
"provision": [
|
"provision": [
|
||||||
{
|
{
|
||||||
"type": "permit",
|
"type": "deny",
|
||||||
"period": {
|
"period": {
|
||||||
"start": "2025-06-23T00:00:00+02:00",
|
"start": "2025-06-23T00:00:00+02:00",
|
||||||
"end": "2055-06-23T00:00:00+02:00"
|
"end": "2055-06-23T00:00:00+02:00"
|
||||||
|
Reference in New Issue
Block a user