Skip to content

Commit c7f8f26

Browse files
committed
Change Consent Key Parsing To Enum
1 parent 596a879 commit c7f8f26

21 files changed

+321
-7515
lines changed

src/main/java/de/medizininformatikinitiative/torch/config/AppConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import de.medizininformatikinitiative.torch.consent.ConsentValidator;
1212
import de.medizininformatikinitiative.torch.cql.CqlClient;
1313
import de.medizininformatikinitiative.torch.cql.FhirHelper;
14+
import de.medizininformatikinitiative.torch.exceptions.ValidationException;
1415
import de.medizininformatikinitiative.torch.management.CompartmentManager;
1516
import de.medizininformatikinitiative.torch.management.ProcessedGroupFactory;
1617
import de.medizininformatikinitiative.torch.management.StructureDefinitionHandler;
@@ -257,7 +258,7 @@ public ObjectMapper objectMapper() {
257258
}
258259

259260
@Bean
260-
public ConsentCodeMapper consentCodeMapper(ObjectMapper objectMapper) throws IOException {
261+
public ConsentCodeMapper consentCodeMapper(ObjectMapper objectMapper) throws IOException, ValidationException {
261262
return new ConsentCodeMapper(torchProperties.mapping().consent(), objectMapper);
262263
}
263264

src/main/java/de/medizininformatikinitiative/torch/consent/ConsentCodeMapper.java

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,52 @@
22

33
import com.fasterxml.jackson.databind.JsonNode;
44
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import de.medizininformatikinitiative.torch.exceptions.ValidationException;
6+
import de.medizininformatikinitiative.torch.model.mapping.ConsentKey;
57

68
import java.io.File;
79
import java.io.IOException;
8-
import java.util.*;
10+
import java.util.ArrayList;
11+
import java.util.Collections;
12+
import java.util.EnumMap;
13+
import java.util.HashSet;
14+
import java.util.Iterator;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.Objects;
18+
import java.util.Set;
919

1020

1121
/**
12-
* Provides a Map of all consent codes belonging to a consent key e.g. "yes-yes-yes-yes"
22+
* Provides a Map of all consent codes belonging to a consent key e.g. "yes-yes-yes-yes" as
23+
* defined in the mapping file.
1324
*/
1425
public class ConsentCodeMapper {
1526

16-
private final Map<String, List<String>> consentMap;
27+
private final Map<ConsentKey, List<String>> consentMap;
1728
private final ObjectMapper objectMapper;
1829

1930

20-
public ConsentCodeMapper(String consentFilePath, ObjectMapper objectMapper) throws IOException {
21-
this.consentMap = new HashMap<>();
31+
/**
32+
* @param consentFilePath Path to the mapping file e.g. consent-mappings_fhir.json
33+
* @param objectMapper Objectmapper for Json Processing
34+
* @throws IOException
35+
* @throws ValidationException
36+
*/
37+
public ConsentCodeMapper(String consentFilePath, ObjectMapper objectMapper) throws IOException, ValidationException {
38+
this.consentMap = new EnumMap<>(ConsentKey.class);
39+
Objects.requireNonNull(objectMapper);
2240
this.objectMapper = objectMapper;
2341
buildConsentMap(consentFilePath);
2442
}
2543

2644
// Method to build the map based on the JSON file
27-
private void buildConsentMap(String filePath) throws IOException {
28-
//Class get Resource as Stream
29-
//init method
45+
private void buildConsentMap(String filePath) throws IOException, ValidationException {
3046
File file = new File(filePath);
3147
JsonNode consentMappingData = objectMapper.readTree(file.getAbsoluteFile());
3248
for (JsonNode consent : consentMappingData) {
33-
String keyCode = consent.get("key").get("code").asText();
49+
50+
ConsentKey keyCode = ConsentKey.fromString(consent.get("key").get("code").asText());
3451
List<String> relevantCodes = new ArrayList<>();
3552

3653
JsonNode fixedCriteria = consent.get("fixedCriteria");
@@ -43,12 +60,23 @@ private void buildConsentMap(String filePath) throws IOException {
4360
}
4461
}
4562
}
46-
4763
consentMap.put(keyCode, relevantCodes);
4864
}
65+
if (consentMap.size() != ConsentKey.values().length) {
66+
throw new ValidationException("Consent map size does not match ConsentKey enum size");
67+
}
4968
}
5069

51-
public Set<String> getRelevantCodes(String key) {
70+
/**
71+
* @param key Consentkey to be handled
72+
* @return All codes associated with that key
73+
* e.g. for "no-no-no-no" in the current version of the codesystem
74+
* "urn:oid:2.16.840.1.113883.3.1937.777.24.5.3" the codes
75+
* "2.16.840.1.113883.3.1937.777.24.5.3.47"
76+
* "2.16.840.1.113883.3.1937.777.24.5.3.49"
77+
* "2.16.840.1.113883.3.1937.777.24.5.3.9" will be returned
78+
*/
79+
public Set<String> getRelevantCodes(ConsentKey key) {
5280
return new HashSet<>(consentMap.getOrDefault(key, Collections.emptyList()));
5381
}
54-
}
82+
}

src/main/java/de/medizininformatikinitiative/torch/consent/ConsentFetcher.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import de.medizininformatikinitiative.torch.model.fhir.Query;
99
import de.medizininformatikinitiative.torch.model.management.PatientBatch;
1010
import de.medizininformatikinitiative.torch.model.management.PatientResourceBundle;
11+
import de.medizininformatikinitiative.torch.model.mapping.ConsentKey;
1112
import de.medizininformatikinitiative.torch.service.DataStore;
1213
import de.medizininformatikinitiative.torch.util.ResourceUtils;
1314
import org.hl7.fhir.r4.model.Consent;
@@ -98,7 +99,7 @@ private static Map<String, Provisions> mergeAllProvisions(Map<String, Collection
9899
* @param batch A list of patient IDs to process in this batch.
99100
* @return A {@link Flux} emitting maps containing consent information structured by patient ID and consent codes.
100101
*/
101-
public Mono<PatientBatchWithConsent> buildConsentInfo(String key, PatientBatch batch) {
102+
public Mono<PatientBatchWithConsent> buildConsentInfo(ConsentKey key, PatientBatch batch) {
102103
logger.debug("Starting to build consent info for key {} and {} patients", key, batch.ids().size());
103104

104105
Set<String> codes = mapper.getRelevantCodes(key);

src/main/java/de/medizininformatikinitiative/torch/consent/ConsentHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import de.medizininformatikinitiative.torch.model.consent.PatientBatchWithConsent;
55
import de.medizininformatikinitiative.torch.model.fhir.Query;
66
import de.medizininformatikinitiative.torch.model.management.PatientBatch;
7+
import de.medizininformatikinitiative.torch.model.mapping.ConsentKey;
78
import de.medizininformatikinitiative.torch.service.DataStore;
89
import de.medizininformatikinitiative.torch.util.ResourceUtils;
910
import org.hl7.fhir.r4.model.Encounter;
@@ -73,7 +74,7 @@ private static Mono<Map<String, Collection<Encounter>>> groupEncounterByPatient(
7374
* @param batch Batch of patient IDs.
7475
* @return {@link Mono<PatientBatchWithConsent>} containing all required provisions by patient with valid times.
7576
*/
76-
public Mono<PatientBatchWithConsent> fetchAndBuildConsentInfo(String consentKey, PatientBatch batch) {
77+
public Mono<PatientBatchWithConsent> fetchAndBuildConsentInfo(ConsentKey consentKey, PatientBatch batch) {
7778
return consentFetcher.buildConsentInfo(consentKey, batch)
7879
.flatMap(this::adjustConsentPeriodsByPatientEncounters);
7980
}

src/main/java/de/medizininformatikinitiative/torch/model/crtdl/Crtdl.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
44
import com.fasterxml.jackson.annotation.JsonProperty;
55
import com.fasterxml.jackson.databind.JsonNode;
6-
import org.slf4j.Logger;
7-
import org.slf4j.LoggerFactory;
6+
import de.medizininformatikinitiative.torch.exceptions.ValidationException;
7+
import de.medizininformatikinitiative.torch.model.mapping.ConsentKey;
88

99
import java.util.Optional;
1010

@@ -17,32 +17,28 @@ public record Crtdl(
1717
@JsonProperty(required = true)
1818
DataExtraction dataExtraction
1919
) {
20-
private static final Logger logger = LoggerFactory.getLogger(Crtdl.class);
2120

2221
public Crtdl {
2322
requireNonNull(cohortDefinition);
2423
requireNonNull(dataExtraction);
2524
}
2625

27-
public Optional<String> consentKey() {
26+
public Optional<ConsentKey> consentKey() throws ValidationException {
2827
JsonNode inclusionCriteria = cohortDefinition.get("inclusionCriteria");
2928
if (inclusionCriteria != null && inclusionCriteria.isArray()) {
3029
for (JsonNode criteriaGroup : inclusionCriteria) {
3130
for (JsonNode criteria : criteriaGroup) {
3231
JsonNode context = criteria.get("context");
3332
if (context != null && "Einwilligung".equals(context.get("code").asText())) {
34-
JsonNode termcodes = criteria.get("termCodes");
35-
if (termcodes != null && termcodes.isArray()) {
36-
JsonNode firstTermcode = termcodes.get(0);
37-
if (firstTermcode != null && firstTermcode.has("code")) {
38-
return Optional.of(firstTermcode.get("code").asText());
39-
}
33+
JsonNode firstTermcode = criteria.get("termCodes").get(0);
34+
if (firstTermcode != null && firstTermcode.has("code")) {
35+
String code = firstTermcode.get("code").asText();
36+
return Optional.of(ConsentKey.fromString(code));
4037
}
4138
}
4239
}
4340
}
4441
}
4542
return Optional.empty();
4643
}
47-
4844
}
Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,18 @@
11
package de.medizininformatikinitiative.torch.model.crtdl.annotated;
22

33
import com.fasterxml.jackson.databind.JsonNode;
4-
import org.slf4j.Logger;
5-
import org.slf4j.LoggerFactory;
4+
import de.medizininformatikinitiative.torch.model.mapping.ConsentKey;
65

76
import java.util.Optional;
87

98
import static java.util.Objects.requireNonNull;
109

11-
public record AnnotatedCrtdl(JsonNode cohortDefinition, AnnotatedDataExtraction dataExtraction) {
10+
public record AnnotatedCrtdl(JsonNode cohortDefinition, AnnotatedDataExtraction dataExtraction,
11+
Optional<ConsentKey> consentKey) {
1212

1313
public AnnotatedCrtdl {
1414
requireNonNull(cohortDefinition);
1515
requireNonNull(dataExtraction);
16-
}
17-
18-
public Optional<String> consentKey() {
19-
JsonNode inclusionCriteria = cohortDefinition.get("inclusionCriteria");
20-
if (inclusionCriteria != null && inclusionCriteria.isArray()) {
21-
for (JsonNode criteriaGroup : inclusionCriteria) {
22-
for (JsonNode criteria : criteriaGroup) {
23-
JsonNode context = criteria.get("context");
24-
if (context != null && "Einwilligung".equals(context.get("code").asText())) {
25-
JsonNode termcodes = criteria.get("termCodes");
26-
if (termcodes != null && termcodes.isArray()) {
27-
JsonNode firstTermcode = termcodes.get(0);
28-
if (firstTermcode != null && firstTermcode.has("code")) {
29-
return Optional.of(firstTermcode.get("code").asText());
30-
}
31-
}
32-
}
33-
}
34-
}
35-
}
36-
return Optional.empty();
16+
requireNonNull(consentKey);
3717
}
3818
}

src/main/java/de/medizininformatikinitiative/torch/model/mapping/ConsentKey.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package de.medizininformatikinitiative.torch.model.mapping;
22

3+
import de.medizininformatikinitiative.torch.exceptions.ValidationException;
4+
35
import static java.util.Objects.requireNonNull;
46

57
public enum ConsentKey {
@@ -31,4 +33,13 @@ public enum ConsentKey {
3133
public String toString() {
3234
return s;
3335
}
36+
37+
public static ConsentKey fromString(String value) throws ValidationException {
38+
for (ConsentKey key : values()) {
39+
if (key.toString().equalsIgnoreCase(value)) {
40+
return key;
41+
}
42+
}
43+
throw new ValidationException("Unknown consent key: " + value);
44+
}
3445
}

src/main/java/de/medizininformatikinitiative/torch/service/CrtdlValidatorService.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import de.medizininformatikinitiative.torch.model.crtdl.annotated.AnnotatedAttributeGroup;
1010
import de.medizininformatikinitiative.torch.model.crtdl.annotated.AnnotatedCrtdl;
1111
import de.medizininformatikinitiative.torch.model.crtdl.annotated.AnnotatedDataExtraction;
12+
import de.medizininformatikinitiative.torch.model.mapping.ConsentKey;
1213
import de.medizininformatikinitiative.torch.util.CompiledStructureDefinition;
1314
import de.medizininformatikinitiative.torch.util.FhirPathBuilder;
1415
import org.hl7.fhir.r4.model.ElementDefinition;
@@ -20,6 +21,7 @@
2021
import java.util.HashSet;
2122
import java.util.List;
2223
import java.util.Objects;
24+
import java.util.Optional;
2325
import java.util.Set;
2426
import java.util.function.Predicate;
2527

@@ -45,26 +47,27 @@ public CrtdlValidatorService(StructureDefinitionHandler profileHandler, Standard
4547
* @return the validated Crtdl or an error signal with ValidationException if a profile is unknown.
4648
*/
4749
public AnnotatedCrtdl validate(Crtdl crtdl) throws ValidationException {
50+
Optional<ConsentKey> consentKey = crtdl.consentKey();
4851
List<AnnotatedAttributeGroup> annotatedAttributeGroups = new ArrayList<>();
4952
Set<String> linkedGroups = new HashSet<>();
5053
Set<String> successfullyAnnotatedGroups = new HashSet<>();
51-
boolean exactlyOnePatientGroup = false;
54+
boolean patientGroupFound = false;
5255
String patientAttributeGroupId = "";
5356

5457
for (AttributeGroup attributeGroup : crtdl.dataExtraction().attributeGroups()) {
5558
CompiledStructureDefinition definition = profileHandler.getDefinition(attributeGroup.groupReference())
5659
.orElseThrow(() -> new ValidationException("Unknown Profile: " + attributeGroup.groupReference()));
5760
if (Objects.equals(definition.type(), "Patient")) {
58-
if (exactlyOnePatientGroup) {
61+
if (patientGroupFound) {
5962
throw new ValidationException(" More than one Patient Attribute Group");
6063
} else {
61-
exactlyOnePatientGroup = true;
64+
patientGroupFound = true;
6265
patientAttributeGroupId = attributeGroup.id();
6366
logger.debug("Found Patient Attribute Group {}", patientAttributeGroupId);
6467
}
6568
}
6669
}
67-
if (!exactlyOnePatientGroup) {
70+
if (!patientGroupFound) {
6871
throw new ValidationException("No Patient Attribute Group");
6972
}
7073

@@ -86,7 +89,7 @@ public AnnotatedCrtdl validate(Crtdl crtdl) throws ValidationException {
8689
}
8790

8891

89-
return new AnnotatedCrtdl(crtdl.cohortDefinition(), new AnnotatedDataExtraction(annotatedAttributeGroups));
92+
return new AnnotatedCrtdl(crtdl.cohortDefinition(), new AnnotatedDataExtraction(annotatedAttributeGroups), consentKey);
9093
}
9194

9295
private AnnotatedAttributeGroup annotateGroup(AttributeGroup attributeGroup, CompiledStructureDefinition

src/test/java/de/medizininformatikinitiative/torch/ConsentFetcherIT.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import de.medizininformatikinitiative.torch.exceptions.ConsentViolatedException;
66
import de.medizininformatikinitiative.torch.model.consent.PatientBatchWithConsent;
77
import de.medizininformatikinitiative.torch.model.management.PatientBatch;
8+
import de.medizininformatikinitiative.torch.model.mapping.ConsentKey;
89
import org.hl7.fhir.r4.model.DateTimeType;
910
import org.hl7.fhir.r4.model.Observation;
1011
import org.hl7.fhir.r4.model.Reference;
@@ -69,7 +70,7 @@ private void assertConsentFalse(PatientBatchWithConsent batch, String patientId,
6970

7071
@Test
7172
void failsOnNoPatientMatchesConsentKeyBuildingConsent() {
72-
var resultBatch = consentFetcher.buildConsentInfo("yes-no-no-yes", BATCH);
73+
var resultBatch = consentFetcher.buildConsentInfo(ConsentKey.YES_NO_NO_YES, BATCH);
7374

7475
StepVerifier.create(resultBatch)
7576
.expectErrorSatisfies(error -> assertThat(error)
@@ -80,7 +81,7 @@ void failsOnNoPatientMatchesConsentKeyBuildingConsent() {
8081

8182
@Test
8283
void failsOnUnknownPatientBuildingConsent() {
83-
var resultBatch = consentFetcher.buildConsentInfo("yes-yes-yes-yes", BATCH_UNKNOWN);
84+
var resultBatch = consentFetcher.buildConsentInfo(ConsentKey.YES_YES_YES_YES, BATCH_UNKNOWN);
8485

8586
StepVerifier.create(resultBatch)
8687
.expectErrorSatisfies(error -> assertThat(error)
@@ -91,7 +92,7 @@ void failsOnUnknownPatientBuildingConsent() {
9192

9293
@Test
9394
void successBuildingConsent() {
94-
var resultBatch = consentFetcher.buildConsentInfo("yes-yes-yes-yes", BATCH);
95+
var resultBatch = consentFetcher.buildConsentInfo(ConsentKey.YES_YES_YES_YES, BATCH);
9596

9697
StepVerifier.create(resultBatch)
9798
.assertNext(batch -> {

src/test/java/de/medizininformatikinitiative/torch/ConsentHandlerIT.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import de.medizininformatikinitiative.torch.consent.ConsentValidator;
55
import de.medizininformatikinitiative.torch.model.consent.PatientBatchWithConsent;
66
import de.medizininformatikinitiative.torch.model.management.PatientBatch;
7+
import de.medizininformatikinitiative.torch.model.mapping.ConsentKey;
78
import org.hl7.fhir.r4.model.DateTimeType;
89
import org.hl7.fhir.r4.model.Observation;
910
import org.hl7.fhir.r4.model.Reference;
@@ -67,7 +68,7 @@ private void assertConsentFalse(PatientBatchWithConsent batch, String patientId,
6768

6869
@Test
6970
void successAfterEncounterUpdatesProvisions() {
70-
var resultBatch = consentHandler.fetchAndBuildConsentInfo("yes-yes-yes-yes", BATCH);
71+
var resultBatch = consentHandler.fetchAndBuildConsentInfo(ConsentKey.YES_YES_YES_YES, BATCH);
7172

7273
StepVerifier.create(resultBatch)
7374
.assertNext(batch -> {

0 commit comments

Comments
 (0)