Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c0cb856
WIP cont-variable score
Capt-Mac Oct 2, 2025
a4f5738
Merge remote-tracking branch 'origin/master' into ld-20251008-cont-va…
lukedegruchy Oct 8, 2025
f8f1aa1
Fix compile errors and spotless.
lukedegruchy Oct 8, 2025
8003414
Merge remote-tracking branch 'origin/master' into ld-20251008-cont-va…
lukedegruchy Oct 8, 2025
1597d9f
Set up a test scenario involving all aggregation methods (min, max, e…
lukedegruchy Oct 8, 2025
c115f6f
Add TODOs indicating the state of continuous variable evaluation.
lukedegruchy Oct 8, 2025
59f6a86
Setup scenario with encounters with different length periods for a mo…
lukedegruchy Oct 8, 2025
1b5cfe0
Add dupes.
lukedegruchy Oct 8, 2025
0b4bacb
Split out IG directories between Encounter and boolean basis, and kee…
lukedegruchy Oct 9, 2025
34be2bb
Adjust test data so there are 4 periods of 120 minutes. Fix bad refe…
lukedegruchy Oct 9, 2025
942c2e8
All tests pass now with a data set that includes one encounter per pa…
lukedegruchy Oct 9, 2025
7c9c821
Move the continuous variable files from the MeasureTest folder to a n…
lukedegruchy Oct 9, 2025
d837b48
Start setting up explicit tests with invalid CQL for measure observat…
lukedegruchy Oct 10, 2025
679ea19
Forgot to add measure JSON.
lukedegruchy Oct 10, 2025
aff3c8a
Fix bug where we were erroring out completely on partial failures, br…
lukedegruchy Oct 10, 2025
725a8d8
Fix tests with legitimate errors to fail gracefully if a continuous v…
lukedegruchy Oct 10, 2025
2ebbaa2
Overloaded constructors to reduce cognitive load on code review. Add…
lukedegruchy Oct 10, 2025
470c1a4
Flip actual and expected so assertion failure is correct.
lukedegruchy Oct 10, 2025
bb62600
Fix DSTU3 test failures by eliminating all IdType logic for building …
lukedegruchy Oct 10, 2025
ff654a3
Add last failing test case. Refactoring is next.
lukedegruchy Oct 10, 2025
7c2f53c
Cleanup cruft.
lukedegruchy Oct 10, 2025
9fd5f4a
Cleanup more cruft. Some refactoring. Better error handling for pro…
lukedegruchy Oct 10, 2025
0af3abb
More refactoring. More cruft cleaning.
lukedegruchy Oct 10, 2025
77995af
Start setting up stratifier continuous variable observation testing.
lukedegruchy Oct 14, 2025
c25b149
Introduce draft algorithm to compute scores per stratum for continuou…
lukedegruchy Oct 14, 2025
230e4b3
Redo test data to support continuous variable stratifiers for boolean…
lukedegruchy Oct 15, 2025
e777f42
Move mutation of the EvaluationResult with continuous variable observ…
lukedegruchy Oct 15, 2025
d3d1d59
Sonar fixes.
lukedegruchy Oct 15, 2025
c05811e
Introduce aggregation method enum.
lukedegruchy Oct 15, 2025
c561333
Implement the rest of the continuous variable tests. Still more ref…
lukedegruchy Oct 15, 2025
f7e4b09
Clean up cruft. Add javadoc. More refactoring.
lukedegruchy Oct 15, 2025
1a7d135
More refactoring of the measure def builder but still not done yet.
lukedegruchy Oct 15, 2025
a49e6a3
Another intermediate refactoring step.
lukedegruchy Oct 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.instance.model.api.IIdType;
import org.opencds.cqf.cql.engine.execution.EvaluationResult;

/**
Expand All @@ -21,40 +20,35 @@
*/
public class CompositeEvaluationResultsPerMeasure {
// The same measure may have successful results AND errors, so account for both
private final Map<IIdType, Map<String, EvaluationResult>> resultsPerMeasure;
private final Map<MeasureDef, Map<String, EvaluationResult>> resultsPerMeasure;
// We may get several errors for a given measure
private final Map<IIdType, List<String>> errorsPerMeasure;
private final Map<MeasureDef, List<String>> errorsPerMeasure;

private CompositeEvaluationResultsPerMeasure(Builder builder) {

var resultsBuilder = ImmutableMap.<IIdType, Map<String, EvaluationResult>>builder();
var resultsBuilder = ImmutableMap.<MeasureDef, Map<String, EvaluationResult>>builder();
builder.resultsPerMeasure.forEach((key, value) -> resultsBuilder.put(key, ImmutableMap.copyOf(value)));
resultsPerMeasure = resultsBuilder.build();

var errorsBuilder = ImmutableMap.<IIdType, List<String>>builder();
var errorsBuilder = ImmutableMap.<MeasureDef, List<String>>builder();
builder.errorsPerMeasure.forEach((key, value) -> errorsBuilder.put(key, List.copyOf(value)));
errorsPerMeasure = errorsBuilder.build();
}

/**
* Retrieves results and populates errors for a given measure.
* This method uses direct map lookups for efficient data retrieval.
* measureDef will occasionally be prepended with the version, which means we need to parse it into an IIdType which
* is too much work, so pass in the measureId directly
*
* @param measureId the ID of the measure to process
* @param measureDef the MeasureDef to populate with errors
*
* @return a map of evaluation results per subject, or an empty map if none exist
*/
public Map<String, EvaluationResult> processMeasureForSuccessOrFailure(IIdType measureId, MeasureDef measureDef) {
var unqualifiedMeasureId = measureId.toUnqualifiedVersionless();

errorsPerMeasure.getOrDefault(unqualifiedMeasureId, List.of()).forEach(measureDef::addError);
public Map<String, EvaluationResult> processMeasureForSuccessOrFailure(MeasureDef measureDef) {
errorsPerMeasure.getOrDefault(measureDef, List.of()).forEach(measureDef::addError);

// We are explicitly maintaining the logic of accepting the lack of any sort of results,
// either errors or successes, and returning an empty map.
return resultsPerMeasure.getOrDefault(unqualifiedMeasureId, Map.of());
return resultsPerMeasure.getOrDefault(measureDef, Map.of());
}

/**
Expand All @@ -63,7 +57,7 @@ public Map<String, EvaluationResult> processMeasureForSuccessOrFailure(IIdType m
* and associated EvaluationResult produced from CQL expression evaluation
* @return {@code Map<IIdType, Map<String, EvaluationResult>>}
*/
public Map<IIdType, Map<String, EvaluationResult>> getResultsPerMeasure() {
public Map<MeasureDef, Map<String, EvaluationResult>> getResultsPerMeasure() {
return this.resultsPerMeasure;
}

Expand All @@ -72,7 +66,7 @@ public Map<IIdType, Map<String, EvaluationResult>> getResultsPerMeasure() {
* When an error is produced while evaluating, we capture the errors generated in this object, which can be rendered per Measure evaluated.
* @return {@code Map<IIdType, List<String>>}
*/
public Map<IIdType, List<String>> getErrorsPerMeasure() {
public Map<MeasureDef, List<String>> getErrorsPerMeasure() {
return this.errorsPerMeasure;
}

Expand All @@ -81,49 +75,59 @@ public static Builder builder() {
}

public static class Builder {
private final Map<IIdType, Map<String, EvaluationResult>> resultsPerMeasure = new HashMap<>();
private final Map<IIdType, List<String>> errorsPerMeasure = new HashMap<>();
private final Map<MeasureDef, Map<String, EvaluationResult>> resultsPerMeasure = new HashMap<>();
private final Map<MeasureDef, List<String>> errorsPerMeasure = new HashMap<>();

public CompositeEvaluationResultsPerMeasure build() {
return new CompositeEvaluationResultsPerMeasure(this);
}

public void addResults(List<IIdType> measureIds, String subjectId, EvaluationResult evaluationResult) {
for (IIdType measureId : measureIds) {
addResult(measureId, subjectId, evaluationResult);
public void addResults(
List<MeasureDef> measureDefs,
String subjectId,
EvaluationResult evaluationResult,
List<MeasureObservationResult> measureObservationResults) {
for (MeasureDef measureDef : measureDefs) {
addResult(measureDef, subjectId, evaluationResult, measureObservationResults);
}
}

public void addResult(IIdType measureId, String subjectId, EvaluationResult evaluationResult) {
public void addResult(
MeasureDef measureDef,
String subjectId,
EvaluationResult evaluationResult,
List<MeasureObservationResult> measureObservationResults) {

// if we have no results, we don't need to add anything
if (evaluationResult == null || evaluationResult.expressionResults.isEmpty()) {
return;
}

var resultPerMeasure =
resultsPerMeasure.computeIfAbsent(measureId.toUnqualifiedVersionless(), k -> new HashMap<>());
// Mutate the evaluationResults to include continuous variable evaluation results
measureObservationResults.forEach(measureObservationResult -> evaluationResult.expressionResults.put(
measureObservationResult.expressionName(), measureObservationResult.expressionResult()));

var resultPerMeasure = resultsPerMeasure.computeIfAbsent(measureDef, k -> new HashMap<>());

resultPerMeasure.put(subjectId, evaluationResult);
}

public void addErrors(List<? extends IIdType> measureIds, String error) {
public void addErrors(List<MeasureDef> measureDefs, String error) {
if (error == null || error.isEmpty()) {
return;
}

for (IIdType measureId : measureIds) {
addError(measureId, error);
for (MeasureDef measureDef : measureDefs) {
addError(measureDef, error);
}
}

public void addError(IIdType measureId, String error) {
public void addError(MeasureDef measureDef, String error) {
if (error == null || error.isBlank()) {
return;
}

errorsPerMeasure
.computeIfAbsent(measureId.toUnqualifiedVersionless(), k -> new ArrayList<>())
.add(error);
errorsPerMeasure.computeIfAbsent(measureDef, k -> new ArrayList<>()).add(error);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.opencds.cqf.fhir.cr.measure.common;

import jakarta.annotation.Nullable;

/**
* All continuous variable scoring aggregation methods.
*/
public enum ContinuousVariableObservationAggregateMethod {
AVG("avg"),
COUNT("count"),
MAX("max"),
MEDIAN("median"),
MIN("min"),
SUM("sum"),
N_A(null);

@Nullable
private final String text;

ContinuousVariableObservationAggregateMethod(@Nullable String text) {
this.text = text;
}

@Nullable
public String getText() {
return text;
}

@Nullable
public static ContinuousVariableObservationAggregateMethod fromString(@Nullable String text) {
for (ContinuousVariableObservationAggregateMethod value : values()) {
if (text.equals(value.getText())) {
return value;
}
}

return null;
}
}
Loading