mirror of
https://github.com/pcvolkmer/etl-processor.git
synced 2025-07-01 14:12:55 +00:00
feat: GicsConsentService can request consent resources from gICS based on domain (WIP)
This commit is contained in:
21
README.md
21
README.md
@ -91,16 +91,29 @@ Wurde die Verwendung von gPAS konfiguriert, so sind weitere Angaben zu konfiguri
|
|||||||
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.
|
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.
|
Vor der MTB-Übertragung kann der zum Sendezeitpunkt verfügbarer Einwilligungsstatus über Endpunkt *isConsented* abgefragt werden.
|
||||||
|
|
||||||
Falls diese Prüfung aktiviert wurde, wird der Einwilligungsstatus dem in der MTB Datei vorgezogen und der Wert in der MTB-Datei wird dabei ignoriert.
|
Falls Anbindung an gICS aktiviert wurde, wird der Einwilligungsstatus der MTB Datei ignoriert.
|
||||||
Falls in diesem Fall die Statusprüfung fehlschlägt, wird Status **abgelehnt** angenommen.
|
Stattdessen werden vorhandene Einwilligungen abgefragt und in die MTB Datei eingebettet.
|
||||||
Ist die Prüfung über gIcs deaktiviert, wird der eingetragene Einwilligungsstatus der übermittelten MTB Datei geprüft.
|
|
||||||
|
|
||||||
|
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_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_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_USERNAME`: gIcs Basic-Auth Benutzername
|
||||||
* `APP_CONSENT_GICS_PASSWORD`: gIcs Basic-Auth Passwort
|
* `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_PERSONIDENTIFIERSYSTEM`: Derzeit wird nur die PID unterstützt. wenn leer wird `https://ths-greifswald.de/fhir/gics/identifiers/Patienten-ID` angenommen
|
||||||
* `APP_CONSENT_GICS_CONSENTDOMAINNAME`: Domäne in der gIcs Einwilligungen verwaltet. Falls Wert leer, wird `MII` 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_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.
|
* `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.
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package dev.dnpm.etl.processor.consent;
|
||||||
|
|
||||||
|
public enum ConsentDomain {
|
||||||
|
BroadConsent,
|
||||||
|
Modelvorhaben64e
|
||||||
|
}
|
@ -4,9 +4,13 @@ import ca.uhn.fhir.context.FhirContext;
|
|||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
||||||
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
|
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
|
||||||
|
import java.util.Date;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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.BooleanType;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
import org.hl7.fhir.r4.model.Coding;
|
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.Identifier;
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||||
import org.hl7.fhir.r4.model.Parameters;
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
@ -40,7 +44,6 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
private final HttpHeaders httpHeader;
|
private final HttpHeaders httpHeader;
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
|
|
||||||
public GicsConsentService(GIcsConfigProperties gIcsConfigProperties,
|
public GicsConsentService(GIcsConfigProperties gIcsConfigProperties,
|
||||||
RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
|
RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
|
||||||
this.gIcsConfigProperties = gIcsConfigProperties;
|
this.gIcsConfigProperties = gIcsConfigProperties;
|
||||||
@ -85,7 +88,7 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
new Identifier().setValue(personIdentifierValue)
|
new Identifier().setValue(personIdentifierValue)
|
||||||
.setSystem(configProperties.getPersonIdentifierSystem())));
|
.setSystem(configProperties.getPersonIdentifierSystem())));
|
||||||
result.addParameter(new ParametersParameterComponent().setName("domain")
|
result.addParameter(new ParametersParameterComponent().setName("domain")
|
||||||
.setValue(new StringType().setValue(configProperties.getConsentDomainName())));
|
.setValue(new StringType().setValue(configProperties.getBroadConsentDomainName())));
|
||||||
result.addParameter(new ParametersParameterComponent().setName("policy").setValue(
|
result.addParameter(new ParametersParameterComponent().setName("policy").setValue(
|
||||||
new Coding().setCode(configProperties.getPolicyCode())
|
new Coding().setCode(configProperties.getPolicyCode())
|
||||||
.setSystem(configProperties.getPolicySystem())));
|
.setSystem(configProperties.getPolicySystem())));
|
||||||
@ -112,7 +115,7 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getConsentStatusResponse(Parameters parameter) {
|
protected String callGicsApi(Parameters parameter) {
|
||||||
var parameterAsXml = fhirContext.newXmlParser().encodeResourceToString(parameter);
|
var parameterAsXml = fhirContext.newXmlParser().encodeResourceToString(parameter);
|
||||||
|
|
||||||
HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.httpHeader);
|
HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.httpHeader);
|
||||||
@ -152,9 +155,97 @@ public class GicsConsentService implements ICheckConsent {
|
|||||||
var parameter = GicsConsentService.getIsConsentedParam(gIcsConfigProperties,
|
var parameter = GicsConsentService.getIsConsentedParam(gIcsConfigProperties,
|
||||||
personIdentifierValue);
|
personIdentifierValue);
|
||||||
|
|
||||||
var consentStatusResponse = getConsentStatusResponse(parameter);
|
var consentStatusResponse = callGicsApi(parameter);
|
||||||
return evaluateConsentResponse(consentStatusResponse);
|
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);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
String consentDomainName = gIcsConfigProperties.getBroadConsentDomainName();
|
||||||
|
return currentConsentForPersonAndTemplate(personIdentifierValue, ConsentDomain.BroadConsent,
|
||||||
|
requestDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bundle getGnomDeConsent(String personIdentifierValue, Date requestDate) {
|
||||||
|
|
||||||
|
return currentConsentForPersonAndTemplate(personIdentifierValue,
|
||||||
|
ConsentDomain.Modelvorhaben64e, requestDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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) {
|
private TtpConsentStatus evaluateConsentResponse(String consentStatusResponse) {
|
||||||
|
@ -80,9 +80,14 @@ data class GIcsConfigProperties(
|
|||||||
"https://ths-greifswald.de/fhir/gics/identifiers/Patienten-ID",
|
"https://ths-greifswald.de/fhir/gics/identifiers/Patienten-ID",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Domain of consent resources
|
* Domain of broad consent resources
|
||||||
* **/
|
**/
|
||||||
val consentDomainName: String = "MII",
|
val broadConsentDomainName: String = "MII",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain of Modelvorhaben 64e consent resources
|
||||||
|
**/
|
||||||
|
val gnomDeConsentDomainName: String = "GenomeDE_MV",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value to expect in case of positiv consent
|
* Value to expect in case of positiv consent
|
||||||
|
@ -35,7 +35,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 constService: ICheckConsent
|
private val requestProcessor: RequestProcessor, private val iCheckConsent: ICheckConsent
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
|
private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
|
||||||
@ -63,7 +63,7 @@ class MtbFileRestController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkConsentStatus(mtbFile: MtbFile): Pair<TtpConsentStatus, Boolean> {
|
private fun checkConsentStatus(mtbFile: MtbFile): Pair<TtpConsentStatus, Boolean> {
|
||||||
var ttpConsentStatus = constService.getTtpConsentStatus(mtbFile.patient.id)
|
var ttpConsentStatus = iCheckConsent.getTtpConsentStatus(mtbFile.patient.id)
|
||||||
|
|
||||||
val isConsentOK =
|
val isConsentOK =
|
||||||
(ttpConsentStatus.equals(TtpConsentStatus.UNKNOWN_CHECK_FILE) && mtbFile.consent.status == Consent.Status.ACTIVE) ||
|
(ttpConsentStatus.equals(TtpConsentStatus.UNKNOWN_CHECK_FILE) && mtbFile.consent.status == Consent.Status.ACTIVE) ||
|
||||||
|
@ -23,6 +23,7 @@ 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
|
||||||
*
|
*
|
||||||
@ -288,6 +289,14 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user