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

fix: serialize fhir consent resources to string, object map

This commit is contained in:
Jakub Lidke
2025-07-15 17:05:26 +02:00
parent be592b1a2a
commit 600ac84ff3
6 changed files with 50 additions and 21 deletions

View File

@ -1,5 +1,9 @@
package dev.dnpm.etl.processor.consent; package dev.dnpm.etl.processor.consent;
import ca.uhn.fhir.context.FhirContext;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.dnpm.etl.processor.config.GIcsConfigProperties; import dev.dnpm.etl.processor.config.GIcsConfigProperties;
import dev.pcvolkmer.mv64e.mtb.ConsentProvision; import dev.pcvolkmer.mv64e.mtb.ConsentProvision;
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose; import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose;
@ -8,7 +12,6 @@ import dev.pcvolkmer.mv64e.mtb.Provision;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Consent; import org.hl7.fhir.r4.model.Consent;
@ -20,24 +23,38 @@ import org.slf4j.LoggerFactory;
public abstract class BaseConsentService implements ICheckConsent { public abstract class BaseConsentService implements ICheckConsent {
protected final GIcsConfigProperties gIcsConfigProperties; protected final GIcsConfigProperties gIcsConfigProperties;
protected Logger logger = LoggerFactory.getLogger(BaseConsentService.class);
public BaseConsentService(GIcsConfigProperties gIcsConfigProperties) { private final ObjectMapper objectMapper;
protected Logger logger = LoggerFactory.getLogger(BaseConsentService.class);
static FhirContext fhirCtx = FhirContext.forR4();
public BaseConsentService(GIcsConfigProperties gIcsConfigProperties,
ObjectMapper objectMapper) {
this.gIcsConfigProperties = gIcsConfigProperties; this.gIcsConfigProperties = gIcsConfigProperties;
this.objectMapper = objectMapper;
} }
public void embedBroadConsentResources(Mtb mtbFile, Bundle broadConsent) { public void embedBroadConsentResources(Mtb mtbFile, Bundle broadConsent) {
for (Bundle.BundleEntryComponent entry : broadConsent.getEntry()) { for (Bundle.BundleEntryComponent entry : broadConsent.getEntry()) {
Resource resource = entry.getResource(); Resource resource = entry.getResource();
if (resource instanceof Consent) { if (resource instanceof Consent) {
Map<String, Object> consentMap = new HashMap<>(); // since jackson convertValue does not work here,
consentMap.put(resource.getIdElement().getIdPart(), resource); // we need another step to back to string, before we convert to object map
mtbFile.getMetadata().getResearchConsents().add(consentMap); var asJsonString = fhirCtx.newJsonParser().encodeResourceToString(resource);
try {
var mapOfJson = objectMapper.readValue(asJsonString,
new TypeReference<HashMap<String, Object>>() {
});
mtbFile.getMetadata().getResearchConsents().add(mapOfJson);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
} }
} }
} }
public void addGenomeDbProvisions(Mtb mtbFile, Bundle consentGnomeDe) { public void addGenomeDbProvisions(Mtb mtbFile, Bundle consentGnomeDe) {
for (Bundle.BundleEntryComponent entry : consentGnomeDe.getEntry()) { for (Bundle.BundleEntryComponent entry : consentGnomeDe.getEntry()) {
Resource resource = entry.getResource(); Resource resource = entry.getResource();
if (!(resource instanceof Consent consentFhirResource)) { if (!(resource instanceof Consent consentFhirResource)) {
@ -67,7 +84,8 @@ public abstract class BaseConsentService implements ICheckConsent {
if (ModelProjectConsentPurpose.SEQUENCING.equals(modelProjectConsentPurpose)) { if (ModelProjectConsentPurpose.SEQUENCING.equals(modelProjectConsentPurpose)) {
// CONVENTION: wrapping date is date of SEQUENCING consent // CONVENTION: wrapping date is date of SEQUENCING consent
mtbFile.getMetadata().getModelProjectConsent().setDate(consentFhirResource.getDateTime()); mtbFile.getMetadata().getModelProjectConsent()
.setDate(consentFhirResource.getDateTime());
} }
Provision provision = Provision.builder() Provision provision = Provision.builder()
@ -79,12 +97,15 @@ public abstract class BaseConsentService implements ICheckConsent {
mtbFile.getMetadata().getModelProjectConsent().getProvisions().add(provision); mtbFile.getMetadata().getModelProjectConsent().getProvisions().add(provision);
} catch (IOException ioe) { } catch (IOException ioe) {
logger.error("Provision code '" + provisionCode + "' is unknown and cannot be mapped.", ioe.toString()); logger.error(
"Provision code '" + provisionCode + "' is unknown and cannot be mapped.",
ioe.toString());
} }
} }
if (!mtbFile.getMetadata().getModelProjectConsent().getProvisions().isEmpty()) { if (!mtbFile.getMetadata().getModelProjectConsent().getProvisions().isEmpty()) {
mtbFile.getMetadata().getModelProjectConsent().setVersion(gIcsConfigProperties.getGenomeDeConsentVersion()); mtbFile.getMetadata().getModelProjectConsent()
.setVersion(gIcsConfigProperties.getGenomeDeConsentVersion());
} }
} }
} }

View File

@ -2,6 +2,7 @@ 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;
@ -51,8 +52,8 @@ public class GicsConsentService extends BaseConsentService {
private String url; private String url;
public GicsConsentService(GIcsConfigProperties gIcsConfigProperties, public GicsConsentService(GIcsConfigProperties gIcsConfigProperties,
RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig) { RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig, ObjectMapper objectMapper) {
super(gIcsConfigProperties); super(gIcsConfigProperties,objectMapper);
this.retryTemplate = retryTemplate; this.retryTemplate = retryTemplate;
this.restTemplate = restTemplate; this.restTemplate = restTemplate;

View File

@ -184,12 +184,13 @@ class AppConfiguration {
@Bean @Bean
@ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true") @ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
fun gicsConsentService( gIcsConfigProperties: GIcsConfigProperties, fun gicsConsentService( gIcsConfigProperties: GIcsConfigProperties,
retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig): ICheckConsent { retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig, getObjectMapper: ObjectMapper): ICheckConsent {
return GicsConsentService( return GicsConsentService(
gIcsConfigProperties, gIcsConfigProperties,
retryTemplate, retryTemplate,
restTemplate, restTemplate,
appFhirConfig appFhirConfig,
getObjectMapper
) )
} }

View File

@ -25,7 +25,6 @@ import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent
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 org.apache.commons.codec.digest.DigestUtils import org.apache.commons.codec.digest.DigestUtils
import org.hl7.fhir.r4.model.Consent
/** Replaces patient ID with generated patient pseudonym /** Replaces patient ID with generated patient pseudonym
* *
@ -295,11 +294,12 @@ infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
this.metadata?.researchConsents?.forEach { it -> this.metadata?.researchConsents?.forEach { it ->
val entry = it ?: return@forEach val entry = it ?: return@forEach
val key = entry.keys.first() if (entry.contains("patient")) {
val consent = entry[key] as? Consent ?: return@forEach // here we expect only a patient reference any other data like display
val patRef= "Patient/$patientPseudonym" // need to be removed, since may contain unsecure data
consent.patient?.setReference(patRef) entry.remove("patient")
consent.patient?.display = null entry["patient"] = mapOf("reference" to "Patient/$patientPseudonym")
}
} }
} }

View File

@ -36,6 +36,7 @@ import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized 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.hl7.fhir.r4.model.Consent
@ -122,6 +123,10 @@ class RequestProcessor(
) )
consentService.addGenomeDbProvisions(mtbFile, genomeDeConsent) consentService.addGenomeDbProvisions(mtbFile, genomeDeConsent)
// fixme: currently we do not have information about submission type
if (!genomeDeConsent.entry.isEmpty()) mtbFile.metadata.type = MvhSubmissionType.INITIAL
consentService.embedBroadConsentResources(mtbFile, broadConsent) consentService.embedBroadConsentResources(mtbFile, broadConsent)
val broadConsentStatus = consentService.getProvisionTypeByPolicyCode( val broadConsentStatus = consentService.getProvisionTypeByPolicyCode(

View File

@ -251,7 +251,8 @@ class ExtensionsTest {
private fun addConsentData(mtbFile: Mtb) { private fun addConsentData(mtbFile: Mtb) {
val gIcsConfigProperties = GIcsConfigProperties("", "", "", true) val gIcsConfigProperties = GIcsConfigProperties("", "", "", true)
val baseConsentService = object : BaseConsentService(gIcsConfigProperties) { val baseConsentService = object : BaseConsentService(gIcsConfigProperties,
JacksonConfig().objectMapper()) {
override fun getTtpBroadConsentStatus(personIdentifierValue: String?): TtpConsentStatus? { override fun getTtpBroadConsentStatus(personIdentifierValue: String?): TtpConsentStatus? {
throw NotImplementedError("dummy") throw NotImplementedError("dummy")
} }