mirror of
https://github.com/pcvolkmer/etl-processor.git
synced 2025-07-01 14:12:55 +00:00
Compare commits
2 Commits
63-check-c
...
master
Author | SHA1 | Date | |
---|---|---|---|
7543785116 | |||
858189aa59 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -39,5 +39,3 @@ out/
|
|||||||
.vscode/
|
.vscode/
|
||||||
/dev/gpas*
|
/dev/gpas*
|
||||||
/deploy/.env
|
/deploy/.env
|
||||||
/dev/gICS*
|
|
||||||
/dev/gPAS*
|
|
||||||
|
30
README.md
30
README.md
@ -87,36 +87,6 @@ Wurde die Verwendung von gPAS konfiguriert, so sind weitere Angaben zu konfiguri
|
|||||||
* `APP_PSEUDONYMIZE_GPAS_USERNAME`: gPas Basic-Auth Benutzername
|
* `APP_PSEUDONYMIZE_GPAS_USERNAME`: gPas Basic-Auth Benutzername
|
||||||
* `APP_PSEUDONYMIZE_GPAS_PASSWORD`: gPas Basic-Auth Passwort
|
* `APP_PSEUDONYMIZE_GPAS_PASSWORD`: gPas Basic-Auth Passwort
|
||||||
|
|
||||||
### Einwilligung gICS
|
|
||||||
Ab gIcs Version 2.13.0 kann per [REST-Schnittstelle](https://simplifier.net/guide/ttp-fhir-gateway-ig/ImplementationGuide-markdown-Einwilligungsmanagement-Operations-isConsented?version=current) der Einwilligungsstatus abgefragt werden.
|
|
||||||
Vor der MTB-Übertragung kann der zum Sendezeitpunkt verfügbarer Einwilligungsstatus über Endpunkt *isConsented* abgefragt werden.
|
|
||||||
|
|
||||||
Falls Anbindung an gICS aktiviert wurde, wird der Einwilligungsstatus der MTB Datei ignoriert.
|
|
||||||
Stattdessen werden vorhandene Einwilligungen abgefragt und in die MTB Datei eingebettet.
|
|
||||||
|
|
||||||
Es werden zwei Einwilligungsdomänen unterstützt, eine für Broad Consent und als zweites GenomDE Modelvorhaben §64e.
|
|
||||||
|
|
||||||
#### Hinweise
|
|
||||||
1. Die aktuelle Impl. nimmt an, dass die hinterlegten Domänen der Einwilligungen ausschließlich für die genannten Art von Einwilligungen genutzt werden. Es finde keine weitere Filterung statt. Wir fragen pro Domäne die Schnittstelle `CurrentPolicyStatesForPerson` - siehe auch [IG TTP-FHIR Gateway
|
|
||||||
](https://www.ths-greifswald.de/wp-content/uploads/tools/fhirgw/ig/2024-3-0/ImplementationGuide-markdown-Einwilligungsmanagement-Operations-currentPolicyStatesForPerson.html) ab.
|
|
||||||
2. Die Einwilligung wird für den Patienten-Identifier der MTB abgerufen und anschließend durch das DNPM Pseudonym ersetzt.
|
|
||||||
3. Abfragen von Einwilligungen über gesonderte Pseudonyme anstatt des MTB-Identifiers fehlt in der ersten Implementierung.
|
|
||||||
4. Bei Verarbeitung von MTB Version 1.x Inhalten ist eine positive Einwilligung für die
|
|
||||||
Weiterverarbeitung notwendig. Das Fehlen einer Einwilligung löst die Löschung des Patienten im Brückenkopf aus.
|
|
||||||
|
|
||||||
#### Konfiguration
|
|
||||||
* `APP_CONSENT_GICS_ENABLED`: Aktiviert oder deaktiviert `true` oder `false`, `false` wenn nicht gesetzt.
|
|
||||||
* `APP_CONSENT_GICS_CHECKGNOMEDE`: Aktiviert oder deaktiviert `true` oder `false`, `false` wenn nicht gesetzt. Versuche Einwilligungsdaten zu GENOM DE Modelvorhaben über gIcs abzurufen.
|
|
||||||
* `APP_CONSENT_GICS_CHECKBROADCONSENT`: Aktiviert oder deaktiviert `true` oder `false`, `false` wenn nicht gesetzt. Versuche Einwilligungsdaten zu Broad Consent über gIcs abzurufen.
|
|
||||||
* `APP_CONSENT_GICS_URI`: URI der gICS-Instanz (z.B. `http://localhost:8090/ttp-fhir/fhir/gics`)
|
|
||||||
* `APP_CONSENT_GICS_USERNAME`: gIcs Basic-Auth Benutzername
|
|
||||||
* `APP_CONSENT_GICS_PASSWORD`: gIcs Basic-Auth Passwort
|
|
||||||
* `APP_CONSENT_GICS_PERSONIDENTIFIERSYSTEM`: Derzeit wird nur die PID unterstützt. wenn leer wird `https://ths-greifswald.de/fhir/gics/identifiers/Patienten-ID` angenommen
|
|
||||||
* `APP_CONSENT_GICS_BROADCONSENTDOMAINNAME`: Domäne in der gIcs Broad Consent Einwilligungen verwaltet. Falls Wert leer, wird `MII` angenommen.
|
|
||||||
* `APP_CONSENT_GICS_GNOMDECONSENTDOMAINNAME`: Domäne in der gIcs GenomDE Modelvorhaben §64e Einwilligungen verwaltet. Falls Wert leer, wird `GenomDE_MV` angenommen.
|
|
||||||
* `APP_CONSENT_GICS_POLICYCODE`: Die entscheidende Objekt-ID der zu prüfenden Einwilligung-Regel. Falls leer wird `2.16.840.1.113883.3.1937.777.24.5.3.6` angenommen.
|
|
||||||
* `APP_CONSENT_GICS_POLICYSYSTEM`: Das System der Einwilligung-Regel der Objekt-IDs. Falls leer wird `urn:oid:2.16.840.1.113883.3.1937.777.24.5.3` angenommen.
|
|
||||||
|
|
||||||
### Anmeldung mit einem Passwort
|
### Anmeldung mit einem Passwort
|
||||||
|
|
||||||
Ein initialer Administrator-Account kann optional konfiguriert werden und sorgt dafür, dass bestimmte Bereiche nur nach
|
Ein initialer Administrator-Account kann optional konfiguriert werden und sorgt dafür, dass bestimmte Bereiche nur nach
|
||||||
|
@ -5,7 +5,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
war
|
war
|
||||||
id("org.springframework.boot") version "3.5.0"
|
id("org.springframework.boot") version "3.5.3"
|
||||||
id("io.spring.dependency-management") version "1.1.7"
|
id("io.spring.dependency-management") version "1.1.7"
|
||||||
kotlin("jvm") version "1.9.25"
|
kotlin("jvm") version "1.9.25"
|
||||||
kotlin("plugin.spring") version "1.9.25"
|
kotlin("plugin.spring") version "1.9.25"
|
||||||
|
@ -16,11 +16,6 @@ services:
|
|||||||
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: true
|
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: true
|
||||||
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093
|
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093
|
||||||
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
||||||
healthcheck:
|
|
||||||
test: kafka-topics --bootstrap-server kafka:9092 --list
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
## Use AKHQ as Kafka web frontend
|
## Use AKHQ as Kafka web frontend
|
||||||
akhq:
|
akhq:
|
||||||
|
@ -2,55 +2,31 @@ version: '3.7'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
zoo:
|
zoo1:
|
||||||
image: zookeeper:3.9.2
|
image: zookeeper:3.8.0
|
||||||
restart: unless-stopped
|
hostname: zoo1
|
||||||
ports:
|
ports:
|
||||||
- "2181:2181"
|
- "2181:2181"
|
||||||
environment:
|
environment:
|
||||||
ZOO_MY_ID: 1
|
ZOO_MY_ID: 1
|
||||||
ZOO_PORT: 2181
|
ZOO_PORT: 2181
|
||||||
ZOO_SERVERS: server.1=zoo:2888:3888;2181
|
ZOO_SERVERS: server.1=zoo1:2888:3888;2181
|
||||||
|
|
||||||
kafka:
|
kafka1:
|
||||||
image: confluentinc/cp-kafka:7.6.1
|
image: confluentinc/cp-kafka:7.2.1
|
||||||
|
hostname: kafka1
|
||||||
ports:
|
ports:
|
||||||
- "9092:9092"
|
- "9092:9092"
|
||||||
environment:
|
environment:
|
||||||
KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka:19092,LISTENER_DOCKER_EXTERNAL://172.17.0.1:9093,LISTENER_EXTERNAL://127.0.0.1:9092
|
KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka1:19092,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092
|
||||||
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT,LISTENER_EXTERNAL:PLAINTEXT
|
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
|
||||||
KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
|
KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
|
||||||
KAFKA_ZOOKEEPER_CONNECT: zoo:2181
|
KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181"
|
||||||
KAFKA_BROKER_ID: 1
|
KAFKA_BROKER_ID: 1
|
||||||
KAFKA_LOG4J_LOGGERS: kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO
|
KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
|
||||||
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
||||||
KAFKA_MESSAGE_MAX_BYTES: 5242880
|
|
||||||
KAFKA_REPLICA_FETCH_MAX_BYTES: 5242880
|
|
||||||
KAFKA_COMPRESSION_TYPE: gzip
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- zoo
|
- zoo1
|
||||||
healthcheck:
|
|
||||||
test: kafka-topics --bootstrap-server kafka:9092 --list
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
akhq:
|
|
||||||
image: tchiotludo/akhq:0.25.0
|
|
||||||
environment:
|
|
||||||
AKHQ_CONFIGURATION: |
|
|
||||||
akhq:
|
|
||||||
ui-options:
|
|
||||||
topic.show-all-consumer-groups: true
|
|
||||||
topic-data.sort: NEWEST
|
|
||||||
connections:
|
|
||||||
docker-kafka-server:
|
|
||||||
properties:
|
|
||||||
bootstrap.servers: "kafka:19092"
|
|
||||||
ports:
|
|
||||||
- "9000:8080"
|
|
||||||
depends_on:
|
|
||||||
- kafka
|
|
||||||
|
|
||||||
kafka-rest-proxy:
|
kafka-rest-proxy:
|
||||||
image: confluentinc/cp-kafka-rest:7.2.1
|
image: confluentinc/cp-kafka-rest:7.2.1
|
||||||
@ -64,8 +40,8 @@ services:
|
|||||||
KAFKA_REST_HOST_NAME: kafka-rest-proxy
|
KAFKA_REST_HOST_NAME: kafka-rest-proxy
|
||||||
KAFKA_REST_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092
|
KAFKA_REST_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092
|
||||||
depends_on:
|
depends_on:
|
||||||
- zoo
|
- zoo1
|
||||||
- kafka
|
- kafka1
|
||||||
|
|
||||||
kafka-connect:
|
kafka-connect:
|
||||||
image: confluentinc/cp-kafka-connect:7.2.1
|
image: confluentinc/cp-kafka-connect:7.2.1
|
||||||
@ -91,6 +67,24 @@ services:
|
|||||||
#volumes:
|
#volumes:
|
||||||
# - ./connectors:/etc/kafka-connect/jars/
|
# - ./connectors:/etc/kafka-connect/jars/
|
||||||
depends_on:
|
depends_on:
|
||||||
- zoo
|
- zoo1
|
||||||
- kafka
|
- kafka1
|
||||||
- kafka-rest-proxy
|
- kafka-rest-proxy
|
||||||
|
|
||||||
|
akhq:
|
||||||
|
image: tchiotludo/akhq:0.21.0
|
||||||
|
environment:
|
||||||
|
AKHQ_CONFIGURATION: |
|
||||||
|
akhq:
|
||||||
|
connections:
|
||||||
|
docker-kafka-server:
|
||||||
|
properties:
|
||||||
|
bootstrap.servers: "kafka1:19092"
|
||||||
|
connect:
|
||||||
|
- name: "kafka-connect"
|
||||||
|
url: "http://kafka-connect:8083"
|
||||||
|
ports:
|
||||||
|
- "8084:8080"
|
||||||
|
depends_on:
|
||||||
|
- kafka1
|
||||||
|
- kafka-connect
|
||||||
|
@ -23,9 +23,6 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||||||
import de.ukw.ccc.bwhc.dto.*
|
import de.ukw.ccc.bwhc.dto.*
|
||||||
import dev.dnpm.etl.processor.anyValueClass
|
import dev.dnpm.etl.processor.anyValueClass
|
||||||
import dev.dnpm.etl.processor.config.AppSecurityConfiguration
|
import dev.dnpm.etl.processor.config.AppSecurityConfiguration
|
||||||
import dev.dnpm.etl.processor.consent.ConsentCheckFileBased
|
|
||||||
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
|
||||||
import dev.dnpm.etl.processor.consent.ICheckConsent
|
|
||||||
import dev.dnpm.etl.processor.security.TokenRepository
|
import dev.dnpm.etl.processor.security.TokenRepository
|
||||||
import dev.dnpm.etl.processor.security.UserRoleRepository
|
import dev.dnpm.etl.processor.security.UserRoleRepository
|
||||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||||
@ -34,7 +31,10 @@ import org.junit.jupiter.api.Nested
|
|||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.mockito.junit.jupiter.MockitoExtension
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
import org.mockito.kotlin.*
|
import org.mockito.kotlin.any
|
||||||
|
import org.mockito.kotlin.never
|
||||||
|
import org.mockito.kotlin.times
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
@ -54,8 +54,7 @@ import org.springframework.test.web.servlet.post
|
|||||||
@ContextConfiguration(
|
@ContextConfiguration(
|
||||||
classes = [
|
classes = [
|
||||||
MtbFileRestController::class,
|
MtbFileRestController::class,
|
||||||
AppSecurityConfiguration::class,
|
AppSecurityConfiguration::class
|
||||||
ConsentCheckFileBased::class, ICheckConsent::class
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@MockitoBean(types = [TokenRepository::class, RequestProcessor::class])
|
@MockitoBean(types = [TokenRepository::class, RequestProcessor::class])
|
||||||
@ -64,8 +63,7 @@ import org.springframework.test.web.servlet.post
|
|||||||
"app.pseudonymize.generator=BUILDIN",
|
"app.pseudonymize.generator=BUILDIN",
|
||||||
"app.security.admin-user=admin",
|
"app.security.admin-user=admin",
|
||||||
"app.security.admin-password={noop}very-secret",
|
"app.security.admin-password={noop}very-secret",
|
||||||
"app.security.enable-tokens=true",
|
"app.security.enable-tokens=true"
|
||||||
"app.consent.gics.enabled=false"
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
class MtbFileRestControllerTest {
|
class MtbFileRestControllerTest {
|
||||||
@ -143,7 +141,7 @@ class MtbFileRestControllerTest {
|
|||||||
status { isAccepted() }
|
status { isAccepted() }
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(requestProcessor, times(1)).processDeletion(anyValueClass(), eq(TtpConsentStatus.UNKNOWN_CHECK_FILE))
|
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -154,7 +152,7 @@ class MtbFileRestControllerTest {
|
|||||||
status { isUnauthorized() }
|
status { isUnauthorized() }
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(requestProcessor, never()).processDeletion(anyValueClass(), any())
|
verify(requestProcessor, never()).processDeletion(anyValueClass())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@ -165,8 +163,7 @@ class MtbFileRestControllerTest {
|
|||||||
"app.security.admin-user=admin",
|
"app.security.admin-user=admin",
|
||||||
"app.security.admin-password={noop}very-secret",
|
"app.security.admin-password={noop}very-secret",
|
||||||
"app.security.enable-tokens=true",
|
"app.security.enable-tokens=true",
|
||||||
"app.security.enable-oidc=true",
|
"app.security.enable-oidc=true"
|
||||||
"app.consent.gics.enabled=false"
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
inner class WithOidcEnabled {
|
inner class WithOidcEnabled {
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package dev.dnpm.etl.processor.pseudonym
|
package dev.dnpm.etl.processor.pseudonym
|
||||||
|
|
||||||
import dev.dnpm.etl.processor.config.AppFhirConfig
|
|
||||||
import dev.dnpm.etl.processor.config.GPasConfigProperties
|
import dev.dnpm.etl.processor.config.GPasConfigProperties
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
@ -43,7 +42,6 @@ class GpasPseudonymGeneratorTest {
|
|||||||
private lateinit var mockRestServiceServer: MockRestServiceServer
|
private lateinit var mockRestServiceServer: MockRestServiceServer
|
||||||
private lateinit var generator: GpasPseudonymGenerator
|
private lateinit var generator: GpasPseudonymGenerator
|
||||||
private lateinit var restTemplate: RestTemplate
|
private lateinit var restTemplate: RestTemplate
|
||||||
private var appFhirConfig: AppFhirConfig = AppFhirConfig()
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
@ -57,8 +55,7 @@ class GpasPseudonymGeneratorTest {
|
|||||||
|
|
||||||
this.restTemplate = RestTemplate()
|
this.restTemplate = RestTemplate()
|
||||||
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
|
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
|
||||||
this.generator =
|
this.generator = GpasPseudonymGenerator(gPasConfigProperties, retryTemplate, restTemplate)
|
||||||
GpasPseudonymGenerator(gPasConfigProperties, retryTemplate, restTemplate, appFhirConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -67,13 +64,7 @@ class GpasPseudonymGeneratorTest {
|
|||||||
method(HttpMethod.POST)
|
method(HttpMethod.POST)
|
||||||
requestTo("https://localhost/ttp-fhir/fhir/gpas/\$pseudonymizeAllowCreate")
|
requestTo("https://localhost/ttp-fhir/fhir/gpas/\$pseudonymizeAllowCreate")
|
||||||
}.andRespond {
|
}.andRespond {
|
||||||
withStatus(HttpStatus.OK).body(
|
withStatus(HttpStatus.OK).body(getDummyResponseBody("1234", "test", "test1234ABCDEF567890"))
|
||||||
getDummyResponseBody(
|
|
||||||
"1234",
|
|
||||||
"test",
|
|
||||||
"test1234ABCDEF567890"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.createResponse(it)
|
.createResponse(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,10 +90,7 @@ class GpasPseudonymGeneratorTest {
|
|||||||
requestTo("https://localhost/ttp-fhir/fhir/gpas/\$pseudonymizeAllowCreate")
|
requestTo("https://localhost/ttp-fhir/fhir/gpas/\$pseudonymizeAllowCreate")
|
||||||
}.andRespond {
|
}.andRespond {
|
||||||
withStatus(HttpStatus.FOUND)
|
withStatus(HttpStatus.FOUND)
|
||||||
.header(
|
.header(HttpHeaders.LOCATION, "https://localhost/ttp-fhir/fhir/gpas/\$pseudonymizeAllowCreate")
|
||||||
HttpHeaders.LOCATION,
|
|
||||||
"https://localhost/ttp-fhir/fhir/gpas/\$pseudonymizeAllowCreate"
|
|
||||||
)
|
|
||||||
.createResponse(it)
|
.createResponse(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ package dev.dnpm.etl.processor.web
|
|||||||
import dev.dnpm.etl.processor.config.AppConfiguration
|
import dev.dnpm.etl.processor.config.AppConfiguration
|
||||||
import dev.dnpm.etl.processor.config.AppSecurityConfiguration
|
import dev.dnpm.etl.processor.config.AppSecurityConfiguration
|
||||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
|
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
|
||||||
import dev.dnpm.etl.processor.monitoring.GIcsConnectionCheckService
|
|
||||||
import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService
|
import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService
|
||||||
import dev.dnpm.etl.processor.monitoring.RestConnectionCheckService
|
import dev.dnpm.etl.processor.monitoring.RestConnectionCheckService
|
||||||
import dev.dnpm.etl.processor.output.MtbFileSender
|
import dev.dnpm.etl.processor.output.MtbFileSender
|
||||||
@ -90,8 +89,7 @@ abstract class MockSink : Sinks.Many<Boolean>
|
|||||||
RequestProcessor::class,
|
RequestProcessor::class,
|
||||||
TransformationService::class,
|
TransformationService::class,
|
||||||
GPasConnectionCheckService::class,
|
GPasConnectionCheckService::class,
|
||||||
RestConnectionCheckService::class,
|
RestConnectionCheckService::class
|
||||||
GIcsConnectionCheckService::class
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
class ConfigControllerTest {
|
class ConfigControllerTest {
|
||||||
@ -184,13 +182,7 @@ class ConfigControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testShouldNotSaveTokenWithExstingName() {
|
fun testShouldNotSaveTokenWithExstingName() {
|
||||||
whenever(tokenService.addToken(anyString())).thenReturn(
|
whenever(tokenService.addToken(anyString())).thenReturn(Result.failure(RuntimeException("Testfailure")))
|
||||||
Result.failure(
|
|
||||||
RuntimeException(
|
|
||||||
"Testfailure"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
mockMvc.post("/configs/tokens") {
|
mockMvc.post("/configs/tokens") {
|
||||||
with(user("admin").roles("ADMIN"))
|
with(user("admin").roles("ADMIN"))
|
||||||
@ -311,10 +303,7 @@ class ConfigControllerTest {
|
|||||||
|
|
||||||
val idCaptor = argumentCaptor<Long>()
|
val idCaptor = argumentCaptor<Long>()
|
||||||
val roleCaptor = argumentCaptor<Role>()
|
val roleCaptor = argumentCaptor<Role>()
|
||||||
verify(userRoleService, times(1)).updateUserRole(
|
verify(userRoleService, times(1)).updateUserRole(idCaptor.capture(), roleCaptor.capture())
|
||||||
idCaptor.capture(),
|
|
||||||
roleCaptor.capture()
|
|
||||||
)
|
|
||||||
|
|
||||||
assertThat(idCaptor.firstValue).isEqualTo(42)
|
assertThat(idCaptor.firstValue).isEqualTo(42)
|
||||||
assertThat(roleCaptor.firstValue).isEqualTo(Role.ADMIN)
|
assertThat(roleCaptor.firstValue).isEqualTo(Role.ADMIN)
|
||||||
@ -352,23 +341,20 @@ class ConfigControllerTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup(
|
fun setup(
|
||||||
applicationContext: WebApplicationContext
|
applicationContext: WebApplicationContext,
|
||||||
) {
|
) {
|
||||||
this.webClient = MockMvcWebTestClient
|
this.webClient = MockMvcWebTestClient
|
||||||
.bindToApplicationContext(applicationContext).build()
|
.bindToApplicationContext(applicationContext).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testShouldRequestGPasSSE() {
|
fun testShouldRequestSSE() {
|
||||||
val expectedEvent =
|
val expectedEvent = ConnectionCheckResult.GPasConnectionCheckResult(true, Instant.now(), Instant.now())
|
||||||
ConnectionCheckResult.GPasConnectionCheckResult(true, Instant.now(), Instant.now())
|
|
||||||
|
|
||||||
connectionCheckUpdateProducer.tryEmitNext(expectedEvent)
|
connectionCheckUpdateProducer.tryEmitNext(expectedEvent)
|
||||||
connectionCheckUpdateProducer.emitComplete { _, _ -> true }
|
connectionCheckUpdateProducer.emitComplete { _, _ -> true }
|
||||||
|
|
||||||
val result =
|
val result = webClient.get().uri("http://localhost/configs/events").accept(TEXT_EVENT_STREAM).exchange()
|
||||||
webClient.get().uri("http://localhost/configs/events").accept(TEXT_EVENT_STREAM)
|
|
||||||
.exchange()
|
|
||||||
.expectStatus().isOk()
|
.expectStatus().isOk()
|
||||||
.expectHeader().contentType(TEXT_EVENT_STREAM)
|
.expectHeader().contentType(TEXT_EVENT_STREAM)
|
||||||
.returnResult(ConnectionCheckResult.GPasConnectionCheckResult::class.java)
|
.returnResult(ConnectionCheckResult.GPasConnectionCheckResult::class.java)
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.consent;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class ConsentCheckFileBased implements ICheckConsent{
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ConsentCheckFileBased.class);
|
|
||||||
|
|
||||||
public ConsentCheckFileBased() {
|
|
||||||
log.info("ConsentCheckFileBased initialized...");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TtpConsentStatus getTtpConsentStatus(String personIdentifierValue) {
|
|
||||||
return TtpConsentStatus.UNKNOWN_CHECK_FILE;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.consent;
|
|
||||||
|
|
||||||
public enum ConsentDomain {
|
|
||||||
BroadConsent,
|
|
||||||
Modelvorhaben64e
|
|
||||||
}
|
|
@ -1,281 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.consent;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
|
||||||
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
|
||||||
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
|
|
||||||
import java.util.Date;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.r4.model.BooleanType;
|
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
|
||||||
import org.hl7.fhir.r4.model.Coding;
|
|
||||||
import org.hl7.fhir.r4.model.DateType;
|
|
||||||
import org.hl7.fhir.r4.model.Identifier;
|
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
|
||||||
import org.hl7.fhir.r4.model.Parameters;
|
|
||||||
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
|
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpEntity;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.retry.TerminatedRetryException;
|
|
||||||
import org.springframework.retry.support.RetryTemplate;
|
|
||||||
import org.springframework.web.client.RestClientException;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
|
||||||
|
|
||||||
|
|
||||||
public class GicsConsentService implements ICheckConsent {
|
|
||||||
|
|
||||||
private final Logger log = LoggerFactory.getLogger(GicsConsentService.class);
|
|
||||||
|
|
||||||
private final GIcsConfigProperties gIcsConfigProperties;
|
|
||||||
|
|
||||||
public static final String IS_CONSENTED_ENDPOINT = "/$isConsented";
|
|
||||||
public static final String IS_POLICY_STATES_FOR_PERSON_ENDPOINT = "/$currentPolicyStatesForPerson";
|
|
||||||
private final RetryTemplate retryTemplate;
|
|
||||||
private final RestTemplate restTemplate;
|
|
||||||
private final FhirContext fhirContext;
|
|
||||||
private final HttpHeaders httpHeader;
|
|
||||||
private String url;
|
|
||||||
|
|
||||||
public GicsConsentService(GIcsConfigProperties gIcsConfigProperties,
|
|
||||||
RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
|
|
||||||
this.gIcsConfigProperties = gIcsConfigProperties;
|
|
||||||
this.retryTemplate = retryTemplate;
|
|
||||||
this.restTemplate = restTemplate;
|
|
||||||
this.fhirContext = appFhirConfig.fhirContext();
|
|
||||||
httpHeader = buildHeader(gIcsConfigProperties.getUsername(),
|
|
||||||
gIcsConfigProperties.getPassword());
|
|
||||||
log.info("GicsConsentService initialized...");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getGicsUri(String endpoint) {
|
|
||||||
if (url == null) {
|
|
||||||
final String gIcsBaseUri = gIcsConfigProperties.getUri();
|
|
||||||
if (StringUtils.isBlank(gIcsBaseUri)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"gICS base URL is empty - should call gICS with false configuration.");
|
|
||||||
}
|
|
||||||
url = UriComponentsBuilder.fromUriString(gIcsBaseUri).path(IS_CONSENTED_ENDPOINT)
|
|
||||||
.toUriString();
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static HttpHeaders buildHeader(String gPasUserName, String gPasPassword) {
|
|
||||||
var headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_XML);
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(gPasUserName) || StringUtils.isBlank(gPasPassword)) {
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
headers.setBasicAuth(gPasUserName, gPasPassword);
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static Parameters getIsConsentedRequestParam(GIcsConfigProperties configProperties,
|
|
||||||
String personIdentifierValue) {
|
|
||||||
var result = new Parameters();
|
|
||||||
result.addParameter(new ParametersParameterComponent().setName("personIdentifier").setValue(
|
|
||||||
new Identifier().setValue(personIdentifierValue)
|
|
||||||
.setSystem(configProperties.getPersonIdentifierSystem())));
|
|
||||||
result.addParameter(new ParametersParameterComponent().setName("domain")
|
|
||||||
.setValue(new StringType().setValue(configProperties.getBroadConsentDomainName())));
|
|
||||||
result.addParameter(new ParametersParameterComponent().setName("policy").setValue(
|
|
||||||
new Coding().setCode(configProperties.getPolicyCode())
|
|
||||||
.setSystem(configProperties.getPolicySystem())));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* is mandatory parameter, but we ignore it via additional configuration parameter
|
|
||||||
* 'ignoreVersionNumber'.
|
|
||||||
*/
|
|
||||||
result.addParameter(new ParametersParameterComponent().setName("version")
|
|
||||||
.setValue(new StringType().setValue("1.1")));
|
|
||||||
|
|
||||||
/* add config parameter with:
|
|
||||||
* ignoreVersionNumber -> true ->> Reason is we cannot know which policy version each patient
|
|
||||||
* has possibly signed or not, therefore we are happy with any version found.
|
|
||||||
* unknownStateIsConsideredAsDecline -> true
|
|
||||||
*/
|
|
||||||
var config = new ParametersParameterComponent().setName("config").addPart(
|
|
||||||
new ParametersParameterComponent().setName("ignoreVersionNumber")
|
|
||||||
.setValue(new BooleanType().setValue(true))).addPart(
|
|
||||||
new ParametersParameterComponent().setName("unknownStateIsConsideredAsDecline")
|
|
||||||
.setValue(new BooleanType().setValue(false)));
|
|
||||||
result.addParameter(config);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String callGicsApi(Parameters parameter, String endpoint) {
|
|
||||||
var parameterAsXml = fhirContext.newXmlParser().encodeResourceToString(parameter);
|
|
||||||
|
|
||||||
HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.httpHeader);
|
|
||||||
ResponseEntity<String> responseEntity;
|
|
||||||
try {
|
|
||||||
var url = getGicsUri(endpoint);
|
|
||||||
|
|
||||||
responseEntity = retryTemplate.execute(
|
|
||||||
ctx -> restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class));
|
|
||||||
} catch (RestClientException e) {
|
|
||||||
var msg = String.format("Get consents status request failed reason: '%s",
|
|
||||||
e.getMessage());
|
|
||||||
log.error(msg);
|
|
||||||
return null;
|
|
||||||
|
|
||||||
} catch (TerminatedRetryException terminatedRetryException) {
|
|
||||||
var msg = String.format(
|
|
||||||
"Get consents status process has been terminated. termination reason: '%s",
|
|
||||||
terminatedRetryException.getMessage());
|
|
||||||
log.error(msg);
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
|
||||||
return responseEntity.getBody();
|
|
||||||
} else {
|
|
||||||
var msg = String.format(
|
|
||||||
"Trusted party system reached but request failed! code: '%s' response: '%s'",
|
|
||||||
responseEntity.getStatusCode(), responseEntity.getBody());
|
|
||||||
log.error(msg);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TtpConsentStatus getTtpConsentStatus(String personIdentifierValue) {
|
|
||||||
var parameter = GicsConsentService.getIsConsentedRequestParam(gIcsConfigProperties,
|
|
||||||
personIdentifierValue);
|
|
||||||
|
|
||||||
var consentStatusResponse = callGicsApi(parameter,
|
|
||||||
GicsConsentService.IS_CONSENTED_ENDPOINT);
|
|
||||||
return evaluateConsentResponse(consentStatusResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bundle currentConsentForPersonAndTemplate(String personIdentifierValue,
|
|
||||||
ConsentDomain targetConsentDomain, Date requestDate) {
|
|
||||||
|
|
||||||
String consentDomain = getConsentDomain(targetConsentDomain);
|
|
||||||
|
|
||||||
var requestParameter = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(
|
|
||||||
gIcsConfigProperties, personIdentifierValue, requestDate, consentDomain);
|
|
||||||
|
|
||||||
var consentDataSerialized = callGicsApi(requestParameter,
|
|
||||||
GicsConsentService.IS_POLICY_STATES_FOR_PERSON_ENDPOINT);
|
|
||||||
|
|
||||||
if (consentDataSerialized == null) {
|
|
||||||
// error occurred - should not process further!
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"consent data request failed - stopping processing! - try again or fix other problems first.");
|
|
||||||
}
|
|
||||||
IBaseResource iBaseResource = fhirContext.newXmlParser()
|
|
||||||
.parseResource(consentDataSerialized);
|
|
||||||
if (iBaseResource instanceof OperationOutcome) {
|
|
||||||
// log error - very likely a configuration error
|
|
||||||
String errorMessage =
|
|
||||||
"Consent request failed! Check outcome:\n " + consentDataSerialized;
|
|
||||||
log.error(errorMessage);
|
|
||||||
throw new IllegalStateException(errorMessage);
|
|
||||||
} else if (iBaseResource instanceof Bundle) {
|
|
||||||
return (Bundle) iBaseResource;
|
|
||||||
} else {
|
|
||||||
String errorMessage = "Consent request failed! Unexpected response received! -> "
|
|
||||||
+ consentDataSerialized;
|
|
||||||
log.error(errorMessage);
|
|
||||||
throw new IllegalStateException(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String getConsentDomain(ConsentDomain targetConsentDomain) {
|
|
||||||
String consentDomain;
|
|
||||||
switch (targetConsentDomain) {
|
|
||||||
case BroadConsent -> {
|
|
||||||
consentDomain = gIcsConfigProperties.getBroadConsentDomainName();
|
|
||||||
}
|
|
||||||
case Modelvorhaben64e -> {
|
|
||||||
consentDomain = gIcsConfigProperties.getGnomDeConsentDomainName();
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"target ConsentDomain is missing but must be provided!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return consentDomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bundle getBroadConsent(String personIdentifierValue, Date requestDate) {
|
|
||||||
return currentConsentForPersonAndTemplate(personIdentifierValue, ConsentDomain.BroadConsent,
|
|
||||||
requestDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bundle getGenomDeConsent(String personIdentifierValue, Date requestDate) {
|
|
||||||
return currentConsentForPersonAndTemplate(personIdentifierValue,
|
|
||||||
ConsentDomain.Modelvorhaben64e, requestDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static Parameters buildRequestParameterCurrentPolicyStatesForPerson(
|
|
||||||
GIcsConfigProperties gIcsConfigProperties, String personIdentifierValue, Date requestDate,
|
|
||||||
String targetDomain) {
|
|
||||||
var requestParameter = new Parameters();
|
|
||||||
requestParameter.addParameter(new ParametersParameterComponent().setName("personIdentifier")
|
|
||||||
.setValue(new Identifier().setValue(personIdentifierValue)
|
|
||||||
.setSystem(gIcsConfigProperties.getPersonIdentifierSystem())));
|
|
||||||
|
|
||||||
requestParameter.addParameter(new ParametersParameterComponent().setName("domain")
|
|
||||||
.setValue(new StringType().setValue(targetDomain)));
|
|
||||||
|
|
||||||
Parameters nestedConfigParameters = new Parameters();
|
|
||||||
nestedConfigParameters.addParameter(
|
|
||||||
new ParametersParameterComponent().setName("idMatchingType").setValue(
|
|
||||||
new Coding().setSystem(
|
|
||||||
"https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType")
|
|
||||||
.setCode("AT_LEAST_ONE"))).addParameter("ignoreVersionNumber", false)
|
|
||||||
.addParameter("unknownStateIsConsideredAsDecline", false)
|
|
||||||
.addParameter("requestDate", new DateType().setValue(requestDate));
|
|
||||||
|
|
||||||
requestParameter.addParameter(new ParametersParameterComponent().setName("config").addPart()
|
|
||||||
.setResource(nestedConfigParameters));
|
|
||||||
|
|
||||||
return requestParameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TtpConsentStatus evaluateConsentResponse(String consentStatusResponse) {
|
|
||||||
if (consentStatusResponse == null) {
|
|
||||||
return TtpConsentStatus.FAILED_TO_ASK;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var response = fhirContext.newJsonParser().parseResource(consentStatusResponse);
|
|
||||||
|
|
||||||
if (response instanceof Parameters responseParameters) {
|
|
||||||
|
|
||||||
var responseValue = responseParameters.getParameter("consented").getValue();
|
|
||||||
var isConsented = responseValue.castToBoolean(responseValue);
|
|
||||||
if (!isConsented.hasValue()) {
|
|
||||||
return TtpConsentStatus.FAILED_TO_ASK;
|
|
||||||
}
|
|
||||||
if (isConsented.booleanValue()) {
|
|
||||||
return TtpConsentStatus.CONSENTED;
|
|
||||||
} else {
|
|
||||||
return TtpConsentStatus.CONSENT_MISSING_OR_REJECTED;
|
|
||||||
}
|
|
||||||
} else if (response instanceof OperationOutcome outcome) {
|
|
||||||
log.error("failed to get consent status from ttp. probably configuration error. "
|
|
||||||
+ "outcome: '{}'", fhirContext.newJsonParser().encodeToString(outcome));
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (DataFormatException dfe) {
|
|
||||||
log.error("failed to parse response to FHIR R4 resource.", dfe);
|
|
||||||
}
|
|
||||||
return TtpConsentStatus.FAILED_TO_ASK;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.consent;
|
|
||||||
|
|
||||||
|
|
||||||
public interface ICheckConsent {
|
|
||||||
|
|
||||||
TtpConsentStatus getTtpConsentStatus(String personIdentifierValue);
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.consent;
|
|
||||||
|
|
||||||
public enum TtpConsentStatus {
|
|
||||||
/**
|
|
||||||
* Valid consent found
|
|
||||||
*/
|
|
||||||
CONSENTED,
|
|
||||||
|
|
||||||
CONSENT_MISSING_OR_REJECTED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Due technical problems consent status is unknown
|
|
||||||
*/
|
|
||||||
FAILED_TO_ASK,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consent status is validate via file property 'consent.status'
|
|
||||||
*/
|
|
||||||
UNKNOWN_CHECK_FILE
|
|
||||||
}
|
|
@ -21,7 +21,6 @@ package dev.dnpm.etl.processor.pseudonym;
|
|||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
|
||||||
import dev.dnpm.etl.processor.config.GPasConfigProperties;
|
import dev.dnpm.etl.processor.config.GPasConfigProperties;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.r4.model.Identifier;
|
import org.hl7.fhir.r4.model.Identifier;
|
||||||
@ -37,7 +36,7 @@ import org.springframework.web.client.RestTemplate;
|
|||||||
|
|
||||||
public class GpasPseudonymGenerator implements Generator {
|
public class GpasPseudonymGenerator implements Generator {
|
||||||
|
|
||||||
private final FhirContext r4Context;
|
private final static FhirContext r4Context = FhirContext.forR4();
|
||||||
private final String gPasUrl;
|
private final String gPasUrl;
|
||||||
private final String psnTargetDomain;
|
private final String psnTargetDomain;
|
||||||
private final HttpHeaders httpHeader;
|
private final HttpHeaders httpHeader;
|
||||||
@ -46,13 +45,11 @@ public class GpasPseudonymGenerator implements Generator {
|
|||||||
|
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
public GpasPseudonymGenerator(GPasConfigProperties gpasCfg, RetryTemplate retryTemplate,
|
public GpasPseudonymGenerator(GPasConfigProperties gpasCfg, RetryTemplate retryTemplate, RestTemplate restTemplate) {
|
||||||
RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
|
|
||||||
this.retryTemplate = retryTemplate;
|
this.retryTemplate = retryTemplate;
|
||||||
this.restTemplate = restTemplate;
|
this.restTemplate = restTemplate;
|
||||||
this.gPasUrl = gpasCfg.getUri();
|
this.gPasUrl = gpasCfg.getUri();
|
||||||
this.psnTargetDomain = gpasCfg.getTarget();
|
this.psnTargetDomain = gpasCfg.getTarget();
|
||||||
this.r4Context = appFhirConfig.fhirContext();
|
|
||||||
httpHeader = getHttpHeaders(gpasCfg.getUsername(), gpasCfg.getPassword());
|
httpHeader = getHttpHeaders(gpasCfg.getUsername(), gpasCfg.getPassword());
|
||||||
|
|
||||||
log.debug(String.format("%s has been initialized", this.getClass().getName()));
|
log.debug(String.format("%s has been initialized", this.getClass().getName()));
|
||||||
@ -100,6 +97,7 @@ public class GpasPseudonymGenerator implements Generator {
|
|||||||
return psnValue.replaceAll(forbiddenCharsRegex, "_");
|
return psnValue.replaceAll(forbiddenCharsRegex, "_");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
protected ResponseEntity<String> getGpasPseudonym(String gPasRequestBody) {
|
protected ResponseEntity<String> getGpasPseudonym(String gPasRequestBody) {
|
||||||
|
|
||||||
|
@ -56,54 +56,6 @@ data class GPasConfigProperties(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConfigurationProperties(GIcsConfigProperties.NAME)
|
|
||||||
data class GIcsConfigProperties(
|
|
||||||
/**
|
|
||||||
* Base URL to gICS System
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
val uri: String?,
|
|
||||||
val username: String?,
|
|
||||||
val password: String?,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If value is 'true' valid consent at processing time is mandatory for transmission of DNPM
|
|
||||||
* files otherwise they will be flagged and skipped.
|
|
||||||
* If value 'false' or missing consent status is assumed to be valid.
|
|
||||||
*/
|
|
||||||
val enabled: Boolean?,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gICS specific system
|
|
||||||
* **/
|
|
||||||
val personIdentifierSystem: String =
|
|
||||||
"https://ths-greifswald.de/fhir/gics/identifiers/Patienten-ID",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Domain of broad consent resources
|
|
||||||
**/
|
|
||||||
val broadConsentDomainName: String = "MII",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Domain of Modelvorhaben 64e consent resources
|
|
||||||
**/
|
|
||||||
val gnomDeConsentDomainName: String = "GenomDE_MV",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value to expect in case of positiv consent
|
|
||||||
*/
|
|
||||||
val policyCode: String = "2.16.840.1.113883.3.1937.777.24.5.3.6",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consent Policy which should be used for consent check
|
|
||||||
*/
|
|
||||||
val policySystem: String = "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3"
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
const val NAME = "app.consent.gics"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConfigurationProperties(RestTargetProperties.NAME)
|
@ConfigurationProperties(RestTargetProperties.NAME)
|
||||||
data class RestTargetProperties(
|
data class RestTargetProperties(
|
||||||
val uri: String?,
|
val uri: String?,
|
||||||
|
@ -20,10 +20,10 @@
|
|||||||
package dev.dnpm.etl.processor.config
|
package dev.dnpm.etl.processor.config
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import dev.dnpm.etl.processor.consent.ConsentCheckFileBased
|
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
|
||||||
import dev.dnpm.etl.processor.consent.ICheckConsent
|
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
|
||||||
import dev.dnpm.etl.processor.consent.GicsConsentService
|
import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService
|
||||||
import dev.dnpm.etl.processor.monitoring.*
|
import dev.dnpm.etl.processor.monitoring.ReportService
|
||||||
import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator
|
import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator
|
||||||
import dev.dnpm.etl.processor.pseudonym.Generator
|
import dev.dnpm.etl.processor.pseudonym.Generator
|
||||||
import dev.dnpm.etl.processor.pseudonym.GpasPseudonymGenerator
|
import dev.dnpm.etl.processor.pseudonym.GpasPseudonymGenerator
|
||||||
@ -60,8 +60,7 @@ import kotlin.time.toJavaDuration
|
|||||||
value = [
|
value = [
|
||||||
AppConfigProperties::class,
|
AppConfigProperties::class,
|
||||||
PseudonymizeConfigProperties::class,
|
PseudonymizeConfigProperties::class,
|
||||||
GPasConfigProperties::class,
|
GPasConfigProperties::class
|
||||||
GIcsConfigProperties::class
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@ -74,15 +73,10 @@ class AppConfiguration {
|
|||||||
return RestTemplate()
|
return RestTemplate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun appFhirConfig(): AppFhirConfig{
|
|
||||||
return AppFhirConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
|
@ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "GPAS")
|
||||||
@Bean
|
@Bean
|
||||||
fun gpasPseudonymGenerator(configProperties: GPasConfigProperties, retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig): Generator {
|
fun gpasPseudonymGenerator(configProperties: GPasConfigProperties, retryTemplate: RetryTemplate, restTemplate: RestTemplate): Generator {
|
||||||
return GpasPseudonymGenerator(configProperties, retryTemplate, restTemplate, appFhirConfig)
|
return GpasPseudonymGenerator(configProperties, retryTemplate, restTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "BUILDIN", matchIfMissing = true)
|
@ConditionalOnProperty(value = ["app.pseudonymize.generator"], havingValue = "BUILDIN", matchIfMissing = true)
|
||||||
@ -100,21 +94,17 @@ class AppConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun reportService(): ReportService {
|
fun reportService(objectMapper: ObjectMapper): ReportService {
|
||||||
return ReportService(getObjectMapper())
|
return ReportService(objectMapper)
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun getObjectMapper () : ObjectMapper{
|
|
||||||
return JacksonConfig().objectMapper()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun transformationService(
|
fun transformationService(
|
||||||
|
objectMapper: ObjectMapper,
|
||||||
configProperties: AppConfigProperties
|
configProperties: AppConfigProperties
|
||||||
): TransformationService {
|
): TransformationService {
|
||||||
logger.info("Apply ${configProperties.transformations.size} transformation rules")
|
logger.info("Apply ${configProperties.transformations.size} transformation rules")
|
||||||
return TransformationService(getObjectMapper(), configProperties.transformations.map {
|
return TransformationService(objectMapper, configProperties.transformations.map {
|
||||||
Transformation.of(it.path) from it.from to it.to
|
Transformation.of(it.path) from it.from to it.to
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -180,33 +170,5 @@ class AppConfiguration {
|
|||||||
fun jdbcConfiguration(): AbstractJdbcConfiguration {
|
fun jdbcConfiguration(): AbstractJdbcConfiguration {
|
||||||
return AppJdbcConfiguration()
|
return AppJdbcConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
|
|
||||||
fun gicsConsentService( gIcsConfigProperties: GIcsConfigProperties,
|
|
||||||
retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig): ICheckConsent {
|
|
||||||
return GicsConsentService(
|
|
||||||
gIcsConfigProperties,
|
|
||||||
retryTemplate,
|
|
||||||
restTemplate,
|
|
||||||
appFhirConfig
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
|
|
||||||
@Bean
|
|
||||||
fun gIcsConnectionCheckService(
|
|
||||||
restTemplate: RestTemplate,
|
|
||||||
gIcsConfigProperties: GIcsConfigProperties,
|
|
||||||
connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
|
|
||||||
): ConnectionCheckService {
|
|
||||||
return GIcsConnectionCheckService(restTemplate, gIcsConfigProperties, connectionCheckUpdateProducer)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
fun constService(): ICheckConsent {
|
|
||||||
return ConsentCheckFileBased()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.config
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class AppFhirConfig {
|
|
||||||
private val fhirCtx: FhirContext = FhirContext.forR4()
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun fhirContext(): FhirContext {
|
|
||||||
return fhirCtx
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.config
|
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource
|
|
||||||
|
|
||||||
class FhirResourceModule : SimpleModule() {
|
|
||||||
init {
|
|
||||||
addSerializer(IBaseResource::class.java, IBaseResourceSerializer())
|
|
||||||
addDeserializer(IBaseResource::class.java, IBaseResourceDeserializer())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.config
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext
|
|
||||||
import com.fasterxml.jackson.core.JsonParser
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext
|
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource
|
|
||||||
|
|
||||||
class IBaseResourceDeserializer : JsonDeserializer<IBaseResource>() {
|
|
||||||
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): IBaseResource {
|
|
||||||
val fhirContext = FhirContext.forR4()
|
|
||||||
|
|
||||||
val jsonNode = p?.readValueAsTree<JsonNode>()
|
|
||||||
val json = jsonNode?.toString()
|
|
||||||
|
|
||||||
return fhirContext.newJsonParser().parseResource(json) as IBaseResource
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.config
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext
|
|
||||||
import com.fasterxml.jackson.core.JsonGenerator
|
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer
|
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource
|
|
||||||
|
|
||||||
class IBaseResourceSerializer : JsonSerializer<IBaseResource>() {
|
|
||||||
override fun serialize(
|
|
||||||
value: IBaseResource,
|
|
||||||
gen: JsonGenerator,
|
|
||||||
serializers: SerializerProvider
|
|
||||||
) {
|
|
||||||
val fhirContext = FhirContext.forR4()
|
|
||||||
val json = fhirContext.newJsonParser().encodeResourceToString(value)
|
|
||||||
gen.writeRawValue(json)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.config
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class JacksonConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun objectMapper(): ObjectMapper =
|
|
||||||
ObjectMapper()
|
|
||||||
.registerModule(FhirResourceModule())
|
|
||||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).registerModule(
|
|
||||||
JavaTimeModule()
|
|
||||||
);
|
|
||||||
}
|
|
@ -25,7 +25,6 @@ import de.ukw.ccc.bwhc.dto.MtbFile
|
|||||||
import dev.dnpm.etl.processor.CustomMediaType
|
import dev.dnpm.etl.processor.CustomMediaType
|
||||||
import dev.dnpm.etl.processor.PatientId
|
import dev.dnpm.etl.processor.PatientId
|
||||||
import dev.dnpm.etl.processor.RequestId
|
import dev.dnpm.etl.processor.RequestId
|
||||||
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
|
||||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@ -77,13 +76,9 @@ class KafkaInputListener(
|
|||||||
} else {
|
} else {
|
||||||
logger.debug("Accepted MTB File and process deletion")
|
logger.debug("Accepted MTB File and process deletion")
|
||||||
if (requestId.isBlank()) {
|
if (requestId.isBlank()) {
|
||||||
requestProcessor.processDeletion(patientId, TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
requestProcessor.processDeletion(patientId)
|
||||||
} else {
|
} else {
|
||||||
requestProcessor.processDeletion(
|
requestProcessor.processDeletion(patientId, requestId)
|
||||||
patientId,
|
|
||||||
requestId,
|
|
||||||
TtpConsentStatus.UNKNOWN_CHECK_FILE
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,6 @@ import de.ukw.ccc.bwhc.dto.Consent
|
|||||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||||
import dev.dnpm.etl.processor.CustomMediaType
|
import dev.dnpm.etl.processor.CustomMediaType
|
||||||
import dev.dnpm.etl.processor.PatientId
|
import dev.dnpm.etl.processor.PatientId
|
||||||
import dev.dnpm.etl.processor.consent.ICheckConsent
|
|
||||||
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
|
||||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@ -35,7 +33,7 @@ import org.springframework.web.bind.annotation.*
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(path = ["mtbfile", "mtb"])
|
@RequestMapping(path = ["mtbfile", "mtb"])
|
||||||
class MtbFileRestController(
|
class MtbFileRestController(
|
||||||
private val requestProcessor: RequestProcessor, private val iCheckConsent: ICheckConsent
|
private val requestProcessor: RequestProcessor,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
|
private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
|
||||||
@ -47,36 +45,17 @@ class MtbFileRestController(
|
|||||||
|
|
||||||
@PostMapping( consumes = [ MediaType.APPLICATION_JSON_VALUE ] )
|
@PostMapping( consumes = [ MediaType.APPLICATION_JSON_VALUE ] )
|
||||||
fun mtbFile(@RequestBody mtbFile: MtbFile): ResponseEntity<Unit> {
|
fun mtbFile(@RequestBody mtbFile: MtbFile): ResponseEntity<Unit> {
|
||||||
val consentStatusBooleanPair = checkConsentStatus(mtbFile)
|
if (mtbFile.consent.status == Consent.Status.ACTIVE) {
|
||||||
val ttpConsentStatus = consentStatusBooleanPair.first
|
|
||||||
val isConsentOK = consentStatusBooleanPair.second
|
|
||||||
if (isConsentOK) {
|
|
||||||
logger.debug("Accepted MTB File (bwHC V1) for processing")
|
logger.debug("Accepted MTB File (bwHC V1) for processing")
|
||||||
requestProcessor.processMtbFile(mtbFile)
|
requestProcessor.processMtbFile(mtbFile)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
logger.debug("Accepted MTB File (bwHC V1) and process deletion")
|
logger.debug("Accepted MTB File (bwHC V1) and process deletion")
|
||||||
val patientId = PatientId(mtbFile.patient.id)
|
val patientId = PatientId(mtbFile.patient.id)
|
||||||
requestProcessor.processDeletion(patientId, ttpConsentStatus)
|
requestProcessor.processDeletion(patientId)
|
||||||
}
|
}
|
||||||
return ResponseEntity.accepted().build()
|
return ResponseEntity.accepted().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkConsentStatus(mtbFile: MtbFile): Pair<TtpConsentStatus, Boolean> {
|
|
||||||
var ttpConsentStatus = iCheckConsent.getTtpConsentStatus(mtbFile.patient.id)
|
|
||||||
|
|
||||||
val isConsentOK =
|
|
||||||
(ttpConsentStatus.equals(TtpConsentStatus.UNKNOWN_CHECK_FILE) && mtbFile.consent.status == Consent.Status.ACTIVE) ||
|
|
||||||
ttpConsentStatus.equals(
|
|
||||||
TtpConsentStatus.CONSENTED
|
|
||||||
)
|
|
||||||
if (ttpConsentStatus.equals(TtpConsentStatus.UNKNOWN_CHECK_FILE) && mtbFile.consent.status == Consent.Status.REJECTED) {
|
|
||||||
// in case ttp check is disabled - we propagate rejected status anyway
|
|
||||||
ttpConsentStatus = TtpConsentStatus.CONSENT_MISSING_OR_REJECTED
|
|
||||||
}
|
|
||||||
return Pair(ttpConsentStatus, isConsentOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping( consumes = [ CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE] )
|
@PostMapping( consumes = [ CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE] )
|
||||||
fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
|
fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
|
||||||
logger.debug("Accepted MTB File (DNPM V2) for processing")
|
logger.debug("Accepted MTB File (DNPM V2) for processing")
|
||||||
@ -87,7 +66,7 @@ class MtbFileRestController(
|
|||||||
@DeleteMapping(path = ["{patientId}"])
|
@DeleteMapping(path = ["{patientId}"])
|
||||||
fun deleteData(@PathVariable patientId: String): ResponseEntity<Unit> {
|
fun deleteData(@PathVariable patientId: String): ResponseEntity<Unit> {
|
||||||
logger.debug("Accepted patient ID to process deletion")
|
logger.debug("Accepted patient ID to process deletion")
|
||||||
requestProcessor.processDeletion(PatientId(patientId), TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
requestProcessor.processDeletion(PatientId(patientId))
|
||||||
return ResponseEntity.accepted().build()
|
return ResponseEntity.accepted().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
package dev.dnpm.etl.processor.monitoring
|
package dev.dnpm.etl.processor.monitoring
|
||||||
|
|
||||||
import dev.dnpm.etl.processor.config.GIcsConfigProperties
|
|
||||||
import dev.dnpm.etl.processor.config.GPasConfigProperties
|
import dev.dnpm.etl.processor.config.GPasConfigProperties
|
||||||
import dev.dnpm.etl.processor.config.RestTargetProperties
|
import dev.dnpm.etl.processor.config.RestTargetProperties
|
||||||
import jakarta.annotation.PostConstruct
|
import jakarta.annotation.PostConstruct
|
||||||
@ -69,12 +68,6 @@ sealed class ConnectionCheckResult {
|
|||||||
override val timestamp: Instant,
|
override val timestamp: Instant,
|
||||||
override val lastChange: Instant
|
override val lastChange: Instant
|
||||||
) : ConnectionCheckResult()
|
) : ConnectionCheckResult()
|
||||||
|
|
||||||
data class GIcsConnectionCheckResult(
|
|
||||||
override val available: Boolean,
|
|
||||||
override val timestamp: Instant,
|
|
||||||
override val lastChange: Instant
|
|
||||||
) : ConnectionCheckResult()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class KafkaConnectionCheckService(
|
class KafkaConnectionCheckService(
|
||||||
@ -215,56 +208,3 @@ class GPasConnectionCheckService(
|
|||||||
return this.result
|
return this.result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GIcsConnectionCheckService(
|
|
||||||
private val restTemplate: RestTemplate,
|
|
||||||
private val gIcsConfigProperties: GIcsConfigProperties,
|
|
||||||
@Qualifier("connectionCheckUpdateProducer")
|
|
||||||
private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
|
|
||||||
) : ConnectionCheckService {
|
|
||||||
|
|
||||||
private var result = ConnectionCheckResult.GIcsConnectionCheckResult(false, Instant.now(), Instant.now())
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
@Scheduled(cron = "0 * * * * *")
|
|
||||||
fun check() {
|
|
||||||
result = try {
|
|
||||||
|
|
||||||
val uri = UriComponentsBuilder.fromUriString(
|
|
||||||
gIcsConfigProperties.uri.toString()).path("/metadata").build().toUri()
|
|
||||||
|
|
||||||
val headers = HttpHeaders()
|
|
||||||
headers.contentType = MediaType.APPLICATION_JSON
|
|
||||||
if (!gIcsConfigProperties.username.isNullOrBlank() && !gIcsConfigProperties.password.isNullOrBlank()) {
|
|
||||||
headers.setBasicAuth(gIcsConfigProperties.username, gIcsConfigProperties.password)
|
|
||||||
}
|
|
||||||
|
|
||||||
val available = restTemplate.exchange(
|
|
||||||
uri,
|
|
||||||
HttpMethod.GET,
|
|
||||||
HttpEntity<Void>(headers),
|
|
||||||
Void::class.java
|
|
||||||
).statusCode == HttpStatus.OK
|
|
||||||
|
|
||||||
ConnectionCheckResult.GIcsConnectionCheckResult(
|
|
||||||
available,
|
|
||||||
Instant.now(),
|
|
||||||
if (result.available == available) { result.lastChange } else { Instant.now() }
|
|
||||||
)
|
|
||||||
} catch (_: Exception) {
|
|
||||||
ConnectionCheckResult.GIcsConnectionCheckResult(
|
|
||||||
false,
|
|
||||||
Instant.now(),
|
|
||||||
if (!result.available) { result.lastChange } else { Instant.now() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
connectionCheckUpdateProducer.emitNext(
|
|
||||||
result,
|
|
||||||
Sinks.EmitFailureHandler.FAIL_FAST
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun connectionAvailable(): ConnectionCheckResult.GIcsConnectionCheckResult {
|
|
||||||
return this.result
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,6 +24,5 @@ enum class RequestStatus(val value: String) {
|
|||||||
WARNING("warning"),
|
WARNING("warning"),
|
||||||
ERROR("error"),
|
ERROR("error"),
|
||||||
UNKNOWN("unknown"),
|
UNKNOWN("unknown"),
|
||||||
DUPLICATION("duplication"),
|
DUPLICATION("duplication")
|
||||||
NO_CONSENT("no-consent")
|
|
||||||
}
|
}
|
@ -23,7 +23,6 @@ import de.ukw.ccc.bwhc.dto.MtbFile
|
|||||||
import dev.dnpm.etl.processor.PatientId
|
import dev.dnpm.etl.processor.PatientId
|
||||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||||
import org.apache.commons.codec.digest.DigestUtils
|
import org.apache.commons.codec.digest.DigestUtils
|
||||||
import org.hl7.fhir.r4.model.Consent
|
|
||||||
|
|
||||||
/** Replaces patient ID with generated patient pseudonym
|
/** Replaces patient ID with generated patient pseudonym
|
||||||
*
|
*
|
||||||
@ -256,6 +255,7 @@ infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
|
|||||||
this.claims?.forEach { it.patient.id = patientPseudonym }
|
this.claims?.forEach { it.patient.id = patientPseudonym }
|
||||||
this.claimResponses?.forEach { it.patient.id = patientPseudonym }
|
this.claimResponses?.forEach { it.patient.id = patientPseudonym }
|
||||||
this.diagnoses?.forEach { it.patient.id = patientPseudonym }
|
this.diagnoses?.forEach { it.patient.id = patientPseudonym }
|
||||||
|
this.familyMemberHistories?.forEach { it.patient.id = patientPseudonym }
|
||||||
this.histologyReports?.forEach {
|
this.histologyReports?.forEach {
|
||||||
it.patient.id = patientPseudonym
|
it.patient.id = patientPseudonym
|
||||||
it.results.tumorMorphology?.patient?.id = patientPseudonym
|
it.results.tumorMorphology?.patient?.id = patientPseudonym
|
||||||
@ -289,14 +289,6 @@ infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
|
|||||||
this.followUps?.forEach {
|
this.followUps?.forEach {
|
||||||
it.patient.id = patientPseudonym
|
it.patient.id = patientPseudonym
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: MUST CREATE TESTCASE - NEEDS TESTING!!
|
|
||||||
this.metadata?.researchConsents?.forEach { it -> {
|
|
||||||
val consent = it as? Consent
|
|
||||||
consent?.patient?.reference = "Patient/$patientPseudonym"
|
|
||||||
consent?.patient?.display = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,8 +23,6 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||||
import dev.dnpm.etl.processor.*
|
import dev.dnpm.etl.processor.*
|
||||||
import dev.dnpm.etl.processor.config.AppConfigProperties
|
import dev.dnpm.etl.processor.config.AppConfigProperties
|
||||||
import dev.dnpm.etl.processor.consent.GicsConsentService
|
|
||||||
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
|
||||||
import dev.dnpm.etl.processor.monitoring.Report
|
import dev.dnpm.etl.processor.monitoring.Report
|
||||||
import dev.dnpm.etl.processor.monitoring.Request
|
import dev.dnpm.etl.processor.monitoring.Request
|
||||||
import dev.dnpm.etl.processor.monitoring.RequestStatus
|
import dev.dnpm.etl.processor.monitoring.RequestStatus
|
||||||
@ -33,17 +31,9 @@ import dev.dnpm.etl.processor.output.*
|
|||||||
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
|
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
|
||||||
import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith
|
import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith
|
||||||
import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
|
import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
|
||||||
import dev.pcvolkmer.mv64e.mtb.ConsentProvision
|
|
||||||
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent
|
|
||||||
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose
|
|
||||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||||
import dev.pcvolkmer.mv64e.mtb.MvhMetadata
|
|
||||||
import dev.pcvolkmer.mv64e.mtb.Provision
|
|
||||||
import org.apache.commons.codec.binary.Base32
|
import org.apache.commons.codec.binary.Base32
|
||||||
import org.apache.commons.codec.digest.DigestUtils
|
import org.apache.commons.codec.digest.DigestUtils
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource
|
|
||||||
import org.hl7.fhir.r4.model.Bundle
|
|
||||||
import org.hl7.fhir.r4.model.Consent
|
|
||||||
import org.springframework.context.ApplicationEventPublisher
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@ -57,8 +47,7 @@ class RequestProcessor(
|
|||||||
private val requestService: RequestService,
|
private val requestService: RequestService,
|
||||||
private val objectMapper: ObjectMapper,
|
private val objectMapper: ObjectMapper,
|
||||||
private val applicationEventPublisher: ApplicationEventPublisher,
|
private val applicationEventPublisher: ApplicationEventPublisher,
|
||||||
private val appConfigProperties: AppConfigProperties,
|
private val appConfigProperties: AppConfigProperties
|
||||||
private val gicsConsentService: GicsConsentService?
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun processMtbFile(mtbFile: MtbFile) {
|
fun processMtbFile(mtbFile: MtbFile) {
|
||||||
@ -79,84 +68,12 @@ class RequestProcessor(
|
|||||||
|
|
||||||
fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
|
fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
|
||||||
val pid = PatientId(mtbFile.patient.id)
|
val pid = PatientId(mtbFile.patient.id)
|
||||||
|
|
||||||
addConsentToMtb(mtbFile)
|
|
||||||
mtbFile pseudonymizeWith pseudonymizeService
|
mtbFile pseudonymizeWith pseudonymizeService
|
||||||
mtbFile anonymizeContentWith pseudonymizeService
|
mtbFile anonymizeContentWith pseudonymizeService
|
||||||
val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
|
val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
|
||||||
saveAndSend(request, pid)
|
saveAndSend(request, pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addConsentToMtb(mtbFile: Mtb) {
|
|
||||||
if (gicsConsentService == null) return
|
|
||||||
// init metadata if necessary
|
|
||||||
if (mtbFile.metadata == null) {
|
|
||||||
val mvhMetadata = MvhMetadata.builder().build();
|
|
||||||
mtbFile.metadata = mvhMetadata
|
|
||||||
if (mtbFile.metadata.researchConsents == null) {
|
|
||||||
mtbFile.metadata.researchConsents = mutableListOf()
|
|
||||||
}
|
|
||||||
if (mtbFile.metadata.modelProjectConsent == null) {
|
|
||||||
mtbFile.metadata.modelProjectConsent = ModelProjectConsent()
|
|
||||||
mtbFile.metadata.modelProjectConsent.provisions = mutableListOf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixme Date should be extracted from mtbFile
|
|
||||||
val consentGnomeDe =
|
|
||||||
gicsConsentService.getGenomDeConsent(mtbFile.patient.id, Date.from(Instant.now()))
|
|
||||||
addGenomeDbProvisions(mtbFile, consentGnomeDe)
|
|
||||||
|
|
||||||
// fixme Date should be extracted from mtbFile
|
|
||||||
val broadConsent =
|
|
||||||
gicsConsentService.getBroadConsent(mtbFile.patient.id, Date.from(Instant.now()))
|
|
||||||
embedBroadConsentResources(mtbFile, broadConsent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun embedBroadConsentResources(
|
|
||||||
mtbFile: Mtb,
|
|
||||||
broadConsent: Bundle
|
|
||||||
) {
|
|
||||||
broadConsent.entry.forEach { it ->
|
|
||||||
mtbFile.metadata.researchConsents.add(mapOf(it.resource.id to it as IBaseResource))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addGenomeDbProvisions(
|
|
||||||
mtbFile: Mtb,
|
|
||||||
consentGnomeDe: Bundle
|
|
||||||
) {
|
|
||||||
consentGnomeDe.entry.forEach { it ->
|
|
||||||
{
|
|
||||||
val consent = it.resource as Consent
|
|
||||||
val provisionComponent = consent.provision.provision.firstOrNull()
|
|
||||||
val provisionCode =
|
|
||||||
provisionComponent?.code?.firstOrNull()?.coding?.firstOrNull()?.code
|
|
||||||
var isValidCode = true
|
|
||||||
if (provisionCode != null) {
|
|
||||||
var modelProjectConsentPurpose: ModelProjectConsentPurpose =
|
|
||||||
ModelProjectConsentPurpose.SEQUENCING
|
|
||||||
if (provisionCode == "Teilnahme") {
|
|
||||||
modelProjectConsentPurpose = ModelProjectConsentPurpose.SEQUENCING
|
|
||||||
} else if (provisionCode == "Fallidentifizierung") {
|
|
||||||
modelProjectConsentPurpose = ModelProjectConsentPurpose.CASE_IDENTIFICATION
|
|
||||||
} else if (provisionCode == "Rekontaktierung") {
|
|
||||||
modelProjectConsentPurpose = ModelProjectConsentPurpose.REIDENTIFICATION
|
|
||||||
} else {
|
|
||||||
isValidCode = false
|
|
||||||
}
|
|
||||||
if (isValidCode) mtbFile.metadata.modelProjectConsent.provisions.add(
|
|
||||||
Provision.builder().type(
|
|
||||||
ConsentProvision.forValue(provisionComponent.type.name)
|
|
||||||
).date(provisionComponent.period.start).purpose(
|
|
||||||
modelProjectConsentPurpose
|
|
||||||
).build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> saveAndSend(request: MtbFileRequest<T>, pid: PatientId) {
|
private fun <T> saveAndSend(request: MtbFileRequest<T>, pid: PatientId) {
|
||||||
requestService.save(
|
requestService.save(
|
||||||
Request(
|
Request(
|
||||||
@ -203,30 +120,21 @@ class RequestProcessor(
|
|||||||
|
|
||||||
val lastMtbFileRequestForPatient =
|
val lastMtbFileRequestForPatient =
|
||||||
requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym)
|
requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym)
|
||||||
val isLastRequestDeletion =
|
val isLastRequestDeletion = requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
|
||||||
requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
|
|
||||||
|
|
||||||
return null != lastMtbFileRequestForPatient
|
return null != lastMtbFileRequestForPatient
|
||||||
&& !isLastRequestDeletion
|
&& !isLastRequestDeletion
|
||||||
&& lastMtbFileRequestForPatient.fingerprint == fingerprint(
|
&& lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFileRequest)
|
||||||
pseudonymizedMtbFileRequest
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processDeletion(patientId: PatientId, isConsented: TtpConsentStatus) {
|
fun processDeletion(patientId: PatientId) {
|
||||||
processDeletion(patientId, randomRequestId(), isConsented)
|
processDeletion(patientId, randomRequestId())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processDeletion(patientId: PatientId, requestId: RequestId, isConsented: TtpConsentStatus) {
|
fun processDeletion(patientId: PatientId, requestId: RequestId) {
|
||||||
try {
|
try {
|
||||||
val patientPseudonym = pseudonymizeService.patientPseudonym(patientId)
|
val patientPseudonym = pseudonymizeService.patientPseudonym(patientId)
|
||||||
|
|
||||||
val requestStatus: RequestStatus = when (isConsented) {
|
|
||||||
TtpConsentStatus.CONSENT_MISSING_OR_REJECTED -> RequestStatus.NO_CONSENT
|
|
||||||
TtpConsentStatus.FAILED_TO_ASK -> RequestStatus.ERROR
|
|
||||||
TtpConsentStatus.CONSENTED, TtpConsentStatus.UNKNOWN_CHECK_FILE -> RequestStatus.UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
requestService.save(
|
requestService.save(
|
||||||
Request(
|
Request(
|
||||||
requestId,
|
requestId,
|
||||||
@ -234,7 +142,7 @@ class RequestProcessor(
|
|||||||
patientId,
|
patientId,
|
||||||
fingerprint(patientPseudonym.value),
|
fingerprint(patientPseudonym.value),
|
||||||
RequestType.DELETE,
|
RequestType.DELETE,
|
||||||
requestStatus
|
RequestStatus.UNKNOWN
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,12 +70,6 @@ class ResponseProcessor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestStatus.NO_CONSENT -> {
|
|
||||||
it.report = Report(
|
|
||||||
"Einwilligung Status fehlt, widerrufen oder ungeklärt."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
logger.error("Cannot process response: Unknown response!")
|
logger.error("Cannot process response: Unknown response!")
|
||||||
return@ifPresentOrElse
|
return@ifPresentOrElse
|
||||||
|
@ -19,7 +19,10 @@
|
|||||||
|
|
||||||
package dev.dnpm.etl.processor.web
|
package dev.dnpm.etl.processor.web
|
||||||
|
|
||||||
import dev.dnpm.etl.processor.monitoring.*
|
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
|
||||||
|
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
|
||||||
|
import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService
|
||||||
|
import dev.dnpm.etl.processor.monitoring.OutputConnectionCheckService
|
||||||
import dev.dnpm.etl.processor.output.MtbFileSender
|
import dev.dnpm.etl.processor.output.MtbFileSender
|
||||||
import dev.dnpm.etl.processor.pseudonym.Generator
|
import dev.dnpm.etl.processor.pseudonym.Generator
|
||||||
import dev.dnpm.etl.processor.security.Role
|
import dev.dnpm.etl.processor.security.Role
|
||||||
@ -58,15 +61,11 @@ class ConfigController(
|
|||||||
val gPasConnectionAvailable =
|
val gPasConnectionAvailable =
|
||||||
connectionCheckServices.filterIsInstance<GPasConnectionCheckService>().firstOrNull()?.connectionAvailable()
|
connectionCheckServices.filterIsInstance<GPasConnectionCheckService>().firstOrNull()?.connectionAvailable()
|
||||||
|
|
||||||
val gIcsConnectionAvailable =
|
|
||||||
connectionCheckServices.filterIsInstance<GIcsConnectionCheckService>().firstOrNull()?.connectionAvailable()
|
|
||||||
|
|
||||||
model.addAttribute("pseudonymGenerator", pseudonymGenerator.javaClass.simpleName)
|
model.addAttribute("pseudonymGenerator", pseudonymGenerator.javaClass.simpleName)
|
||||||
model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName)
|
model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName)
|
||||||
model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint())
|
model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint())
|
||||||
model.addAttribute("outputConnectionAvailable", outputConnectionAvailable)
|
model.addAttribute("outputConnectionAvailable", outputConnectionAvailable)
|
||||||
model.addAttribute("gPasConnectionAvailable", gPasConnectionAvailable)
|
model.addAttribute("gPasConnectionAvailable", gPasConnectionAvailable)
|
||||||
model.addAttribute("gIcsConnectionAvailable", gIcsConnectionAvailable)
|
|
||||||
model.addAttribute("tokensEnabled", tokenService != null)
|
model.addAttribute("tokensEnabled", tokenService != null)
|
||||||
if (tokenService != null) {
|
if (tokenService != null) {
|
||||||
model.addAttribute("tokens", tokenService.findAll())
|
model.addAttribute("tokens", tokenService.findAll())
|
||||||
@ -120,24 +119,6 @@ class ConfigController(
|
|||||||
return "configs/gPasConnectionAvailable"
|
return "configs/gPasConnectionAvailable"
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(params = ["gIcsConnectionAvailable"])
|
|
||||||
fun gIcsConnectionAvailable(model: Model): String {
|
|
||||||
val gIcsConnectionAvailable =
|
|
||||||
connectionCheckServices.filterIsInstance<GIcsConnectionCheckService>().firstOrNull()?.connectionAvailable()
|
|
||||||
|
|
||||||
model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName)
|
|
||||||
model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint())
|
|
||||||
model.addAttribute("gIcsConnectionAvailable", gIcsConnectionAvailable)
|
|
||||||
if (tokenService != null) {
|
|
||||||
model.addAttribute("tokensEnabled", true)
|
|
||||||
model.addAttribute("tokens", tokenService.findAll())
|
|
||||||
} else {
|
|
||||||
model.addAttribute("tokens", listOf<Token>())
|
|
||||||
}
|
|
||||||
|
|
||||||
return "configs/gIcsConnectionAvailable"
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(path = ["tokens"])
|
@PostMapping(path = ["tokens"])
|
||||||
fun addToken(@ModelAttribute("name") name: String, model: Model): String {
|
fun addToken(@ModelAttribute("name") name: String, model: Model): String {
|
||||||
if (tokenService == null) {
|
if (tokenService == null) {
|
||||||
@ -209,7 +190,6 @@ class ConfigController(
|
|||||||
is ConnectionCheckResult.KafkaConnectionCheckResult -> "output-connection-check"
|
is ConnectionCheckResult.KafkaConnectionCheckResult -> "output-connection-check"
|
||||||
is ConnectionCheckResult.RestConnectionCheckResult -> "output-connection-check"
|
is ConnectionCheckResult.RestConnectionCheckResult -> "output-connection-check"
|
||||||
is ConnectionCheckResult.GPasConnectionCheckResult -> "gpas-connection-check"
|
is ConnectionCheckResult.GPasConnectionCheckResult -> "gpas-connection-check"
|
||||||
is ConnectionCheckResult.GIcsConnectionCheckResult -> "gics-connection-check"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerSentEvent.builder<Any>()
|
ServerSentEvent.builder<Any>()
|
||||||
|
@ -49,11 +49,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section hx-ext="sse" th:sse-connect="@{/configs/events}">
|
|
||||||
<div th:insert="~{configs/gIcsConnectionAvailable.html}" th:hx-get="@{/configs?gIcsConnectionAvailable}" hx-trigger="sse:gics-connection-check">
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section hx-ext="sse" th:sse-connect="@{/configs/events}">
|
<section hx-ext="sse" th:sse-connect="@{/configs/events}">
|
||||||
<div th:insert="~{configs/outputConnectionAvailable.html}" th:hx-get="@{/configs?outputConnectionAvailable}" hx-trigger="sse:output-connection-check">
|
<div th:insert="~{configs/outputConnectionAvailable.html}" th:hx-get="@{/configs?outputConnectionAvailable}" hx-trigger="sse:output-connection-check">
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
<th:block th:if="${gIcsConnectionAvailable == null}">
|
|
||||||
<h2><span>🟦</span> gICS nicht konfiguriert - Einwilligung wird über Dateiinhalt geprüft</h2>
|
|
||||||
</th:block>
|
|
||||||
<th:block th:if="${gIcsConnectionAvailable != null}">
|
|
||||||
<h2><span th:if="${gIcsConnectionAvailable.available}">✅</span><span th:if="${not(gIcsConnectionAvailable.available)}">⚡</span> Verbindung zu gICS</h2>
|
|
||||||
<div>
|
|
||||||
Stand: <time style="font-weight: bold" th:datetime="${#temporals.formatISO(gIcsConnectionAvailable.timestamp)}" th:text="${#temporals.formatISO(gIcsConnectionAvailable.timestamp)}"></time>
|
|
||||||
|
|
|
||||||
Letzte Änderung: <time style="font-weight: bold" th:datetime="${#temporals.formatISO(gIcsConnectionAvailable.lastChange)}" th:text="${#temporals.formatISO(gIcsConnectionAvailable.lastChange)}"></time>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>Die Verbindung ist aktuell</span>
|
|
||||||
<strong th:if="${gIcsConnectionAvailable.available}" style="color: green">verfügbar.</strong>
|
|
||||||
<strong th:if="${not(gIcsConnectionAvailable.available)}" style="color: red">nicht verfügbar.</strong>
|
|
||||||
</div>
|
|
||||||
<div class="connection-display border">
|
|
||||||
<img th:src="@{/server.png}" alt="ETL-Processor" />
|
|
||||||
<span class="connection" th:classappend="${gIcsConnectionAvailable.available ? 'available' : ''}"></span>
|
|
||||||
<img th:src="@{/server.png}" alt="gICS" />
|
|
||||||
<span>ETL-Processor</span>
|
|
||||||
<span></span>
|
|
||||||
<span>gICS</span>
|
|
||||||
</div>
|
|
||||||
</th:block>
|
|
@ -1,124 +0,0 @@
|
|||||||
package dev.dnpm.etl.processor.consent;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
|
|
||||||
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import dev.dnpm.etl.processor.config.AppConfiguration;
|
|
||||||
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
|
||||||
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Date;
|
|
||||||
import org.hl7.fhir.r4.model.BooleanType;
|
|
||||||
import org.hl7.fhir.r4.model.Identifier;
|
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
|
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome.IssueType;
|
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent;
|
|
||||||
import org.hl7.fhir.r4.model.Parameters;
|
|
||||||
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
|
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.TestPropertySource;
|
|
||||||
import org.springframework.test.web.client.MockRestServiceServer;
|
|
||||||
|
|
||||||
|
|
||||||
@ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class})
|
|
||||||
@TestPropertySource(properties = {"app.consent.gics.enabled=true",
|
|
||||||
"app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics"})
|
|
||||||
@RestClientTest
|
|
||||||
public class GicsConsentServiceTest {
|
|
||||||
|
|
||||||
public static final String GICS_BASE_URI = "http://localhost:8090/ttp-fhir/fhir/gics";
|
|
||||||
@Autowired
|
|
||||||
MockRestServiceServer mockRestServiceServer;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
GicsConsentService gicsConsentService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
AppConfiguration appConfiguration;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
AppFhirConfig appFhirConfig;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
GIcsConfigProperties gIcsConfigProperties;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setUp() {
|
|
||||||
mockRestServiceServer = MockRestServiceServer.createServer(appConfiguration.restTemplate());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getTtpConsentStatus() {
|
|
||||||
final Parameters responseConsented = new Parameters().addParameter(
|
|
||||||
new ParametersParameterComponent().setName("consented")
|
|
||||||
.setValue(new BooleanType().setValue(true)));
|
|
||||||
|
|
||||||
mockRestServiceServer.expect(
|
|
||||||
requestTo("http://localhost:8090/ttp-fhir/fhir/gics"
|
|
||||||
+ GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond(
|
|
||||||
withSuccess(appFhirConfig.fhirContext().newJsonParser()
|
|
||||||
.encodeResourceToString(responseConsented),
|
|
||||||
MediaType.APPLICATION_JSON));
|
|
||||||
|
|
||||||
var consentStatus = gicsConsentService.getTtpConsentStatus("123456");
|
|
||||||
assertThat(consentStatus).isEqualTo(TtpConsentStatus.CONSENTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void consentRevoced() {
|
|
||||||
final Parameters responseRevoced = new Parameters().addParameter(
|
|
||||||
new ParametersParameterComponent().setName("consented")
|
|
||||||
.setValue(new BooleanType().setValue(false)));
|
|
||||||
|
|
||||||
mockRestServiceServer.expect(
|
|
||||||
requestTo("http://localhost:8090/ttp-fhir/fhir/gics"
|
|
||||||
+ GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond(
|
|
||||||
withSuccess(appFhirConfig.fhirContext().newJsonParser()
|
|
||||||
.encodeResourceToString(responseRevoced),
|
|
||||||
MediaType.APPLICATION_JSON));
|
|
||||||
|
|
||||||
var consentStatus = gicsConsentService.getTtpConsentStatus("123456");
|
|
||||||
assertThat(consentStatus).isEqualTo(TtpConsentStatus.CONSENT_MISSING_OR_REJECTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void gicsParameterInvalid() {
|
|
||||||
final OperationOutcome responseErrorOutcome = new OperationOutcome().addIssue(
|
|
||||||
new OperationOutcomeIssueComponent().setSeverity(
|
|
||||||
IssueSeverity.ERROR).setCode(IssueType.PROCESSING)
|
|
||||||
.setDiagnostics("Invalid policy parameter..."));
|
|
||||||
|
|
||||||
mockRestServiceServer.expect(
|
|
||||||
requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond(
|
|
||||||
withSuccess(appFhirConfig.fhirContext().newJsonParser()
|
|
||||||
.encodeResourceToString(responseErrorOutcome),
|
|
||||||
MediaType.APPLICATION_JSON));
|
|
||||||
|
|
||||||
var consentStatus = gicsConsentService.getTtpConsentStatus("123456");
|
|
||||||
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void buildRequestParameterCurrentPolicyStatesForPersonTest() {
|
|
||||||
|
|
||||||
String pid = "12345678";
|
|
||||||
var result = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(gIcsConfigProperties,
|
|
||||||
pid, Date.from(Instant.now()),gIcsConfigProperties.getGnomDeConsentDomainName());
|
|
||||||
|
|
||||||
assertThat(result.getParameter().size()).as("should contain 3 parameter resources").isEqualTo(3);
|
|
||||||
|
|
||||||
assertThat(((StringType)result.getParameter("domain").getValue()).getValue()).isEqualTo(gIcsConfigProperties.getGnomDeConsentDomainName());
|
|
||||||
assertThat(((Identifier)result.getParameter("personIdentifier").getValue()).getValue()).isEqualTo(pid);
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,7 +23,6 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||||||
import de.ukw.ccc.bwhc.dto.Consent
|
import de.ukw.ccc.bwhc.dto.Consent
|
||||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||||
import de.ukw.ccc.bwhc.dto.Patient
|
import de.ukw.ccc.bwhc.dto.Patient
|
||||||
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
|
||||||
import dev.dnpm.etl.processor.CustomMediaType
|
import dev.dnpm.etl.processor.CustomMediaType
|
||||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||||
@ -35,7 +34,10 @@ import org.junit.jupiter.api.Test
|
|||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.junit.jupiter.MockitoExtension
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
import org.mockito.kotlin.*
|
import org.mockito.kotlin.any
|
||||||
|
import org.mockito.kotlin.anyValueClass
|
||||||
|
import org.mockito.kotlin.times
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension::class)
|
@ExtendWith(MockitoExtension::class)
|
||||||
@ -47,7 +49,7 @@ class KafkaInputListenerTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup(
|
fun setup(
|
||||||
@Mock requestProcessor: RequestProcessor,
|
@Mock requestProcessor: RequestProcessor
|
||||||
) {
|
) {
|
||||||
this.requestProcessor = requestProcessor
|
this.requestProcessor = requestProcessor
|
||||||
this.objectMapper = ObjectMapper()
|
this.objectMapper = ObjectMapper()
|
||||||
@ -92,10 +94,7 @@ class KafkaInputListenerTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
verify(requestProcessor, times(1)).processDeletion(
|
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
|
||||||
anyValueClass(),
|
|
||||||
eq(TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -148,8 +147,7 @@ class KafkaInputListenerTest {
|
|||||||
Optional.empty()
|
Optional.empty()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
verify(requestProcessor, times(1)).processDeletion(anyValueClass(), anyValueClass(), eq(
|
verify(requestProcessor, times(1)).processDeletion(anyValueClass(), anyValueClass())
|
||||||
TtpConsentStatus.UNKNOWN_CHECK_FILE))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -180,8 +178,7 @@ class KafkaInputListenerTest {
|
|||||||
Optional.empty()
|
Optional.empty()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
verify(requestProcessor, times(0)).processDeletion(anyValueClass(), anyValueClass(), eq(
|
verify(requestProcessor, times(0)).processDeletion(anyValueClass(), anyValueClass())
|
||||||
TtpConsentStatus.UNKNOWN_CHECK_FILE))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,29 +21,21 @@ package dev.dnpm.etl.processor.input
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import de.ukw.ccc.bwhc.dto.*
|
import de.ukw.ccc.bwhc.dto.*
|
||||||
import de.ukw.ccc.bwhc.dto.Consent.Status
|
|
||||||
import dev.dnpm.etl.processor.CustomMediaType
|
import dev.dnpm.etl.processor.CustomMediaType
|
||||||
import dev.dnpm.etl.processor.consent.ConsentCheckFileBased
|
|
||||||
import dev.dnpm.etl.processor.consent.GicsConsentService
|
|
||||||
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
|
||||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.junit.jupiter.params.ParameterizedTest
|
|
||||||
import org.junit.jupiter.params.provider.ValueSource
|
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.times
|
import org.mockito.Mockito.times
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.junit.jupiter.MockitoExtension
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
import org.mockito.kotlin.any
|
import org.mockito.kotlin.any
|
||||||
import org.mockito.kotlin.anyValueClass
|
import org.mockito.kotlin.anyValueClass
|
||||||
import org.mockito.kotlin.whenever
|
|
||||||
import org.springframework.core.io.ClassPathResource
|
import org.springframework.core.io.ClassPathResource
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.test.context.TestPropertySource
|
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
import org.springframework.test.web.servlet.delete
|
import org.springframework.test.web.servlet.delete
|
||||||
import org.springframework.test.web.servlet.post
|
import org.springframework.test.web.servlet.post
|
||||||
@ -61,22 +53,19 @@ class MtbFileRestControllerTest {
|
|||||||
|
|
||||||
private lateinit var requestProcessor: RequestProcessor
|
private lateinit var requestProcessor: RequestProcessor
|
||||||
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup(
|
fun setup(
|
||||||
@Mock requestProcessor: RequestProcessor
|
@Mock requestProcessor: RequestProcessor
|
||||||
) {
|
) {
|
||||||
this.requestProcessor = requestProcessor
|
this.requestProcessor = requestProcessor
|
||||||
val controller = MtbFileRestController(requestProcessor,
|
val controller = MtbFileRestController(requestProcessor)
|
||||||
ConsentCheckFileBased()
|
|
||||||
)
|
|
||||||
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldProcessPostRequest() {
|
fun shouldProcessPostRequest() {
|
||||||
mockMvc.post("/mtbfile") {
|
mockMvc.post("/mtbfile") {
|
||||||
content = objectMapper.writeValueAsString(bwhcMtbFileContent(Status.ACTIVE))
|
content = objectMapper.writeValueAsString(bwhcMtbFileContent(Consent.Status.ACTIVE))
|
||||||
contentType = MediaType.APPLICATION_JSON
|
contentType = MediaType.APPLICATION_JSON
|
||||||
}.andExpect {
|
}.andExpect {
|
||||||
status {
|
status {
|
||||||
@ -90,8 +79,7 @@ class MtbFileRestControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
fun shouldProcessPostRequestWithRejectedConsent() {
|
fun shouldProcessPostRequestWithRejectedConsent() {
|
||||||
mockMvc.post("/mtbfile") {
|
mockMvc.post("/mtbfile") {
|
||||||
content =
|
content = objectMapper.writeValueAsString(bwhcMtbFileContent(Consent.Status.REJECTED))
|
||||||
objectMapper.writeValueAsString(bwhcMtbFileContent(Status.REJECTED))
|
|
||||||
contentType = MediaType.APPLICATION_JSON
|
contentType = MediaType.APPLICATION_JSON
|
||||||
}.andExpect {
|
}.andExpect {
|
||||||
status {
|
status {
|
||||||
@ -99,10 +87,7 @@ class MtbFileRestControllerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(requestProcessor, times(1)).processDeletion(
|
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
|
||||||
anyValueClass(),
|
|
||||||
org.mockito.kotlin.eq(TtpConsentStatus.CONSENT_MISSING_OR_REJECTED)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -113,100 +98,10 @@ class MtbFileRestControllerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(requestProcessor, times(1)).processDeletion(
|
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
|
||||||
anyValueClass(),
|
|
||||||
org.mockito.kotlin.eq(TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestPropertySource(
|
|
||||||
properties = ["app.consent.gics.enabled=true",
|
|
||||||
"app.consent.gics.gIcsBaseUri=http://localhost:8090/ttp-fhir/fhir/gics"]
|
|
||||||
)
|
|
||||||
@Nested
|
|
||||||
inner class BwhcRequestsCheckConsentViaTtp {
|
|
||||||
|
|
||||||
private lateinit var mockMvc: MockMvc
|
|
||||||
|
|
||||||
private lateinit var requestProcessor: RequestProcessor
|
|
||||||
|
|
||||||
private lateinit var gicsConsentService: GicsConsentService
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun setup(
|
|
||||||
@Mock requestProcessor: RequestProcessor,
|
|
||||||
@Mock gicsConsentService: GicsConsentService
|
|
||||||
) {
|
|
||||||
this.requestProcessor = requestProcessor
|
|
||||||
val controller = MtbFileRestController(requestProcessor, gicsConsentService)
|
|
||||||
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
|
||||||
this.gicsConsentService = gicsConsentService
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@ValueSource(strings = ["ACTIVE", "REJECTED"])
|
|
||||||
fun shouldProcessPostRequest(status: String) {
|
|
||||||
|
|
||||||
whenever(gicsConsentService.getTtpConsentStatus(any())).thenReturn(TtpConsentStatus.CONSENTED)
|
|
||||||
|
|
||||||
mockMvc.post("/mtbfile") {
|
|
||||||
content =
|
|
||||||
objectMapper.writeValueAsString(bwhcMtbFileContent(Status.valueOf(status)))
|
|
||||||
contentType = MediaType.APPLICATION_JSON
|
|
||||||
}.andExpect {
|
|
||||||
status {
|
|
||||||
isAccepted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
verify(requestProcessor, times(1)).processMtbFile(any<MtbFile>())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@ValueSource(strings = ["ACTIVE", "REJECTED"])
|
|
||||||
fun shouldProcessPostRequestWithRejectedConsent(status: String) {
|
|
||||||
|
|
||||||
whenever(gicsConsentService.getTtpConsentStatus(any())).thenReturn(TtpConsentStatus.CONSENT_MISSING_OR_REJECTED)
|
|
||||||
|
|
||||||
mockMvc.post("/mtbfile") {
|
|
||||||
content =
|
|
||||||
objectMapper.writeValueAsString(bwhcMtbFileContent(Status.valueOf(status)))
|
|
||||||
contentType = MediaType.APPLICATION_JSON
|
|
||||||
}.andExpect {
|
|
||||||
status {
|
|
||||||
isAccepted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// consent status from ttp should override file consent value
|
|
||||||
verify(requestProcessor, times(1)).processDeletion(
|
|
||||||
anyValueClass(),
|
|
||||||
org.mockito.kotlin.eq(TtpConsentStatus.CONSENT_MISSING_OR_REJECTED)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun shouldProcessDeleteRequest() {
|
|
||||||
|
|
||||||
mockMvc.delete("/mtbfile/TEST_12345678").andExpect {
|
|
||||||
status {
|
|
||||||
isAccepted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
verify(requestProcessor, times(1)).processDeletion(
|
|
||||||
anyValueClass(),
|
|
||||||
org.mockito.kotlin.eq(TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
|
||||||
)
|
|
||||||
verify(gicsConsentService, times(0)).getTtpConsentStatus(any())
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
inner class BwhcRequestsWithAlias {
|
inner class BwhcRequestsWithAlias {
|
||||||
|
|
||||||
@ -219,16 +114,14 @@ class MtbFileRestControllerTest {
|
|||||||
@Mock requestProcessor: RequestProcessor
|
@Mock requestProcessor: RequestProcessor
|
||||||
) {
|
) {
|
||||||
this.requestProcessor = requestProcessor
|
this.requestProcessor = requestProcessor
|
||||||
val controller = MtbFileRestController(requestProcessor,
|
val controller = MtbFileRestController(requestProcessor)
|
||||||
ConsentCheckFileBased()
|
|
||||||
)
|
|
||||||
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldProcessPostRequest() {
|
fun shouldProcessPostRequest() {
|
||||||
mockMvc.post("/mtb") {
|
mockMvc.post("/mtb") {
|
||||||
content = objectMapper.writeValueAsString(bwhcMtbFileContent(Status.ACTIVE))
|
content = objectMapper.writeValueAsString(bwhcMtbFileContent(Consent.Status.ACTIVE))
|
||||||
contentType = MediaType.APPLICATION_JSON
|
contentType = MediaType.APPLICATION_JSON
|
||||||
}.andExpect {
|
}.andExpect {
|
||||||
status {
|
status {
|
||||||
@ -242,8 +135,7 @@ class MtbFileRestControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
fun shouldProcessPostRequestWithRejectedConsent() {
|
fun shouldProcessPostRequestWithRejectedConsent() {
|
||||||
mockMvc.post("/mtb") {
|
mockMvc.post("/mtb") {
|
||||||
content =
|
content = objectMapper.writeValueAsString(bwhcMtbFileContent(Consent.Status.REJECTED))
|
||||||
objectMapper.writeValueAsString(bwhcMtbFileContent(Status.REJECTED))
|
|
||||||
contentType = MediaType.APPLICATION_JSON
|
contentType = MediaType.APPLICATION_JSON
|
||||||
}.andExpect {
|
}.andExpect {
|
||||||
status {
|
status {
|
||||||
@ -251,11 +143,7 @@ class MtbFileRestControllerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(requestProcessor, times(1)).processDeletion(
|
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
|
||||||
anyValueClass(), org.mockito.kotlin.eq(
|
|
||||||
TtpConsentStatus.CONSENT_MISSING_OR_REJECTED
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -266,11 +154,7 @@ class MtbFileRestControllerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(requestProcessor, times(1)).processDeletion(
|
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
|
||||||
anyValueClass(), org.mockito.kotlin.eq(
|
|
||||||
TtpConsentStatus.UNKNOWN_CHECK_FILE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,17 +170,13 @@ class MtbFileRestControllerTest {
|
|||||||
@Mock requestProcessor: RequestProcessor
|
@Mock requestProcessor: RequestProcessor
|
||||||
) {
|
) {
|
||||||
this.requestProcessor = requestProcessor
|
this.requestProcessor = requestProcessor
|
||||||
val controller = MtbFileRestController(requestProcessor,
|
val controller = MtbFileRestController(requestProcessor)
|
||||||
ConsentCheckFileBased()
|
|
||||||
)
|
|
||||||
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldRespondPostRequest() {
|
fun shouldRespondPostRequest() {
|
||||||
val mtbFileContent =
|
val mtbFileContent = ClassPathResource("mv64e-mtb-fake-patient.json").inputStream.readAllBytes().toString(Charsets.UTF_8)
|
||||||
ClassPathResource("mv64e-mtb-fake-patient.json").inputStream.readAllBytes()
|
|
||||||
.toString(Charsets.UTF_8)
|
|
||||||
|
|
||||||
mockMvc.post("/mtb") {
|
mockMvc.post("/mtb") {
|
||||||
content = mtbFileContent
|
content = mtbFileContent
|
||||||
@ -313,7 +193,7 @@ class MtbFileRestControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun bwhcMtbFileContent(consentStatus: Status) = MtbFile.builder()
|
fun bwhcMtbFileContent(consentStatus: Consent.Status) = MtbFile.builder()
|
||||||
.withPatient(
|
.withPatient(
|
||||||
Patient.builder()
|
Patient.builder()
|
||||||
.withId("TEST_12345678")
|
.withId("TEST_12345678")
|
||||||
|
@ -22,7 +22,6 @@ package dev.dnpm.etl.processor.pseudonym
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import de.ukw.ccc.bwhc.dto.*
|
import de.ukw.ccc.bwhc.dto.*
|
||||||
import de.ukw.ccc.bwhc.dto.Patient
|
import de.ukw.ccc.bwhc.dto.Patient
|
||||||
import dev.dnpm.etl.processor.config.JacksonConfig
|
|
||||||
import dev.pcvolkmer.mv64e.mtb.*
|
import dev.pcvolkmer.mv64e.mtb.*
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
@ -40,9 +39,6 @@ import java.util.*
|
|||||||
|
|
||||||
@ExtendWith(MockitoExtension::class)
|
@ExtendWith(MockitoExtension::class)
|
||||||
class ExtensionsTest {
|
class ExtensionsTest {
|
||||||
fun getObjectMapper() : ObjectMapper {
|
|
||||||
return JacksonConfig().objectMapper()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
inner class UsingBwhcDatamodel {
|
inner class UsingBwhcDatamodel {
|
||||||
@ -50,14 +46,13 @@ class ExtensionsTest {
|
|||||||
val FAKE_MTB_FILE_PATH = "fake_MTBFile.json"
|
val FAKE_MTB_FILE_PATH = "fake_MTBFile.json"
|
||||||
val CLEAN_PATIENT_ID = "5dad2f0b-49c6-47d8-a952-7b9e9e0f7549"
|
val CLEAN_PATIENT_ID = "5dad2f0b-49c6-47d8-a952-7b9e9e0f7549"
|
||||||
|
|
||||||
|
|
||||||
private fun fakeMtbFile(): MtbFile {
|
private fun fakeMtbFile(): MtbFile {
|
||||||
val mtbFile = ClassPathResource(FAKE_MTB_FILE_PATH).inputStream
|
val mtbFile = ClassPathResource(FAKE_MTB_FILE_PATH).inputStream
|
||||||
return getObjectMapper().readValue(mtbFile, MtbFile::class.java)
|
return ObjectMapper().readValue(mtbFile, MtbFile::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MtbFile.serialized(): String {
|
private fun MtbFile.serialized(): String {
|
||||||
return getObjectMapper().writeValueAsString(this)
|
return ObjectMapper().writeValueAsString(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -212,15 +207,15 @@ class ExtensionsTest {
|
|||||||
inner class UsingDnpmV2Datamodel {
|
inner class UsingDnpmV2Datamodel {
|
||||||
|
|
||||||
val FAKE_MTB_FILE_PATH = "mv64e-mtb-fake-patient.json"
|
val FAKE_MTB_FILE_PATH = "mv64e-mtb-fake-patient.json"
|
||||||
val CLEAN_PATIENT_ID = "e14bf9b6-7982-4933-a648-cfdea6484f1c"
|
val CLEAN_PATIENT_ID = "aca5a971-28be-4089-8128-0036a4fe6f1a"
|
||||||
|
|
||||||
private fun fakeMtbFile(): Mtb {
|
private fun fakeMtbFile(): Mtb {
|
||||||
val mtbFile = ClassPathResource(FAKE_MTB_FILE_PATH).inputStream
|
val mtbFile = ClassPathResource(FAKE_MTB_FILE_PATH).inputStream
|
||||||
return getObjectMapper().readValue(mtbFile, Mtb::class.java)
|
return ObjectMapper().readValue(mtbFile, Mtb::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Mtb.serialized(): String {
|
private fun Mtb.serialized(): String {
|
||||||
return getObjectMapper().writeValueAsString(this)
|
return ObjectMapper().writeValueAsString(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -25,8 +25,6 @@ import dev.dnpm.etl.processor.Fingerprint
|
|||||||
import dev.dnpm.etl.processor.PatientId
|
import dev.dnpm.etl.processor.PatientId
|
||||||
import dev.dnpm.etl.processor.PatientPseudonym
|
import dev.dnpm.etl.processor.PatientPseudonym
|
||||||
import dev.dnpm.etl.processor.config.AppConfigProperties
|
import dev.dnpm.etl.processor.config.AppConfigProperties
|
||||||
import dev.dnpm.etl.processor.consent.GicsConsentService
|
|
||||||
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
|
||||||
import dev.dnpm.etl.processor.monitoring.Request
|
import dev.dnpm.etl.processor.monitoring.Request
|
||||||
import dev.dnpm.etl.processor.monitoring.RequestStatus
|
import dev.dnpm.etl.processor.monitoring.RequestStatus
|
||||||
import dev.dnpm.etl.processor.monitoring.RequestType
|
import dev.dnpm.etl.processor.monitoring.RequestType
|
||||||
@ -60,7 +58,7 @@ class RequestProcessorTest {
|
|||||||
private lateinit var requestService: RequestService
|
private lateinit var requestService: RequestService
|
||||||
private lateinit var applicationEventPublisher: ApplicationEventPublisher
|
private lateinit var applicationEventPublisher: ApplicationEventPublisher
|
||||||
private lateinit var appConfigProperties: AppConfigProperties
|
private lateinit var appConfigProperties: AppConfigProperties
|
||||||
private lateinit var gicsConsentService : GicsConsentService
|
|
||||||
private lateinit var requestProcessor: RequestProcessor
|
private lateinit var requestProcessor: RequestProcessor
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@ -69,8 +67,7 @@ class RequestProcessorTest {
|
|||||||
@Mock transformationService: TransformationService,
|
@Mock transformationService: TransformationService,
|
||||||
@Mock sender: RestMtbFileSender,
|
@Mock sender: RestMtbFileSender,
|
||||||
@Mock requestService: RequestService,
|
@Mock requestService: RequestService,
|
||||||
@Mock applicationEventPublisher: ApplicationEventPublisher,
|
@Mock applicationEventPublisher: ApplicationEventPublisher
|
||||||
@Mock gicsConsentService: GicsConsentService
|
|
||||||
) {
|
) {
|
||||||
this.pseudonymizeService = pseudonymizeService
|
this.pseudonymizeService = pseudonymizeService
|
||||||
this.transformationService = transformationService
|
this.transformationService = transformationService
|
||||||
@ -78,7 +75,6 @@ class RequestProcessorTest {
|
|||||||
this.requestService = requestService
|
this.requestService = requestService
|
||||||
this.applicationEventPublisher = applicationEventPublisher
|
this.applicationEventPublisher = applicationEventPublisher
|
||||||
this.appConfigProperties = AppConfigProperties(null)
|
this.appConfigProperties = AppConfigProperties(null)
|
||||||
this.gicsConsentService = gicsConsentService
|
|
||||||
|
|
||||||
val objectMapper = ObjectMapper()
|
val objectMapper = ObjectMapper()
|
||||||
|
|
||||||
@ -89,8 +85,7 @@ class RequestProcessorTest {
|
|||||||
requestService,
|
requestService,
|
||||||
objectMapper,
|
objectMapper,
|
||||||
applicationEventPublisher,
|
applicationEventPublisher,
|
||||||
appConfigProperties,
|
appConfigProperties
|
||||||
gicsConsentService
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +343,7 @@ class RequestProcessorTest {
|
|||||||
MtbFileSender.Response(status = RequestStatus.UNKNOWN)
|
MtbFileSender.Response(status = RequestStatus.UNKNOWN)
|
||||||
}.whenever(sender).send(any<DeleteRequest>())
|
}.whenever(sender).send(any<DeleteRequest>())
|
||||||
|
|
||||||
this.requestProcessor.processDeletion(TEST_PATIENT_ID, isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
this.requestProcessor.processDeletion(TEST_PATIENT_ID)
|
||||||
|
|
||||||
val requestCaptor = argumentCaptor<Request>()
|
val requestCaptor = argumentCaptor<Request>()
|
||||||
verify(requestService, times(1)).save(requestCaptor.capture())
|
verify(requestService, times(1)).save(requestCaptor.capture())
|
||||||
@ -366,7 +361,7 @@ class RequestProcessorTest {
|
|||||||
MtbFileSender.Response(status = RequestStatus.SUCCESS)
|
MtbFileSender.Response(status = RequestStatus.SUCCESS)
|
||||||
}.whenever(sender).send(any<DeleteRequest>())
|
}.whenever(sender).send(any<DeleteRequest>())
|
||||||
|
|
||||||
this.requestProcessor.processDeletion(TEST_PATIENT_ID, isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
this.requestProcessor.processDeletion(TEST_PATIENT_ID)
|
||||||
|
|
||||||
val eventCaptor = argumentCaptor<ResponseEvent>()
|
val eventCaptor = argumentCaptor<ResponseEvent>()
|
||||||
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
|
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
|
||||||
@ -384,7 +379,7 @@ class RequestProcessorTest {
|
|||||||
MtbFileSender.Response(status = RequestStatus.ERROR)
|
MtbFileSender.Response(status = RequestStatus.ERROR)
|
||||||
}.whenever(sender).send(any<DeleteRequest>())
|
}.whenever(sender).send(any<DeleteRequest>())
|
||||||
|
|
||||||
this.requestProcessor.processDeletion(TEST_PATIENT_ID, isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
this.requestProcessor.processDeletion(TEST_PATIENT_ID)
|
||||||
|
|
||||||
val eventCaptor = argumentCaptor<ResponseEvent>()
|
val eventCaptor = argumentCaptor<ResponseEvent>()
|
||||||
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
|
verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
|
||||||
@ -396,7 +391,7 @@ class RequestProcessorTest {
|
|||||||
fun testShouldSendDeleteRequestWithPseudonymErrorAndSaveErrorRequestStatus() {
|
fun testShouldSendDeleteRequestWithPseudonymErrorAndSaveErrorRequestStatus() {
|
||||||
doThrow(RuntimeException()).whenever(pseudonymizeService).patientPseudonym(anyValueClass())
|
doThrow(RuntimeException()).whenever(pseudonymizeService).patientPseudonym(anyValueClass())
|
||||||
|
|
||||||
this.requestProcessor.processDeletion(TEST_PATIENT_ID, isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE)
|
this.requestProcessor.processDeletion(TEST_PATIENT_ID)
|
||||||
|
|
||||||
val requestCaptor = argumentCaptor<Request>()
|
val requestCaptor = argumentCaptor<Request>()
|
||||||
verify(requestService, times(1)).save(requestCaptor.capture())
|
verify(requestService, times(1)).save(requestCaptor.capture())
|
||||||
|
@ -19,22 +19,14 @@
|
|||||||
|
|
||||||
package dev.dnpm.etl.processor.services
|
package dev.dnpm.etl.processor.services
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import de.ukw.ccc.bwhc.dto.Consent
|
import de.ukw.ccc.bwhc.dto.Consent
|
||||||
import de.ukw.ccc.bwhc.dto.Diagnosis
|
import de.ukw.ccc.bwhc.dto.Diagnosis
|
||||||
import de.ukw.ccc.bwhc.dto.Icd10
|
import de.ukw.ccc.bwhc.dto.Icd10
|
||||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||||
import dev.dnpm.etl.processor.config.JacksonConfig
|
|
||||||
import dev.pcvolkmer.mv64e.mtb.ModelProjectConsent
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
|
||||||
import dev.pcvolkmer.mv64e.mtb.MvhMetadata
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource
|
|
||||||
import org.hl7.fhir.r4.model.CodeableConcept
|
|
||||||
import org.hl7.fhir.r4.model.Coding
|
|
||||||
import java.time.Instant
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class TransformationServiceTest {
|
class TransformationServiceTest {
|
||||||
|
|
||||||
@ -43,7 +35,7 @@ class TransformationServiceTest {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
this.service = TransformationService(
|
this.service = TransformationService(
|
||||||
JacksonConfig().objectMapper(), listOf(
|
ObjectMapper(), listOf(
|
||||||
Transformation.of("consent.status") from Consent.Status.ACTIVE to Consent.Status.REJECTED,
|
Transformation.of("consent.status") from Consent.Status.ACTIVE to Consent.Status.REJECTED,
|
||||||
Transformation.of("diagnoses[*].icd10.version") from "2013" to "2014",
|
Transformation.of("diagnoses[*].icd10.version") from "2013" to "2014",
|
||||||
)
|
)
|
||||||
@ -100,79 +92,4 @@ class TransformationServiceTest {
|
|||||||
assertThat(actual.consent.status).isEqualTo(Consent.Status.REJECTED)
|
assertThat(actual.consent.status).isEqualTo(Consent.Status.REJECTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun shouldTransformConsentValues() {
|
|
||||||
val mtbFile = MtbFile.builder().withDiagnoses(
|
|
||||||
listOf(
|
|
||||||
Diagnosis.builder().withId("1234").withIcd10(Icd10("F79.9").also {
|
|
||||||
it.version = "2013"
|
|
||||||
}).build(),
|
|
||||||
Diagnosis.builder().withId("5678").withIcd10(Icd10("F79.8").also {
|
|
||||||
it.version = "2019"
|
|
||||||
}).build()
|
|
||||||
)
|
|
||||||
).build()
|
|
||||||
|
|
||||||
val actual = this.service.transform(mtbFile)
|
|
||||||
|
|
||||||
assertThat(actual).isNotNull
|
|
||||||
assertThat(actual.diagnoses[0].icd10.code).isEqualTo("F79.9")
|
|
||||||
assertThat(actual.diagnoses[0].icd10.version).isEqualTo("2014")
|
|
||||||
assertThat(actual.diagnoses[1].icd10.code).isEqualTo("F79.8")
|
|
||||||
assertThat(actual.diagnoses[1].icd10.version).isEqualTo("2019")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun shouldTransformConsent() {
|
|
||||||
val mvhMetadata = MvhMetadata.builder().transferTan("transfertan12345").build();
|
|
||||||
|
|
||||||
assertThat(mvhMetadata).isNotNull
|
|
||||||
mvhMetadata.modelProjectConsent =
|
|
||||||
ModelProjectConsent.builder().date(Date.from(Instant.now())).version("1").build()
|
|
||||||
val consent1 = org.hl7.fhir.r4.model.Consent()
|
|
||||||
consent1.id = "consent 1 id"
|
|
||||||
consent1.patient.reference = "Patient/1234-pat1"
|
|
||||||
|
|
||||||
consent1.provision.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("deny"))
|
|
||||||
consent1.provision.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
|
||||||
consent1.provision.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
|
||||||
|
|
||||||
|
|
||||||
val addProvision1 = consent1.provision.addProvision()
|
|
||||||
addProvision1.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("permit"))
|
|
||||||
addProvision1.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
|
||||||
addProvision1.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
|
||||||
addProvision1.code.addLast(
|
|
||||||
CodeableConcept(
|
|
||||||
Coding(
|
|
||||||
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"Teilnahme",
|
|
||||||
"Teilnahme am Modellvorhaben und Einwilligung zur Genomsequenzierung"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val addProvision2 = consent1.provision.addProvision()
|
|
||||||
addProvision2.setType(org.hl7.fhir.r4.model.Consent.ConsentProvisionType.fromCode("deny"))
|
|
||||||
addProvision2.period.start = Date.from(Instant.parse("2025-06-23T00:00:00.00Z"))
|
|
||||||
addProvision2.period.end = Date.from(Instant.parse("3000-01-01T00:00:00.00Z"))
|
|
||||||
addProvision2.code.addLast(
|
|
||||||
CodeableConcept(
|
|
||||||
Coding(
|
|
||||||
"https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"Rekontaktierung",
|
|
||||||
"Re-Identifizierung meiner Daten über die Vertrauensstelle beim Robert Koch-Institut und in die erneute Kontaktaufnahme durch meine behandelnde Ärztin oder meinen behandelnden Arzt"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
mvhMetadata.researchConsents = mutableListOf()
|
|
||||||
mvhMetadata.researchConsents.add(mapOf(consent1.id to consent1 as IBaseResource))
|
|
||||||
|
|
||||||
val mtbFile = Mtb.builder().metadata(mvhMetadata).build()
|
|
||||||
|
|
||||||
val transformed = service.transform(mtbFile)
|
|
||||||
assertThat(transformed.metadata.modelProjectConsent.date).isNotNull
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,333 +0,0 @@
|
|||||||
{
|
|
||||||
"resourceType": "Bundle",
|
|
||||||
"type": "collection",
|
|
||||||
"entry": [
|
|
||||||
{
|
|
||||||
"fullUrl": "http://127.0.0.1:8090/ttp-fhir/fhir/gics/Consent/24673204-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"resource": {
|
|
||||||
"resourceType": "Consent",
|
|
||||||
"id": "24673204-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"meta": {
|
|
||||||
"lastUpdated": "2025-06-24T11:58:27.178+02:00",
|
|
||||||
"profile": [
|
|
||||||
"http://fhir.de/ConsentManagement/StructureDefinition/Consent"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "http://fhir.de/ConsentManagement/StructureDefinition/DomainReference",
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "domain",
|
|
||||||
"valueReference": {
|
|
||||||
"reference": "ResearchStudy/ef86d80e-50e0-11f0-a144-661e92ac9503"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "status",
|
|
||||||
"valueCoding": {
|
|
||||||
"system": "http://hl7.org/fhir/publication-status",
|
|
||||||
"code": "active"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "active",
|
|
||||||
"scope": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://terminology.hl7.org/CodeSystem/consentscope",
|
|
||||||
"code": "research"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"category": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://loinc.org",
|
|
||||||
"code": "59284-0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://fhir.de/ConsentManagement/CodeSystem/ResultType",
|
|
||||||
"code": "policy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"patient": {
|
|
||||||
"reference": "Patient/2466d49b-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"display": "Patienten-ID 999999"
|
|
||||||
},
|
|
||||||
"dateTime": "2025-06-24T00:00:00+02:00",
|
|
||||||
"organization": [
|
|
||||||
{
|
|
||||||
"display": "GenomDE_MV"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sourceReference": {
|
|
||||||
"reference": "QuestionnaireResponse/24670c77-50e1-11f0-a144-661e92ac9503"
|
|
||||||
},
|
|
||||||
"policyRule": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Teilnahme",
|
|
||||||
"display": "Teilnahme am Modellvorhaben und Einwilligung zur Genomsequenzierung"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"provision": {
|
|
||||||
"type": "deny",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"provision": [
|
|
||||||
{
|
|
||||||
"type": "deny",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"code": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Teilnahme",
|
|
||||||
"display": "Teilnahme am Modellvorhaben und Einwilligung zur Genomsequenzierung"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fullUrl": "http://127.0.0.1:8090/ttp-fhir/fhir/gics/Consent/24673913-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"resource": {
|
|
||||||
"resourceType": "Consent",
|
|
||||||
"id": "24673913-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"meta": {
|
|
||||||
"lastUpdated": "2025-06-24T11:58:27.194+02:00",
|
|
||||||
"profile": [
|
|
||||||
"http://fhir.de/ConsentManagement/StructureDefinition/Consent"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "http://fhir.de/ConsentManagement/StructureDefinition/DomainReference",
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "domain",
|
|
||||||
"valueReference": {
|
|
||||||
"reference": "ResearchStudy/ef86d80e-50e0-11f0-a144-661e92ac9503"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "status",
|
|
||||||
"valueCoding": {
|
|
||||||
"system": "http://hl7.org/fhir/publication-status",
|
|
||||||
"code": "active"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "active",
|
|
||||||
"scope": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://terminology.hl7.org/CodeSystem/consentscope",
|
|
||||||
"code": "research"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"category": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://loinc.org",
|
|
||||||
"code": "59284-0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://fhir.de/ConsentManagement/CodeSystem/ResultType",
|
|
||||||
"code": "policy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"patient": {
|
|
||||||
"reference": "Patient/2466d49b-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"display": "Patienten-ID 999999"
|
|
||||||
},
|
|
||||||
"dateTime": "2025-06-24T00:00:00+02:00",
|
|
||||||
"organization": [
|
|
||||||
{
|
|
||||||
"display": "GenomDE_MV"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sourceReference": {
|
|
||||||
"reference": "QuestionnaireResponse/24670c77-50e1-11f0-a144-661e92ac9503"
|
|
||||||
},
|
|
||||||
"policyRule": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Fallidentifizierung",
|
|
||||||
"display": "Fallidentifizierung zum fachlichen Austausch unter Behandelnden"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"provision": {
|
|
||||||
"type": "deny",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"provision": [
|
|
||||||
{
|
|
||||||
"type": "deny",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"code": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Fallidentifizierung",
|
|
||||||
"display": "Fallidentifizierung zum fachlichen Austausch unter Behandelnden"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fullUrl": "http://127.0.0.1:8090/ttp-fhir/fhir/gics/Consent/24673da9-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"resource": {
|
|
||||||
"resourceType": "Consent",
|
|
||||||
"id": "24673da9-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"meta": {
|
|
||||||
"lastUpdated": "2025-06-24T11:58:27.211+02:00",
|
|
||||||
"profile": [
|
|
||||||
"http://fhir.de/ConsentManagement/StructureDefinition/Consent"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "http://fhir.de/ConsentManagement/StructureDefinition/DomainReference",
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "domain",
|
|
||||||
"valueReference": {
|
|
||||||
"reference": "ResearchStudy/ef86d80e-50e0-11f0-a144-661e92ac9503"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "status",
|
|
||||||
"valueCoding": {
|
|
||||||
"system": "http://hl7.org/fhir/publication-status",
|
|
||||||
"code": "active"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "active",
|
|
||||||
"scope": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://terminology.hl7.org/CodeSystem/consentscope",
|
|
||||||
"code": "research"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"category": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://loinc.org",
|
|
||||||
"code": "59284-0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://fhir.de/ConsentManagement/CodeSystem/ResultType",
|
|
||||||
"code": "policy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"patient": {
|
|
||||||
"reference": "Patient/2466d49b-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"display": "Patienten-ID 999999"
|
|
||||||
},
|
|
||||||
"dateTime": "2025-06-24T00:00:00+02:00",
|
|
||||||
"organization": [
|
|
||||||
{
|
|
||||||
"display": "GenomDE_MV"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sourceReference": {
|
|
||||||
"reference": "QuestionnaireResponse/24670c77-50e1-11f0-a144-661e92ac9503"
|
|
||||||
},
|
|
||||||
"policyRule": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Rekontaktierung",
|
|
||||||
"display": "Re-Identifizierung meiner Daten über die Vertrauensstelle beim Robert Koch-Institut und in die erneute Kontaktaufnahme durch meine behandelnde Ärztin oder meinen behandelnden Arzt"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"provision": {
|
|
||||||
"type": "deny",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"provision": [
|
|
||||||
{
|
|
||||||
"type": "deny",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"code": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Rekontaktierung",
|
|
||||||
"display": "Re-Identifizierung meiner Daten über die Vertrauensstelle beim Robert Koch-Institut und in die erneute Kontaktaufnahme durch meine behandelnde Ärztin oder meinen behandelnden Arzt"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,333 +0,0 @@
|
|||||||
{
|
|
||||||
"resourceType": "Bundle",
|
|
||||||
"type": "collection",
|
|
||||||
"entry": [
|
|
||||||
{
|
|
||||||
"fullUrl": "http://127.0.0.1:8090/ttp-fhir/fhir/gics/Consent/121a8368-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"resource": {
|
|
||||||
"resourceType": "Consent",
|
|
||||||
"id": "121a8368-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"meta": {
|
|
||||||
"lastUpdated": "2025-06-24T11:55:42.079+02:00",
|
|
||||||
"profile": [
|
|
||||||
"http://fhir.de/ConsentManagement/StructureDefinition/Consent"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "http://fhir.de/ConsentManagement/StructureDefinition/DomainReference",
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "domain",
|
|
||||||
"valueReference": {
|
|
||||||
"reference": "ResearchStudy/ef86d80e-50e0-11f0-a144-661e92ac9503"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "status",
|
|
||||||
"valueCoding": {
|
|
||||||
"system": "http://hl7.org/fhir/publication-status",
|
|
||||||
"code": "active"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "active",
|
|
||||||
"scope": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://terminology.hl7.org/CodeSystem/consentscope",
|
|
||||||
"code": "research"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"category": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://loinc.org",
|
|
||||||
"code": "59284-0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://fhir.de/ConsentManagement/CodeSystem/ResultType",
|
|
||||||
"code": "policy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"patient": {
|
|
||||||
"reference": "Patient/12194791-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"display": "Patienten-ID 12345678"
|
|
||||||
},
|
|
||||||
"dateTime": "2025-06-24T00:00:00+02:00",
|
|
||||||
"organization": [
|
|
||||||
{
|
|
||||||
"display": "GenomDE_MV"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sourceReference": {
|
|
||||||
"reference": "QuestionnaireResponse/1219ca42-50e1-11f0-a144-661e92ac9503"
|
|
||||||
},
|
|
||||||
"policyRule": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Teilnahme",
|
|
||||||
"display": "Teilnahme am Modellvorhaben und Einwilligung zur Genomsequenzierung"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"provision": {
|
|
||||||
"type": "deny",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"provision": [
|
|
||||||
{
|
|
||||||
"type": "permit",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"code": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Teilnahme",
|
|
||||||
"display": "Teilnahme am Modellvorhaben und Einwilligung zur Genomsequenzierung"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fullUrl": "http://127.0.0.1:8090/ttp-fhir/fhir/gics/Consent/121aad40-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"resource": {
|
|
||||||
"resourceType": "Consent",
|
|
||||||
"id": "121aad40-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"meta": {
|
|
||||||
"lastUpdated": "2025-06-24T11:55:42.096+02:00",
|
|
||||||
"profile": [
|
|
||||||
"http://fhir.de/ConsentManagement/StructureDefinition/Consent"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "http://fhir.de/ConsentManagement/StructureDefinition/DomainReference",
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "domain",
|
|
||||||
"valueReference": {
|
|
||||||
"reference": "ResearchStudy/ef86d80e-50e0-11f0-a144-661e92ac9503"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "status",
|
|
||||||
"valueCoding": {
|
|
||||||
"system": "http://hl7.org/fhir/publication-status",
|
|
||||||
"code": "active"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "active",
|
|
||||||
"scope": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://terminology.hl7.org/CodeSystem/consentscope",
|
|
||||||
"code": "research"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"category": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://loinc.org",
|
|
||||||
"code": "59284-0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://fhir.de/ConsentManagement/CodeSystem/ResultType",
|
|
||||||
"code": "policy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"patient": {
|
|
||||||
"reference": "Patient/12194791-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"display": "Patienten-ID 12345678"
|
|
||||||
},
|
|
||||||
"dateTime": "2025-06-24T00:00:00+02:00",
|
|
||||||
"organization": [
|
|
||||||
{
|
|
||||||
"display": "GenomDE_MV"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sourceReference": {
|
|
||||||
"reference": "QuestionnaireResponse/1219ca42-50e1-11f0-a144-661e92ac9503"
|
|
||||||
},
|
|
||||||
"policyRule": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Fallidentifizierung",
|
|
||||||
"display": "Fallidentifizierung zum fachlichen Austausch unter Behandelnden"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"provision": {
|
|
||||||
"type": "deny",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"provision": [
|
|
||||||
{
|
|
||||||
"type": "permit",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"code": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Fallidentifizierung",
|
|
||||||
"display": "Fallidentifizierung zum fachlichen Austausch unter Behandelnden"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fullUrl": "http://127.0.0.1:8090/ttp-fhir/fhir/gics/Consent/121ac5f8-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"resource": {
|
|
||||||
"resourceType": "Consent",
|
|
||||||
"id": "121ac5f8-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"meta": {
|
|
||||||
"lastUpdated": "2025-06-24T11:55:42.110+02:00",
|
|
||||||
"profile": [
|
|
||||||
"http://fhir.de/ConsentManagement/StructureDefinition/Consent"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "http://fhir.de/ConsentManagement/StructureDefinition/DomainReference",
|
|
||||||
"extension": [
|
|
||||||
{
|
|
||||||
"url": "domain",
|
|
||||||
"valueReference": {
|
|
||||||
"reference": "ResearchStudy/ef86d80e-50e0-11f0-a144-661e92ac9503"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "status",
|
|
||||||
"valueCoding": {
|
|
||||||
"system": "http://hl7.org/fhir/publication-status",
|
|
||||||
"code": "active"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"status": "active",
|
|
||||||
"scope": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://terminology.hl7.org/CodeSystem/consentscope",
|
|
||||||
"code": "research"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"category": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://loinc.org",
|
|
||||||
"code": "59284-0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "http://fhir.de/ConsentManagement/CodeSystem/ResultType",
|
|
||||||
"code": "policy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"patient": {
|
|
||||||
"reference": "Patient/12194791-50e1-11f0-a144-661e92ac9503",
|
|
||||||
"display": "Patienten-ID 12345678"
|
|
||||||
},
|
|
||||||
"dateTime": "2025-06-24T00:00:00+02:00",
|
|
||||||
"organization": [
|
|
||||||
{
|
|
||||||
"display": "GenomDE_MV"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sourceReference": {
|
|
||||||
"reference": "QuestionnaireResponse/1219ca42-50e1-11f0-a144-661e92ac9503"
|
|
||||||
},
|
|
||||||
"policyRule": {
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Rekontaktierung",
|
|
||||||
"display": "Re-Identifizierung meiner Daten über die Vertrauensstelle beim Robert Koch-Institut und in die erneute Kontaktaufnahme durch meine behandelnde Ärztin oder meinen behandelnden Arzt"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"provision": {
|
|
||||||
"type": "deny",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"provision": [
|
|
||||||
{
|
|
||||||
"type": "permit",
|
|
||||||
"period": {
|
|
||||||
"start": "2025-06-24T00:00:00+02:00",
|
|
||||||
"end": "3000-01-01T00:00:00+01:00"
|
|
||||||
},
|
|
||||||
"code": [
|
|
||||||
{
|
|
||||||
"coding": [
|
|
||||||
{
|
|
||||||
"system": "https://ths-greifswald.de/fhir/CodeSystem/gics/Policy/GenomDE_MV",
|
|
||||||
"code": "Rekontaktierung",
|
|
||||||
"display": "Re-Identifizierung meiner Daten über die Vertrauensstelle beim Robert Koch-Institut und in die erneute Kontaktaufnahme durch meine behandelnde Ärztin oder meinen behandelnden Arzt"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user