1
0
mirror of https://github.com/pcvolkmer/mv64e-etl-processor synced 2025-09-13 17:02:52 +00:00

1 Commits

Author SHA1 Message Date
328bf019af chore: bump version 2025-09-03 22:17:48 +02:00
8 changed files with 214 additions and 264 deletions

View File

@@ -7,22 +7,22 @@ plugins {
war war
id("org.springframework.boot") version "3.5.5" id("org.springframework.boot") version "3.5.5"
id("io.spring.dependency-management") version "1.1.7" id("io.spring.dependency-management") version "1.1.7"
kotlin("jvm") version "2.2.10" kotlin("jvm") version "1.9.25"
kotlin("plugin.spring") version "2.2.10" kotlin("plugin.spring") version "1.9.25"
jacoco jacoco
} }
group = "dev.dnpm" group = "dev.dnpm"
version = "0.12.0-SNAPSHOT" version = "0.11.1"
var versions = mapOf( var versions = mapOf(
"mtb-dto" to "0.1.0-SNAPSHOT", "mtb-dto" to "0.1.0-SNAPSHOT",
"hapi-fhir" to "8.4.0", "hapi-fhir" to "7.6.1",
"mockito-kotlin" to "6.0.0", "mockito-kotlin" to "5.4.0",
"archunit" to "1.4.1", "archunit" to "1.3.0",
// Webjars // Webjars
"webjars-locator" to "0.52", "webjars-locator" to "0.52",
"echarts" to "6.0.0", "echarts" to "5.4.3",
"htmx.org" to "1.9.12" "htmx.org" to "1.9.12"
) )

View File

