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

fix: added missing genomeDe consent version and date; moved embedding consent resources into base class of GicsConsentService

This commit is contained in:
Jakub Lidke
2025-07-10 11:43:37 +02:00
parent 48fd491278
commit cc525d3f4f
6 changed files with 142 additions and 72 deletions

View File

@ -0,0 +1,91 @@
package dev.dnpm.etl.processor.consent;
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
import dev.pcvolkmer.mv64e.mtb.ConsentProvision;
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose;
import dev.pcvolkmer.mv64e.mtb.Mtb;
import dev.pcvolkmer.mv64e.mtb.Provision;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Consent;
import org.hl7.fhir.r4.model.Consent.ProvisionComponent;
import org.hl7.fhir.r4.model.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class BaseConsentService implements ICheckConsent {
protected final GIcsConfigProperties gIcsConfigProperties;
protected Logger logger = LoggerFactory.getLogger(BaseConsentService.class);
public BaseConsentService(GIcsConfigProperties gIcsConfigProperties) {
this.gIcsConfigProperties = gIcsConfigProperties;
}
public void embedBroadConsentResources(Mtb mtbFile, Bundle broadConsent) {
for (Bundle.BundleEntryComponent entry : broadConsent.getEntry()) {
Resource resource = entry.getResource();
if (resource instanceof Consent) {
Map<String, Object> consentMap = new HashMap<>();
consentMap.put(resource.getIdElement().getIdPart(), resource);
mtbFile.getMetadata().getResearchConsents().add(consentMap);
}
}
}
public void addGenomeDbProvisions(Mtb mtbFile, Bundle consentGnomeDe) {
for (Bundle.BundleEntryComponent entry : consentGnomeDe.getEntry()) {
Resource resource = entry.getResource();
if (!(resource instanceof Consent consentFhirResource)) {
continue;
}
// We expect only one provision in collection, therefore get first or none
List<ProvisionComponent> provisions = consentFhirResource.getProvision().getProvision();
if (provisions.isEmpty()) {
continue;
}
var provisionComponent = provisions.getFirst();
String provisionCode = null;
if (provisionComponent.getCode() != null && !provisionComponent.getCode().isEmpty()) {
CodeableConcept codeableConcept = provisionComponent.getCode().getFirst();
if (codeableConcept.getCoding() != null && !codeableConcept.getCoding().isEmpty()) {
provisionCode = codeableConcept.getCoding().getFirst().getCode();
}
}
if (provisionCode != null) {
try {
ModelProjectConsentPurpose modelProjectConsentPurpose =
ModelProjectConsentPurpose.forValue(provisionCode);
if (ModelProjectConsentPurpose.SEQUENCING.equals(modelProjectConsentPurpose)) {
// CONVENTION: wrapping date is date of SEQUENCING consent
mtbFile.getMetadata().getModelProjectConsent().setDate(consentFhirResource.getDateTime());
}
Provision provision = Provision.builder()
.type(ConsentProvision.valueOf(provisionComponent.getType().name()))
.date(provisionComponent.getPeriod().getStart())
.purpose(modelProjectConsentPurpose)
.build();
mtbFile.getMetadata().getModelProjectConsent().getProvisions().add(provision);
} catch (IOException ioe) {
logger.error("Provision code '" + provisionCode + "' is unknown and cannot be mapped.", ioe.toString());
}
}
if (!mtbFile.getMetadata().getModelProjectConsent().getProvisions().isEmpty()) {
mtbFile.getMetadata().getModelProjectConsent().setVersion(gIcsConfigProperties.getGenomeDeConsentVersion());
}
}
}
}

View File

@ -1,12 +1,14 @@
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.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 ConsentCheckFileBased implements ICheckConsent {
private static final Logger log = LoggerFactory.getLogger(ConsentCheckFileBased.class); private static final Logger log = LoggerFactory.getLogger(ConsentCheckFileBased.class);
@ -40,4 +42,14 @@ public class ConsentCheckFileBased implements ICheckConsent{
Date requestDate, ConsentDomain consentDomain) { Date requestDate, ConsentDomain consentDomain) {
return ConsentProvisionType.NULL; return ConsentProvisionType.NULL;
} }
@Override
public void embedBroadConsentResources(Mtb mtbFile, Bundle broadConsent) {
throw new NotImplementedException("not intended to be implemented here!");
}
@Override
public void addGenomeDbProvisions(Mtb mtbFile, Bundle consentGnomeDe) {
throw new NotImplementedException("not intended to be implemented here!");
}
} }

