From 7440fe1e23e730fd526a814cfde7cc86e105cf70 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Thu, 5 Oct 2023 10:51:49 +0200 Subject: [PATCH] Issue #12: Basic implementation of transformation service --- build.gradle.kts | 1 + .../services/TransformationService.kt | 62 ++++++++++++++ .../services/TransformationServiceTest.kt | 83 +++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt create mode 100644 src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 3f053f5..0ed1f37 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -69,6 +69,7 @@ dependencies { implementation("ca.uhn.hapi.fhir:hapi-fhir-base:${versions["hapi-fhir"]}") implementation("ca.uhn.hapi.fhir:hapi-fhir-structures-r4:${versions["hapi-fhir"]}") implementation("org.apache.httpcomponents.client5:httpclient5:${versions["httpclient5"]}") + implementation("com.jayway.jsonpath:json-path") runtimeOnly("org.mariadb.jdbc:mariadb-java-client") runtimeOnly("org.postgresql:postgresql") developmentOnly("org.springframework.boot:spring-boot-devtools") diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt new file mode 100644 index 0000000..9be0216 --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt @@ -0,0 +1,62 @@ +package dev.dnpm.etl.processor.services + +import com.fasterxml.jackson.databind.ObjectMapper +import com.jayway.jsonpath.JsonPath +import com.jayway.jsonpath.PathNotFoundException +import de.ukw.ccc.bwhc.dto.MtbFile + +class TransformationService(private val objectMapper: ObjectMapper) { + fun transform(mtbFile: MtbFile, vararg transformations: Transformation): MtbFile { + var json = objectMapper.writeValueAsString(mtbFile) + + transformations.forEach { transformation -> + val jsonPath = JsonPath.parse(json) + + try { + val before = transformation.path.substringBeforeLast(".") + val last = transformation.path.substringAfterLast(".") + + val existingValue = if (transformation.existingValue is Number) transformation.existingValue else transformation.existingValue.toString() + val newValue = if (transformation.newValue is Number) transformation.newValue else transformation.newValue.toString() + + jsonPath.set("$.$before.[?]$last", newValue, { + it.item(HashMap::class.java)[last] == existingValue + }) + } catch (e: PathNotFoundException) { + // Ignore + } + + json = jsonPath.jsonString() + } + + return objectMapper.readValue(json, MtbFile::class.java) + } + +} + +class Transformation private constructor(internal val path: String) { + + lateinit var existingValue: Any + private set + lateinit var newValue: Any + private set + + infix fun from(value: Any): Transformation { + this.existingValue = value + return this + } + + infix fun to(value: Any): Transformation { + this.newValue = value + return this + } + + companion object { + + fun of(path: String): Transformation { + return Transformation(path) + } + + } + +} diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt new file mode 100644 index 0000000..c6cd645 --- /dev/null +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt @@ -0,0 +1,83 @@ +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.Diagnosis +import de.ukw.ccc.bwhc.dto.Icd10 +import de.ukw.ccc.bwhc.dto.MtbFile +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class TransformationServiceTest { + + private lateinit var service: TransformationService + + @BeforeEach + fun setup() { + this.service = TransformationService(ObjectMapper()) + } + + @Test + fun shouldTransformMtbFile() { + val transformations = arrayOf( + Transformation.of("diagnoses[*].icd10.version") from "2013" to "2014", + ) + + val mtbFile = MtbFile.builder().withDiagnoses( + listOf( + Diagnosis.builder().withId("1234").withIcd10(Icd10("F79.9").also { + it.version = "2013" + }).build() + ) + ).build() + + val actual = this.service.transform(mtbFile, *transformations) + + assertThat(actual).isNotNull + assertThat(actual.diagnoses[0].icd10.version).isEqualTo("2014") + } + + @Test + fun shouldOnlyTransformGivenValues() { + val transformations = arrayOf( + Transformation.of("diagnoses[*].icd10.version") from "2013" to "2014", + ) + + 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, *transformations) + + 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 shouldTransformMtbFileWithConsentEnum() { + val transformations = arrayOf( + Transformation.of("consent.status") from Consent.Status.ACTIVE to Consent.Status.REJECTED, + ) + + val mtbFile = MtbFile.builder().withConsent( + Consent("123", "456", Consent.Status.ACTIVE) + ).build() + + val actual = this.service.transform(mtbFile, *transformations) + + assertThat(actual.consent).isNotNull + assertThat(actual.consent.status).isEqualTo(Consent.Status.REJECTED) + } + +} \ No newline at end of file