diff --git a/README.md b/README.md index a2e6c6b..c3384ba 100644 --- a/README.md +++ b/README.md @@ -19,4 +19,5 @@ Das `*` wird als Wildcard Match für ein beliebiges Jahr gemäß der Topics in [onco-analytics-on-fhir](https://github.com/bzkf/onco-analytics-on-fhir) verwendet. * `onkostar.MELDUNG_EXPORT.*`: Alle eingehenden Meldungen aus Onkostar -* `fhir.obds.Condition.*`: Alle Conditions, die aus den oBDS-Meldungen erzeugt wurden. \ No newline at end of file +* `fhir.obds.Condition.*`: Alle Conditions, die aus den oBDS-Meldungen erzeugt wurden. +* `fhir.pseudonymized.*`: Alle pseudonymisierten Conditions. \ No newline at end of file diff --git a/docs/screenshot.jpeg b/docs/screenshot.jpeg index 0506cb0..a69181a 100644 Binary files a/docs/screenshot.jpeg and b/docs/screenshot.jpeg differ diff --git a/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/OncoAnalyticsMonitorApplication.kt b/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/OncoAnalyticsMonitorApplication.kt index 2e8239e..48cef6a 100644 --- a/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/OncoAnalyticsMonitorApplication.kt +++ b/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/OncoAnalyticsMonitorApplication.kt @@ -27,6 +27,11 @@ class OncoAnalyticsMonitorApplication { return ConditionInMemoryRepository() } + @Bean + fun fhirPseudonymizedConditionRepository(): ConditionInMemoryRepository { + return ConditionInMemoryRepository() + } + } fun main(args: Array) { diff --git a/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/topiclisteners/FhirPseudonymizedTopicMonitor.kt b/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/topiclisteners/FhirPseudonymizedTopicMonitor.kt new file mode 100644 index 0000000..526ee8a --- /dev/null +++ b/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/topiclisteners/FhirPseudonymizedTopicMonitor.kt @@ -0,0 +1,55 @@ +package dev.pcvolkmer.oncoanalytics.monitor.topiclisteners + +import ca.uhn.fhir.context.FhirContext +import dev.pcvolkmer.oncoanalytics.monitor.StatisticsSink +import dev.pcvolkmer.oncoanalytics.monitor.conditions.Condition +import dev.pcvolkmer.oncoanalytics.monitor.conditions.ConditionId +import dev.pcvolkmer.oncoanalytics.monitor.conditions.ConditionInMemoryRepository +import dev.pcvolkmer.oncoanalytics.monitor.fetchStatistics +import org.hl7.fhir.r4.model.Bundle +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.kafka.annotation.KafkaListener +import org.springframework.kafka.support.KafkaHeaders +import org.springframework.messaging.handler.annotation.Header +import org.springframework.messaging.handler.annotation.Payload +import org.springframework.stereotype.Component + +@Component +class FhirPseudonymizedTopicMonitor( + @Qualifier("fhirPseudonymizedConditionRepository") + private val conditionRepository: ConditionInMemoryRepository, + statisticsEventProducer: StatisticsSink, +) : TopicMonitor(statisticsEventProducer) { + + @KafkaListener(topicPattern = "fhir.pseudonymized.*") + override fun handleTopicRecord( + @Header(KafkaHeaders.RECEIVED_TOPIC) topic: String, + @Header(KafkaHeaders.RECEIVED_TIMESTAMP) timestamp: Long, + @Header(KafkaHeaders.RECEIVED_KEY) key: String, + @Payload payload: String, + ) { + try { + val ctx = FhirContext.forR4() + val parser = ctx.newJsonParser() + + val bundle = parser.parseResource(Bundle::class.java, payload) + val firstEntry = bundle.entry.firstOrNull() ?: return + + if (firstEntry.resource.fhirType() == "Condition") { + val condition = firstEntry.resource as org.hl7.fhir.r4.model.Condition + val updated = conditionRepository.save( + Condition( + ConditionId(condition.id), + condition.code.coding.first { "http://fhir.de/CodeSystem/bfarm/icd-10-gm" == it.system }.code + ) + ) + + if (updated) { + sendUpdatedStatistics(fetchStatistics("fhirpseudonymized", conditionRepository)) + } + } + } catch (e: Exception) { + // Ignore + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/web/StatisticsController.kt b/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/web/StatisticsController.kt index 6d126ce..35b2964 100644 --- a/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/web/StatisticsController.kt +++ b/src/main/kotlin/dev/pcvolkmer/oncoanalytics/monitor/web/StatisticsController.kt @@ -15,6 +15,8 @@ class StatisticsController( private val obdsXmlConditionRepository: ConditionInMemoryRepository, @Qualifier("obdsFhirConditionRepository") private val obdsFhirConditionRepository: ConditionInMemoryRepository, + @Qualifier("fhirPseudonymizedConditionRepository") + private val fhirPseudonymizedConditionRepository: ConditionInMemoryRepository, ) { @GetMapping(path = ["obdsxml"]) @@ -27,4 +29,9 @@ class StatisticsController( return fetchStatistics("obdfhir", obdsFhirConditionRepository) } + @GetMapping(path = ["fhirpseudonymized"]) + fun fhirpseudonymizedStatistics(): Statistics { + return fetchStatistics("fhirpseudonymized", fhirPseudonymizedConditionRepository) + } + } \ No newline at end of file diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css index 33f19ab..0639173 100644 --- a/src/main/resources/static/css/style.css +++ b/src/main/resources/static/css/style.css @@ -1,11 +1,26 @@ +* { + box-sizing: border-box; + color: #333; + font-family: sans-serif; +} + body { - margin: 0; + margin: 0 auto; background-color: #f5f5f5; + + display: grid; + width: fit-content; } header { - margin: 1em 0 6em 0; text-align: left; + margin: 1em 0; +} + +main { + width: fit-content; + margin: 1em auto; + text-align: center; } .statistics table { @@ -39,18 +54,15 @@ tr > td:last-of-type { text-align: right; } -.content { - width: fit-content; - height: 100vh; - margin: 0 auto; - padding: 1em; - text-align: center; -} - .step { display: inline-block; width: fit-content; vertical-align: top; + margin-top: 1em; +} + +.step:has(.item .statistics) { + margin-top: 1px; } .step > .item { @@ -65,7 +77,7 @@ tr > td:last-of-type { } .step .item:has(.statistics) { - width: 14em; + width: 11em; background: white; border-radius: 2em 2em .4em .4em; @@ -76,7 +88,7 @@ tr > td:last-of-type { .step:before, .step:after { content: ""; margin: 2em 0; - width: 1em; + width: 8px; height: 2px; background: black; display: inline-block; diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 6e3407a..237aaa1 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -7,14 +7,14 @@ -
+
+

onco-analytics-monitor

+

+ Überwachung der einzelnen Kafka Topics und enthaltener Conditions - aufgeteilt nach ICD10-Gruppe. +

+
-
-

onco-analytics-monitor

-

- Überwachung der einzelnen Kafka Topics und enthaltener Conditions - aufgeteilt nach ICD10-Gruppe. -

-
+
@@ -29,7 +29,7 @@
-
Kafka Topic oBDS XML
+
Kafka Topic
oBDS XML
@@ -42,12 +42,25 @@
-
Kafka Topic oBDS FHIR
+
Kafka Topic
oBDS FHIR
+
+
+ +
fhir-pseudonymizer
+
+
+ +
Kafka Topic
FHIR pseudonymized
+
+
+
+
+
@@ -66,6 +79,7 @@ fetch('/statistics/obdsxml').then(res => res.json()).then(data => updateData(data, 'obdsxml')); fetch('/statistics/obdsfhir').then(res => res.json()).then(data => updateData(data, 'obdsfhir')); + fetch('/statistics/fhirpseudonymized').then(res => res.json()).then(data => updateData(data, 'fhirpseudonymized')); window.addEventListener('load', () => { const evtSource = new EventSource('/events'); @@ -77,6 +91,10 @@ evtSource.addEventListener('obdsfhir', (event) => { updateData(JSON.parse(event.data), 'obdsfhir') }); + + evtSource.addEventListener('fhirpseudonymized', (event) => { + updateData(JSON.parse(event.data), 'fhirpseudonymized') + }); });