Skip to content

Commit 27f8773

Browse files
committed
Change Consent Key Parsing To Enum
1 parent 6de804a commit 27f8773

19 files changed

+330
-7540
lines changed

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

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,66 @@
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.Set;
918

1019

1120
/**
1221
* Provides a Map of all consent codes belonging to a consent key e.g. "yes-yes-yes-yes"
1322
*/
1423
public class ConsentCodeMapper {
1524

16-
private final Map<String, List<String>> consentMap;
25+
private final Map<ConsentKey, List<String>> consentMap;
1726
private final ObjectMapper objectMapper;
1827

1928

2029
public ConsentCodeMapper(String consentFilePath, ObjectMapper objectMapper) throws IOException {
21-
this.consentMap = new HashMap<>();
30+
this.consentMap = new EnumMap<>(ConsentKey.class);
2231
this.objectMapper = objectMapper;
2332
buildConsentMap(consentFilePath);
2433
}
2534

2635
// Method to build the map based on the JSON file
2736
private void buildConsentMap(String filePath) throws IOException {
28-
//Class get Resource as Stream
29-
//init method
3037
File file = new File(filePath);
3138
JsonNode consentMappingData = objectMapper.readTree(file.getAbsoluteFile());
3239
for (JsonNode consent : consentMappingData) {
33-
String keyCode = consent.get("key").get("code").asText();
34-
List<String> relevantCodes = new ArrayList<>();
35-
36-
JsonNode fixedCriteria = consent.get("fixedCriteria");
37-
if (fixedCriteria != null) {
38-
for (JsonNode criterion : fixedCriteria) {
39-
Iterator<JsonNode> values = criterion.get("value").elements();
40-
while (values.hasNext()) {
41-
JsonNode value = values.next();
42-
relevantCodes.add(value.get("code").asText());
40+
try {
41+
ConsentKey keyCode = ConsentKey.fromString(consent.get("key").get("code").asText());
42+
List<String> relevantCodes = new ArrayList<>();
43+
44+
JsonNode fixedCriteria = consent.get("fixedCriteria");
45+
if (fixedCriteria != null) {
46+
for (JsonNode criterion : fixedCriteria) {
47+
Iterator<JsonNode> values = criterion.get("value").elements();
48+
while (values.hasNext()) {
49+
JsonNode value = values.next();
50+
relevantCodes.add(value.get("code").asText());
51+
}
4352
}
4453
}
54+
consentMap.put(keyCode, relevantCodes);
55+
} catch (ValidationException e) {
56+
throw new IllegalStateException("Consent map has invalid key" + e.getMessage());
4557
}
46-
47-
consentMap.put(keyCode, relevantCodes);
58+
}
59+
if (consentMap.size() != ConsentKey.values().length) {
60+
throw new IllegalStateException("Consent map size does not match ConsentKey enum size");
4861
}
4962
}
5063

51-
public Set<String> getRelevantCodes(String key) {
64+
public Set<String> getRelevantCodes(ConsentKey key) {
5265
return new HashSet<>(consentMap.getOrDefault(key, Collections.emptyList()));
5366
}
54-
}
67+
}

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: 8 additions & 10 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,26 +17,23 @@ 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
}
@@ -45,4 +42,5 @@ public Optional<String> consentKey() {
4542
return Optional.empty();
4643
}
4744

45+
4846
}
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 ConsentKey string: " + value);
44+
}
3445
}

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

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@
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.FhirPathBuilder;
1314
import org.hl7.fhir.r4.model.ElementDefinition;
1415
import org.hl7.fhir.r4.model.Resource;
1516
import org.hl7.fhir.r4.model.StructureDefinition;
1617
import org.slf4j.Logger;
1718
import org.slf4j.LoggerFactory;
1819

19-
import java.io.IOException;
20-
import java.util.*;
20+
import java.util.ArrayList;
21+
import java.util.HashSet;
22+
import java.util.List;
23+
import java.util.Optional;
24+
import java.util.Set;
2125
import java.util.function.Predicate;
2226