@@ -39,14 +39,12 @@ public class GicsConsentService implements IConsentService {
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
private final FhirContext fhirContext; private final FhirContext fhirContext;
private final GIcsConfigProperties gIcsConfigProperties; private final GIcsConfigProperties gIcsConfigProperties;
private final String BROAD_CONSENT_PROFILE_URI = "https://www.medizininformatik-initiative.de/fhir/modul-consent/StructureDefinition/mii-pr-consent-einwilligung";
private final String BROAD_CONSENT_POLICY = "urn:oid:2.16.840.1.113883.3.1937.777.24.2.1791";
public GicsConsentService( public GicsConsentService(
GIcsConfigProperties gIcsConfigProperties, GIcsConfigProperties gIcsConfigProperties,
RetryTemplate retryTemplate, RetryTemplate retryTemplate,
RestTemplate restTemplate, RestTemplate restTemplate,
AppFhirConfig appFhirConfig AppFhirConfig appFhirConfig
) { ) {
this.retryTemplate = retryTemplate; this.retryTemplate = retryTemplate;
this.restTemplate = restTemplate; this.restTemplate = restTemplate;
@@ -56,34 +54,34 @@ public class GicsConsentService implements IConsentService {
} }
protected Parameters getFhirRequestParameters( protected Parameters getFhirRequestParameters(
String personIdentifierValue String personIdentifierValue
) { ) {
var result = new Parameters(); var result = new Parameters();
result.addParameter( result.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("personIdentifier") .setName("personIdentifier")
.setValue( .setValue(
new Identifier() new Identifier()
.setValue(personIdentifierValue) .setValue(personIdentifierValue)
.setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem()) .setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem())
) )
); );
result.addParameter( result.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("domain") .setName("domain")
.setValue( .setValue(
new StringType() new StringType()
.setValue(this.gIcsConfigProperties.getBroadConsentDomainName()) .setValue(this.gIcsConfigProperties.getBroadConsentDomainName())
) )
); );
result.addParameter( result.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("policy") .setName("policy")
.setValue( .setValue(
new Coding() new Coding()
.setCode(this.gIcsConfigProperties.getBroadConsentPolicyCode()) .setCode(this.gIcsConfigProperties.getBroadConsentPolicyCode())
.setSystem(this.gIcsConfigProperties.getBroadConsentPolicySystem()) .setSystem(this.gIcsConfigProperties.getBroadConsentPolicySystem())
) )
); );
/* /*
@@ -91,10 +89,10 @@ public class GicsConsentService implements IConsentService {
* 'ignoreVersionNumber'. * 'ignoreVersionNumber'.
*/ */
result.addParameter( result.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("version") .setName("version")
.setValue(new StringType().setValue("1.1") .setValue(new StringType().setValue("1.1")
) )
); );
/* add config parameter with: /* add config parameter with:
@@ -103,17 +101,17 @@ public class GicsConsentService implements IConsentService {
* unknownStateIsConsideredAsDecline -> true * unknownStateIsConsideredAsDecline -> true
*/ */
var config = new ParametersParameterComponent() var config = new ParametersParameterComponent()
.setName("config") .setName("config")
.addPart( .addPart(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("ignoreVersionNumber") .setName("ignoreVersionNumber")
.setValue(new BooleanType().setValue(true)) .setValue(new BooleanType().setValue(true))
) )
.addPart( .addPart(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("unknownStateIsConsideredAsDecline") .setName("unknownStateIsConsideredAsDecline")
.setValue(new BooleanType().setValue(false)) .setValue(new BooleanType().setValue(false))
); );
result.addParameter(config); result.addParameter(config);
@@ -132,8 +130,8 @@ public class GicsConsentService implements IConsentService {
headers.setContentType(MediaType.APPLICATION_XML); headers.setContentType(MediaType.APPLICATION_XML);
if ( if (
StringUtils.isBlank(this.gIcsConfigProperties.getUsername()) StringUtils.isBlank(this.gIcsConfigProperties.getUsername())
|| StringUtils.isBlank(this.gIcsConfigProperties.getPassword()) || StringUtils.isBlank(this.gIcsConfigProperties.getPassword())
) { ) {
return headers; return headers;
} }
@@ -147,28 +145,28 @@ public class GicsConsentService implements IConsentService {
HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.headersWithHttpBasicAuth()); HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.headersWithHttpBasicAuth());
try { try {
var responseEntity = retryTemplate.execute( var responseEntity = retryTemplate.execute(
ctx -> restTemplate.exchange(endpointUri(endpoint), HttpMethod.POST, requestEntity, String.class) ctx -> restTemplate.exchange(endpointUri(endpoint), HttpMethod.POST, requestEntity, String.class)
); );
if (responseEntity.getStatusCode().is2xxSuccessful()) { if (responseEntity.getStatusCode().is2xxSuccessful()) {
return responseEntity.getBody(); return responseEntity.getBody();
} else { } else {
var msg = String.format( var msg = String.format(
"Trusted party system reached but request failed! code: '%s' response: '%s'", "Trusted party system reached but request failed! code: '%s' response: '%s'",
responseEntity.getStatusCode(), responseEntity.getBody()); responseEntity.getStatusCode(), responseEntity.getBody());
log.error(msg); log.error(msg);
return null; return null;
} }
} catch (RestClientException e) { } catch (RestClientException e) {
var msg = String.format("Get consents status request failed reason: '%s", var msg = String.format("Get consents status request failed reason: '%s",
e.getMessage()); e.getMessage());
log.error(msg); log.error(msg);
return null; return null;
} catch (TerminatedRetryException terminatedRetryException) { } catch (TerminatedRetryException terminatedRetryException) {
var msg = String.format( var msg = String.format(
"Get consents status process has been terminated. termination reason: '%s", "Get consents status process has been terminated. termination reason: '%s",
terminatedRetryException.getMessage()); terminatedRetryException.getMessage());
log.error(msg); log.error(msg);
return null; return null;
} }
@@ -177,45 +175,45 @@ public class GicsConsentService implements IConsentService {
@Override @Override
public TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue) { public TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue) {
var consentStatusResponse = callGicsApi( var consentStatusResponse = callGicsApi(
getFhirRequestParameters(personIdentifierValue), getFhirRequestParameters(personIdentifierValue),
GicsConsentService.IS_CONSENTED_ENDPOINT GicsConsentService.IS_CONSENTED_ENDPOINT
); );
return evaluateConsentResponse(consentStatusResponse); return evaluateConsentResponse(consentStatusResponse);
} }
protected Bundle currentConsentForPersonAndTemplate( protected Bundle currentConsentForPersonAndTemplate(
String personIdentifierValue, String personIdentifierValue,
ConsentDomain consentDomain, ConsentDomain consentDomain,
Date requestDate Date requestDate
) { ) {
var requestParameter = buildRequestParameterCurrentPolicyStatesForPerson( var requestParameter = buildRequestParameterCurrentPolicyStatesForPerson(
personIdentifierValue, personIdentifierValue,
requestDate, requestDate,
consentDomain consentDomain
); );
var consentDataSerialized = callGicsApi(requestParameter, var consentDataSerialized = callGicsApi(requestParameter,
GicsConsentService.IS_POLICY_STATES_FOR_PERSON_ENDPOINT); GicsConsentService.IS_POLICY_STATES_FOR_PERSON_ENDPOINT);
if (consentDataSerialized == null) { if (consentDataSerialized == null) {
// error occurred - should not process further! // error occurred - should not process further!
throw new IllegalStateException( throw new IllegalStateException(
"consent data request failed - stopping processing! - try again or fix other problems first."); "consent data request failed - stopping processing! - try again or fix other problems first.");
} }
var iBaseResource = fhirContext.newJsonParser() var iBaseResource = fhirContext.newJsonParser()
.parseResource(consentDataSerialized); .parseResource(consentDataSerialized);
if (iBaseResource instanceof OperationOutcome) { if (iBaseResource instanceof OperationOutcome) {
// log error - very likely a configuration error // log error - very likely a configuration error
String errorMessage = String errorMessage =
"Consent request failed! Check outcome:\n " + consentDataSerialized; "Consent request failed! Check outcome:\n " + consentDataSerialized;
log.error(errorMessage); log.error(errorMessage);
throw new IllegalStateException(errorMessage); throw new IllegalStateException(errorMessage);
} else if (iBaseResource instanceof Bundle bundle) { } else if (iBaseResource instanceof Bundle bundle) {
return bundle; return bundle;
} else { } else {
String errorMessage = "Consent request failed! Unexpected response received! -> " String errorMessage = "Consent request failed! Unexpected response received! -> "
+ consentDataSerialized; + consentDataSerialized;
log.error(errorMessage); log.error(errorMessage);
throw new IllegalStateException(errorMessage); throw new IllegalStateException(errorMessage);
} }
@@ -230,43 +228,43 @@ public class GicsConsentService implements IConsentService {
} }
protected Parameters buildRequestParameterCurrentPolicyStatesForPerson( protected Parameters buildRequestParameterCurrentPolicyStatesForPerson(
String personIdentifierValue, String personIdentifierValue,
Date requestDate, Date requestDate,
ConsentDomain consentDomain ConsentDomain consentDomain
) { ) {
var requestParameter = new Parameters(); var requestParameter = new Parameters();
requestParameter.addParameter( requestParameter.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("personIdentifier") .setName("personIdentifier")
.setValue( .setValue(
new Identifier() new Identifier()
.setValue(personIdentifierValue) .setValue(personIdentifierValue)
.setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem()) .setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem())
) )
); );
requestParameter.addParameter( requestParameter.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("domain") .setName("domain")
.setValue(new StringType().setValue(getConsentDomainName(consentDomain))) .setValue(new StringType().setValue(getConsentDomainName(consentDomain)))
); );
Parameters nestedConfigParameters = new Parameters(); Parameters nestedConfigParameters = new Parameters();
nestedConfigParameters nestedConfigParameters
.addParameter( .addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("idMatchingType") .setName("idMatchingType")
.setValue(new Coding() .setValue(new Coding()
.setSystem("https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType") .setSystem("https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType")
.setCode("AT_LEAST_ONE") .setCode("AT_LEAST_ONE")
) )
) )
.addParameter("ignoreVersionNumber", false) .addParameter("ignoreVersionNumber", false)
.addParameter("unknownStateIsConsideredAsDecline", false) .addParameter("unknownStateIsConsideredAsDecline", false)
.addParameter("requestDate", new DateType().setValue(requestDate)); .addParameter("requestDate", new DateType().setValue(requestDate));
requestParameter.addParameter( requestParameter.addParameter(
new ParametersParameterComponent().setName("config").addPart().setResource(nestedConfigParameters) new ParametersParameterComponent().setName("config").addPart().setResource(nestedConfigParameters)
); );
return requestParameter; return requestParameter;
@@ -293,7 +291,7 @@ public class GicsConsentService implements IConsentService {
} }
} else if (response instanceof OperationOutcome outcome) { } else if (response instanceof OperationOutcome outcome) {
log.error("failed to get consent status from ttp. probably configuration error. " log.error("failed to get consent status from ttp. probably configuration error. "
+ "outcome: '{}'", fhirContext.newJsonParser().encodeToString(outcome)); + "outcome: '{}'", fhirContext.newJsonParser().encodeToString(outcome));
} }
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
@@ -304,36 +302,6 @@ public class GicsConsentService implements IConsentService {
@Override @Override
public Bundle getConsent(String patientId, Date requestDate, ConsentDomain consentDomain) { public Bundle getConsent(String patientId, Date requestDate, ConsentDomain consentDomain) {
Bundle gIcsResultBundle = currentConsentForPersonAndTemplate(patientId, consentDomain, requestDate); return currentConsentForPersonAndTemplate(patientId, consentDomain, requestDate);
if (ConsentDomain.BROAD_CONSENT == consentDomain) {
return convertGicsResultToMiiBroadConsent(gIcsResultBundle);
}
return gIcsResultBundle;
}
protected Bundle convertGicsResultToMiiBroadConsent(Bundle gIcsResultBundle) {
if (gIcsResultBundle == null
|| gIcsResultBundle.getEntry().isEmpty()
|| !(gIcsResultBundle.getEntry().getFirst().getResource() instanceof Consent))
return gIcsResultBundle;
Bundle.BundleEntryComponent bundleEntryComponent = gIcsResultBundle.getEntry().getFirst();
var consentAsOne = (Consent) bundleEntryComponent.getResource();
if (consentAsOne.getPolicy().stream().noneMatch(p -> p.getUri().equals(BROAD_CONSENT_POLICY))) {
consentAsOne.addPolicy(new Consent.ConsentPolicyComponent().setUri(BROAD_CONSENT_POLICY));
}
if (consentAsOne.getMeta().getProfile().stream().noneMatch(p -> p.getValue().equals(BROAD_CONSENT_PROFILE_URI))) {
consentAsOne.getMeta().addProfile(BROAD_CONSENT_PROFILE_URI);
}
consentAsOne.setPolicyRule(null);
gIcsResultBundle.getEntry().stream().skip(1).forEach(c -> consentAsOne.getProvision().addProvision(((Consent) c.getResource()).getProvision().getProvisionFirstRep()));
gIcsResultBundle.getEntry().clear();
gIcsResultBundle.addEntry(bundleEntryComponent);
return gIcsResultBundle;
} }
} }

