summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.release-please-manifest.json2
-rw-r--r--CHANGELOG.md26
-rw-r--r--README.md2
-rw-r--r--build.gradle.kts8
-rw-r--r--src/integrationTest/kotlin/dev/dnpm/etl/processor/EtlProcessorApplicationTests.kt19
-rw-r--r--src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt32
-rw-r--r--src/integrationTest/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt13
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt1
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt12
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt8
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt7
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt2
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt8
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/output/MtbRequest.kt8
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt36
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt73
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt55
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt2
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt6
-rw-r--r--src/main/resources/db/migration/mariadb/V0_16_1_1__FollowUpCount.sql2
-rw-r--r--src/main/resources/db/migration/mariadb/V0_16_3_1__ExpectedFollowUpCount.sql2
-rw-r--r--src/main/resources/db/migration/postgresql/V0_16_1_1__FollowUpCount.sql2
-rw-r--r--src/main/resources/db/migration/postgresql/V0_16_3_1__ExpectedFollowUpCount.sql2
-rw-r--r--src/main/resources/templates/fragments.html3
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/config/Jackson3ConfigTest.kt9
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/config/JacksonConfigTest.kt9
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/consent/ConsentProcessorTest.kt80
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt81
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt64
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt48
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt16
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt13
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt70
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt12
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt63
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt929
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt33
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt56
38 files changed, 1292 insertions, 522 deletions
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index dc93a58..ec155c6 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.16.1"
+ ".": "0.16.4"
} \ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a9faed..55b7068 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,31 @@
# Changelog
+## [0.16.4](https://github.com/pcvolkmer/mv64e-etl-processor/compare/v0.16.3...v0.16.4) (2026-06-23)
+
+
+### Features
+
+* support for asynchronous follow-up count calculation ([#300](https://github.com/pcvolkmer/mv64e-etl-processor/issues/300)) ([b16cd39](https://github.com/pcvolkmer/mv64e-etl-processor/commit/b16cd39aa622f018e318ebef2006c517708cf06e))
+
+## [0.16.3](https://github.com/pcvolkmer/mv64e-etl-processor/compare/v0.16.2...v0.16.3) (2026-06-16)
+
+
+### Features
+
+* do not request research consent if reason missing is given ([#298](https://github.com/pcvolkmer/mv64e-etl-processor/issues/298)) ([5565771](https://github.com/pcvolkmer/mv64e-etl-processor/commit/556577102ed8287b25351f68241264c7062fd362))
+
+## [0.16.2](https://github.com/pcvolkmer/mv64e-etl-processor/compare/v0.16.1...v0.16.2) (2026-06-11)
+
+
+### Features
+
+* count follow-ups to update submission type ([#297](https://github.com/pcvolkmer/mv64e-etl-processor/issues/297)) ([7298078](https://github.com/pcvolkmer/mv64e-etl-processor/commit/7298078077d27380255ad5eb42a30876a4babede))
+
+
+### Bug Fixes
+
+* add template icon for followups ([#294](https://github.com/pcvolkmer/mv64e-etl-processor/issues/294)) ([20d1182](https://github.com/pcvolkmer/mv64e-etl-processor/commit/20d1182da9d8f9900d5580b8f4525847e03cbaba))
+
## [0.16.1](https://github.com/pcvolkmer/mv64e-etl-processor/compare/v0.16.0...v0.16.1) (2026-05-26)
diff --git a/README.md b/README.md
index 58f158b..5f55937 100644
--- a/README.md
+++ b/README.md
@@ -495,7 +495,7 @@ Severity-Stufe als Ergebnis verwendet.
## Docker-Images
Diese Anwendung ist auch als Docker-Image
-verfügbar: https://github.com/pcvolkmer/etl-processor/pkgs/container/etl-processor
+verfügbar: https://github.com/pcvolkmer/mv64e-etl-processor/pkgs/container/mv64e-etl-processor
### Images lokal bauen
diff --git a/build.gradle.kts b/build.gradle.kts
index c029e58..1fe3621 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -7,7 +7,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
plugins {
java
- id("org.springframework.boot") version "4.0.6"
+ id("org.springframework.boot") version "4.0.7"
id("io.spring.dependency-management") version "1.1.7"
id("com.diffplug.spotless") version "8.0.0"
id("net.ltgt.errorprone") version "5.1.0"
@@ -18,10 +18,10 @@ plugins {
}
group = "dev.dnpm"
-version = "0.16.1" // x-release-please-version
+version = "0.16.4" // x-release-please-version
// Additional versions
-val mtbDtoVersion by extra("0.3.0")
+val mtbModelVersion by extra("0.4.2")
val hapiFhirVersion by extra("8.8.1")
val apacheCxfVersion by extra("4.1.5")
val springModulithVersion by extra("2.0.5")
@@ -81,7 +81,7 @@ dependencies {
implementation("org.flywaydb:flyway-mysql")
implementation("commons-codec:commons-codec")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
- implementation("dev.pcvolkmer.mv64e:mtb-dto:${mtbDtoVersion}")
+ implementation("dev.pcvolkmer.mv64e:mtb-model:${mtbModelVersion}")
implementation("ca.uhn.hapi.fhir:hapi-fhir-base:${hapiFhirVersion}")
implementation("ca.uhn.hapi.fhir:hapi-fhir-structures-r4:${hapiFhirVersion}")
implementation("org.apache.httpcomponents.client5:httpclient5")
diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/EtlProcessorApplicationTests.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/EtlProcessorApplicationTests.kt
index 248f889..9f1e183 100644
--- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/EtlProcessorApplicationTests.kt
+++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/EtlProcessorApplicationTests.kt
@@ -24,7 +24,16 @@ import dev.dnpm.etl.processor.monitoring.RequestRepository
import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.output.DnpmV2MtbFileRequest
import dev.dnpm.etl.processor.output.MtbFileSender
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.Coding
+import dev.pcvolkmer.mv64e.model.ConsentProvisionType
+import dev.pcvolkmer.mv64e.model.ModelProjectConsentPurpose
+import dev.pcvolkmer.mv64e.model.MtbDiagnosis
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsent
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsentProvisionsInner
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.Reference
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
@@ -105,16 +114,16 @@ class EtlProcessorApplicationTests : AbstractTestcontainerTest() {
.send(any<DnpmV2MtbFileRequest>())
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("TEST_12345678").build())
.metadata(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
- .type(ConsentProvision.PERMIT)
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.PERMIT)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.build()
)
diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
index ba793f2..a9427b7 100644
--- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
+++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
@@ -29,7 +29,11 @@ import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.security.TokenRepository
import dev.dnpm.etl.processor.security.UserRoleRepository
import dev.dnpm.etl.processor.services.RequestProcessor
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.MtbEpisodeOfCare
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.PeriodDate
+import dev.pcvolkmer.mv64e.model.Reference
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
@@ -112,7 +116,7 @@ class MtbFileRestControllerTest {
]
)
fun testShouldGrantPermissionToSendMtbFile(url: String) {
- whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+ whenever { requestProcessor.processMtbFile(any<PatientRecord>()) }.thenReturn(true)
mockMvc
.post(url) {
@@ -122,7 +126,7 @@ class MtbFileRestControllerTest {
}
.andExpect { status { isAccepted() } }
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>())
}
@ParameterizedTest
@@ -140,7 +144,7 @@ class MtbFileRestControllerTest {
]
)
fun testShouldGrantPermissionToSendMtbFileToAdminUser(url: String) {
- whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+ whenever { requestProcessor.processMtbFile(any<PatientRecord>()) }.thenReturn(true)
mockMvc
.post(url) {
@@ -150,7 +154,7 @@ class MtbFileRestControllerTest {
}
.andExpect { status { isAccepted() } }
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>())
}
@ParameterizedTest
@@ -168,7 +172,7 @@ class MtbFileRestControllerTest {
]
)
fun testShouldGrantPermissionToSendMtbFileToUser(url: String) {
- whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+ whenever { requestProcessor.processMtbFile(any<PatientRecord>()) }.thenReturn(true)
mockMvc
.post(url) {
@@ -178,7 +182,7 @@ class MtbFileRestControllerTest {
}
.andExpect { status { isAccepted() } }
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>())
}
@ParameterizedTest
@@ -203,7 +207,7 @@ class MtbFileRestControllerTest {
}
.andExpect { status { isUnauthorized() } }
- verify(requestProcessor, never()).processMtbFile(any<Mtb>())
+ verify(requestProcessor, never()).processMtbFile(any<PatientRecord>())
}
@ParameterizedTest
@@ -269,7 +273,7 @@ class MtbFileRestControllerTest {
}
.andExpect { status { isBadRequest() } }
- val result = verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ val result = verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>())
assertThat(result).isFalse()
}
@@ -301,7 +305,7 @@ class MtbFileRestControllerTest {
]
)
fun testShouldGrantPermissionToSendMtbFileToAdminUser(url: String) {
- whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+ whenever { requestProcessor.processMtbFile(any<PatientRecord>()) }.thenReturn(true)
mockMvc
.post(url) {
@@ -311,7 +315,7 @@ class MtbFileRestControllerTest {
}
.andExpect { status { isAccepted() } }
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>())
}
@ParameterizedTest
@@ -329,7 +333,7 @@ class MtbFileRestControllerTest {
]
)
fun testShouldGrantPermissionToSendMtbFileToUser(url: String) {
- whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+ whenever { requestProcessor.processMtbFile(any<PatientRecord>()) }.thenReturn(true)
mockMvc
.post(url) {
@@ -339,14 +343,14 @@ class MtbFileRestControllerTest {
}
.andExpect { status { isAccepted() } }
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>())
}
}
companion object {
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("PID").build())
.episodesOfCare(
listOf(
diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt
index 9c94c4a..2e42174 100644
--- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt
+++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt
@@ -25,7 +25,12 @@ import dev.dnpm.etl.processor.config.*
import dev.dnpm.etl.processor.consent.ConsentEvaluator
import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.monitoring.RequestStatus
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.GenderCoding
+import dev.pcvolkmer.mv64e.model.MtbEpisodeOfCare
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.PeriodDate
+import dev.pcvolkmer.mv64e.model.Reference
import org.assertj.core.api.Assertions.assertThat
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matchers.containsString
@@ -121,13 +126,13 @@ class RestDipMtbFileSenderTest {
}
companion object {
- fun dnpmV2MtbFile(): Mtb {
- return Mtb().apply {
+ fun dnpmV2MtbFile(): PatientRecord {
+ return PatientRecord().apply {
this.patient =
Patient().apply {
this.id = "PID"
this.birthDate = Date.from(Instant.now())
- this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE }
+ this.gender = GenderCoding().apply { this.code = GenderCoding.CodeEnum.MALE }
}
this.episodesOfCare =
listOf(
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt
index 3e88ffd..d780642 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt
@@ -20,7 +20,6 @@
package dev.dnpm.etl.processor.config
-import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.consent.GicsConsentService
import dev.dnpm.etl.processor.consent.GicsGetBroadConsentService
import dev.dnpm.etl.processor.consent.IConsentService
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt b/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt
index a8b8fdb..1f867e8 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt
@@ -20,9 +20,9 @@
package dev.dnpm.etl.processor.consent
-import dev.pcvolkmer.mv64e.mtb.ConsentProvision
-import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose
-import dev.pcvolkmer.mv64e.mtb.Mtb
+import dev.pcvolkmer.mv64e.model.ConsentProvisionType
+import dev.pcvolkmer.mv64e.model.ModelProjectConsentPurpose
+import dev.pcvolkmer.mv64e.model.PatientRecord
import org.springframework.stereotype.Service
/** Evaluates consent using provided consent service and file based consent information */
@@ -30,8 +30,8 @@ import org.springframework.stereotype.Service
class ConsentEvaluator(
private val consentService: IConsentService,
) {
- fun check(mtbFile: Mtb): ConsentEvaluation {
- val ttpConsentStatus = consentService.getTtpBroadConsentStatus(mtbFile.patient.id)
+ fun check(mtbFile: PatientRecord): ConsentEvaluation {
+ val ttpConsentStatus = consentService.getTtpBroadConsentStatus(mtbFile.patient!!.id)
val consentGiven =
ttpConsentStatus == TtpConsentStatus.BROAD_CONSENT_GIVEN ||
ttpConsentStatus == TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT ||
@@ -39,7 +39,7 @@ class ConsentEvaluator(
ttpConsentStatus == TtpConsentStatus.UNKNOWN_CHECK_FILE &&
mtbFile.metadata?.modelProjectConsent?.provisions?.any {
it.purpose == ModelProjectConsentPurpose.SEQUENCING &&
- it.type == ConsentProvision.PERMIT
+ it.type == ConsentProvisionType.PERMIT
} == true
return ConsentEvaluation(ttpConsentStatus, consentGiven)
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt b/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt
index a126e07..51d093b 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt
@@ -26,7 +26,7 @@ import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.consent.ConsentEvaluator
import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.services.RequestProcessor
-import dev.pcvolkmer.mv64e.mtb.Mtb
+import dev.pcvolkmer.mv64e.model.PatientRecord
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.slf4j.LoggerFactory
import org.springframework.http.MediaType
@@ -72,8 +72,8 @@ class KafkaInputListener(
private fun handleDnpmV2Message(record: ConsumerRecord<String, String>) {
try {
- val mtbFile = jsonMapper.readValue(record.value(), Mtb::class.java)
- val patientId = PatientId(mtbFile.patient.id)
+ val mtbFile = jsonMapper.readValue(record.value(), PatientRecord::class.java)
+ val patientId = PatientId(mtbFile.patient!!.id)
val firstRequestIdHeader = record.headers().headers("requestId")?.firstOrNull()
val requestId =
if (null != firstRequestIdHeader) {
@@ -106,7 +106,7 @@ class KafkaInputListener(
}
} catch (e: Exception) {
logger.error("Error while processing MtbFile", e)
- requestProcessor.processMtbFile(Mtb.builder().build())
+ requestProcessor.processMtbFile(PatientRecord.builder().build())
}
}
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
index 47f0766..7ab846a 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
@@ -22,10 +22,9 @@ package dev.dnpm.etl.processor.input
import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.PatientId
-import dev.dnpm.etl.processor.consent.ConsentEvaluator
import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.services.RequestProcessor
-import dev.pcvolkmer.mv64e.mtb.Mtb
+import dev.pcvolkmer.mv64e.model.PatientRecord
import org.slf4j.LoggerFactory
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
@@ -52,7 +51,7 @@ class MtbFileRestController(
CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE,
],
)
- fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
+ fun mtbFile(@RequestBody mtbFile: PatientRecord): ResponseEntity<Unit> {
logger.debug("Accepted MTB File (DNPM V2) for processing")
if (requestProcessor.processMtbFile(mtbFile)) {
return ResponseEntity.accepted().build()
@@ -80,7 +79,7 @@ class MtbFileRestControllerAdvice(
@ExceptionHandler(HttpMessageNotReadableException::class)
fun handleMessageNotReadableException(e: Exception): ResponseEntity<Unit> {
logger.error("Error while processing MtbFile", e)
- requestProcessor.processMtbFile(Mtb.builder().build())
+ requestProcessor.processMtbFile(PatientRecord.builder().build())
return ResponseEntity.badRequest().build()
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt
index a1f5c3d..d62a503 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt
@@ -53,6 +53,8 @@ data class Request(
@LastModifiedBy var updatedBy: String? = null,
@Embedded.Nullable var report: Report? = null,
@Column("submission_accepted") var submissionAccepted: Boolean = false,
+ @Column("followup_count") var followupCount: Int = 0,
+ @Column("expected_followup_count") var expectedFollowupCount: Int = 0,
) {
constructor(
uuid: RequestId,
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt b/src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt
index 8c20b7c..e198952 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSender.kt
@@ -23,8 +23,8 @@ package dev.dnpm.etl.processor.output
import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.config.KafkaProperties
import dev.dnpm.etl.processor.monitoring.RequestStatus
-import dev.pcvolkmer.mv64e.mtb.Mtb
-import dev.pcvolkmer.mv64e.mtb.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.PatientRecord
import org.apache.kafka.clients.producer.ProducerRecord
import org.slf4j.LoggerFactory
import org.springframework.kafka.core.KafkaTemplate
@@ -75,7 +75,7 @@ class KafkaMtbFileSender(
}
override fun send(request: DeleteRequest): MtbFileSender.Response {
- val dummyMtbFile = Mtb.builder().metadata(MvhMetadata()).build()
+ val dummyMtbFile = PatientRecord.builder().metadata(MvhMetadata()).build()
return try {
return retryTemplate.execute<MtbFileSender.Response, Exception> {
@@ -114,7 +114,7 @@ class KafkaMtbFileSender(
private fun key(request: MtbRequest): String =
when (request) {
- is DnpmV2MtbFileRequest -> "{\"pid\": \"${request.content.patient.id}\"}"
+ is DnpmV2MtbFileRequest -> "{\"pid\": \"${request.content.patient?.id}\"}"
is DeleteRequest -> "{\"pid\": \"${request.patientId.value}\"}"
else ->
throw IllegalArgumentException("Unsupported request type: ${request::class.simpleName}")
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/output/MtbRequest.kt b/src/main/kotlin/dev/dnpm/etl/processor/output/MtbRequest.kt
index 75401e6..e066d54 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/output/MtbRequest.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/output/MtbRequest.kt
@@ -22,7 +22,7 @@ package dev.dnpm.etl.processor.output
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId
-import dev.pcvolkmer.mv64e.mtb.Mtb
+import dev.pcvolkmer.mv64e.model.PatientRecord
interface MtbRequest {
val requestId: RequestId
@@ -37,9 +37,9 @@ sealed interface MtbFileRequest<out T> : MtbRequest {
data class DnpmV2MtbFileRequest(
override val requestId: RequestId,
- override val content: Mtb,
-) : MtbFileRequest<Mtb> {
- override fun patientPseudonym(): PatientPseudonym = PatientPseudonym(content.patient.id)
+ override val content: PatientRecord,
+) : MtbFileRequest<PatientRecord> {
+ override fun patientPseudonym(): PatientPseudonym = PatientPseudonym(content.patient!!.id)
}
data class DeleteRequest(
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt
index 6615b35..21f3a7f 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt
@@ -21,9 +21,9 @@
package dev.dnpm.etl.processor.pseudonym
import dev.dnpm.etl.processor.PatientId
-import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent
-import dev.pcvolkmer.mv64e.mtb.Mtb
-import dev.pcvolkmer.mv64e.mtb.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsent
+import dev.pcvolkmer.mv64e.model.PatientRecord
import org.apache.commons.codec.digest.DigestUtils
/**
@@ -33,8 +33,8 @@ import org.apache.commons.codec.digest.DigestUtils
* @return The MTB file containing patient pseudonymes
* @since 0.11.0
*/
-infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
- val patientPseudonym = pseudonymizeService.patientPseudonym(PatientId(this.patient.id)).value
+infix fun PatientRecord.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
+ val patientPseudonym = pseudonymizeService.patientPseudonym(PatientId(this.patient!!.id)).value
this.episodesOfCare?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym }
this.carePlans?.filterNotNull()?.forEach { carePlan ->
@@ -57,7 +57,7 @@ infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
this.diagnoses?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym }
this.guidelineTherapies?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym }
this.guidelineProcedures?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym }
- this.patient.id = patientPseudonym
+ this.patient?.id = patientPseudonym
this.claims?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym }
this.claimResponses?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym }
this.familyMemberHistories?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym }
@@ -116,7 +116,7 @@ infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
* @return The MTB file containing rehashed content IDs
* @since 0.11.0
*/
-infix fun Mtb.anonymizeContentWith(pseudonymizeService: PseudonymizeService) {
+infix fun PatientRecord.anonymizeContentWith(pseudonymizeService: PseudonymizeService) {
val prefix = pseudonymizeService.prefix()
fun anonymize(id: String): String {
@@ -280,25 +280,25 @@ infix fun Mtb.anonymizeContentWith(pseudonymizeService: PseudonymizeService) {
}
}
-fun Mtb.ensureMetaDataIsInitialized() {
+fun PatientRecord.ensureMetaDataIsInitialized() {
// init metadata if necessary
if (this.metadata == null) {
val mvhMetadata = MvhMetadata.builder().build()
this.metadata = mvhMetadata
}
- if (this.metadata.researchConsents == null) {
- this.metadata.researchConsents = mutableListOf()
+ if (this.metadata?.researchConsents == null) {
+ this.metadata?.researchConsents = mutableListOf()
}
- if (this.metadata.modelProjectConsent == null) {
- this.metadata.modelProjectConsent = ModelProjectConsent()
- this.metadata.modelProjectConsent.provisions = mutableListOf()
- } else if (this.metadata.modelProjectConsent.provisions != null) {
+ if (this.metadata?.modelProjectConsent == null) {
+ this.metadata?.modelProjectConsent = MvhMetadataModelProjectConsent()
+ this.metadata?.modelProjectConsent?.provisions = mutableListOf()
+ } else if (this.metadata?.modelProjectConsent?.provisions != null) {
// make sure list can be changed
- this.metadata.modelProjectConsent.provisions =
- this.metadata.modelProjectConsent.provisions.toMutableList()
+ this.metadata?.modelProjectConsent?.provisions =
+ this.metadata?.modelProjectConsent?.provisions?.toMutableList()
}
}
-infix fun Mtb.addGenomDeTan(pseudonymizeService: PseudonymizeService) {
- this.metadata?.transferTan = pseudonymizeService.genomDeTan(PatientId(this.patient.id))
+infix fun PatientRecord.addGenomDeTan(pseudonymizeService: PseudonymizeService) {
+ this.metadata?.transferTAN = pseudonymizeService.genomDeTan(PatientId(this.patient!!.id))
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
index a95420a..47ab919 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
@@ -28,7 +28,13 @@ import dev.dnpm.etl.processor.consent.ConsentDomain
import dev.dnpm.etl.processor.consent.IConsentService
import dev.dnpm.etl.processor.consent.MtbFileConsentService
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.ConsentProvisionType
+import dev.pcvolkmer.mv64e.model.ModelProjectConsentPurpose
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsentProvisionsInner
+import dev.pcvolkmer.mv64e.model.MvhSubmissionType
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.ResearchConsent
import org.apache.commons.lang3.NotImplementedException
import org.hl7.fhir.r4.model.*
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent
@@ -65,7 +71,7 @@ class ConsentProcessor(
* @param mtbFile File v2 (will be enriched with consent data)
* @return true if consent is given
*/
- fun consentGatedCheckAndTryEmbedding(mtbFile: Mtb): Boolean {
+ fun consentGatedCheckAndTryEmbedding(mtbFile: PatientRecord): Boolean {
mtbFile.ensureMetaDataIsInitialized()
if (consentService is MtbFileConsentService) {
@@ -73,7 +79,7 @@ class ConsentProcessor(
return true
}
- val personIdentifierValue = mtbFile.patient.id
+ val personIdentifierValue = mtbFile.patient!!.id
val requestDate = Date.from(Instant.now(Clock.systemUTC()))
// 1. Broad consent Entry exists?
@@ -83,16 +89,6 @@ class ConsentProcessor(
// 2.1 -> yes -> send mtb file
// 2.2 -> no -> warn/info no consent given
- /*
- * broad consent
- */
- val broadConsent =
- consentService.getConsent(personIdentifierValue, requestDate, ConsentDomain.BROAD_CONSENT)
- val broadConsentHasBeenAsked = broadConsent.entry.isNotEmpty()
-
- // fast exit - if patient has not been asked, we can skip and exit
- if (!broadConsentHasBeenAsked) return false
-
// GenomDE_MV-Consent only if domain name available else fallback to file check
val genomDeSequencingStatus = if (null != gIcsConfigProperties.genomDeConsentDomainName) {
val genomeDeConsent =
@@ -111,6 +107,21 @@ class ConsentProcessor(
true
}
+ /*
+ * broad consent
+ */
+ if (null != mtbFile.metadata?.reasonResearchConsentMissing) {
+ // early return if there is a reason for missing broad consent
+ return false
+ }
+
+ val broadConsent =
+ consentService.getConsent(personIdentifierValue, requestDate, ConsentDomain.BROAD_CONSENT)
+ val broadConsentHasBeenAsked = broadConsent.entry.isNotEmpty()
+
+ // fast exit - if patient has not been asked, we can skip and exit
+ if (!broadConsentHasBeenAsked) return false
+
embedBroadConsentResources(mtbFile, broadConsent)
val broadConsentStatus =
@@ -129,7 +140,7 @@ class ConsentProcessor(
return false
}
- fun embedBroadConsentResources(mtbFile: Mtb, broadConsent: Bundle) {
+ fun embedBroadConsentResources(mtbFile: PatientRecord, broadConsent: Bundle) {
for (entry in broadConsent.entry) {
val resource = entry.resource
if (resource is Consent) {
@@ -137,12 +148,12 @@ class ConsentProcessor(
// we need another step to back to string, before we convert to object map
val asJsonString = fhirContext.newJsonParser().encodeResourceToString(resource)
try {
- val mapOfJson: MvhMetadata.ResearchConsent? =
+ val mapOfJson: ResearchConsent? =
jsonMapper.readValue(
asJsonString,
- MvhMetadata.ResearchConsent::class.java,
+ ResearchConsent::class.java,
)
- mtbFile.metadata.researchConsents.add(mapOfJson)
+ mtbFile.metadata?.researchConsents?.add(mapOfJson)
} catch (e: JsonProcessingException) {
throw RuntimeException(e)
}
@@ -150,7 +161,7 @@ class ConsentProcessor(
}
}
- fun addGenomeDbProvisions(mtbFile: Mtb, consentGnomeDe: Bundle) {
+ fun addGenomeDbProvisions(mtbFile: PatientRecord, consentGnomeDe: Bundle) {
for (entry in consentGnomeDe.entry) {
val resource = entry.resource
if (resource !is Consent) {
@@ -167,22 +178,22 @@ class ConsentProcessor(
val provisionCode = getProvisionCode(provisionComponent)
if (provisionCode != null) {
try {
- val modelProjectConsentPurpose = ModelProjectConsentPurpose.forValue(provisionCode)
+ val modelProjectConsentPurpose = ModelProjectConsentPurpose.fromValue(provisionCode)
if (ModelProjectConsentPurpose.SEQUENCING == modelProjectConsentPurpose) {
// CONVENTION: wrapping date is date of SEQUENCING consent
- mtbFile.metadata.modelProjectConsent.date = resource.dateTime
+ mtbFile.metadata?.modelProjectConsent?.date = resource.dateTime
}
val provision =
- Provision.builder()
- .type(ConsentProvision.valueOf(provisionComponent.type.name))
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.valueOf(provisionComponent.type.name))
.date(provisionComponent.period.start)
.purpose(modelProjectConsentPurpose)
.build()
- mtbFile.metadata.modelProjectConsent.provisions.add(provision)
- } catch (ioe: IOException) {
+ mtbFile.metadata?.modelProjectConsent?.provisions?.add(provision)
+ } catch (ioe: IllegalArgumentException) {
logger.error(
"Provision code '$provisionCode' is unknown and cannot be mapped.",
ioe.toString(),
@@ -190,8 +201,8 @@ class ConsentProcessor(
}
}
- if (mtbFile.metadata.modelProjectConsent.provisions.isNotEmpty()) {
- mtbFile.metadata.modelProjectConsent.version = gIcsConfigProperties.genomeDeConsentVersion
+ if (mtbFile.metadata?.modelProjectConsent?.provisions?.isNotEmpty() == true) {
+ mtbFile.metadata?.modelProjectConsent?.version = gIcsConfigProperties.genomeDeConsentVersion
}
}
}
@@ -207,15 +218,15 @@ class ConsentProcessor(
return provisionCode
}
- private fun setGenomDeSubmissionType(mtbFile: Mtb) {
+ private fun setGenomDeSubmissionType(mtbFile: PatientRecord) {
if (appConfigProperties.genomDeTestSubmission) {
- mtbFile.metadata.type = MvhSubmissionType.TEST
+ mtbFile.metadata?.type = MvhSubmissionType.TEST
logger.info("genomeDe submission mit TEST")
} else {
- mtbFile.metadata.type =
- when (mtbFile.metadata.type) {
+ mtbFile.metadata?.type =
+ when (mtbFile.metadata?.type) {
null -> MvhSubmissionType.INITIAL
- else -> mtbFile.metadata.type
+ else -> mtbFile.metadata?.type
}
}
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
index 4b7afff..ac9cfdc 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
@@ -32,8 +32,8 @@ import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
import dev.dnpm.etl.processor.pseudonym.addGenomDeTan
import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith
import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
-import dev.pcvolkmer.mv64e.mtb.Mtb
-import dev.pcvolkmer.mv64e.mtb.MvhSubmissionType
+import dev.pcvolkmer.mv64e.model.MvhSubmissionType
+import dev.pcvolkmer.mv64e.model.PatientRecord
import org.apache.commons.codec.binary.Base32
import org.apache.commons.codec.digest.DigestUtils
import org.slf4j.Logger
@@ -58,11 +58,11 @@ class RequestProcessor(
private var logger: Logger = LoggerFactory.getLogger("RequestProcessor")
- fun processMtbFile(mtbFile: Mtb): Boolean {
+ fun processMtbFile(mtbFile: PatientRecord): Boolean {
return processMtbFile(mtbFile, randomRequestId())
}
- fun processMtbFile(mtbFile: Mtb, requestId: RequestId): Boolean {
+ fun processMtbFile(mtbFile: PatientRecord, requestId: RequestId): Boolean {
val isConsentOk =
consentProcessor != null && consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) ||
consentProcessor == null
@@ -113,6 +113,10 @@ class RequestProcessor(
}
}
+ val maxFollowUpCount = this.requestService.allRequestsByPatientPseudonym(request.patientPseudonym())
+ .maxByOrNull { it.followupCount }
+ ?.followupCount ?: -1
+
if (
appConfigProperties.postInitialSubmissionBlock &&
hasSuccessfulInitialSubmission(request.patientPseudonym()) &&
@@ -120,14 +124,16 @@ class RequestProcessor(
) {
requestService.save(
Request(
- request.requestId,
- request.patientPseudonym(),
- emptyPatientId(),
- fingerprint(request),
- RequestType.MTB_FILE,
- submissionType,
- RequestStatus.BLOCKED_INITIAL,
- Tan(request.content.metadata?.transferTan.orEmpty())
+ uuid = request.requestId,
+ patientPseudonym = request.patientPseudonym(),
+ pid = emptyPatientId(),
+ fingerprint = fingerprint(request),
+ type = RequestType.MTB_FILE,
+ submissionType = submissionType,
+ status = RequestStatus.BLOCKED_INITIAL,
+ tan = Tan(request.content.metadata?.transferTAN.orEmpty()),
+ followupCount = maxFollowUpCount,
+ expectedFollowupCount = request.content.followUps?.size ?: 0,
)
)
// Exit - no further processing
@@ -157,14 +163,16 @@ class RequestProcessor(
requestService.save(
Request(
- request.requestId,
- request.patientPseudonym(),
- emptyPatientId(),
- fingerprint(request),
- RequestType.MTB_FILE,
- submissionType,
- RequestStatus.UNKNOWN,
- Tan(request.content.metadata?.transferTan.orEmpty()),
+ uuid = request.requestId,
+ patientPseudonym = request.patientPseudonym(),
+ pid = emptyPatientId(),
+ fingerprint = fingerprint(request),
+ type = RequestType.MTB_FILE,
+ submissionType = submissionType,
+ status = RequestStatus.UNKNOWN,
+ tan = Tan(request.content.metadata?.transferTAN.orEmpty()),
+ followupCount = maxFollowUpCount,
+ expectedFollowupCount = request.content.followUps?.size ?: 0,
)
)
@@ -207,7 +215,10 @@ class RequestProcessor(
?.sortedBy { it.date }
?.lastOrNull { it.date != null } ?: return false
- return lastSuccessfulSubmission.processedAt.isBefore( lastFollowUp.date.toInstant())
+ // Follow-up after last successful submission by date or ...
+ return lastSuccessfulSubmission.processedAt.isBefore(lastFollowUp.date.toInstant()) ||
+ // ... more follow-ups than on last successful submission
+ lastSuccessfulSubmission.followupCount < (request.content.followUps?.size ?: 0)
}
private fun hasSuccessfulInitialSubmission(patientPseudonym: PatientPseudonym): Boolean {
@@ -229,7 +240,7 @@ class RequestProcessor(
val patientPseudonym =
when (pseudonymizedMtbFileRequest) {
is DnpmV2MtbFileRequest ->
- PatientPseudonym(pseudonymizedMtbFileRequest.content.patient.id)
+ PatientPseudonym(pseudonymizedMtbFileRequest.content.patient!!.id)
}
val lastMtbFileRequestForPatient =
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt
index 947ec17..2818e00 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/ResponseProcessor.kt
@@ -53,10 +53,12 @@ class ResponseProcessor(
Report(
"Keine Probleme erkannt",
)
+ it.followupCount = it.expectedFollowupCount
}
RequestStatus.WARNING -> {
it.report = Report("Warnungen über mangelhafte Daten", event.body.orElse(""))
+ it.followupCount = it.expectedFollowupCount
}
RequestStatus.ERROR -> {
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt
index 755e6b3..d6318ce 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/TransformationService.kt
@@ -22,16 +22,16 @@ package dev.dnpm.etl.processor.services
import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.PathNotFoundException
-import dev.pcvolkmer.mv64e.mtb.Mtb
+import dev.pcvolkmer.mv64e.model.PatientRecord
import tools.jackson.databind.json.JsonMapper
class TransformationService(
private val jsonMapper: JsonMapper,
private val transformations: List<Transformation>,
) {
- fun transform(mtbFile: Mtb): Mtb {
+ fun transform(mtbFile: PatientRecord): PatientRecord {
val json = transform(jsonMapper.writeValueAsString(mtbFile))
- return jsonMapper.readValue(json, Mtb::class.java)
+ return jsonMapper.readValue(json, PatientRecord::class.java)
}
private fun transform(content: String): String {
diff --git a/src/main/resources/db/migration/mariadb/V0_16_1_1__FollowUpCount.sql b/src/main/resources/db/migration/mariadb/V0_16_1_1__FollowUpCount.sql
new file mode 100644
index 0000000..a0a1fda
--- /dev/null
+++ b/src/main/resources/db/migration/mariadb/V0_16_1_1__FollowUpCount.sql
@@ -0,0 +1,2 @@
+ALTER TABLE request ADD COLUMN followup_count int DEFAULT 0;
+UPDATE request SET followup_count = -1; \ No newline at end of file
diff --git a/src/main/resources/db/migration/mariadb/V0_16_3_1__ExpectedFollowUpCount.sql b/src/main/resources/db/migration/mariadb/V0_16_3_1__ExpectedFollowUpCount.sql
new file mode 100644
index 0000000..ded8172
--- /dev/null
+++ b/src/main/resources/db/migration/mariadb/V0_16_3_1__ExpectedFollowUpCount.sql
@@ -0,0 +1,2 @@
+ALTER TABLE request ADD COLUMN expected_followup_count int DEFAULT 0;
+UPDATE request SET expected_followup_count = -1; \ No newline at end of file
diff --git a/src/main/resources/db/migration/postgresql/V0_16_1_1__FollowUpCount.sql b/src/main/resources/db/migration/postgresql/V0_16_1_1__FollowUpCount.sql
new file mode 100644
index 0000000..a0a1fda
--- /dev/null
+++ b/src/main/resources/db/migration/postgresql/V0_16_1_1__FollowUpCount.sql
@@ -0,0 +1,2 @@
+ALTER TABLE request ADD COLUMN followup_count int DEFAULT 0;
+UPDATE request SET followup_count = -1; \ No newline at end of file
diff --git a/src/main/resources/db/migration/postgresql/V0_16_3_1__ExpectedFollowUpCount.sql b/src/main/resources/db/migration/postgresql/V0_16_3_1__ExpectedFollowUpCount.sql
new file mode 100644
index 0000000..ded8172
--- /dev/null
+++ b/src/main/resources/db/migration/postgresql/V0_16_3_1__ExpectedFollowUpCount.sql
@@ -0,0 +1,2 @@
+ALTER TABLE request ADD COLUMN expected_followup_count int DEFAULT 0;
+UPDATE request SET expected_followup_count = -1; \ No newline at end of file
diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html
index ae52ff5..1af6875 100644
--- a/src/main/resources/templates/fragments.html
+++ b/src/main/resources/templates/fragments.html
@@ -48,6 +48,7 @@
<div th:if="${request.type.value != 'delete'}">
<span th:if="${request.submissionType.value == 'initial'}"><span>📨 Übertragung vom </span><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></span>
<span th:if="${request.submissionType.value == 'addition'}"><span>🔄 Übertragung vom </span><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></span>
+ <span th:if="${request.submissionType.value == 'followup'}"><span>📅 Übertragung vom </span><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></span>
<span th:if="${request.submissionType.value == 'unknown'}"><span>❓ Übertragung vom </span><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></span>
</div>
<div th:if="${request.type.value == 'delete'}">
@@ -97,7 +98,7 @@
<div class="container">
<div>
<span>ETL-Processor Version:</span>
- <span class="footer_version">0.16.1</span>
+ <span class="footer_version">0.16.4</span>
</div>
<div>
<a href="https://github.com/pcvolkmer/mv64e-etl-processor">
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/config/Jackson3ConfigTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/config/Jackson3ConfigTest.kt
index b940537..d543f87 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/config/Jackson3ConfigTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/config/Jackson3ConfigTest.kt
@@ -21,8 +21,9 @@
package dev.dnpm.etl.processor.config
import com.fasterxml.jackson.databind.node.ObjectNode
-import dev.pcvolkmer.mv64e.mtb.Mtb
-import dev.pcvolkmer.mv64e.mtb.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.ResearchConsent
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -78,10 +79,10 @@ class Jackson3ConfigTest {
val inputConsentJson =
Objects.requireNonNull(this.javaClass.classLoader.getResourceAsStream("fake_broadConsent_mii_response_permit.json"))?.readAllBytes()?.decodeToString()
- val mtb = this.jacksonConfig.jsonMapper().readValue<Mtb>(inputMtbFileJson, Mtb::class.java)
+ val mtb = this.jacksonConfig.jsonMapper().readValue<PatientRecord>(inputMtbFileJson, PatientRecord::class.java)
// Still use Jackson2 ObjectMapper since MTB DTO requires Jackson2 ObjectNode
val consentJsonNode = Jackson2Config().objectMapper().readTree(inputConsentJson)
- mtb.metadata = MvhMetadata.builder().researchConsents(listOf(MvhMetadata.ResearchConsent.from(consentJsonNode as ObjectNode))).build()
+ mtb.metadata = MvhMetadata.builder().researchConsents(listOf(ResearchConsent.from(consentJsonNode as ObjectNode))).build()
val actual = this.jacksonConfig.jsonMapper().writeValueAsString(mtb)
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/config/JacksonConfigTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/config/JacksonConfigTest.kt
index e734c6f..4411e70 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/config/JacksonConfigTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/config/JacksonConfigTest.kt
@@ -21,8 +21,9 @@
package dev.dnpm.etl.processor.config
import com.fasterxml.jackson.databind.node.ObjectNode
-import dev.pcvolkmer.mv64e.mtb.Mtb
-import dev.pcvolkmer.mv64e.mtb.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.ResearchConsent
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -78,9 +79,9 @@ class JacksonConfigTest {
val inputConsentJson =
Objects.requireNonNull(this.javaClass.classLoader.getResourceAsStream("fake_broadConsent_mii_response_permit.json"))?.readAllBytes()?.decodeToString()
- val mtb = this.jacksonConfig.objectMapper().readValue<Mtb>(inputMtbFileJson, Mtb::class.java)
+ val mtb = this.jacksonConfig.objectMapper().readValue<PatientRecord>(inputMtbFileJson, PatientRecord::class.java)
val consentJsonNode = this.jacksonConfig.objectMapper().readTree(inputConsentJson)
- mtb.metadata = MvhMetadata.builder().researchConsents(listOf(MvhMetadata.ResearchConsent.from(consentJsonNode as ObjectNode))).build()
+ mtb.metadata = MvhMetadata.builder().researchConsents(listOf(ResearchConsent.from(consentJsonNode as ObjectNode))).build()
val actual = this.jacksonConfig.objectMapper().writeValueAsString(mtb)
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/consent/ConsentProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/consent/ConsentProcessorTest.kt
deleted file mode 100644
index 30e3823..0000000
--- a/src/test/kotlin/dev/dnpm/etl/processor/consent/ConsentProcessorTest.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * This file is part of ETL-Processor
- *
- * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken
- * Copyright (c) 2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published
- * by the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package dev.dnpm.etl.processor.consent
-
-import ca.uhn.fhir.context.FhirContext
-import dev.dnpm.etl.processor.config.AppConfigProperties
-import dev.dnpm.etl.processor.config.GIcsConfigProperties
-import dev.dnpm.etl.processor.services.ConsentProcessor
-import org.assertj.core.api.Assertions.assertThat
-import org.hl7.fhir.r4.model.Bundle
-import org.hl7.fhir.r4.model.Consent
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.extension.ExtendWith
-import org.junit.jupiter.params.ParameterizedTest
-import org.junit.jupiter.params.provider.CsvSource
-import org.mockito.Mock
-import org.mockito.junit.jupiter.MockitoExtension
-import tools.jackson.databind.json.JsonMapper
-import java.util.*
-
-@ExtendWith(MockitoExtension::class)
-class ConsentProcessorTest {
-
- lateinit var consentProcessor: ConsentProcessor
-
- val jsonMapper = JsonMapper()
- val fhirContext = FhirContext.forR4()
-
- @BeforeEach
- fun setup(@Mock consentService: IConsentService) {
- val appConfigProperties = AppConfigProperties()
- val gIcsConfigProperties = GIcsConfigProperties("http://localhost")
-
- this.consentProcessor =
- ConsentProcessor(
- appConfigProperties,
- gIcsConfigProperties,
- jsonMapper,
- fhirContext,
- consentService,
- )
- }
-
- @ParameterizedTest
- @CsvSource(value = ["permittedConsentBundle.json,permit", "deniedConsentBundle.json,deny"])
- fun checkGetProvisionTypeByPolicyCode(filename: String, expected: String) {
- val bundle =
- fhirContext
- .newJsonParser()
- .parseResource(this.javaClass.classLoader.getResourceAsStream(filename))
- assertThat(bundle).isInstanceOf(Bundle::class.java)
-
- val actual =
- consentProcessor.getProvisionTypeByPolicyCode(
- bundle as Bundle,
- Date(),
- ConsentDomain.BROAD_CONSENT,
- )
-
- assertThat(actual).isEqualTo(Consent.ConsentProvisionType.valueOf(expected.uppercase()))
- }
-}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt
index 284c552..3cf54fe 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt
@@ -21,7 +21,16 @@
package dev.dnpm.etl.processor.consent
import dev.dnpm.etl.processor.ArgProvider
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.ConsentProvisionType
+import dev.pcvolkmer.mv64e.model.GenderCoding
+import dev.pcvolkmer.mv64e.model.ModelProjectConsentPurpose
+import dev.pcvolkmer.mv64e.model.MtbEpisodeOfCare
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsent
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsentProvisionsInner
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.Reference
import java.time.Instant
import java.util.*
import org.assertj.core.api.Assertions.assertThat
@@ -54,7 +63,7 @@ class Dnpm21BasedConsentEvaluatorTest {
@ParameterizedTest
@ArgumentsSource(WithGicsMtbFileProvider::class)
fun test(
- mtbFile: Mtb,
+ mtbFile: PatientRecord,
ttpConsentStatus: TtpConsentStatus,
expectedConsentEvaluation: ConsentEvaluation,
) {
@@ -77,7 +86,7 @@ class Dnpm21BasedConsentEvaluatorTest {
@ParameterizedTest
@ArgumentsSource(MtbFileProvider::class)
- fun test(mtbFile: Mtb, expectedConsentEvaluation: ConsentEvaluation) {
+ fun test(mtbFile: PatientRecord, expectedConsentEvaluation: ConsentEvaluation) {
assertThat(consentEvaluator.check(mtbFile)).isEqualTo(expectedConsentEvaluation)
}
}
@@ -88,120 +97,120 @@ class Dnpm21BasedConsentEvaluatorTest {
ArgProvider(
// Has file ModelProjectConsent and broad consent => consent given
Arguments.of(
- buildMtb(ConsentProvision.PERMIT),
+ buildMtb(ConsentProvisionType.PERMIT),
TtpConsentStatus.BROAD_CONSENT_GIVEN,
ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_GIVEN, true),
),
// Has file ModelProjectConsent and broad consent missing => no consent given
Arguments.of(
- buildMtb(ConsentProvision.PERMIT),
+ buildMtb(ConsentProvisionType.PERMIT),
TtpConsentStatus.BROAD_CONSENT_MISSING,
ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_MISSING, false),
),
// Has file ModelProjectConsent and broad consent missing or rejected => no consent given
Arguments.of(
- buildMtb(ConsentProvision.PERMIT),
+ buildMtb(ConsentProvisionType.PERMIT),
TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED,
ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED, false),
),
// Has file ModelProjectConsent and MV consent => consent given
Arguments.of(
- buildMtb(ConsentProvision.PERMIT),
+ buildMtb(ConsentProvisionType.PERMIT),
TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT,
ConsentEvaluation(TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT, true),
),
// Has file ModelProjectConsent and MV consent rejected => no consent given
Arguments.of(
- buildMtb(ConsentProvision.PERMIT),
+ buildMtb(ConsentProvisionType.PERMIT),
TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED,
ConsentEvaluation(TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED, false),
),
// Has file ModelProjectConsent and MV consent missing => no consent given
Arguments.of(
- buildMtb(ConsentProvision.PERMIT),
+ buildMtb(ConsentProvisionType.PERMIT),
TtpConsentStatus.GENOM_DE_CONSENT_MISSING,
ConsentEvaluation(TtpConsentStatus.GENOM_DE_CONSENT_MISSING, false),
),
// Has file ModelProjectConsent and no broad consent result => consent given
Arguments.of(
- buildMtb(ConsentProvision.PERMIT),
+ buildMtb(ConsentProvisionType.PERMIT),
TtpConsentStatus.UNKNOWN_CHECK_FILE,
ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, true),
),
// Has file ModelProjectConsent and failed to ask => no consent given
Arguments.of(
- buildMtb(ConsentProvision.PERMIT),
+ buildMtb(ConsentProvisionType.PERMIT),
TtpConsentStatus.FAILED_TO_ASK,
ConsentEvaluation(TtpConsentStatus.FAILED_TO_ASK, false),
),
// File ModelProjectConsent rejected and broad consent => consent given
Arguments.of(
- buildMtb(ConsentProvision.DENY),
+ buildMtb(ConsentProvisionType.DENY),
TtpConsentStatus.BROAD_CONSENT_GIVEN,
ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_GIVEN, true),
),
// File ModelProjectConsent rejected and broad consent missing => no consent given
Arguments.of(
- buildMtb(ConsentProvision.DENY),
+ buildMtb(ConsentProvisionType.DENY),
TtpConsentStatus.BROAD_CONSENT_MISSING,
ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_MISSING, false),
),
// File ModelProjectConsent rejected and broad consent missing or rejected => no consent
// given
Arguments.of(
- buildMtb(ConsentProvision.DENY),
+ buildMtb(ConsentProvisionType.DENY),
TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED,
ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED, false),
),
// File ModelProjectConsent rejected and MV consent => consent given
Arguments.of(
- buildMtb(ConsentProvision.DENY),
+ buildMtb(ConsentProvisionType.DENY),
TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT,
ConsentEvaluation(TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT, true),
),
// File ModelProjectConsent rejected and MV consent rejected => no consent given
Arguments.of(
- buildMtb(ConsentProvision.DENY),
+ buildMtb(ConsentProvisionType.DENY),
TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED,
ConsentEvaluation(TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED, false),
),
// File ModelProjectConsent rejected and MV consent missing => no consent given
Arguments.of(
- buildMtb(ConsentProvision.DENY),
+ buildMtb(ConsentProvisionType.DENY),
TtpConsentStatus.GENOM_DE_CONSENT_MISSING,
ConsentEvaluation(TtpConsentStatus.GENOM_DE_CONSENT_MISSING, false),
),
// File ModelProjectConsent rejected and no broad consent result => no consent given
Arguments.of(
- buildMtb(ConsentProvision.DENY),
+ buildMtb(ConsentProvisionType.DENY),
TtpConsentStatus.UNKNOWN_CHECK_FILE,
ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false),
),
// File ModelProjectConsent rejected and failed to ask => no consent given
Arguments.of(
- buildMtb(ConsentProvision.DENY),
+ buildMtb(ConsentProvisionType.DENY),
TtpConsentStatus.FAILED_TO_ASK,
ConsentEvaluation(TtpConsentStatus.FAILED_TO_ASK, false),
),
) {
companion object {
- fun buildMtb(consentProvision: ConsentProvision): Mtb {
- return Mtb.builder()
+ fun buildMtb(consentProvision: ConsentProvisionType): PatientRecord {
+ return PatientRecord.builder()
.patient(
Patient.builder()
.id("TEST_12345678")
.birthDate(Date.from(Instant.parse("2000-08-08T12:34:56Z")))
- .gender(GenderCoding.builder().code(GenderCodingCode.MALE).build())
+ .gender(GenderCoding.builder().code(GenderCoding.CodeEnum.MALE).build())
.build()
)
.metadata(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
.date(Date())
.type(consentProvision)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
@@ -229,55 +238,55 @@ class Dnpm21BasedConsentEvaluatorTest {
ArgProvider(
// Has file consent => consent given
Arguments.of(
- buildMtb(ConsentProvision.PERMIT),
+ buildMtb(ConsentProvisionType.PERMIT),
ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, true),
),
// File consent rejected => no consent given
Arguments.of(
- buildMtb(ConsentProvision.DENY),
+ buildMtb(ConsentProvisionType.DENY),
ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false),
),
// policy REIDENTIFICATION has no effect on ConsentEvaluation
Arguments.of(
- buildMtb(ModelProjectConsentPurpose.REIDENTIFICATION, ConsentProvision.DENY),
+ buildMtb(ModelProjectConsentPurpose.REIDENTIFICATION, ConsentProvisionType.DENY),
ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false),
),
Arguments.of(
- buildMtb(ModelProjectConsentPurpose.REIDENTIFICATION, ConsentProvision.PERMIT),
+ buildMtb(ModelProjectConsentPurpose.REIDENTIFICATION, ConsentProvisionType.PERMIT),
ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false),
),
// policy CASE_IDENTIFICATION has no effect on ConsentEvaluation
Arguments.of(
- buildMtb(ModelProjectConsentPurpose.CASE_IDENTIFICATION, ConsentProvision.DENY),
+ buildMtb(ModelProjectConsentPurpose.CASE_IDENTIFICATION, ConsentProvisionType.DENY),
ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false),
),
Arguments.of(
- buildMtb(ModelProjectConsentPurpose.CASE_IDENTIFICATION, ConsentProvision.PERMIT),
+ buildMtb(ModelProjectConsentPurpose.CASE_IDENTIFICATION, ConsentProvisionType.PERMIT),
ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false),
),
) {
companion object {
- fun buildMtb(consentProvision: ConsentProvision): Mtb {
+ fun buildMtb(consentProvision: ConsentProvisionType): PatientRecord {
return buildMtb(ModelProjectConsentPurpose.SEQUENCING, consentProvision)
}
- fun buildMtb(policy: ModelProjectConsentPurpose, consentProvision: ConsentProvision): Mtb {
- return Mtb.builder()
+ fun buildMtb(policy: ModelProjectConsentPurpose, consentProvision: ConsentProvisionType): PatientRecord {
+ return PatientRecord.builder()
.patient(
Patient.builder()
.id("TEST_12345678")
.birthDate(Date.from(Instant.parse("2000-08-08T12:34:56Z")))
- .gender(GenderCoding.builder().code(GenderCodingCode.MALE).build())
+ .gender(GenderCoding.builder().code(GenderCoding.CodeEnum.MALE).build())
.build()
)
.metadata(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
.date(Date())
.type(consentProvision)
.purpose(policy)
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt
index eeb6288..9a5a2ee 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt
@@ -25,7 +25,13 @@ import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.consent.ConsentEvaluator
import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.services.RequestProcessor
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.ConsentProvisionType
+import dev.pcvolkmer.mv64e.model.ModelProjectConsentPurpose
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsent
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsentProvisionsInner
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.header.internals.RecordHeader
import org.apache.kafka.common.header.internals.RecordHeaders
@@ -69,16 +75,16 @@ class KafkaInputListenerTest {
@Test
fun shouldProcessMtbFileRequest() {
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("DUMMY_12345678").build())
.metadata(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
- .type(ConsentProvision.PERMIT)
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.PERMIT)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.build()
)
@@ -93,18 +99,18 @@ class KafkaInputListenerTest {
ConsumerRecord("testtopic", 0, 0, "", this.jsonMapper.writeValueAsString(mtbFile))
)
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>())
}
@Test
fun shouldProcessRequestEvenIfNoConsentInformation() {
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("DUMMY_12345678").build())
.metadata(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf()
)
@@ -117,22 +123,22 @@ class KafkaInputListenerTest {
kafkaInputListener.onMessage(
ConsumerRecord("testtopic", 0, 0, "", this.jsonMapper.writeValueAsString(mtbFile))
)
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>())
}
@Test
fun shouldProcessMtbFileRequestWithExistingRequestId() {
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("DUMMY_12345678").build())
.metadata(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
- .type(ConsentProvision.PERMIT)
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.PERMIT)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.build()
)
@@ -161,22 +167,22 @@ class KafkaInputListenerTest {
)
)
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>(), anyValueClass())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>(), anyValueClass())
}
@Test
fun shouldProcessRequestWithoutConsentGiven() {
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("DUMMY_12345678").build())
.metadata(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
- .type(ConsentProvision.DENY)
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.DENY)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.build()
)
@@ -204,22 +210,22 @@ class KafkaInputListenerTest {
Optional.empty(),
)
)
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>(), anyValueClass())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>(), anyValueClass())
}
@Test
fun shouldProcessDnpmV2Request() {
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("DUMMY_12345678").build())
.metadata(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
- .type(ConsentProvision.DENY)
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.DENY)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.build()
)
@@ -255,22 +261,22 @@ class KafkaInputListenerTest {
Optional.empty(),
)
)
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>(), anyValueClass())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>(), anyValueClass())
}
@Test
fun shouldProcessDnpmV2DeleteRequest() {
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("DUMMY_12345678").build())
.metadata(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
- .type(ConsentProvision.DENY)
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.DENY)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.build()
)
@@ -323,7 +329,7 @@ class KafkaInputListenerTest {
ConsumerRecord("testtopic", 0, 0, "", content)
)
- val result = verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ val result = verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>())
assertThat(result).isFalse()
}
}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
index 9ee4b2b..75d69dc 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
@@ -27,7 +27,17 @@ import dev.dnpm.etl.processor.consent.ConsentEvaluator
import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.input.Dnpm21MtbFile.Companion.buildMtb
import dev.dnpm.etl.processor.services.RequestProcessor
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.ConsentProvisionType
+import dev.pcvolkmer.mv64e.model.GenderCoding
+import dev.pcvolkmer.mv64e.model.ModelProjectConsentPurpose
+import dev.pcvolkmer.mv64e.model.MtbEpisodeOfCare
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsent
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsentProvisionsInner
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.Reference
+import org.flywaydb.core.internal.configuration.resolvers.ProvisionerMode
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@@ -78,7 +88,7 @@ class MtbFileRestControllerTest {
.readAllBytes()
.toString(Charsets.UTF_8)
- whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+ whenever { requestProcessor.processMtbFile(any<PatientRecord>()) }.thenReturn(true)
mockMvc
.post("/mtb") {
@@ -87,13 +97,13 @@ class MtbFileRestControllerTest {
}
.andExpect { status { isAccepted() } }
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ verify(requestProcessor, times(1)).processMtbFile(any<PatientRecord>())
}
@ParameterizedTest
@ArgumentsSource(Dnpm21MtbFile::class)
- fun shouldProcessPostRequest(mtb: Mtb) {
- whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+ fun shouldProcessPostRequest(mtb: PatientRecord) {
+ whenever { requestProcessor.processMtbFile(any<PatientRecord>()) }.thenReturn(true)
mockMvc
.post("/mtbfile") {
@@ -132,12 +142,12 @@ class MtbFileRestControllerTest {
buildMtb(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
.date(Date())
- .type(ConsentProvision.PERMIT)
+ .type(ConsentProvisionType.PERMIT)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.build()
)
@@ -147,7 +157,7 @@ class MtbFileRestControllerTest {
.build()
)
- whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+ whenever { requestProcessor.processMtbFile(any<PatientRecord>()) }.thenReturn(true)
mockMvc
.post(url) {
@@ -200,7 +210,7 @@ class Dnpm21MtbFile :
Arguments.of(
buildMtb(
MvhMetadata.builder()
- .modelProjectConsent(ModelProjectConsent.builder().build())
+ .modelProjectConsent(MvhMetadataModelProjectConsent.builder().build())
.build()
)
),
@@ -209,12 +219,12 @@ class Dnpm21MtbFile :
buildMtb(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
.date(Date())
- .type(ConsentProvision.PERMIT)
+ .type(ConsentProvisionType.PERMIT)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.build()
)
@@ -229,12 +239,12 @@ class Dnpm21MtbFile :
buildMtb(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
.date(Date())
- .type(ConsentProvision.PERMIT)
+ .type(ConsentProvisionType.PERMIT)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.build()
)
@@ -247,13 +257,13 @@ class Dnpm21MtbFile :
) {
companion object {
- fun buildMtb(metadata: MvhMetadata?): Mtb {
- return Mtb.builder()
+ fun buildMtb(metadata: MvhMetadata?): PatientRecord {
+ return PatientRecord.builder()
.patient(
Patient.builder()
.id("TEST_12345678")
.birthDate(Date.from(Instant.parse("2000-08-08T12:34:56Z")))
- .gender(GenderCoding.builder().code(GenderCodingCode.MALE).build())
+ .gender(GenderCoding.builder().code(GenderCoding.CodeEnum.MALE).build())
.build()
)
.metadata(metadata)
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt
index ee840a8..9384aae 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt
@@ -25,7 +25,11 @@ import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.config.KafkaProperties
import dev.dnpm.etl.processor.monitoring.RequestStatus
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.GenderCoding
+import dev.pcvolkmer.mv64e.model.MtbEpisodeOfCare
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.PeriodDate
+import dev.pcvolkmer.mv64e.model.Reference
import org.apache.kafka.clients.producer.ProducerRecord
import org.apache.kafka.clients.producer.RecordMetadata
import org.apache.kafka.common.TopicPartition
@@ -252,13 +256,13 @@ class KafkaMtbFileSenderTest {
val TEST_REQUEST_ID = RequestId("TestId")
val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID")
- fun dnpmV2MtbFile(): Mtb {
- return Mtb().apply {
+ fun dnpmV2MtbFile(): PatientRecord {
+ return PatientRecord().apply {
this.patient =
- dev.pcvolkmer.mv64e.mtb.Patient().apply {
+ dev.pcvolkmer.mv64e.model.Patient().apply {
this.id = "PID"
this.birthDate = Date.from(Instant.now())
- this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE }
+ this.gender = GenderCoding().apply { this.code = GenderCoding.CodeEnum.MALE }
}
this.episodesOfCare =
listOf(
@@ -271,7 +275,7 @@ class KafkaMtbFileSenderTest {
}
}
- fun dnmpV2kafkaRecordData(requestId: RequestId): Mtb {
+ fun dnmpV2kafkaRecordData(requestId: RequestId): PatientRecord {
return DnpmV2MtbFileRequest(requestId, dnpmV2MtbFile()).content
}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt
index d95ced0..3a0b3f0 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt
@@ -28,7 +28,12 @@ import dev.dnpm.etl.processor.config.AppConfiguration
import dev.dnpm.etl.processor.config.RestTargetProperties
import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.monitoring.RequestStatus
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.GenderCoding
+import dev.pcvolkmer.mv64e.model.MtbEpisodeOfCare
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.PeriodDate
+import dev.pcvolkmer.mv64e.model.Reference
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
@@ -194,13 +199,13 @@ class RestDipMtbFileSenderTest {
val TEST_REQUEST_ID = RequestId("TestId")
val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID")
- fun dnpmV2MtbFile(): Mtb {
- return Mtb().apply {
+ fun dnpmV2MtbFile(): PatientRecord {
+ return PatientRecord().apply {
this.patient =
Patient().apply {
this.id = "PID"
this.birthDate = Date.from(Instant.now())
- this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE }
+ this.gender = GenderCoding().apply { this.code = GenderCoding.CodeEnum.MALE }
}
this.episodesOfCare =
listOf(
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt
index abc17c6..93c9106 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt
@@ -27,7 +27,17 @@ import dev.dnpm.etl.processor.config.JacksonConfig
import dev.dnpm.etl.processor.consent.MtbFileConsentService
import dev.dnpm.etl.processor.services.ConsentProcessor
import dev.dnpm.etl.processor.services.ConsentProcessorTest
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.GenderCoding
+import dev.pcvolkmer.mv64e.model.Msi
+import dev.pcvolkmer.mv64e.model.MtbDiagnosis
+import dev.pcvolkmer.mv64e.model.MtbEpisodeOfCare
+import dev.pcvolkmer.mv64e.model.MtbSystemicTherapy
+import dev.pcvolkmer.mv64e.model.OncoProcedure
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.PeriodDate
+import dev.pcvolkmer.mv64e.model.Reference
+import dev.pcvolkmer.mv64e.model.TumorSpecimen
import org.assertj.core.api.Assertions.assertThat
import org.hl7.fhir.r4.model.Bundle
import org.junit.jupiter.api.Nested
@@ -56,12 +66,12 @@ class ExtensionsTest {
val FAKE_MTB_FILE_PATH = "mv64e-mtb-fake-patient.json"
val CLEAN_PATIENT_ID = "644bae7a-56f6-4ee8-b02f-c532e65af5b1"
- private fun fakeMtbFile(): Mtb {
+ private fun fakeMtbFile(): PatientRecord {
val mtbFile = ClassPathResource(FAKE_MTB_FILE_PATH).inputStream
- return getJsonMapper().readValue(mtbFile, Mtb::class.java)
+ return getJsonMapper().readValue(mtbFile, PatientRecord::class.java)
}
- private fun Mtb.serialized(): String {
+ private fun PatientRecord.serialized(): String {
return getJsonMapper().writeValueAsString(this)
}
@@ -80,11 +90,11 @@ class ExtensionsTest {
mtbFile.pseudonymizeWith(pseudonymizeService)
- assertThat(mtbFile.patient.id).isEqualTo("PSEUDO-ID")
+ assertThat(mtbFile.patient?.id).isEqualTo("PSEUDO-ID")
assertThat(mtbFile.serialized()).doesNotContain(CLEAN_PATIENT_ID)
}
- private fun addConsentData(mtbFile: Mtb) {
+ private fun addConsentData(mtbFile: PatientRecord) {
val gIcsConfigProperties = GIcsConfigProperties("", "", "")
val appConfigProperties = AppConfigProperties(emptyList())
@@ -115,12 +125,12 @@ class ExtensionsTest {
doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix()
val mtbFile =
- Mtb().apply {
+ PatientRecord().apply {
this.patient =
Patient().apply {
this.id = "PID"
this.birthDate = Date.from(Instant.now())
- this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE }
+ this.gender = GenderCoding().apply { this.code = GenderCoding.CodeEnum.MALE }
}
this.episodesOfCare =
listOf(
@@ -136,7 +146,7 @@ class ExtensionsTest {
mtbFile.anonymizeContentWith(pseudonymizeService)
assertThat(mtbFile.episodesOfCare).hasSize(1)
- assertThat(mtbFile.episodesOfCare.map { it.id }).isNotNull
+ assertThat(mtbFile.episodesOfCare?.map { it.id }).isNotNull
}
@Test
@@ -153,20 +163,20 @@ class ExtensionsTest {
val mtbFile = fakeMtbFile()
/** replace hex values with random long, so our test does not match false positives */
- mtbFile.ngsReports.forEach { report ->
- report.results.simpleVariants.forEach { simpleVariant ->
- simpleVariant.externalIds.forEach { extIdValue ->
+ mtbFile.ngsReports?.forEach { report ->
+ report.results.simpleVariants?.forEach { simpleVariant ->
+ simpleVariant.externalIds?.forEach { extIdValue ->
extIdValue.value = Math.random().toLong().toString()
}
}
}
- mtbFile.ngsReports.forEach { report ->
- report.results.rnaFusions.forEach { simpleVariant ->
- simpleVariant.externalIds.forEach { extIdValue ->
+ mtbFile.ngsReports?.forEach { report ->
+ report.results.rnaFusions?.forEach { simpleVariant ->
+ simpleVariant.externalIds?.forEach { extIdValue ->
extIdValue.value = Math.random().toLong().toString()
}
- simpleVariant.fusionPartner3Prime?.transcriptId?.value = Math.random().toLong().toString()
- simpleVariant.fusionPartner5Prime?.transcriptId?.value = Math.random().toLong().toString()
+ simpleVariant.fusionPartner3prime?.transcriptId?.value = Math.random().toLong().toString()
+ simpleVariant.fusionPartner5prime?.transcriptId?.value = Math.random().toLong().toString()
simpleVariant.externalIds?.forEach { it?.value = Math.random().toLong().toString() }
}
}
@@ -203,12 +213,12 @@ class ExtensionsTest {
doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix()
val mtbFile =
- Mtb().apply {
+ PatientRecord().apply {
this.patient =
Patient().apply {
this.id = "PID"
this.birthDate = Date.from(Instant.now())
- this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE }
+ this.gender = GenderCoding().apply { this.code = GenderCoding.CodeEnum.MALE }
}
this.diagnoses = listOf(MtbDiagnosis().apply { this.id = "Diagnosis-1" })
this.episodesOfCare =
@@ -244,12 +254,12 @@ class ExtensionsTest {
mtbFile.pseudonymizeWith(pseudonymizeService)
mtbFile.anonymizeContentWith(pseudonymizeService)
- assertThat(mtbFile.diagnoses.first().id)
- .isEqualTo(mtbFile.episodesOfCare.first().diagnoses.first().id)
- assertThat(mtbFile.diagnoses.first().id).isEqualTo(mtbFile.guidelineTherapies.first().reason.id)
- assertThat(mtbFile.diagnoses.first().id)
- .isEqualTo(mtbFile.guidelineProcedures.first().reason.id)
- assertThat(mtbFile.diagnoses.first().id).isEqualTo(mtbFile.specimens.first().diagnosis.id)
+ assertThat(mtbFile.diagnoses?.first()?.id)
+ .isEqualTo(mtbFile.episodesOfCare?.first()?.diagnoses?.first()?.id)
+ assertThat(mtbFile.diagnoses?.first()?.id).isEqualTo(mtbFile.guidelineTherapies?.first()?.reason?.id)
+ assertThat(mtbFile.diagnoses?.first()?.id)
+ .isEqualTo(mtbFile.guidelineProcedures?.first()?.reason?.id)
+ assertThat(mtbFile.diagnoses?.first()?.id).isEqualTo(mtbFile.specimens?.first()?.diagnosis?.id)
}
@Test
@@ -265,12 +275,12 @@ class ExtensionsTest {
doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix()
val mtbFile =
- Mtb().apply {
+ PatientRecord().apply {
this.patient =
Patient().apply {
this.id = "PID"
this.birthDate = Date.from(Instant.now())
- this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE }
+ this.gender = GenderCoding().apply { this.code = GenderCoding.CodeEnum.MALE }
}
this.msiFindings =
listOf(
@@ -286,10 +296,10 @@ class ExtensionsTest {
mtbFile.anonymizeContentWith(pseudonymizeService)
assertThat(mtbFile.msiFindings).isNotNull
- assertThat(mtbFile.msiFindings[1])
+ assertThat(mtbFile.msiFindings?.get(1))
.satisfiesAnyOf(
- { assertThat(it.id).isNull() },
- { assertThat(it.id).isEqualTo("TESTDOMAIN44e20a53bbbf9f3ae39626d05df7014dcd77d6098") },
+ { assertThat(it?.id).isNull() },
+ { assertThat(it?.id).isEqualTo("TESTDOMAIN44e20a53bbbf9f3ae39626d05df7014dcd77d6098") },
)
}
}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt
index 7c6d6c9..00487d0 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt
@@ -21,7 +21,11 @@
package dev.dnpm.etl.processor.pseudonym
import dev.dnpm.etl.processor.config.PseudonymizeConfigProperties
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.MtbEpisodeOfCare
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.PeriodDate
+import dev.pcvolkmer.mv64e.model.Reference
import java.time.Instant
import java.util.*
import org.assertj.core.api.Assertions.assertThat
@@ -37,7 +41,7 @@ import org.mockito.kotlin.whenever
class PseudonymizeServiceTest {
private val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.episodesOfCare(
listOf(
@@ -62,7 +66,7 @@ class PseudonymizeServiceTest {
mtbFile.pseudonymizeWith(pseudonymizeService)
- assertThat(mtbFile.patient.id).isEqualTo("123")
+ assertThat(mtbFile.patient?.id).isEqualTo("123")
}
@Test
@@ -80,7 +84,7 @@ class PseudonymizeServiceTest {
mtbFile.pseudonymizeWith(pseudonymizeService)
- assertThat(mtbFile.patient.id).isEqualTo("UNKNOWN_123")
+ assertThat(mtbFile.patient?.id).isEqualTo("UNKNOWN_123")
}
@Test
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt
index a85d555..6cb8c43 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt
@@ -27,9 +27,11 @@ import dev.dnpm.etl.processor.config.JacksonConfig
import dev.dnpm.etl.processor.consent.ConsentDomain
import dev.dnpm.etl.processor.consent.GicsConsentService
import dev.dnpm.etl.processor.consent.MtbFileConsentService
-import dev.pcvolkmer.mv64e.mtb.Mtb
-import dev.pcvolkmer.mv64e.mtb.MvhSubmissionType
-import dev.pcvolkmer.mv64e.mtb.Patient
+import dev.pcvolkmer.mv64e.model.BroadConsentReasonMissing
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhSubmissionType
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
import org.assertj.core.api.Assertions.assertThat
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.CodeableConcept
@@ -46,6 +48,8 @@ import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.eq
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.springframework.core.io.ClassPathResource
import tools.jackson.databind.json.JsonMapper
@@ -99,13 +103,13 @@ class ConsentProcessorTest {
.getConsent(any(), any(), eq(ConsentDomain.MODELLVORHABEN_64E))
val inputMtb =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("d611d429-5003-11f0-a144-661e92ac9503").build())
.build()
val checkResult = consentProcessor.consentGatedCheckAndTryEmbedding(inputMtb)
assertThat(checkResult).isTrue
- assertThat(inputMtb.metadata.researchConsents).isNotEmpty
+ assertThat(inputMtb.metadata?.researchConsents).isNotEmpty
}
@Test
@@ -122,7 +126,7 @@ class ConsentProcessorTest {
assertThat(consentProcessor.toString()).isNotNull
val inputMtb =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("d611d429-5003-11f0-a144-661e92ac9503").build())
.build()
val checkResult = consentProcessor.consentGatedCheckAndTryEmbedding(inputMtb)
@@ -256,15 +260,56 @@ class ConsentProcessorTest {
.getConsent(any(), any(), eq(ConsentDomain.MODELLVORHABEN_64E))
val inputMtb =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("d611d429-5003-11f0-a144-661e92ac9503").build())
.build()
val checkResult = fixture.consentGatedCheckAndTryEmbedding(inputMtb)
assertThat(checkResult).isNotNull
- if (isTestSubmission) assertThat(inputMtb.metadata.type).isEqualTo(MvhSubmissionType.TEST)
+ if (isTestSubmission) assertThat(inputMtb.metadata?.type).isEqualTo(MvhSubmissionType.TEST)
else {
- assertThat(inputMtb.metadata.type).isEqualTo(MvhSubmissionType.INITIAL)
+ assertThat(inputMtb.metadata?.type).isEqualTo(MvhSubmissionType.INITIAL)
}
}
+
+ @Test
+ fun doNotRequestBroadConsentIfReasonMissingIsGiven() {
+ doAnswer { Bundle() }
+ .whenever(gicsConsentService)
+ .getConsent(any(), any(), eq(ConsentDomain.MODELLVORHABEN_64E))
+
+ val inputMtb =
+ PatientRecord.builder()
+ .patient(Patient.builder().id("d611d429-5003-11f0-a144-661e92ac9503").build())
+ .metadata(MvhMetadata.builder().reasonResearchConsentMissing(BroadConsentReasonMissing.OTHER_PATIENT_REASON).build())
+ .build()
+ val checkResult = consentProcessor.consentGatedCheckAndTryEmbedding(inputMtb)
+
+ verify(gicsConsentService, times(1))
+ .getConsent(any(), any(), eq(ConsentDomain.MODELLVORHABEN_64E))
+ verify(gicsConsentService, times(0))
+ .getConsent(any(), any(), eq(ConsentDomain.BROAD_CONSENT))
+
+ assertThat(checkResult).isFalse
+ assertThat(inputMtb.metadata?.researchConsents).isEmpty()
+ }
+
+ @ParameterizedTest
+ @CsvSource(value = ["permittedConsentBundle.json,permit", "deniedConsentBundle.json,deny"])
+ fun checkGetProvisionTypeByPolicyCode(filename: String, expected: String) {
+ val bundle =
+ fhirContext
+ .newJsonParser()
+ .parseResource(this.javaClass.classLoader.getResourceAsStream(filename))
+ assertThat(bundle).isInstanceOf(Bundle::class.java)
+
+ val actual =
+ consentProcessor.getProvisionTypeByPolicyCode(
+ bundle as Bundle,
+ Date(),
+ ConsentDomain.BROAD_CONSENT,
+ )
+
+ assertThat(actual).isEqualTo(Consent.ConsentProvisionType.valueOf(expected.uppercase()))
+ }
}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt
index 6827a4f..17570d4 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt
@@ -20,7 +20,6 @@
package dev.dnpm.etl.processor.services
-import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.*
import dev.dnpm.etl.processor.config.AppConfigProperties
import dev.dnpm.etl.processor.consent.TtpConsentStatus
@@ -33,9 +32,19 @@ import dev.dnpm.etl.processor.output.DnpmV2MtbFileRequest
import dev.dnpm.etl.processor.output.MtbFileSender
import dev.dnpm.etl.processor.output.RestMtbFileSender
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.ConsentProvisionType
+import dev.pcvolkmer.mv64e.model.FollowUp
+import dev.pcvolkmer.mv64e.model.ModelProjectConsentPurpose
+import dev.pcvolkmer.mv64e.model.MtbEpisodeOfCare
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsent
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsentProvisionsInner
+import dev.pcvolkmer.mv64e.model.MvhSubmissionType
+import dev.pcvolkmer.mv64e.model.Patient
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.PeriodDate
+import dev.pcvolkmer.mv64e.model.Reference
import org.assertj.core.api.Assertions.assertThat
-import org.hl7.fhir.r4.model.Consent
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@@ -123,12 +132,12 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.episodesOfCare(
listOf(
@@ -180,12 +189,12 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.episodesOfCare(
listOf(
@@ -241,12 +250,12 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.episodesOfCare(
listOf(
@@ -306,21 +315,21 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.genomDeTan(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.metadata(
MvhMetadata.builder()
.modelProjectConsent(
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.provisions(
listOf(
- Provision.builder()
- .type(ConsentProvision.PERMIT)
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.PERMIT)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.build()
)
@@ -406,7 +415,7 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.genomDeTan(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
@@ -423,7 +432,7 @@ class RequestProcessorTest {
)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.metadata(MvhMetadata())
.episodesOfCare(
@@ -446,8 +455,8 @@ class RequestProcessorTest {
val requestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
verify(sender, times(1)).send(requestCaptor.capture())
assertThat(requestCaptor.firstValue).isNotNull
- assertThat(requestCaptor.firstValue.content.metadata.type).isEqualTo(MvhSubmissionType.ADDITION)
- assertThat(requestCaptor.firstValue.content.metadata.transferTan).isEqualTo("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2")
+ assertThat(requestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.ADDITION)
+ assertThat(requestCaptor.firstValue.content.metadata?.transferTAN).isEqualTo("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2")
val eventCaptor = argumentCaptor<ResponseEvent>()
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
@@ -503,10 +512,10 @@ class RequestProcessorTest {
doAnswer { it.arguments.first() }
.whenever(transformationService)
- .transform(any<Mtb>())
+ .transform(any<PatientRecord>())
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.metadata(MvhMetadata())
.episodesOfCare(
@@ -577,7 +586,7 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
.whenever(sender)
@@ -586,7 +595,7 @@ class RequestProcessorTest {
whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.episodesOfCare(
listOf(
@@ -630,7 +639,7 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.genomDeTan(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
@@ -647,7 +656,7 @@ class RequestProcessorTest {
)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.metadata(MvhMetadata())
.episodesOfCare(
@@ -772,7 +781,7 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
@@ -789,7 +798,7 @@ class RequestProcessorTest {
)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.episodesOfCare(
listOf(
@@ -812,6 +821,7 @@ class RequestProcessorTest {
verify(requestService, times(1)).save(requestCaptor.capture())
assertThat(requestCaptor.firstValue).isNotNull
assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.BLOCKED_INITIAL)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(0)
verify(applicationEventPublisher, times(0)).publishEvent(any())
verify(sender, times(0)).send(any<DnpmV2MtbFileRequest>())
@@ -872,7 +882,7 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
.whenever(sender)
@@ -893,7 +903,7 @@ class RequestProcessorTest {
)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
.episodesOfCare(
@@ -918,6 +928,7 @@ class RequestProcessorTest {
assertThat(requestCaptor.firstValue).isNotNull
assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.ADDITION)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(0)
val eventCaptor = argumentCaptor<ResponseEvent>()
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
@@ -925,7 +936,7 @@ class RequestProcessorTest {
val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
verify(sender, times(1)).send(sendRequestCaptor.capture())
- assertThat(sendRequestCaptor.firstValue.content.metadata.type).isEqualTo(MvhSubmissionType.ADDITION)
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.ADDITION)
}
@Test
@@ -983,7 +994,7 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
.whenever(sender)
@@ -1004,7 +1015,7 @@ class RequestProcessorTest {
)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
.episodesOfCare(
@@ -1029,6 +1040,7 @@ class RequestProcessorTest {
assertThat(requestCaptor.firstValue).isNotNull
assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.ADDITION)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(0)
val eventCaptor = argumentCaptor<ResponseEvent>()
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
@@ -1036,7 +1048,7 @@ class RequestProcessorTest {
val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
verify(sender, times(1)).send(sendRequestCaptor.capture())
- assertThat(sendRequestCaptor.firstValue.content.metadata.type).isEqualTo(MvhSubmissionType.ADDITION)
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.ADDITION)
}
@Test
@@ -1081,7 +1093,7 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
.whenever(sender)
@@ -1102,7 +1114,7 @@ class RequestProcessorTest {
)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
.episodesOfCare(
@@ -1127,6 +1139,7 @@ class RequestProcessorTest {
assertThat(requestCaptor.firstValue).isNotNull
assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.INITIAL)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(0)
val eventCaptor = argumentCaptor<ResponseEvent>()
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
@@ -1134,13 +1147,58 @@ class RequestProcessorTest {
val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
verify(sender, times(1)).send(sendRequestCaptor.capture())
- assertThat(sendRequestCaptor.firstValue.content.metadata.type).isEqualTo(MvhSubmissionType.INITIAL)
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.INITIAL)
+ }
+
+ }
+
+ @Nested
+ inner class FollowUpSubmission {
+
+ private lateinit var pseudonymizeService: PseudonymizeService
+ private lateinit var transformationService: TransformationService
+ private lateinit var sender: MtbFileSender
+ private lateinit var requestService: RequestService
+ private lateinit var applicationEventPublisher: ApplicationEventPublisher
+ private lateinit var appConfigProperties: AppConfigProperties
+ private lateinit var consentProcessor: ConsentProcessor
+ private lateinit var requestProcessor: RequestProcessor
+ private lateinit var jsonMapper: JsonMapper
+
+ @BeforeEach
+ fun setup(
+ @Mock pseudonymizeService: PseudonymizeService,
+ @Mock transformationService: TransformationService,
+ @Mock sender: RestMtbFileSender,
+ @Mock requestService: RequestService,
+ @Mock applicationEventPublisher: ApplicationEventPublisher,
+ @Mock consentProcessor: ConsentProcessor,
+ ) {
+ this.pseudonymizeService = pseudonymizeService
+ this.transformationService = transformationService
+ this.sender = sender
+ this.requestService = requestService
+ this.applicationEventPublisher = applicationEventPublisher
+ this.appConfigProperties = AppConfigProperties()
+ this.consentProcessor = consentProcessor
+ this.jsonMapper = JsonMapper()
+
+ requestProcessor =
+ RequestProcessor(
+ pseudonymizeService,
+ transformationService,
+ sender,
+ requestService,
+ jsonMapper,
+ applicationEventPublisher,
+ appConfigProperties,
+ consentProcessor,
+ )
}
@Test
fun testShouldSendFollowUpMtbFileIfUnacceptedErrorsAndAcceptedInitialFileWasSent() {
- // One failed attempt and one successful but not accepted
val lastRequests =
listOf(
Request(
@@ -1192,7 +1250,7 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
.whenever(sender)
@@ -1213,7 +1271,7 @@ class RequestProcessorTest {
)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
.episodesOfCare(
@@ -1247,6 +1305,8 @@ class RequestProcessorTest {
assertThat(requestCaptor.firstValue).isNotNull
assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.FOLLOWUP)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(0)
+ assertThat(requestCaptor.firstValue.expectedFollowupCount).isEqualTo(1)
val eventCaptor = argumentCaptor<ResponseEvent>()
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
@@ -1254,14 +1314,13 @@ class RequestProcessorTest {
val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
verify(sender, times(1)).send(sendRequestCaptor.capture())
- assertThat(sendRequestCaptor.firstValue.content.metadata.type).isEqualTo(MvhSubmissionType.FOLLOWUP)
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.FOLLOWUP)
}
@Test
fun testShouldSendFollowUpMtbFileAfterAdditionWasSent() {
- // One failed attempt and one successful but not accepted
val lastRequests =
listOf(
Request(
@@ -1313,7 +1372,7 @@ class RequestProcessorTest {
.whenever(pseudonymizeService)
.patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
.whenever(sender)
@@ -1334,7 +1393,7 @@ class RequestProcessorTest {
)
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.patient(Patient.builder().id("123").build())
.metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
.episodesOfCare(
@@ -1368,6 +1427,8 @@ class RequestProcessorTest {
assertThat(requestCaptor.firstValue).isNotNull
assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.FOLLOWUP)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(0)
+ assertThat(requestCaptor.firstValue.expectedFollowupCount).isEqualTo(1)
val eventCaptor = argumentCaptor<ResponseEvent>()
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
@@ -1375,133 +1436,709 @@ class RequestProcessorTest {
val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
verify(sender, times(1)).send(sendRequestCaptor.capture())
- assertThat(sendRequestCaptor.firstValue.content.metadata.type).isEqualTo(MvhSubmissionType.FOLLOWUP)
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.FOLLOWUP)
}
- }
- @Test
- fun testShouldSendAdditionMtbFileAfterFollowUpWasSent() {
+ @Test
+ fun testShouldSendFollowUpMtbFileAfterInitialWasSent() {
- // One failed attempt and one successful but not accepted
- val lastRequests =
- listOf(
- Request(
- 1L,
- randomRequestId(),
- PatientPseudonym("TEST_12345678901"),
- PatientId("P1"),
- Fingerprint("initial"),
- RequestType.MTB_FILE,
- SubmissionType.INITIAL,
- RequestStatus.ERROR,
- Tan.empty(),
- Instant.parse("2026-02-10T09:00:00Z"),
- submissionAccepted = false,
- ),
- Request(
- 2L,
- randomRequestId(),
- PatientPseudonym("TEST_12345678901"),
- PatientId("P1"),
- Fingerprint("initial"),
- RequestType.MTB_FILE,
- SubmissionType.INITIAL,
- RequestStatus.WARNING,
- Tan.empty(),
- Instant.parse("2026-02-11T09:00:00Z"),
- submissionAccepted = true,
- ),
- Request(
- 3L,
- randomRequestId(),
- PatientPseudonym("TEST_12345678901"),
- PatientId("P1"),
- Fingerprint("initial"),
- RequestType.MTB_FILE,
- SubmissionType.FOLLOWUP,
- RequestStatus.WARNING,
- Tan.empty(),
- Instant.parse("2026-05-20T09:00:00Z"),
- submissionAccepted = false,
+ val lastRequests =
+ listOf(
+ Request(
+ 1L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.ERROR,
+ Tan.empty(),
+ Instant.parse("2026-02-10T09:00:00Z"),
+ submissionAccepted = false,
+ ),
+ Request(
+ 2L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.WARNING,
+ Tan.empty(),
+ Instant.parse("2026-05-20T09:00:00Z"),
+ submissionAccepted = true,
+ )
)
- )
- doAnswer { lastRequests }
- .whenever(requestService)
- .allRequestsByPatientPseudonym(anyValueClass())
+ doAnswer { lastRequests }
+ .whenever(requestService)
+ .allRequestsByPatientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] as String }
- .whenever(pseudonymizeService)
- .patientPseudonym(anyValueClass())
+ doAnswer { it.arguments[0] as String }
+ .whenever(pseudonymizeService)
+ .patientPseudonym(anyValueClass())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
- doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
- .whenever(sender)
- .send(any<DnpmV2MtbFileRequest>())
+ doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
+ .whenever(sender)
+ .send(any<DnpmV2MtbFileRequest>())
- whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
+ whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
- requestProcessor =
- RequestProcessor(
- pseudonymizeService,
- transformationService,
- sender,
- requestService,
- jsonMapper,
- applicationEventPublisher,
- AppConfigProperties(postInitialSubmissionBlock = true),
- consentProcessor,
- )
+ requestProcessor =
+ RequestProcessor(
+ pseudonymizeService,
+ transformationService,
+ sender,
+ requestService,
+ jsonMapper,
+ applicationEventPublisher,
+ AppConfigProperties(postInitialSubmissionBlock = true),
+ consentProcessor,
+ )
- val mtbFile =
- Mtb.builder()
- .patient(Patient.builder().id("123").build())
- .metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
- .episodesOfCare(
- listOf(
- MtbEpisodeOfCare.builder()
- .id("1")
- .patient(Reference.builder().id("123").build())
- .period(
- PeriodDate.builder()
- .start(Date.from(Instant.parse("2026-01-20T00:00:00.00Z")))
- .build()
- )
- .build()
+ val mtbFile =
+ PatientRecord.builder()
+ .patient(Patient.builder().id("123").build())
+ .metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
+ .episodesOfCare(
+ listOf(
+ MtbEpisodeOfCare.builder()
+ .id("1")
+ .patient(Reference.builder().id("123").build())
+ .period(
+ PeriodDate.builder()
+ .start(Date.from(Instant.parse("2026-01-20T00:00:00.00Z")))
+ .build()
+ )
+ .build()
+ )
+ )
+ .followUps(
+ listOf(
+ FollowUp.builder()
+ .patient(Reference.builder().id("123").build())
+ .date(Date.from(Instant.parse("2026-05-21T00:00:00.00Z")))
+ .lastContactDate(Date.from(Instant.parse("2026-05-21T00:00:00.00Z")))
+ .build()
+ )
+ )
+ .build()
+
+ this.requestProcessor.processMtbFile(mtbFile)
+
+ val requestCaptor = argumentCaptor<Request>()
+ verify(requestService, times(1)).save(requestCaptor.capture())
+ assertThat(requestCaptor.firstValue).isNotNull
+ assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
+ assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.FOLLOWUP)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(0)
+ assertThat(requestCaptor.firstValue.expectedFollowupCount).isEqualTo(1)
+
+ val eventCaptor = argumentCaptor<ResponseEvent>()
+ verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
+ assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS)
+
+ val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
+ verify(sender, times(1)).send(sendRequestCaptor.capture())
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.FOLLOWUP)
+ }
+
+ @Test
+ fun testShouldSendAdditionMtbFileAfterFollowUpWasSent() {
+
+ val lastRequests =
+ listOf(
+ Request(
+ 1L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.ERROR,
+ Tan.empty(),
+ Instant.parse("2026-02-10T09:00:00Z"),
+ submissionAccepted = false,
+ followupCount = 0
+ ),
+ Request(
+ 2L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.WARNING,
+ Tan.empty(),
+ Instant.parse("2026-02-11T09:00:00Z"),
+ submissionAccepted = true,
+ followupCount = 0
+ ),
+ Request(
+ 3L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.FOLLOWUP,
+ RequestStatus.WARNING,
+ Tan.empty(),
+ Instant.parse("2026-05-20T09:00:00Z"),
+ submissionAccepted = false,
+ followupCount = 1
)
)
- .followUps(
- listOf(
- FollowUp.builder()
- .patient(Reference.builder().id("123").build())
- .date(Date.from(Instant.parse("2026-05-20T00:00:00.00Z")))
- .lastContactDate(Date.from(Instant.parse("2026-05-20T00:00:00.00Z")))
- .build()
+
+ doAnswer { lastRequests }
+ .whenever(requestService)
+ .allRequestsByPatientPseudonym(anyValueClass())
+
+ doAnswer { it.arguments[0] as String }
+ .whenever(pseudonymizeService)
+ .patientPseudonym(anyValueClass())
+
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
+
+ doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
+ .whenever(sender)
+ .send(any<DnpmV2MtbFileRequest>())
+
+ whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
+
+ requestProcessor =
+ RequestProcessor(
+ pseudonymizeService,
+ transformationService,
+ sender,
+ requestService,
+ jsonMapper,
+ applicationEventPublisher,
+ AppConfigProperties(postInitialSubmissionBlock = true),
+ consentProcessor,
+ )
+
+ val mtbFile =
+ PatientRecord.builder()
+ .patient(Patient.builder().id("123").build())
+ .metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
+ .episodesOfCare(
+ listOf(
+ MtbEpisodeOfCare.builder()
+ .id("1")
+ .patient(Reference.builder().id("123").build())
+ .period(
+ PeriodDate.builder()
+ .start(Date.from(Instant.parse("2026-01-20T00:00:00.00Z")))
+ .build()
+ )
+ .build()
+ )
+ )
+ .followUps(
+ listOf(
+ FollowUp.builder()
+ .patient(Reference.builder().id("123").build())
+ .date(Date.from(Instant.parse("2026-05-20T00:00:00.00Z")))
+ .lastContactDate(Date.from(Instant.parse("2026-05-20T00:00:00.00Z")))
+ .build()
+ )
+ )
+ .build()
+
+ this.requestProcessor.processMtbFile(mtbFile)
+
+ val requestCaptor = argumentCaptor<Request>()
+ verify(requestService, times(1)).save(requestCaptor.capture())
+ assertThat(requestCaptor.firstValue).isNotNull
+ assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
+ assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.ADDITION)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(1)
+ assertThat(requestCaptor.firstValue.expectedFollowupCount).isEqualTo(1)
+
+ val eventCaptor = argumentCaptor<ResponseEvent>()
+ verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
+ assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS)
+
+ val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
+ verify(sender, times(1)).send(sendRequestCaptor.capture())
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.ADDITION)
+ }
+
+ @Test
+ fun testShouldSendAdditionalFollowUpMtbFileAfterAdditionWasSentOnSameDay() {
+
+ val lastRequests =
+ listOf(
+ Request(
+ 1L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.ERROR,
+ Tan.empty(),
+ Instant.parse("2026-02-10T09:00:00Z"),
+ submissionAccepted = false,
+ followupCount = 0
+ ),
+ Request(
+ 2L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.WARNING,
+ Tan.empty(),
+ Instant.parse("2026-02-11T09:00:00Z"),
+ submissionAccepted = true,
+ followupCount = 0
+ ),
+ Request(
+ 3L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.FOLLOWUP,
+ RequestStatus.WARNING,
+ Tan.empty(),
+ Instant.parse("2026-05-20T09:00:00Z"),
+ submissionAccepted = false,
+ followupCount = 1
+ ),
+ Request(
+ 4L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.ADDITION,
+ RequestStatus.WARNING,
+ Tan.empty(),
+ Instant.parse("2026-06-11T05:41:00Z"),
+ submissionAccepted = false,
+ followupCount = 1
)
)
- .build()
- this.requestProcessor.processMtbFile(mtbFile)
+ doAnswer { lastRequests }
+ .whenever(requestService)
+ .allRequestsByPatientPseudonym(anyValueClass())
- val requestCaptor = argumentCaptor<Request>()
- verify(requestService, times(1)).save(requestCaptor.capture())
- assertThat(requestCaptor.firstValue).isNotNull
- assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
- assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.ADDITION)
+ doAnswer { it.arguments[0] as String }
+ .whenever(pseudonymizeService)
+ .patientPseudonym(anyValueClass())
- val eventCaptor = argumentCaptor<ResponseEvent>()
- verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
- assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS)
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
+
+ doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
+ .whenever(sender)
+ .send(any<DnpmV2MtbFileRequest>())
+
+ whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
+
+ requestProcessor =
+ RequestProcessor(
+ pseudonymizeService,
+ transformationService,
+ sender,
+ requestService,
+ jsonMapper,
+ applicationEventPublisher,
+ AppConfigProperties(postInitialSubmissionBlock = true),
+ consentProcessor,
+ )
+
+ val mtbFile =
+ PatientRecord.builder()
+ .patient(Patient.builder().id("123").build())
+ .metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
+ .episodesOfCare(
+ listOf(
+ MtbEpisodeOfCare.builder()
+ .id("1")
+ .patient(Reference.builder().id("123").build())
+ .period(
+ PeriodDate.builder()
+ .start(Date.from(Instant.parse("2026-01-20T00:00:00.00Z")))
+ .build()
+ )
+ .build()
+ )
+ )
+ .followUps(
+ listOf(
+ FollowUp.builder()
+ .patient(Reference.builder().id("123").build())
+ .date(Date.from(Instant.parse("2026-05-20T00:00:00.00Z")))
+ .lastContactDate(Date.from(Instant.parse("2026-05-20T00:00:00.00Z")))
+ .build(),
+ FollowUp.builder()
+ .patient(Reference.builder().id("123").build())
+ .date(Date.from(Instant.parse("2026-06-11T00:00:00.00Z")))
+ .lastContactDate(Date.from(Instant.parse("2026-06-11T00:00:00.00Z")))
+ .build()
+ )
+ )
+ .build()
+
+ this.requestProcessor.processMtbFile(mtbFile)
+
+ val requestCaptor = argumentCaptor<Request>()
+ verify(requestService, times(1)).save(requestCaptor.capture())
+ assertThat(requestCaptor.firstValue).isNotNull
+ assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
+ assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.FOLLOWUP)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(1)
+ assertThat(requestCaptor.firstValue.expectedFollowupCount).isEqualTo(2)
+
+ val eventCaptor = argumentCaptor<ResponseEvent>()
+ verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
+ assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS)
+
+ val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
+ verify(sender, times(1)).send(sendRequestCaptor.capture())
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.FOLLOWUP)
+ }
+
+ @Test
+ fun testShouldSendAdditionMtbFileWithoutNewHigherFollowUpsCount() {
+
+ val lastRequests =
+ listOf(
+ Request(
+ 1L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.ERROR,
+ Tan.empty(),
+ Instant.parse("2026-02-10T09:00:00Z"),
+ submissionAccepted = false,
+ followupCount = -1
+ ),
+ Request(
+ 2L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.WARNING,
+ Tan.empty(),
+ Instant.parse("2026-02-11T09:00:00Z"),
+ submissionAccepted = true,
+ followupCount = -1
+ ),
+ Request(
+ 3L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.FOLLOWUP,
+ RequestStatus.WARNING,
+ Tan.empty(),
+ Instant.parse("2026-06-11T05:41:00Z"),
+ submissionAccepted = false,
+ followupCount = 1
+ )
+ )
+
+ doAnswer { lastRequests }
+ .whenever(requestService)
+ .allRequestsByPatientPseudonym(anyValueClass())
+
+ doAnswer { it.arguments[0] as String }
+ .whenever(pseudonymizeService)
+ .patientPseudonym(anyValueClass())
+
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
+
+ doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
+ .whenever(sender)
+ .send(any<DnpmV2MtbFileRequest>())
+
+ whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
+
+ requestProcessor =
+ RequestProcessor(
+ pseudonymizeService,
+ transformationService,
+ sender,
+ requestService,
+ jsonMapper,
+ applicationEventPublisher,
+ AppConfigProperties(postInitialSubmissionBlock = true),
+ consentProcessor,
+ )
+
+ val mtbFile =
+ PatientRecord.builder()
+ .patient(Patient.builder().id("123").build())
+ .metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
+ .episodesOfCare(
+ listOf(
+ MtbEpisodeOfCare.builder()
+ .id("1")
+ .patient(Reference.builder().id("123").build())
+ .period(
+ PeriodDate.builder()
+ .start(Date.from(Instant.parse("2026-01-20T00:00:00.00Z")))
+ .build()
+ )
+ .build()
+ )
+ )
+ .followUps(
+ listOf(
+ FollowUp.builder()
+ .patient(Reference.builder().id("123").build())
+ .date(Date.from(Instant.parse("2026-06-11T00:00:00.00Z")))
+ .lastContactDate(Date.from(Instant.parse("2026-06-11T00:00:00.00Z")))
+ .build()
+ )
+ )
+ .build()
+
+ this.requestProcessor.processMtbFile(mtbFile)
+
+ val requestCaptor = argumentCaptor<Request>()
+ verify(requestService, times(1)).save(requestCaptor.capture())
+ assertThat(requestCaptor.firstValue).isNotNull
+ assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
+ assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.ADDITION)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(1)
+ assertThat(requestCaptor.firstValue.expectedFollowupCount).isEqualTo(1)
+
+ val eventCaptor = argumentCaptor<ResponseEvent>()
+ verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
+ assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS)
+
+ val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
+ verify(sender, times(1)).send(sendRequestCaptor.capture())
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.ADDITION)
+ }
+
+ @Test
+ fun testShouldSendInitialMtbFileContainingFollowUpsWithoutSuccessfulInitialSubmission() {
+
+ val lastRequests =
+ listOf(
+ Request(
+ 1L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.ERROR,
+ Tan.empty(),
+ Instant.parse("2026-02-10T09:00:00Z"),
+ submissionAccepted = false,
+ followupCount = 0
+ )
+ )
+
+ doAnswer { lastRequests }
+ .whenever(requestService)
+ .allRequestsByPatientPseudonym(anyValueClass())
+
+ doAnswer { it.arguments[0] as String }
+ .whenever(pseudonymizeService)
+ .patientPseudonym(anyValueClass())
+
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
+
+ doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
+ .whenever(sender)
+ .send(any<DnpmV2MtbFileRequest>())
+
+ whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
+
+ requestProcessor =
+ RequestProcessor(
+ pseudonymizeService,
+ transformationService,
+ sender,
+ requestService,
+ jsonMapper,
+ applicationEventPublisher,
+ AppConfigProperties(postInitialSubmissionBlock = true),
+ consentProcessor,
+ )
+
+ val mtbFile =
+ PatientRecord.builder()
+ .patient(Patient.builder().id("123").build())
+ .metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
+ .episodesOfCare(
+ listOf(
+ MtbEpisodeOfCare.builder()
+ .id("1")
+ .patient(Reference.builder().id("123").build())
+ .period(
+ PeriodDate.builder()
+ .start(Date.from(Instant.parse("2026-01-20T00:00:00.00Z")))
+ .build()
+ )
+ .build()
+ )
+ )
+ .followUps(
+ listOf(
+ FollowUp.builder()
+ .patient(Reference.builder().id("123").build())
+ .date(Date.from(Instant.parse("2026-06-11T00:00:00.00Z")))
+ .lastContactDate(Date.from(Instant.parse("2026-06-11T00:00:00.00Z")))
+ .build()
+ )
+ )
+ .build()
+
+ this.requestProcessor.processMtbFile(mtbFile)
+
+ val requestCaptor = argumentCaptor<Request>()
+ verify(requestService, times(1)).save(requestCaptor.capture())
+ assertThat(requestCaptor.firstValue).isNotNull
+ assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
+ assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.INITIAL)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(0)
+ assertThat(requestCaptor.firstValue.expectedFollowupCount).isEqualTo(1)
+
+ val eventCaptor = argumentCaptor<ResponseEvent>()
+ verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
+ assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS)
+
+ val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
+ verify(sender, times(1)).send(sendRequestCaptor.capture())
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.INITIAL)
+ }
- val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
- verify(sender, times(1)).send(sendRequestCaptor.capture())
- assertThat(sendRequestCaptor.firstValue.content.metadata.type).isEqualTo(MvhSubmissionType.ADDITION)
+ @Test
+ fun testShouldSendAdditionMtbFileWithoutFollowUpsAndInitialNegativeCount() {
+
+ val lastRequests =
+ listOf(
+ Request(
+ 1L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.ERROR,
+ Tan.empty(),
+ Instant.parse("2026-02-10T09:00:00Z"),
+ submissionAccepted = false,
+ followupCount = -1
+ ),
+ Request(
+ 2L,
+ randomRequestId(),
+ PatientPseudonym("TEST_12345678901"),
+ PatientId("P1"),
+ Fingerprint("initial"),
+ RequestType.MTB_FILE,
+ SubmissionType.INITIAL,
+ RequestStatus.WARNING,
+ Tan.empty(),
+ Instant.parse("2026-02-11T09:00:00Z"),
+ submissionAccepted = true,
+ followupCount = -1
+ ),
+ )
+
+ doAnswer { lastRequests }
+ .whenever(requestService)
+ .allRequestsByPatientPseudonym(anyValueClass())
+
+ doAnswer { it.arguments[0] as String }
+ .whenever(pseudonymizeService)
+ .patientPseudonym(anyValueClass())
+
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<PatientRecord>())
+
+ doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
+ .whenever(sender)
+ .send(any<DnpmV2MtbFileRequest>())
+
+ whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
+
+ requestProcessor =
+ RequestProcessor(
+ pseudonymizeService,
+ transformationService,
+ sender,
+ requestService,
+ jsonMapper,
+ applicationEventPublisher,
+ AppConfigProperties(postInitialSubmissionBlock = true),
+ consentProcessor,
+ )
+
+ val mtbFile =
+ PatientRecord.builder()
+ .patient(Patient.builder().id("123").build())
+ .metadata(MvhMetadata.builder().type(MvhSubmissionType.INITIAL).build())
+ .episodesOfCare(
+ listOf(
+ MtbEpisodeOfCare.builder()
+ .id("1")
+ .patient(Reference.builder().id("123").build())
+ .period(
+ PeriodDate.builder()
+ .start(Date.from(Instant.parse("2026-01-20T00:00:00.00Z")))
+ .build()
+ )
+ .build()
+ )
+ )
+ .build()
+
+ this.requestProcessor.processMtbFile(mtbFile)
+
+ val requestCaptor = argumentCaptor<Request>()
+ verify(requestService, times(1)).save(requestCaptor.capture())
+ assertThat(requestCaptor.firstValue).isNotNull
+ assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN)
+ assertThat(requestCaptor.firstValue.submissionType).isEqualTo(SubmissionType.ADDITION)
+ assertThat(requestCaptor.firstValue.followupCount).isEqualTo(-1)
+ assertThat(requestCaptor.firstValue.expectedFollowupCount).isEqualTo(0)
+
+ val eventCaptor = argumentCaptor<ResponseEvent>()
+ verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
+ assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS)
+
+ val sendRequestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
+ verify(sender, times(1)).send(sendRequestCaptor.capture())
+ assertThat(sendRequestCaptor.firstValue.content.metadata?.type).isEqualTo(MvhSubmissionType.ADDITION)
+ }
}
@Test
fun shouldCatchExceptionsWhenProcessingMtbFileAndSaveError() {
- val invalidMtbFile = Mtb.builder().build()
+ val invalidMtbFile = PatientRecord.builder().build()
val success = this.requestProcessor.processMtbFile(invalidMtbFile)
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt
index 441f3a9..c7a0af7 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt
@@ -32,6 +32,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
@@ -115,6 +116,27 @@ class ResponseProcessorTest {
assertThat(captor.firstValue.status).isEqualTo(requestStatus)
}
+ @ParameterizedTest
+ @MethodSource("requestFollowUpCount")
+ fun shoulUpdateFollowUpCountFromExpectedFollowUpCountOnSuccess(requestStatus: RequestStatus, followUpCount: Int, expectedFollowUpCount: Int) {
+ doAnswer {
+ testRequest.followupCount = followUpCount
+ testRequest.expectedFollowupCount = expectedFollowUpCount
+ Optional.of(testRequest)
+ }.whenever(requestService).findByUuid(anyValueClass())
+
+ val event =
+ ResponseEvent(RequestId("TestID1234"), Instant.parse("2023-09-09T00:00:00Z"), requestStatus)
+
+ this.responseProcessor.handleResponseEvent(event)
+
+ val captor = argumentCaptor<Request>()
+ verify(requestService, times(1)).save(captor.capture())
+ assertThat(captor.firstValue).isNotNull
+ assertThat(captor.firstValue.status).isEqualTo(requestStatus)
+ assertThat(captor.firstValue.followupCount).isEqualTo(expectedFollowUpCount)
+ }
+
companion object {
@JvmStatic
@@ -126,5 +148,16 @@ class ResponseProcessorTest {
RequestStatus.DUPLICATION,
)
}
+
+ @JvmStatic
+ fun requestFollowUpCount(): Set<Arguments> {
+ return setOf(
+ Arguments.of(RequestStatus.SUCCESS, 1, 2),
+ Arguments.of(RequestStatus.WARNING, 1, 2),
+ Arguments.of(RequestStatus.ERROR, 1, 1),
+ Arguments.of(RequestStatus.DUPLICATION, 1, 1),
+ Arguments.of(RequestStatus.NO_CONSENT, 1, 1),
+ )
+ }
}
}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt
index 66e45f8..cbca4f7 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt
@@ -24,7 +24,15 @@ import ca.uhn.fhir.context.FhirContext
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import dev.dnpm.etl.processor.config.JacksonConfig
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.model.Coding
+import dev.pcvolkmer.mv64e.model.ConsentProvisionType
+import dev.pcvolkmer.mv64e.model.ModelProjectConsentPurpose
+import dev.pcvolkmer.mv64e.model.MtbDiagnosis
+import dev.pcvolkmer.mv64e.model.MvhMetadata
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsent
+import dev.pcvolkmer.mv64e.model.MvhMetadataModelProjectConsentProvisionsInner
+import dev.pcvolkmer.mv64e.model.PatientRecord
+import dev.pcvolkmer.mv64e.model.ResearchConsent
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -49,7 +57,7 @@ class TransformationServiceTest {
@Test
fun shouldTransformMtbFile() {
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.diagnoses(
listOf(
MtbDiagnosis.builder()
@@ -63,13 +71,13 @@ class TransformationServiceTest {
val actual = this.service.transform(mtbFile)
assertThat(actual).isNotNull
- assertThat(actual.diagnoses[0].code.version).isEqualTo("2014")
+ assertThat(actual.diagnoses!![0].code.version).isEqualTo("2014")
}
@Test
fun shouldOnlyTransformGivenValues() {
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.diagnoses(
listOf(
MtbDiagnosis.builder()
@@ -87,16 +95,16 @@ class TransformationServiceTest {
val actual = this.service.transform(mtbFile)
assertThat(actual).isNotNull
- assertThat(actual.diagnoses[0].code.code).isEqualTo("F79.9")
- assertThat(actual.diagnoses[0].code.version).isEqualTo("2014")
- assertThat(actual.diagnoses[1].code.code).isEqualTo("F79.8")
- assertThat(actual.diagnoses[1].code.version).isEqualTo("2019")
+ assertThat(actual.diagnoses!![0].code.code).isEqualTo("F79.9")
+ assertThat(actual.diagnoses!![0].code.version).isEqualTo("2014")
+ assertThat(actual.diagnoses!![1].code.code).isEqualTo("F79.8")
+ assertThat(actual.diagnoses!![1].code.version).isEqualTo("2019")
}
@Test
fun shouldTransformConsentValues() {
val mtbFile =
- Mtb.builder()
+ PatientRecord.builder()
.diagnoses(
listOf(
MtbDiagnosis.builder()
@@ -114,35 +122,35 @@ class TransformationServiceTest {
val actual = this.service.transform(mtbFile)
assertThat(actual).isNotNull
- assertThat(actual.diagnoses[0].code.code).isEqualTo("F79.9")
- assertThat(actual.diagnoses[0].code.version).isEqualTo("2014")
- assertThat(actual.diagnoses[1].code.code).isEqualTo("F79.8")
- assertThat(actual.diagnoses[1].code.version).isEqualTo("2019")
+ assertThat(actual.diagnoses!![0].code.code).isEqualTo("F79.9")
+ assertThat(actual.diagnoses!![0].code.version).isEqualTo("2014")
+ assertThat(actual.diagnoses!![1].code.code).isEqualTo("F79.8")
+ assertThat(actual.diagnoses!![1].code.version).isEqualTo("2019")
}
@Test
fun shouldTransformConsent() {
- val mvhMetadata = MvhMetadata.builder().transferTan("transfertan12345").build()
+ val mvhMetadata = MvhMetadata.builder().transferTAN("transfertan12345").build()
assertThat(mvhMetadata).isNotNull
mvhMetadata.modelProjectConsent =
- ModelProjectConsent.builder()
+ MvhMetadataModelProjectConsent.builder()
.date(Date.from(Instant.parse("2025-08-15T00:00:00.00Z")))
.version("1")
.provisions(
listOf(
- Provision.builder()
- .type(ConsentProvision.PERMIT)
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.PERMIT)
.purpose(ModelProjectConsentPurpose.SEQUENCING)
.date(Date.from(Instant.parse("2025-08-15T00:00:00.00Z")))
.build(),
- Provision.builder()
- .type(ConsentProvision.PERMIT)
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.PERMIT)
.purpose(ModelProjectConsentPurpose.REIDENTIFICATION)
.date(Date.from(Instant.parse("2025-08-15T00:00:00.00Z")))
.build(),
- Provision.builder()
- .type(ConsentProvision.DENY)
+ MvhMetadataModelProjectConsentProvisionsInner.builder()
+ .type(ConsentProvisionType.DENY)
.purpose(ModelProjectConsentPurpose.CASE_IDENTIFICATION)
.date(Date.from(Instant.parse("2025-08-15T00:00:00.00Z")))
.build(),
@@ -158,11 +166,11 @@ class TransformationServiceTest {
)
mvhMetadata.researchConsents = mutableListOf()
- mvhMetadata.researchConsents.add(MvhMetadata.ResearchConsent.from(jsonNode))
+ mvhMetadata.researchConsents?.add(ResearchConsent.from(jsonNode))
- val mtbFile = Mtb.builder().metadata(mvhMetadata).build()
+ val mtbFile = PatientRecord.builder().metadata(mvhMetadata).build()
val transformed = service.transform(mtbFile)
- assertThat(transformed.metadata.modelProjectConsent.date).isNotNull
+ assertThat(transformed.metadata?.modelProjectConsent?.date).isNotNull
}
}