2327
public class CrtdlValidatorService {
@@ -29,7 +33,7 @@ public class CrtdlValidatorService {
2933
private final FilterService filterService;
3034

3135

32-
public CrtdlValidatorService(StructureDefinitionHandler profileHandler, StandardAttributeGenerator attributeGenerator, FilterService filterService) throws IOException {
36+
public CrtdlValidatorService(StructureDefinitionHandler profileHandler, StandardAttributeGenerator attributeGenerator, FilterService filterService) {
3337
this.profileHandler = profileHandler;
3438
this.attributeGenerator = attributeGenerator;
3539
this.filterService = filterService;
@@ -42,28 +46,31 @@ public CrtdlValidatorService(StructureDefinitionHandler profileHandler, Standard
4246
* @return the validated Crtdl or an error signal with ValidationException if a profile is unknown.
4347
*/
4448
public AnnotatedCrtdl validate(Crtdl crtdl) throws ValidationException {
49+
Optional<ConsentKey> crtdlKey;
50+
try {
51+
crtdlKey = crtdl.consentKey();
52+
} catch (ValidationException e) {
53+
throw new ValidationException("No valid Consent Key Found");
54+
}
4555
List<AnnotatedAttributeGroup> annotatedAttributeGroups = new ArrayList<>();
4656
Set<String> linkedGroups = new HashSet<>();
4757
Set<String> successfullyAnnotatedGroups = new HashSet<>();
48-
boolean exactlyOnePatientGroup = false;
58+
boolean patientGroupFound = false;
4959
String patientAttributeGroupId = "";
5060

5161
for (AttributeGroup attributeGroup : crtdl.dataExtraction().attributeGroups()) {
5262
StructureDefinition definition = profileHandler.getDefinition(attributeGroup.groupReference());
53-
if (definition != null) {
54-
if (Objects.equals(definition.getType(), "Patient")) {
55-
if (exactlyOnePatientGroup) {
56-
throw new ValidationException(" More than one Patient Attribute Group");
57-
} else {
58-
exactlyOnePatientGroup = true;
59-
patientAttributeGroupId = attributeGroup.id();
60-
logger.debug("Found Patient Attribute Group {}", patientAttributeGroupId);
61-
}
62-
63+
if (definition != null && "Patient".equals(definition.getType())) {
64+
if (patientGroupFound) {
65+
throw new ValidationException("More than one Patient Attribute Group");
66+
} else {
67+
patientGroupFound = true;
68+
patientAttributeGroupId = attributeGroup.id();
69+
logger.debug("Found Patient Attribute Group {}", patientAttributeGroupId);
6370
}
6471
}
6572
}
66-
if (!exactlyOnePatientGroup) {
73+
if (!patientGroupFound) {
6774
throw new ValidationException("No Patient Attribute Group");
6875
}
6976

@@ -85,11 +92,11 @@ public AnnotatedCrtdl validate(Crtdl crtdl) throws ValidationException {
8592

8693
linkedGroups.removeAll(successfullyAnnotatedGroups);
8794
if (!linkedGroups.isEmpty()) {
88-
throw new ValidationException("Missing defintion for linked groups: " + linkedGroups);
95+
throw new ValidationException("Missing definition for linked groups: " + linkedGroups);
8996
}
9097

9198

92-
return new AnnotatedCrtdl(crtdl.cohortDefinition(), new AnnotatedDataExtraction(annotatedAttributeGroups));
99+
return new AnnotatedCrtdl(crtdl.cohortDefinition(), new AnnotatedDataExtraction(annotatedAttributeGroups), crtdlKey);
93100
}
94101

95102
private AnnotatedAttributeGroup annotateGroup(AttributeGroup attributeGroup, StructureDefinition
@@ -115,14 +122,10 @@ private AnnotatedAttributeGroup annotateGroup(AttributeGroup attributeGroup, Str
115122
}
116123

117124
AnnotatedAttributeGroup standardGroup = attributeGenerator.generate(attributeGroup, patientGroupId);
118-
try {
119-
Predicate<Resource> filter = filterService.compileFilter(attributeGroup.filter(), definition.getType());
120-
return standardGroup
121-
.addAttributes(annotatedAttributes)
122-
.setCompiledFilter(filter);
123-
} catch (Exception e) {
124-
throw new RuntimeException(e);
125-
}
125+
Predicate<Resource> filter = filterService.compileFilter(attributeGroup.filter(), definition.getType());
126+
return standardGroup
127+
.addAttributes(annotatedAttributes)
128+
.setCompiledFilter(filter);
126129

127130
}
128131

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)