Skip to content

Commit 8ffcaad

Browse files
lukedegruchyc-schulerJPercival
authored
Migrate SDE accumulation logic from DSTU3/R4 to SdeDef (#982)
* First round of changes to migrate SDE accumulation logic from R4 builder specific logic to SdeDef. Get Dstu3 to use StratumValueWrapper instead of rolling its own. * Get rid of SdeAccumulator and its tests, and roll the tests into a new MeasureEvaluationResultHandlerTest. * Make the SdeDef accumulation code more self-contained and readable. Get rid of lazily instantiated collections in the class state. Get rid of isAccumulated(). * Error handling if a SDE expression was not found. --------- Co-authored-by: c-schuler <hoofschu@gmail.com> Co-authored-by: JP <jonathan.percival@smilecdr.com>
1 parent e7b0be1 commit 8ffcaad

9 files changed

Lines changed: 250 additions & 213 deletions

File tree

cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CriteriaResult.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package org.opencds.cqf.fhir.cr.measure.common;
22

3+
import java.util.ArrayList;
34
import java.util.Collections;
45
import java.util.HashSet;
6+
import java.util.List;
7+
import java.util.Objects;
58
import java.util.Set;
9+
import java.util.stream.StreamSupport;
610

711
public class CriteriaResult {
812
private final Object value;
@@ -46,6 +50,22 @@ public Set<Object> evaluatedResources() {
4650
return this.evaluatedResources;
4751
}
4852

53+
/**
54+
* Returns the value(s) as a list with nulls filtered out.
55+
* Handles single values, iterables, and null values uniformly.
56+
*/
57+
public List<Object> nonNullValues() {
58+
if (this.value == null) {
59+
return Collections.emptyList();
60+
}
61+
if (this.value instanceof Iterable<?> iterable) {
62+
return StreamSupport.stream(iterable.spliterator(), false)
63+
.filter(Objects::nonNull)
64+
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
65+
}
66+
return List.of(this.value);
67+
}
68+
4969
private Set<Object> buildSet(Iterable<Object> iterable) {
5070
return new HashSetForFhirResourcesAndCqlTypes<>(iterable);
5171
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.opencds.cqf.fhir.cr.measure.common;
2+
3+
/**
4+
* Capture a post CQL evaluation error in which an expression result was not found when expected.
5+
*/
6+
public class ExpressionResultNotFoundException extends RuntimeException {
7+
public ExpressionResultNotFoundException(String expressionType, String expressionName) {
8+
super("Expression result for type: '%s' and expression name: '%s' not found"
9+
.formatted(expressionType, expressionName));
10+
}
11+
}

cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEvaluator.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,9 @@ protected Object evaluateDateOfCompliance(PopulationDef populationDef, Evaluatio
664664
protected void evaluateSdes(String subjectId, List<SdeDef> sdes, EvaluationResult evaluationResult) {
665665
for (SdeDef sde : sdes) {
666666
var expressionResult = evaluationResult.get(sde.expression());
667+
if (expressionResult == null) {
668+
throw new ExpressionResultNotFoundException("SDE", sde.expression());
669+
}
667670
Object result = expressionResult.getValue();
668671
// TODO: This is a hack-around for an cql engine bug. Need to investigate.
669672
if ((result instanceof List<?> list) && (list.size() == 1) && list.get(0) == null) {

cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureMultiSubjectEvaluator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ public static void postEvaluationMultiSubject(FhirContext fhirContext, MeasureDe
119119
stratifierDef.addAllStratum(stratumDefs);
120120
}
121121
}
122+
123+
// Accumulate SDE results across subjects (version-agnostic)
124+
for (SdeDef sde : measureDef.sdes()) {
125+
sde.accumulate();
126+
}
122127
}
123128

124129
private static List<StratumDef> buildCriteriaStrata(

cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/SdeDef.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
package org.opencds.cqf.fhir.cr.measure.common;
22

33
import java.util.HashMap;
4+
import java.util.HashSet;
45
import java.util.Map;
56
import java.util.Set;
7+
import java.util.function.Function;
8+
import java.util.stream.Collectors;
69

710
public class SdeDef {
811

912
private final String id;
1013
private final ConceptDef code;
1114
private final String expression;
1215
private final String description;
13-
private Map<String, CriteriaResult> results;
16+
private final Map<String, CriteriaResult> results = new HashMap<>();
17+
18+
// Pre-accumulated state (populated by MeasureMultiSubjectEvaluator)
19+
private final Map<StratumValueWrapper, Long> accumulatedValues = new HashMap<>();
20+
private final Set<Object> allEvaluatedResources = new HashSet<>();
1421

1522
public SdeDef(String id, ConceptDef code, String expression) {
1623
this(id, code, expression, null);
@@ -40,14 +47,32 @@ public String description() {
4047
}
4148

4249
public void putResult(String subject, Object value, Set<Object> evaluatedResources) {
43-
this.getResults().put(subject, new CriteriaResult(value, evaluatedResources));
50+
this.results.put(subject, new CriteriaResult(value, evaluatedResources));
51+
}
52+
53+
public Map<StratumValueWrapper, Long> getAccumulatedValues() {
54+
return this.accumulatedValues;
55+
}
56+
57+
public Set<Object> getAllEvaluatedResources() {
58+
return this.allEvaluatedResources;
4459
}
4560

46-
public Map<String, CriteriaResult> getResults() {
47-
if (this.results == null) {
48-
this.results = new HashMap<>();
61+
/**
62+
* Aggregates per-subject SDE results into value counts and a merged set of evaluated resources.
63+
* Called by {@link MeasureMultiSubjectEvaluator#postEvaluationMultiSubject} for population reports.
64+
*/
65+
public void accumulate() {
66+
// Merge all evaluated resources across subjects
67+
for (CriteriaResult result : results.values()) {
68+
allEvaluatedResources.addAll(result.evaluatedResources());
4969
}
5070

51-
return this.results;
71+
// Count occurrences of each distinct value across all subjects
72+
Map<StratumValueWrapper, Long> counts = results.values().stream()
73+
.flatMap(result -> result.nonNullValues().stream())
74+
.map(StratumValueWrapper::new)
75+
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
76+
accumulatedValues.putAll(counts);
5277
}
5378
}

0 commit comments

Comments
 (0)