Skip to content

Commit 8423c1a

Browse files
committed
Change Consent Key Parsing To Enum
1 parent c8d1617 commit 8423c1a

19 files changed

+324
-7531
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: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,20 @@
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;
13+
import de.medizininformatikinitiative.torch.model.mapping.ConsentKey;
1214
import de.medizininformatikinitiative.torch.util.FhirPathBuilder;
1315
import org.hl7.fhir.r4.model.ElementDefinition;
1416
import org.hl7.fhir.r4.model.Resource;
1517
import org.hl7.fhir.r4.model.StructureDefinition;
1618
import org.slf4j.Logger;
1719
import org.slf4j.LoggerFactory;
1820

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

2328
public class CrtdlValidatorService {
@@ -29,7 +34,7 @@ public class CrtdlValidatorService {
2934
private final FilterService filterService;
3035

3136

32-
public CrtdlValidatorService(StructureDefinitionHandler profileHandler, StandardAttributeGenerator attributeGenerator, FilterService filterService) throws IOException {
37+
public CrtdlValidatorService(StructureDefinitionHandler profileHandler, StandardAttributeGenerator attributeGenerator, FilterService filterService) {
3338
this.profileHandler = profileHandler;
3439
this.attributeGenerator = attributeGenerator;
3540
this.filterService = filterService;
@@ -42,28 +47,31 @@ public CrtdlValidatorService(StructureDefinitionHandler profileHandler, Standard
4247
* @return the validated Crtdl or an error signal with ValidationException if a profile is unknown.
4348
*/
4449
public AnnotatedCrtdl validate(Crtdl crtdl) throws ValidationException {
50+
Optional<ConsentKey> crtdlKey;
51+
try {
52+
crtdlKey = crtdl.consentKey();
53+
} catch (ValidationException e) {
54+
throw new ValidationException("No valid Consent Key Found");
55+
}
4556
List<AnnotatedAttributeGroup> annotatedAttributeGroups = new ArrayList<>();
4657
Set<String> linkedGroups = new HashSet<>();
4758
Set<String> successfullyAnnotatedGroups = new HashSet<>();
48-
boolean exactlyOnePatientGroup = false;
59+
boolean patientGroupFound = false;
4960
String patientAttributeGroupId = "";
5061

5162
for (AttributeGroup attributeGroup : crtdl.dataExtraction().attributeGroups()) {
5263
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-
64+
if (definition != null && "Patient".equals(definition.getType())) {
65+
if (patientGroupFound) {
66+
throw new ValidationException("More than one Patient Attribute Group");
67+
} else {
68+
patientGroupFound = true;
69+
patientAttributeGroupId = attributeGroup.id();
70+
logger.debug("Found Patient Attribute Group {}", patientAttributeGroupId);
6371
}
6472
}
6573
}
66-
if (!exactlyOnePatientGroup) {
74+
if (!patientGroupFound) {
6775
throw new ValidationException("No Patient Attribute Group");
6876
}
6977

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

8694
linkedGroups.removeAll(successfullyAnnotatedGroups);
8795
if (!linkedGroups.isEmpty()) {
88-
throw new ValidationException("Missing defintion for linked groups: " + linkedGroups);
96+
throw new ValidationException("Missing definition for linked groups: " + linkedGroups);
8997
}
9098

9199

92-
return new AnnotatedCrtdl(crtdl.cohortDefinition(), new AnnotatedDataExtraction(annotatedAttributeGroups));
100+
return new AnnotatedCrtdl(crtdl.cohortDefinition(), new AnnotatedDataExtraction(annotatedAttributeGroups), crtdlKey);
93101
}
94102

95103
private AnnotatedAttributeGroup annotateGroup(AttributeGroup attributeGroup, StructureDefinition

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)