mirror of
https://github.com/pcvolkmer/onco-analytics-monitor.git
synced 2025-04-19 19:16:52 +00:00
feat: add topic fhir.pseudonymized.*
This commit is contained in:
parent
c4d5d609da
commit
c24587a5bd
@ -20,3 +20,4 @@ in [onco-analytics-on-fhir](https://github.com/bzkf/onco-analytics-on-fhir) verw
|
|||||||
|
|
||||||
* `onkostar.MELDUNG_EXPORT.*`: Alle eingehenden Meldungen aus Onkostar
|
* `onkostar.MELDUNG_EXPORT.*`: Alle eingehenden Meldungen aus Onkostar
|
||||||
* `fhir.obds.Condition.*`: Alle Conditions, die aus den oBDS-Meldungen erzeugt wurden.
|
* `fhir.obds.Condition.*`: Alle Conditions, die aus den oBDS-Meldungen erzeugt wurden.
|
||||||
|
* `fhir.pseudonymized.*`: Alle pseudonymisierten Conditions.
|
Binary file not shown.
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 54 KiB |
@ -27,6 +27,11 @@ class OncoAnalyticsMonitorApplication {
|
|||||||
return ConditionInMemoryRepository()
|
return ConditionInMemoryRepository()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun fhirPseudonymizedConditionRepository(): ConditionInMemoryRepository {
|
||||||
|
return ConditionInMemoryRepository()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,8 @@ class StatisticsController(
|
|||||||
private val obdsXmlConditionRepository: ConditionInMemoryRepository,
|
private val obdsXmlConditionRepository: ConditionInMemoryRepository,
|
||||||
@Qualifier("obdsFhirConditionRepository")
|
@Qualifier("obdsFhirConditionRepository")
|
||||||
private val obdsFhirConditionRepository: ConditionInMemoryRepository,
|
private val obdsFhirConditionRepository: ConditionInMemoryRepository,
|
||||||
|
@Qualifier("fhirPseudonymizedConditionRepository")
|
||||||
|
private val fhirPseudonymizedConditionRepository: ConditionInMemoryRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@GetMapping(path = ["obdsxml"])
|
@GetMapping(path = ["obdsxml"])
|
||||||
@ -27,4 +29,9 @@ class StatisticsController(
|
|||||||
return fetchStatistics("obdfhir", obdsFhirConditionRepository)
|
return fetchStatistics("obdfhir", obdsFhirConditionRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = ["fhirpseudonymized"])
|
||||||
|
fun fhirpseudonymizedStatistics(): Statistics {
|
||||||
|
return fetchStatistics("fhirpseudonymized", fhirPseudonymizedConditionRepository)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,11 +1,26 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #333;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0 auto;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
margin: 1em 0 6em 0;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: fit-content;
|
||||||
|
margin: 1em auto;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statistics table {
|
.statistics table {
|
||||||
@ -39,18 +54,15 @@ tr > td:last-of-type {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
|
||||||
width: fit-content;
|
|
||||||
height: 100vh;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step {
|
.step {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step:has(.item .statistics) {
|
||||||
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step > .item {
|
.step > .item {
|
||||||
@ -65,7 +77,7 @@ tr > td:last-of-type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.step .item:has(.statistics) {
|
.step .item:has(.statistics) {
|
||||||
width: 14em;
|
width: 11em;
|
||||||
|
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 2em 2em .4em .4em;
|
border-radius: 2em 2em .4em .4em;
|
||||||
@ -76,7 +88,7 @@ tr > td:last-of-type {
|
|||||||
.step:before, .step:after {
|
.step:before, .step:after {
|
||||||
content: "";
|
content: "";
|
||||||
margin: 2em 0;
|
margin: 2em 0;
|
||||||
width: 1em;
|
width: 8px;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background: black;
|
background: black;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -7,14 +7,14 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<main class="content">
|
<header>
|
||||||
|
|
||||||
<header>
|
|
||||||
<h1>onco-analytics-monitor</h1>
|
<h1>onco-analytics-monitor</h1>
|
||||||
<p>
|
<p>
|
||||||
Überwachung der einzelnen Kafka Topics und enthaltener Conditions - aufgeteilt nach ICD10-Gruppe.
|
Überwachung der einzelnen Kafka Topics und enthaltener Conditions - aufgeteilt nach ICD10-Gruppe.
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
<div class="step">
|
<div class="step">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
@ -29,7 +29,7 @@
|
|||||||
</div><div class="step">
|
</div><div class="step">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<img class="logo" th:src="@{/images/topic.png}" alt="topic"/>
|
<img class="logo" th:src="@{/images/topic.png}" alt="topic"/>
|
||||||
<div class="description">Kafka Topic oBDS XML</div>
|
<div class="description">Kafka Topic<br/>oBDS XML</div>
|
||||||
<div class="statistics">
|
<div class="statistics">
|
||||||
<table id="obdsxml"></table>
|
<table id="obdsxml"></table>
|
||||||
</div>
|
</div>
|
||||||
@ -42,12 +42,25 @@
|
|||||||
</div><div class="step">
|
</div><div class="step">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<img class="logo" th:src="@{/images/topic.png}" alt="topic"/>
|
<img class="logo" th:src="@{/images/topic.png}" alt="topic"/>
|
||||||
<div class="description">Kafka Topic oBDS FHIR</div>
|
<div class="description">Kafka Topic<br/>oBDS FHIR</div>
|
||||||
<div class="statistics">
|
<div class="statistics">
|
||||||
<table id="obdsfhir"></table>
|
<table id="obdsfhir"></table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div><div class="step">
|
||||||
|
<div class="item">
|
||||||
|
<img class="logo" th:src="@{/images/job.png}" alt="job"/>
|
||||||
|
<div class="description">fhir-pseudonymizer</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div><div class="step">
|
||||||
|
<div class="item">
|
||||||
|
<img class="logo" th:src="@{/images/topic.png}" alt="topic"/>
|
||||||
|
<div class="description">Kafka Topic<br/>FHIR pseudonymized</div>
|
||||||
|
<div class="statistics">
|
||||||
|
<table id="fhirpseudonymized"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@ -66,6 +79,7 @@
|
|||||||
|
|
||||||
fetch('/statistics/obdsxml').then(res => res.json()).then(data => updateData(data, 'obdsxml'));
|
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/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', () => {
|
window.addEventListener('load', () => {
|
||||||
const evtSource = new EventSource('/events');
|
const evtSource = new EventSource('/events');
|
||||||
@ -77,6 +91,10 @@
|
|||||||
evtSource.addEventListener('obdsfhir', (event) => {
|
evtSource.addEventListener('obdsfhir', (event) => {
|
||||||
updateData(JSON.parse(event.data), 'obdsfhir')
|
updateData(JSON.parse(event.data), 'obdsfhir')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
evtSource.addEventListener('fhirpseudonymized', (event) => {
|
||||||
|
updateData(JSON.parse(event.data), 'fhirpseudonymized')
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user