mirror of
https://github.com/pcvolkmer/etl-processor.git
synced 2025-07-01 14:12:55 +00:00
chore: add HL7 Fhir serialization to ObjectMapper, so we can add FHIR consent resources to mtb file
This commit is contained in:
@ -38,6 +38,7 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
private final GIcsConfigProperties gIcsConfigProperties;
|
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";
|
||||||
private final RetryTemplate retryTemplate;
|
private final RetryTemplate retryTemplate;
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
private final FhirContext fhirContext;
|
private final FhirContext fhirContext;
|
||||||
@ -55,7 +56,7 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
log.info("GicsConsentService initialized...");
|
log.info("GicsConsentService initialized...");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getGicsUri() {
|
public String getGicsUri(String endpoint) {
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
final String gIcsBaseUri = gIcsConfigProperties.getUri();
|
final String gIcsBaseUri = gIcsConfigProperties.getUri();
|
||||||
if (StringUtils.isBlank(gIcsBaseUri)) {
|
if (StringUtils.isBlank(gIcsBaseUri)) {
|
||||||
@ -81,7 +82,7 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Parameters getIsConsentedRequestParam(GIcsConfigProperties configProperties,
|
protected static Parameters getIsConsentedRequestParam(GIcsConfigProperties configProperties,
|
||||||
String personIdentifierValue) {
|
String personIdentifierValue) {
|
||||||
var result = new Parameters();
|
var result = new Parameters();
|
||||||
result.addParameter(new ParametersParameterComponent().setName("personIdentifier").setValue(
|
result.addParameter(new ParametersParameterComponent().setName("personIdentifier").setValue(
|
||||||
@ -115,13 +116,13 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String callGicsApi(Parameters parameter) {
|
protected String callGicsApi(Parameters parameter, String endpoint) {
|
||||||
var parameterAsXml = fhirContext.newXmlParser().encodeResourceToString(parameter);
|
var parameterAsXml = fhirContext.newXmlParser().encodeResourceToString(parameter);
|
||||||
|
|
||||||
HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.httpHeader);
|
HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.httpHeader);
|
||||||
ResponseEntity<String> responseEntity;
|
ResponseEntity<String> responseEntity;
|
||||||
try {
|
try {
|
||||||
var url = getGicsUri();
|
var url = getGicsUri(endpoint);
|
||||||
|
|
||||||
responseEntity = retryTemplate.execute(
|
responseEntity = retryTemplate.execute(
|
||||||
ctx -> restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class));
|
ctx -> restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class));
|
||||||
@ -155,7 +156,8 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
var parameter = GicsConsentService.getIsConsentedRequestParam(gIcsConfigProperties,
|
var parameter = GicsConsentService.getIsConsentedRequestParam(gIcsConfigProperties,
|
||||||
personIdentifierValue);
|
personIdentifierValue);
|
||||||
|
|
||||||
var consentStatusResponse = callGicsApi(parameter);
|
var consentStatusResponse = callGicsApi(parameter,
|
||||||
|
GicsConsentService.IS_CONSENTED_ENDPOINT);
|
||||||
return evaluateConsentResponse(consentStatusResponse);
|
return evaluateConsentResponse(consentStatusResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +169,8 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
var requestParameter = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(
|
var requestParameter = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(
|
||||||
gIcsConfigProperties, personIdentifierValue, requestDate, consentDomain);
|
gIcsConfigProperties, personIdentifierValue, requestDate, consentDomain);
|
||||||
|
|
||||||
var consentDataSerialized = callGicsApi(requestParameter);
|
var consentDataSerialized = callGicsApi(requestParameter,
|
||||||
|
GicsConsentService.IS_POLICY_STATES_FOR_PERSON_ENDPOINT);
|
||||||
|
|
||||||
if (consentDataSerialized == null) {
|
if (consentDataSerialized == null) {
|
||||||
// error occurred - should not process further!
|
// error occurred - should not process further!
|
||||||
@ -222,7 +225,7 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
ConsentDomain.Modelvorhaben64e, requestDate);
|
ConsentDomain.Modelvorhaben64e, requestDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Parameters buildRequestParameterCurrentPolicyStatesForPerson(
|
protected static Parameters buildRequestParameterCurrentPolicyStatesForPerson(
|
||||||
GIcsConfigProperties gIcsConfigProperties, String personIdentifierValue, Date requestDate,
|
GIcsConfigProperties gIcsConfigProperties, String personIdentifierValue, Date requestDate,
|
||||||
String targetDomain) {
|
String targetDomain) {
|
||||||
var requestParameter = new Parameters();
|
var requestParameter = new Parameters();
|
||||||
|
@ -87,7 +87,7 @@ data class GIcsConfigProperties(
|
|||||||
/**
|
/**
|
||||||
* Domain of Modelvorhaben 64e consent resources
|
* Domain of Modelvorhaben 64e consent resources
|
||||||
**/
|
**/
|
||||||
val gnomDeConsentDomainName: String = "GenomeDE_MV",
|
val gnomDeConsentDomainName: String = "GenomDE_MV",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value to expect in case of positiv consent
|
* Value to expect in case of positiv consent
|
||||||
|
@ -100,17 +100,21 @@ class AppConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun reportService(objectMapper: ObjectMapper): ReportService {
|
fun reportService(): ReportService {
|
||||||
return ReportService(objectMapper)
|
return ReportService(getObjectMapper())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun getObjectMapper () : ObjectMapper{
|
||||||
|
return JacksonConfig().objectMapper()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun transformationService(
|
fun transformationService(
|
||||||
objectMapper: ObjectMapper,
|
|
||||||
configProperties: AppConfigProperties
|
configProperties: AppConfigProperties
|
||||||
): TransformationService {
|
): TransformationService {
|
||||||
logger.info("Apply ${configProperties.transformations.size} transformation rules")
|
logger.info("Apply ${configProperties.transformations.size} transformation rules")
|
||||||
return TransformationService(objectMapper, configProperties.transformations.map {
|
return TransformationService(getObjectMapper(), configProperties.transformations.map {
|
||||||
Transformation.of(it.path) from it.from to it.to
|
Transformation.of(it.path) from it.from to it.to
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package dev.dnpm.etl.processor.config
|
||||||
|
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource
|
||||||
|
|
||||||
|
class FhirResourceModule : SimpleModule() {
|
||||||
|
init {
|
||||||
|
addSerializer(IBaseResource::class.java, IBaseResourceSerializer())
|
||||||
|
addDeserializer(IBaseResource::class.java, IBaseResourceDeserializer())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package dev.dnpm.etl.processor.config
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext
|
||||||
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource
|
||||||
|
|
||||||
|
class IBaseResourceDeserializer : JsonDeserializer<IBaseResource>() {
|
||||||
|
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): IBaseResource {
|
||||||
|
val fhirContext = FhirContext.forR4()
|
||||||
|
|
||||||
|
val jsonNode = p?.readValueAsTree<JsonNode>()
|
||||||
|
val json = jsonNode?.toString()
|
||||||
|
|
||||||
|
return fhirContext.newJsonParser().parseResource(json) as IBaseResource
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package dev.dnpm.etl.processor.config
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource
|
||||||
|
|
||||||
|
class IBaseResourceSerializer : JsonSerializer<IBaseResource>() {
|
||||||
|
override fun serialize(
|
||||||
|
value: IBaseResource,
|
||||||
|
gen: JsonGenerator,
|
||||||
|
serializers: SerializerProvider
|
||||||
|
) {
|
||||||
|
val fhirContext = FhirContext.forR4()
|
||||||
|
val json = fhirContext.newJsonParser().encodeResourceToString(value)
|
||||||
|
gen.writeRawValue(json)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package dev.dnpm.etl.processor.config
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class JacksonConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun objectMapper(): ObjectMapper =
|
||||||
|
ObjectMapper()
|
||||||
|
.registerModule(FhirResourceModule())
|
||||||
|
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).registerModule(
|
||||||
|
JavaTimeModule()
|
||||||
|
);
|
||||||
|
}
|
@ -7,13 +7,18 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat
|
|||||||
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 java.time.Instant;
|
||||||
|
import java.util.Date;
|
||||||
import org.hl7.fhir.r4.model.BooleanType;
|
import org.hl7.fhir.r4.model.BooleanType;
|
||||||
|
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;
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome.IssueType;
|
import org.hl7.fhir.r4.model.OperationOutcome.IssueType;
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent;
|
import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent;
|
||||||
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.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.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -43,6 +48,9 @@ public class GicsConsentServiceTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
AppFhirConfig appFhirConfig;
|
AppFhirConfig appFhirConfig;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
GIcsConfigProperties gIcsConfigProperties;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
mockRestServiceServer = MockRestServiceServer.createServer(appConfiguration.restTemplate());
|
mockRestServiceServer = MockRestServiceServer.createServer(appConfiguration.restTemplate());
|
||||||
@ -100,4 +108,17 @@ public class GicsConsentServiceTest {
|
|||||||
var consentStatus = gicsConsentService.getTtpConsentStatus("123456");
|
var consentStatus = gicsConsentService.getTtpConsentStatus("123456");
|
||||||
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
|
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void buildRequestParameterCurrentPolicyStatesForPersonTest() {
|
||||||
|
|
||||||
|
String pid = "12345678";
|
||||||
|
var result = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(gIcsConfigProperties,
|
||||||
|
pid, Date.from(Instant.now()),gIcsConfigProperties.getGnomDeConsentDomainName());
|
||||||
|
|
||||||
|
assertThat(result.getParameter().size()).as("should contain 3 parameter resources").isEqualTo(3);
|
||||||
|
|
||||||
|
assertThat(((StringType)result.getParameter("domain").getValue()).getValue()).isEqualTo(gIcsConfigProperties.getGnomDeConsentDomainName());
|
||||||
|
assertThat(((Identifier)result.getParameter("personIdentifier").getValue()).getValue()).isEqualTo(pid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,22 @@
|
|||||||
|
|
||||||
package dev.dnpm.etl.processor.services
|
package dev.dnpm.etl.processor.services
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import de.ukw.ccc.bwhc.dto.Consent
|
import de.ukw.ccc.bwhc.dto.Consent
|
||||||
import de.ukw.ccc.bwhc.dto.Diagnosis
|
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.pcvolkmer.mv64e.mtb.ModelProjectConsent
|
||||||
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.MvhMetadata
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource
|
||||||
|
import org.hl7.fhir.r4.model.CodeableConcept
|
||||||
|
import org.hl7.fhir.r4.model.Coding
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
class TransformationServiceTest {
|
class TransformationServiceTest {
|
||||||
|
|
||||||
@ -35,7 +43,7 @@ class TransformationServiceTest {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
this.service = TransformationService(
|
this.service = TransformationService(
|
||||||
ObjectMapper(), listOf(
|
JacksonConfig().objectMapper(), listOf(
|
||||||
Transformation.of("consent.status") from Consent.Status.ACTIVE to Consent.Status.REJECTED,
|
Transformation.of("consent.status") from Consent.Status.ACTIVE to Consent.Status.REJECTED,
|
||||||
Transformation.of("diagnoses[*].icd10.version") from "2013" to "2014",
|
Transformation.of("diagnoses[*].icd10.version") from "2013" to "2014",
|
||||||
)
|
)
|
||||||
@ -92,4 +100,79 @@ class TransformationServiceTest {
|
|||||||
assertThat(actual.consent.status).isEqualTo(Consent.Status.REJECTED)
|
assertThat(actual.consent.status).isEqualTo(Consent.Status.REJECTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldTransformConsentValues() {
|
||||||
|
val mtbFile = MtbFile.builder().withDiagnoses(
|
||||||
|
listOf(
|
||||||
|
Diagnosis.builder().withId("1234").withIcd10(Icd10("F79.9").also {
|
||||||
|
it.version = "2013"
|
||||||
|
}).build(),
|
||||||
|
Diagnosis.builder().withId("5678").withIcd10(Icd10("F79.8").also {
|
||||||
|
it.version = "2019"
|
||||||
|
}).build()
|
||||||
|
)
|
||||||
|
).build()
|
||||||
|
|
||||||
|
val actual = this.service.transform(mtbFile)
|
||||||
|
|
||||||
|
assertThat(actual).isNotNull
|
||||||
|
assertThat(actual.diagnoses[0].icd10.code).isEqualTo("F79.9")
|
||||||
|
assertThat(actual.diagnoses[0].icd10.version).isEqualTo("2014")
|
||||||
|
assertThat(actual.diagnoses[1].icd10.code).isEqualTo("F79.8")
|
||||||
|
assertThat(actual.diagnoses[1].icd10.version).isEqualTo("2019")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldTransformConsent() {
|
||||||
|
val mvhMetadata = MvhMetadata.builder().transferTan("transfertan12345").build();
|
||||||
|
|
||||||
|
assertThat(mvhMetadata).isNotNull
|
||||||
|
mvhMetadata.modelProjectConsent =
|
||||||
|
ModelProjectConsent.builder().date(Date.from(Instant.now())).version("1").build()
|
||||||
|
val consent1 = org.hl7.fhir.r4.model.Consent()
|
||||||
|
consent1.id = "consent 1 id"
|
||||||
|
consent1.patient.reference = "Patient/1234-pat1"
|
||||||
|
|
||||||
|
consent1.provision.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("deny"))
|
||||||
|
consent1.provision.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
||||||
|
consent1.provision.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
||||||
|
|
||||||
|
|
||||||
|
val addProvision1 = consent1.provision.addProvision()
|
||||||
|
addProvision1.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("permit"))
|
||||||
|
addProvision1.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
||||||
|
addProvision1.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
||||||
|
addProvision1.code.addLast(
|
||||||
|
CodeableConcept(
|
||||||
|
Coding(
|
||||||
|
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
||||||
|
"Teilnahme",
|
||||||
|
"Teilnahme am Modellvorhaben und Einwilligung zur Genomsequenzierung"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val addProvision2 = consent1.provision.addProvision()
|
||||||
|
addProvision2.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("deny"))
|
||||||
|
addProvision2.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
||||||
|
addProvision2.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
||||||
|
addProvision2.code.addLast(
|
||||||
|
CodeableConcept(
|
||||||
|
Coding(
|
||||||
|
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
||||||
|
"Rekontaktierung",
|
||||||
|
"Re-Identifizierung meiner Daten über die Vertrauensstelle beim Robert Koch-Institut und in die erneute Kontaktaufnahme durch meine behandelnde Ärztin oder meinen behandelnden Arzt"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user