View File

@ -38,12 +38,10 @@ 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 extends BaseConsentService {
private final Logger log = LoggerFactory.getLogger(GicsConsentService.class); private final Logger log = LoggerFactory.getLogger(GicsConsentService.class);
private final GIcsConfigProperties gIcsConfigProperties;
public static final String IS_CONSENTED_ENDPOINT = "/$isConsented"; public static final String IS_CONSENTED_ENDPOINT = "/$isConsented";
public static final String IS_POLICY_STATES_FOR_PERSON_ENDPOINT = "/$currentPolicyStatesForPerson"; public static final String IS_POLICY_STATES_FOR_PERSON_ENDPOINT = "/$currentPolicyStatesForPerson";
private final RetryTemplate retryTemplate; private final RetryTemplate retryTemplate;
@ -54,7 +52,8 @@ public class GicsConsentService implements ICheckConsent {
public GicsConsentService(GIcsConfigProperties gIcsConfigProperties, public GicsConsentService(GIcsConfigProperties gIcsConfigProperties,
RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig) { RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
this.gIcsConfigProperties = gIcsConfigProperties; super(gIcsConfigProperties);
this.retryTemplate = retryTemplate; this.retryTemplate = retryTemplate;
this.restTemplate = restTemplate; this.restTemplate = restTemplate;
this.fhirContext = appFhirConfig.fhirContext(); this.fhirContext = appFhirConfig.fhirContext();

View File

@ -1,6 +1,6 @@
package dev.dnpm.etl.processor.consent; package dev.dnpm.etl.processor.consent;
import dev.pcvolkmer.mv64e.mtb.Mtb;
import java.util.Date; import java.util.Date;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Consent.ConsentProvisionType; import org.hl7.fhir.r4.model.Consent.ConsentProvisionType;
@ -36,8 +36,8 @@ public interface ICheckConsent {
* @return consent policies as bundle; <p>if empty patient has not been asked, yet.</p> * @return consent policies as bundle; <p>if empty patient has not been asked, yet.</p>
*/ */
default Bundle getGenomDeConsent(String personIdentifierValue, Date requestDate) { default Bundle getGenomDeConsent(String personIdentifierValue, Date requestDate) {
return currentConsentForPersonAndTemplate(personIdentifierValue, ConsentDomain.Modelvorhaben64e, return currentConsentForPersonAndTemplate(personIdentifierValue,
requestDate); ConsentDomain.Modelvorhaben64e, requestDate);
} }
/** /**
@ -50,12 +50,17 @@ public interface ICheckConsent {
*/ */
Bundle currentConsentForPersonAndTemplate(String personIdentifierValue, Bundle currentConsentForPersonAndTemplate(String personIdentifierValue,
ConsentDomain targetConsentDomain, Date requestDate); ConsentDomain targetConsentDomain, Date requestDate);
/** /**
*
* @param consentBundle consent resource * @param consentBundle consent resource
* @param requestDate date which must be within validation period of provision * @param requestDate date which must be within validation period of provision
* @return type of provision, will be {@link ConsentProvisionType#NULL} if none is found. * @return type of provision, will be {@link ConsentProvisionType#NULL} if none is found.
*/ */
ConsentProvisionType getProvisionTypeByPolicyCode(Bundle consentBundle, ConsentProvisionType getProvisionTypeByPolicyCode(Bundle consentBundle, Date requestDate,
Date requestDate, ConsentDomain consentDomain); ConsentDomain consentDomain);
void embedBroadConsentResources(Mtb mtbFile, Bundle broadConsent);
void addGenomeDbProvisions(Mtb mtbFile, Bundle consentGnomeDe);
} }

View File

@ -107,7 +107,13 @@ data class GIcsConfigProperties(
/** /**
* Consent Policy which should be used for consent check * Consent Policy which should be used for consent check
*/ */
val genomeDePolicySystem: String = "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV" val genomeDePolicySystem: String = "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
/**
* Consent version (fixed version)
*
*/
val genomeDeConsentVersion: String = "2.0"
) { ) {
companion object { companion object {
const val NAME = "app.consent.gics" const val NAME = "app.consent.gics"

View File

@ -34,21 +34,16 @@ 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.pseudonymizeWith import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
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 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.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.Bundle
import org.hl7.fhir.r4.model.Consent 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.io.IOException
import java.lang.RuntimeException import java.lang.RuntimeException
import java.time.Clock import java.time.Clock
import java.time.Instant import java.time.Instant
@ -96,7 +91,7 @@ class RequestProcessor(
* @return true if consent is given * @return true if consent is given
* *
*/ */
fun consentGatedCheck(mtbFile: Mtb): Boolean { fun consentGatedCheckAndTryEmbedding(mtbFile: Mtb): Boolean {
if (consentService == null) { if (consentService == null) {
// consent check seems to be disabled // consent check seems to be disabled
return true return true
@ -127,8 +122,8 @@ class RequestProcessor(
personIdentifierValue, requestDate personIdentifierValue, requestDate
) )
addGenomeDbProvisions(mtbFile, genomeDeConsent) consentService.addGenomeDbProvisions(mtbFile, genomeDeConsent)
embedBroadConsentResources(mtbFile, broadConsent) consentService.embedBroadConsentResources(mtbFile, broadConsent)
val broadConsentStatus = consentService.getProvisionTypeByPolicyCode( val broadConsentStatus = consentService.getProvisionTypeByPolicyCode(
broadConsent, broadConsent,
@ -157,20 +152,25 @@ class RequestProcessor(
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) {
mtbFile.metadata.researchConsents = mutableListOf()
}
if (mtbFile.metadata.modelProjectConsent == null) {
mtbFile.metadata.modelProjectConsent = ModelProjectConsent()
mtbFile.metadata.modelProjectConsent.provisions = mutableListOf()
}
} }
if (mtbFile.metadata.researchConsents == null) {
mtbFile.metadata.researchConsents = mutableListOf()
}
if (mtbFile.metadata.modelProjectConsent == null) {
mtbFile.metadata.modelProjectConsent = ModelProjectConsent()
mtbFile.metadata.modelProjectConsent.provisions = mutableListOf()
} else
if (mtbFile.metadata.modelProjectConsent.provisions != null) {
// make sure list can be changed
mtbFile.metadata.modelProjectConsent.provisions =
mtbFile.metadata.modelProjectConsent.provisions.toMutableList()
}
} }
fun processMtbFile(mtbFile: Mtb, requestId: RequestId) { fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
val pid = PatientId(extractPatientIdentifier(mtbFile)) val pid = PatientId(extractPatientIdentifier(mtbFile))
if (consentGatedCheck(mtbFile)) { if (consentGatedCheckAndTryEmbedding(mtbFile)) {
mtbFile pseudonymizeWith pseudonymizeService mtbFile pseudonymizeWith pseudonymizeService
mtbFile anonymizeContentWith pseudonymizeService mtbFile anonymizeContentWith pseudonymizeService
val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile)) val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
@ -186,49 +186,6 @@ class RequestProcessor(
} }
fun embedBroadConsentResources(
mtbFile: Mtb, broadConsent: Bundle
) {
broadConsent.entry.forEach { it ->
mtbFile.metadata.researchConsents.add(mapOf(it.resource.id to it.resource as Consent))
}
}
fun addGenomeDbProvisions(
mtbFile: Mtb, consentGnomeDe: Bundle
) {
consentGnomeDe.entry.forEach { it ->
{
val consentFhirResource = it.resource as Consent
// we expect only one provision in collection, therefore get first or none
val provisionComponent = consentFhirResource.provision.provision.firstOrNull()
val provisionCode =
provisionComponent?.code?.firstOrNull()?.coding?.firstOrNull()?.code
if (provisionCode != null) {
try {
val modelProjectConsentPurpose: ModelProjectConsentPurpose =
ModelProjectConsentPurpose.valueOf(provisionCode)
mtbFile.metadata.modelProjectConsent.provisions.add(
Provision.builder().type(
ConsentProvision.forValue(provisionComponent.type.name)
).date(provisionComponent.period.start).purpose(
modelProjectConsentPurpose
).build()
)
} catch (ioe: IOException) {
logger.error(
"provision code '$provisionCode' is unknown and cannot be mapped.",
ioe.toString()
)
}
}
}
}
}
private fun <T> saveAndSend(request: MtbFileRequest<T>, pid: PatientId) { private fun <T> saveAndSend(request: MtbFileRequest<T>, pid: PatientId) {
requestService.save( requestService.save(
Request( Request(