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;
import dev.pcvolkmer.mv64e.mtb.Mtb;
import java.util.Date;
import org.apache.commons.lang3.NotImplementedException;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Consent.ConsentProvisionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConsentCheckFileBased implements ICheckConsent{
public class ConsentCheckFileBased implements ICheckConsent {
private static final Logger log = LoggerFactory.getLogger(ConsentCheckFileBased.class);
@ -40,4 +42,14 @@ public class ConsentCheckFileBased implements ICheckConsent{
Date requestDate, ConsentDomain consentDomain) {
return ConsentProvisionType.NULL;
}
@Override
public void embedBroadConsentResources(Mtb mtbFile, Bundle broadConsent) {
throw new NotImplementedException("not intended to be implemented here!");
}
@Override
public void addGenomeDbProvisions(Mtb mtbFile, Bundle consentGnomeDe) {
throw new NotImplementedException("not intended to be implemented here!");
}
}

View File

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

View File

@ -1,6 +1,6 @@
package dev.dnpm.etl.processor.consent;
import dev.pcvolkmer.mv64e.mtb.Mtb;
import java.util.Date;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Consent.ConsentProvisionType;
@ -36,8 +36,8 @@ public interface ICheckConsent {
* @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.Modelvorhaben64e,
requestDate);
return currentConsentForPersonAndTemplate(personIdentifierValue,
ConsentDomain.Modelvorhaben64e, requestDate);
}
/**
@ -50,12 +50,17 @@ public interface ICheckConsent {
*/
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);
ConsentProvisionType getProvisionTypeByPolicyCode(Bundle consentBundle, Date requestDate,
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
*/
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 {
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.anonymizeContentWith
import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
import dev.pcvolkmer.mv64e.mtb.ConsentProvision
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose
import dev.pcvolkmer.mv64e.mtb.Mtb
import dev.pcvolkmer.mv64e.mtb.MvhMetadata
import dev.pcvolkmer.mv64e.mtb.Provision
import org.apache.commons.codec.binary.Base32
import org.apache.commons.codec.digest.DigestUtils
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.Consent
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Service
import java.io.IOException
import java.lang.RuntimeException
import java.time.Clock
import java.time.Instant
@ -96,7 +91,7 @@ class RequestProcessor(
* @return true if consent is given
*
*/
fun consentGatedCheck(mtbFile: Mtb): Boolean {
fun consentGatedCheckAndTryEmbedding(mtbFile: Mtb): Boolean {
if (consentService == null) {
// consent check seems to be disabled
return true
@ -127,8 +122,8 @@ class RequestProcessor(
personIdentifierValue, requestDate
)
addGenomeDbProvisions(mtbFile, genomeDeConsent)
embedBroadConsentResources(mtbFile, broadConsent)
consentService.addGenomeDbProvisions(mtbFile, genomeDeConsent)
consentService.embedBroadConsentResources(mtbFile, broadConsent)
val broadConsentStatus = consentService.getProvisionTypeByPolicyCode(
broadConsent,
@ -157,20 +152,25 @@ class RequestProcessor(
if (mtbFile.metadata == null) {
val mvhMetadata = MvhMetadata.builder().build()
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()
}
} 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) {
val pid = PatientId(extractPatientIdentifier(mtbFile))
if (consentGatedCheck(mtbFile)) {
if (consentGatedCheckAndTryEmbedding(mtbFile)) {
mtbFile pseudonymizeWith pseudonymizeService
mtbFile anonymizeContentWith pseudonymizeService
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) {
requestService.save(
Request(