View File

@@ -23,6 +23,8 @@ 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.AppFhirConfig;
import dev.dnpm.etl.processor.config.GPasConfigProperties; import dev.dnpm.etl.processor.config.GPasConfigProperties;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.hc.core5.net.URIBuilder; import org.apache.hc.core5.net.URIBuilder;
@@ -37,11 +39,9 @@ import org.springframework.http.*;
import org.springframework.retry.support.RetryTemplate; import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.HttpClientErrorException.BadRequest; import org.springframework.web.client.HttpClientErrorException.BadRequest;
import org.springframework.web.client.HttpClientErrorException.Unauthorized; import org.springframework.web.client.HttpClientErrorException.Unauthorized;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.net.URISyntaxException;
public class GpasPseudonymGenerator implements Generator { public class GpasPseudonymGenerator implements Generator {
private final FhirContext r4Context; private final FhirContext r4Context;
@@ -52,10 +52,10 @@ public class GpasPseudonymGenerator implements Generator {
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
private final @NotNull String genomDeTanDomain; private final @NotNull String genomDeTanDomain;
private final @NotNull String pidPsnDomain; private final @NotNull String pidPsnDomain;
protected static final String CREATE_OR_GET_PSN = "$pseudonymizeAllowCreate"; protected final static String createOrGetPsn = "$pseudonymizeAllowCreate";
protected static final String CREATE_MULTI_DOMAIN_PSN = "$pseudonymize-secondary"; protected final static String createMultiDomainPsn = "$pseudonymize-secondary";
private static final String SINGLE_PSN_PART_NAME = "pseudonym"; private final static String SINGLE_PSN_PART_NAME = "pseudonym";
private static final String MULTI_PSN_PART_NAME = "value"; private final static String MULTI_PSN_PART_NAME = "value";
public GpasPseudonymGenerator(GPasConfigProperties gpasCfg, RetryTemplate retryTemplate, public GpasPseudonymGenerator(GPasConfigProperties gpasCfg, RetryTemplate retryTemplate,
RestTemplate restTemplate, AppFhirConfig appFhirConfig) { RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
@@ -85,7 +85,7 @@ public class GpasPseudonymGenerator implements Generator {
switch (domainType) { switch (domainType) {
case SINGLE_PSN_DOMAIN -> { case SINGLE_PSN_DOMAIN -> {
final var requestBody = createSinglePsnRequestBody(id, pidPsnDomain); final var requestBody = createSinglePsnRequestBody(id, pidPsnDomain);
final var responseEntity = getGpasPseudonym(requestBody, CREATE_OR_GET_PSN); final var responseEntity = getGpasPseudonym(requestBody, createOrGetPsn);
final var gPasPseudonymResult = (Parameters) r4Context.newJsonParser() final var gPasPseudonymResult = (Parameters) r4Context.newJsonParser()
.parseResource(responseEntity.getBody()); .parseResource(responseEntity.getBody());
@@ -93,7 +93,7 @@ public class GpasPseudonymGenerator implements Generator {
} }
case MULTI_PSN_DOMAIN -> { case MULTI_PSN_DOMAIN -> {
final var requestBody = createMultiPsnRequestBody(id, genomDeTanDomain); final var requestBody = createMultiPsnRequestBody(id, genomDeTanDomain);
final var responseEntity = getGpasPseudonym(requestBody, CREATE_MULTI_DOMAIN_PSN); final var responseEntity = getGpasPseudonym(requestBody, createMultiDomainPsn);
final var gPasPseudonymResult = (Parameters) r4Context.newJsonParser() final var gPasPseudonymResult = (Parameters) r4Context.newJsonParser()
.parseResource(responseEntity.getBody()); .parseResource(responseEntity.getBody());
@@ -150,22 +150,23 @@ public class GpasPseudonymGenerator implements Generator {
log.debug("API request succeeded. Response: {}", responseEntity.getStatusCode()); log.debug("API request succeeded. Response: {}", responseEntity.getStatusCode());
return responseEntity; return responseEntity;
} }
} catch (BadRequest e) { } catch (RestClientException rce) {
String msg = "gPas or request configuration is incorrect. Please check both." if (rce instanceof BadRequest) {
+ e.getMessage(); String msg = "gPas or request configuration is incorrect. Please check both."
log.error(msg); + rce.getMessage();
throw new PseudonymRequestFailed(msg, e); log.debug(
} catch (Unauthorized e) { msg);
var msg = "gPas access credentials are invalid check your configuration. msg: '%s" throw new PseudonymRequestFailed(msg, rce);
.formatted(e.getMessage()); }
log.error(msg); if (rce instanceof Unauthorized) {
throw new PseudonymRequestFailed(msg, e); var msg = "gPas access credentials are invalid check your configuration. msg: '%s".formatted(
} rce.getMessage());
catch (Exception unexpected) { log.error(msg);
throw new PseudonymRequestFailed(msg, rce);
}
} catch (Exception unexpected) {
throw new PseudonymRequestFailed( throw new PseudonymRequestFailed(
"API request due unexpected error unsuccessful gPas unsuccessful.", "API request due unexpected error unsuccessful gPas unsuccessful.", unexpected);
unexpected
);
} }
throw new PseudonymRequestFailed( throw new PseudonymRequestFailed(
"API request due unexpected error unsuccessful gPas unsuccessful."); "API request due unexpected error unsuccessful gPas unsuccessful.");

View File

@@ -30,7 +30,6 @@ import org.springframework.data.relational.core.mapping.Table
import org.springframework.data.repository.CrudRepository import org.springframework.data.repository.CrudRepository
import org.springframework.data.repository.PagingAndSortingRepository import org.springframework.data.repository.PagingAndSortingRepository
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.* import java.util.*
@Table("request") @Table("request")
@@ -66,12 +65,6 @@ data class Request(
processedAt: Instant processedAt: Instant
) : ) :
this(null, uuid, patientPseudonym, pid, fingerprint, type, status, processedAt) this(null, uuid, patientPseudonym, pid, fingerprint, type, status, processedAt)
fun isPendingUnknown(): Boolean {
return this.status == RequestStatus.UNKNOWN && this.processedAt.isBefore(
Instant.now().minus(10, ChronoUnit.MINUTES)
)
}
} }
@JvmRecord @JvmRecord
@@ -97,23 +90,19 @@ interface RequestRepository : CrudRepository<Request, Long>, PagingAndSortingRep
@Query("SELECT count(*) AS count, status FROM request WHERE type = 'MTB_FILE' GROUP BY status ORDER BY status, count DESC;") @Query("SELECT count(*) AS count, status FROM request WHERE type = 'MTB_FILE' GROUP BY status ORDER BY status, count DESC;")
fun countStates(): List<CountedState> fun countStates(): List<CountedState>
@Query( @Query("SELECT count(*) AS count, status FROM (" +
"SELECT count(*) AS count, status FROM (" + "SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " +
"SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " + "WHERE type = 'MTB_FILE' AND status NOT IN ('DUPLICATION') " +
"WHERE type = 'MTB_FILE' AND status NOT IN ('DUPLICATION') " + ") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;")
") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;"
)
fun findPatientUniqueStates(): List<CountedState> fun findPatientUniqueStates(): List<CountedState>
@Query("SELECT count(*) AS count, status FROM request WHERE type = 'DELETE' GROUP BY status ORDER BY status, count DESC;") @Query("SELECT count(*) AS count, status FROM request WHERE type = 'DELETE' GROUP BY status ORDER BY status, count DESC;")
fun countDeleteStates(): List<CountedState> fun countDeleteStates(): List<CountedState>
@Query( @Query("SELECT count(*) AS count, status FROM (" +
"SELECT count(*) AS count, status FROM (" + "SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " +
"SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " + "WHERE type = 'DELETE'" +
"WHERE type = 'DELETE'" + ") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;")
") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;"
)
fun findPatientUniqueDeleteStates(): List<CountedState> fun findPatientUniqueDeleteStates(): List<CountedState>
} }

View File

@@ -137,7 +137,15 @@ class ConsentProcessor(
} }
val provisionComponent: ProvisionComponent = provisions.first() val provisionComponent: ProvisionComponent = provisions.first()
val provisionCode = getProvisionCode(provisionComponent)
var provisionCode: String? = null
if (provisionComponent.code != null && provisionComponent.code.isNotEmpty()) {
val codableConcept: CodeableConcept = provisionComponent.code.first()
if (codableConcept.coding != null && codableConcept.coding.isNotEmpty()) {
provisionCode = codableConcept.coding.first().code
}
}
if (provisionCode != null) { if (provisionCode != null) {
try { try {
val modelProjectConsentPurpose = val modelProjectConsentPurpose =
@@ -169,17 +177,6 @@ class ConsentProcessor(
} }
} }
private fun getProvisionCode(provisionComponent: ProvisionComponent): String? {
var provisionCode: String? = null
if (provisionComponent.code != null && provisionComponent.code.isNotEmpty()) {
val codableConcept: CodeableConcept = provisionComponent.code.first()
if (codableConcept.coding != null && codableConcept.coding.isNotEmpty()) {
provisionCode = codableConcept.coding.first().code
}
}
return provisionCode
}
private fun setGenomDeSubmissionType(mtbFile: Mtb) { private fun setGenomDeSubmissionType(mtbFile: Mtb) {
if (appConfigProperties.genomDeTestSubmission) { if (appConfigProperties.genomDeTestSubmission) {
mtbFile.metadata.type = MvhSubmissionType.TEST mtbFile.metadata.type = MvhSubmissionType.TEST
@@ -241,9 +238,9 @@ class ConsentProcessor(
consent.provision.provision.filter { subProvision -> consent.provision.provision.filter { subProvision ->
isRequestDateInRange(requestDate, subProvision.period) isRequestDateInRange(requestDate, subProvision.period)
// search coding entries of current provision for code and system // search coding entries of current provision for code and system
subProvision.code.map { c -> c.coding }.flatten().any { code -> subProvision.code.map { c -> c.coding }.flatten().firstOrNull { code ->
targetCode.equals(code.code) && targetSystem.equals(code.system) targetCode.equals(code.code) && targetSystem.equals(code.system)
} } != null
}.map { subProvision -> }.map { subProvision ->
subProvision subProvision
} }
@@ -260,11 +257,11 @@ class ConsentProcessor(
researchAllowedPolicySystem: String?, researchAllowedPolicySystem: String?,
policyRules: Collection<Coding> policyRules: Collection<Coding>
): Boolean { ): Boolean {
return policyRules.any { code -> return policyRules.find { code ->
researchAllowedPolicySystem.equals(code.getSystem()) && (researchAllowedPolicyOid.equals( researchAllowedPolicySystem.equals(code.getSystem()) && (researchAllowedPolicyOid.equals(
code.getCode() code.getCode()
)) ))
} } != null
} }
fun isRequestDateInRange(requestDate: Date?, provPeriod: Period): Boolean { fun isRequestDateInRange(requestDate: Date?, provPeriod: Period): Boolean {

View File

@@ -52,8 +52,7 @@
<td th:if="${request.status.value.contains('success')}" class="bg-green"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value.contains('success')}" class="bg-green"><small>[[ ${request.status} ]]</small></td>
<td th:if="${request.status.value.contains('warning')}" class="bg-yellow"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value.contains('warning')}" class="bg-yellow"><small>[[ ${request.status} ]]</small></td>
<td th:if="${request.status.value.contains('error')}" class="bg-red"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value.contains('error')}" class="bg-red"><small>[[ ${request.status} ]]</small></td>
<td th:if="${request.status.value == 'unknown' and not request.isPendingUnknown()}" class="bg-gray"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value == 'unknown'}" class="bg-gray"><small>[[ ${request.status} ]]</small></td>
<td th:if="${request.status.value == 'unknown' and request.isPendingUnknown()}" class="bg-yellow"><small>⏰ [[ ${request.status} ]] ⏰</small></td>
<td th:if="${request.status.value == 'duplication'}" class="bg-gray"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value == 'duplication'}" class="bg-gray"><small>[[ ${request.status} ]]</small></td>
<td th:if="${request.status.value == 'no-consent'}" class="bg-blue"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value == 'no-consent'}" class="bg-blue"><small>[[ ${request.status} ]]</small></td>
<td th:style="${request.type.value == 'delete'} ? 'color: red;'"><small>[[ ${request.type} ]]</small></td> <td th:style="${request.type.value == 'delete'} ? 'color: red;'"><small>[[ ${request.type} ]]</small></td>

View File

@@ -24,15 +24,14 @@ import java.time.Instant;
import java.util.Date; import java.util.Date;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError; import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
@ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class}) @ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class})
@TestPropertySource(properties = { @TestPropertySource(properties = {
"app.consent.service=gics", "app.consent.service=gics",
"app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics" "app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics"
}) })
@RestClientTest @RestClientTest
class GicsConsentServiceTest { class GicsConsentServiceTest {
@@ -47,8 +46,8 @@ class GicsConsentServiceTest {
@BeforeEach @BeforeEach
void setUp( void setUp(
@Autowired AppFhirConfig appFhirConfig, @Autowired AppFhirConfig appFhirConfig,
@Autowired GIcsConfigProperties gIcsConfigProperties @Autowired GIcsConfigProperties gIcsConfigProperties
) { ) {
this.appFhirConfig = appFhirConfig; this.appFhirConfig = appFhirConfig;
this.gIcsConfigProperties = gIcsConfigProperties; this.gIcsConfigProperties = gIcsConfigProperties;
@@ -57,33 +56,33 @@ class GicsConsentServiceTest {
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate); this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate);
this.gicsConsentService = new GicsConsentService( this.gicsConsentService = new GicsConsentService(
this.gIcsConfigProperties, this.gIcsConfigProperties,
RetryTemplate.builder().maxAttempts(1).build(), RetryTemplate.builder().maxAttempts(1).build(),
restTemplate, restTemplate,
this.appFhirConfig this.appFhirConfig
); );
} }
@Test @Test
void shouldReturnTtpBroadConsentStatus() { void shouldReturnTtpBroadConsentStatus() {
final Parameters consentedResponse = new Parameters() final Parameters consentedResponse = new Parameters()
.addParameter( .addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("consented") .setName("consented")
.setValue(new BooleanType().setValue(true)) .setValue(new BooleanType().setValue(true))
); );
mockRestServiceServer mockRestServiceServer
.expect( .expect(
requestTo( requestTo(
"http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT) "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT)
)
.andRespond(
withSuccess(
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(consentedResponse),
MediaType.APPLICATION_JSON
) )
.andRespond( );
withSuccess(
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(consentedResponse),
MediaType.APPLICATION_JSON
)
);
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN); assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN);
@@ -92,22 +91,22 @@ class GicsConsentServiceTest {
@Test @Test
void shouldReturnRevokedConsent() { void shouldReturnRevokedConsent() {
final Parameters revokedResponse = new Parameters() final Parameters revokedResponse = new Parameters()
.addParameter( .addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("consented") .setName("consented")
.setValue(new BooleanType().setValue(false)) .setValue(new BooleanType().setValue(false))
); );
mockRestServiceServer mockRestServiceServer
.expect( .expect(
requestTo( requestTo(
"http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT) "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT)
) )
.andRespond( .andRespond(
withSuccess( withSuccess(
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(revokedResponse), appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(revokedResponse),
MediaType.APPLICATION_JSON) MediaType.APPLICATION_JSON)
); );
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED); assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED);
@@ -117,23 +116,23 @@ class GicsConsentServiceTest {
@Test @Test
void shouldReturnInvalidParameterResponse() { void shouldReturnInvalidParameterResponse() {
final OperationOutcome responseWithErrorOutcome = new OperationOutcome() final OperationOutcome responseWithErrorOutcome = new OperationOutcome()
.addIssue( .addIssue(
new OperationOutcomeIssueComponent() new OperationOutcomeIssueComponent()
.setSeverity(IssueSeverity.ERROR) .setSeverity(IssueSeverity.ERROR)
.setCode(IssueType.PROCESSING) .setCode(IssueType.PROCESSING)
.setDiagnostics("Invalid policy parameter...") .setDiagnostics("Invalid policy parameter...")
); );
mockRestServiceServer mockRestServiceServer
.expect( .expect(
requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT) requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)
)
.andRespond(
withSuccess(
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseWithErrorOutcome),
MediaType.APPLICATION_JSON
) )
.andRespond( );
withSuccess(
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseWithErrorOutcome),
MediaType.APPLICATION_JSON
)
);
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK); assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
@@ -142,12 +141,12 @@ class GicsConsentServiceTest {
@Test @Test
void shouldReturnRequestError() { void shouldReturnRequestError() {
mockRestServiceServer mockRestServiceServer
.expect( .expect(
requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT) requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)
) )
.andRespond( .andRespond(
withServerError() withServerError()
); );
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK); assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
@@ -157,29 +156,26 @@ class GicsConsentServiceTest {
void buildRequestParameterCurrentPolicyStatesForPersonTest() { void buildRequestParameterCurrentPolicyStatesForPersonTest() {
String pid = "12345678"; String pid = "12345678";
var result = gicsConsentService var result = gicsConsentService
.buildRequestParameterCurrentPolicyStatesForPerson( .buildRequestParameterCurrentPolicyStatesForPerson(
pid, pid,
Date.from(Instant.now()), Date.from(Instant.now()),
ConsentDomain.MODELLVORHABEN_64E ConsentDomain.MODELLVORHABEN_64E
); );
assertThat(result.getParameter()) assertThat(result.getParameter())
.as("should contain 3 parameter resources") .as("should contain 3 parameter resources")
.hasSize(3); .hasSize(3);
assertThat(((StringType) result.getParameter("domain").getValue()).getValue()) assertThat(((StringType) result.getParameter("domain").getValue()).getValue())
.isEqualTo( .isEqualTo(
gIcsConfigProperties.getGenomDeConsentDomainName() gIcsConfigProperties.getGenomDeConsentDomainName()
); );
assertThat(((Identifier) result.getParameter("personIdentifier").getValue()).getValue()) assertThat(((Identifier) result.getParameter("personIdentifier").getValue()).getValue())
.isEqualTo( .isEqualTo(
pid pid
); );
} }
@Test
public void convertGicsResultToMiiBroadConsent() {
fail("todo: implement Test gicsConsentService.convertGicsResultToMiiBroadConsent");
}
} }

View File

@@ -80,7 +80,7 @@ class ConsentProcessorTest {
val checkResult = consentProcessor.consentGatedCheckAndTryEmbedding(inputMtb) val checkResult = consentProcessor.consentGatedCheckAndTryEmbedding(inputMtb)
assertThat(checkResult).isTrue assertThat(checkResult).isTrue
assertThat(inputMtb.metadata.researchConsents).isNotEmpty assertThat(inputMtb.metadata.researchConsents).hasSize(26)
} }
companion object { companion object {