Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
4240647
Introduce changes to continuous variable observations from another br…
lukedegruchy Oct 15, 2025
b5c6e49
Add TODOs about error handling.
lukedegruchy Oct 15, 2025
3d9ec5e
Merge with changes in R4MeasureDefBuilder. Tweak a couple of tests. …
lukedegruchy Oct 16, 2025
9dd224c
Merge changes from R4MeasureReportBuilder. More tests are fixed, but…
lukedegruchy Oct 16, 2025
17d0dc6
Fix all tests in MeasureStratifierTest by fixing at least of bad merg…
lukedegruchy Oct 16, 2025
b634702
Fix last set of test due to missing test files. Add more TODOs.
lukedegruchy Oct 16, 2025
753a49d
Sonar.
lukedegruchy Oct 16, 2025
3766a1e
Finish refactoring R4MeasureDefBuilder.
lukedegruchy Oct 16, 2025
f39510b
More refactoring for ContinuousVariableObservationHandler, including …
lukedegruchy Oct 16, 2025
d13a2ba
Refactor tests and clean up more cruft.
lukedegruchy Oct 16, 2025
9019842
More refactoring: Make it easier to reason about the workflow in ques…
lukedegruchy Oct 16, 2025
57b338e
Add a new record and resolve LibraryInitHandler TODOs.
lukedegruchy Oct 16, 2025
f387a04
Final round of refactoring to make code easier to reason about. Also…
lukedegruchy Oct 16, 2025
861d566
Merge remote-tracking branch 'origin/master' into ld-20251015-continu…
lukedegruchy Oct 16, 2025
dfcc0f1
Optimize algorithm for initializing the library for a given measure o…
lukedegruchy Oct 16, 2025
877e0eb
Merge remote-tracking branch 'origin/master' into ld-20251015-continu…
lukedegruchy Oct 22, 2025
0443573
Implement optimization to PopulationDef subject and resource structur…
lukedegruchy Oct 22, 2025
756f055
Optimize the algorithms for both removeAll() and retainAll().
lukedegruchy Oct 22, 2025
ac66896
Merge remote-tracking branch 'origin/master' into ld-20251017-populat…
lukedegruchy Oct 23, 2025
a198593
Get rid of call to "legacy" continuous variable observation logic fro…
lukedegruchy Oct 23, 2025
f1edfa8
Use Quantities instead of Observations for continuous variable scorin…
lukedegruchy Oct 23, 2025
c333d1e
delete QuantityHolder.java and test.
lukedegruchy Oct 23, 2025
2d6aa36
TODOs.
lukedegruchy Oct 23, 2025
4dd9026
Push up logic for Set intersection comparison for stratifier populati…
lukedegruchy Oct 23, 2025
3fd1f1d
First stab at new algorithm to collect StratumDef and StratumPopulati…
lukedegruchy Oct 23, 2025
eed38b0
Start cleaning up dead code.
lukedegruchy Oct 24, 2025
42dab54
Start moving up adding stratum population defs and stratum defs highe…
lukedegruchy Oct 24, 2025
76be529
Only add stratum and stratum populations in the outer methods so as n…
lukedegruchy Oct 24, 2025
03867ee
Add more test data for continuous variable boolean basis to prove tha…
lukedegruchy Oct 24, 2025
92be178
Move measure evaluation logic to a new dedicate class: MeasureEvalua…
lukedegruchy Oct 24, 2025
7636a41
Move logic to strip resource qualifiers from measure scorer to strati…
lukedegruchy Oct 24, 2025
e7de89a
Merge remote-tracking branch 'origin/master' into ld-20251017-populat…
lukedegruchy Oct 24, 2025
18d23e8
Spotless.
lukedegruchy Oct 24, 2025
673ed47
Renames and clean up cruft from bad merge with master.
lukedegruchy Oct 24, 2025
7603328
Cleanup more cruft.
lukedegruchy Oct 24, 2025
ea945e3
Start implementing tests for component criteria stratifiers and sprin…
lukedegruchy Oct 27, 2025
56dcacc
Add CQL for 5 distinct scenarios for component criteria stratifiers. …
lukedegruchy Oct 27, 2025
d222b85
More work to set up the tests for scenarios 1 and 2 but still more wo…
lukedegruchy Oct 27, 2025
ea20edc
Merge remote-tracking branch 'origin/master' into ld-20251020-compone…
lukedegruchy Oct 30, 2025
19bd44a
Fix compile errors.
lukedegruchy Oct 30, 2025
891eff5
Capture more scenarios in tests with comments and empty test methods.
lukedegruchy Oct 30, 2025
b0e4fe8
Set up mismatch test files and assertions. JSON and SQL expressions …
lukedegruchy Oct 30, 2025
e8e7603
Start fixing test measures and assertions. Improve error handling fo…
lukedegruchy Oct 30, 2025
043a2f3
Add more safety to execution.
lukedegruchy Oct 30, 2025
41e9dc6
Setup more tests with correct assertions.
lukedegruchy Oct 30, 2025
17ef8fa
More tweaks to tests.
lukedegruchy Oct 30, 2025
8857091
Get tests in a sufficient state that it's time to fix the production …
lukedegruchy Oct 31, 2025
36ec543
Spotless.
lukedegruchy Oct 31, 2025
1279b36
Add toString() to Def classes.
lukedegruchy Oct 31, 2025
cd4b491
javadoc.
lukedegruchy Oct 31, 2025
5fd709f
Start moving population/stratum population intersection code from R4S…
lukedegruchy Oct 31, 2025
8d3ad63
Flip switch on stratum population count. Start migrating resource ID…
lukedegruchy Oct 31, 2025
4aac3f7
Migrate more logic to the 2nd step measure evaluator.
lukedegruchy Oct 31, 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
@@ -1,20 +1,10 @@
package org.opencds.cqf.fhir.cr.measure.common;

import jakarta.annotation.Nonnull;
import java.util.List;
import java.util.StringJoiner;

public class ConceptDef {

private final List<CodeDef> codes;
private final String text;

public ConceptDef(List<CodeDef> codes, String text) {
this.codes = codes;
this.text = text;
}

public List<CodeDef> codes() {
return this.codes;
}
public record ConceptDef(List<CodeDef> codes, String text) {

public boolean isEmpty() {
return this.codes.isEmpty();
Expand All @@ -28,7 +18,12 @@ public CodeDef first() {
return this.codes.get(0);
}

public String text() {
return this.text;
@Override
@Nonnull
public String toString() {
return new StringJoiner(", ", ConceptDef.class.getSimpleName() + "[", "]")
.add("codes=" + codes)
.add("text='" + text + "'")
.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public static void processResults(
}
}

evaluator.postEvaluation(measureDef);
MeasureMultiSubjectEvaluator.postEvaluationMultiSubject(measureDef);
}

/**
Expand Down Expand Up @@ -126,6 +126,7 @@ public static <T extends ICompositeType> CompositeEvaluationResultsPerMeasure ge
libraryVersionedIdentifier, evaluationResultsForMultiLib);
var evaluationResult = evaluationResultsForMultiLib.getResultFor(libraryVersionedIdentifier);

// LUKETODO: add functionality for warnings versus errors from CQL results and some clear tests
var measureDefs =
multiLibraryIdMeasureEngineDetails.getMeasureDefsForLibrary(libraryVersionedIdentifier);

Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.opencds.cqf.fhir.cr.measure.r4.utils.R4ResourceIdUtils;

public class PopulationDef {

Expand Down Expand Up @@ -62,6 +63,12 @@ public Set<String> getSubjects() {
return this.getSubjectResources().keySet();
}

public Set<String> getSubjectsWithPatientQualifier() {
return getSubjects().stream()
.map(R4ResourceIdUtils::addPatientQualifier)
.collect(Collectors.toUnmodifiableSet());
}

public void retainAllResources(Set<Object> resourcesToRetain) {
getSubjectResources().forEach((key, value) -> value.retainAll(resourcesToRetain));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;

public class StratifierComponentDef {
private final String id;
Expand Down Expand Up @@ -40,4 +41,14 @@ public Map<String, CriteriaResult> getResults() {

return this.results;
}

@Override
public String toString() {
return new StringJoiner(", ", StratifierComponentDef.class.getSimpleName() + "[", "]")
.add("id='" + id + "'")
.add("code=" + code)
.add("expression='" + expression + "'")
.add("results=" + results)
.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,6 @@ public Set<StratumValueDef> getValueDefs() {
public List<String> getSubjectIds() {
return subjectIds;
}

// LUKETODO: toString
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.opencds.cqf.fhir.cr.measure.common;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
Expand All @@ -14,6 +16,10 @@ public class StratumPopulationDef {

private final String id;
private final Set<String> subjectsQualifiedOrUnqualified;
// Temporary: this needs to be captured as number of intersected resources
private int count = 0;
// Temporary: figure out what to do with this
private List<String> resourceIds = new ArrayList<>();

public StratumPopulationDef(String id, Set<String> subjectsQualifiedOrUnqualified) {
this.id = id;
Expand All @@ -24,16 +30,40 @@ public String getId() {
return id;
}

// LUKETODO: javadoc
/**
* @return The subjectIds as they are, whether they are qualified with a resource
* (ex: [Patient/pat1, Patient/pat2] or [pat1, pat2]
*/
public Set<String> getSubjectsQualifiedOrUnqualified() {
return subjectsQualifiedOrUnqualified;
}

// LUKETODO: javadoc
/**
* @return The subjectIds without a FHIR resource qualifier, whether they previously had a
* qualifier or not
*/
public Set<String> getSubjectsUnqualified() {
return subjectsQualifiedOrUnqualified.stream()
.filter(Objects::nonNull)
.map(R4ResourceIdUtils::stripAnyResourceQualifier)
.collect(Collectors.toUnmodifiableSet());
}

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}

public List<String> getResourceIds() {
return resourceIds;
}

public void addAllResourceIds(List<String> resourceIds) {
this.resourceIds.addAll(resourceIds);
}

// LUKETODO: toString
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@
* <p> (v3.18.0 and below) Previous calculation of measure score from MeasureReport only interpreted Numerator, Denominator membership since exclusions and exceptions were already applied. Now exclusions and exceptions are present in Denominator and Numerator populations, the measure scorer calculation has to take into account additional population membership to determine Final-Numerator and Final-Denominator values</p>
*/
@SuppressWarnings("squid:S1135")
// LUKETODO: we does this have to be an R4 class at all? why can't we base this entirely off defs?
// LUKETODO: as a migration path, why can't we push up logic gradually to the BaseMeasureReportScorer, moving away from
// FHIR-version specific logic
public class R4MeasureReportScorer extends BaseMeasureReportScorer<MeasureReport> {

private static final Logger logger = LoggerFactory.getLogger(R4MeasureReportScorer.class);
Expand Down Expand Up @@ -176,6 +179,9 @@ protected void scoreGroup(

switch (measureScoring) {
case PROPORTION, RATIO:
// LUKETODO: here, we're taking the counts from the R4 populations, but why do we have to store them
// there?
// why can't we put the counts in the population defs?
var score = calcProportionScore(
getCountFromGroupPopulation(mrgc.getPopulation(), NUMERATOR)
- getCountFromGroupPopulation(mrgc.getPopulation(), NUMERATOR_EXCLUSION),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.opencds.cqf.cql.engine.execution.EvaluationResult;
import org.opencds.cqf.cql.engine.runtime.Code;
import org.opencds.cqf.fhir.cr.measure.MeasureStratifierType;
import org.opencds.cqf.fhir.cr.measure.common.CodeDef;
import org.opencds.cqf.fhir.cr.measure.common.GroupDef;
import org.opencds.cqf.fhir.cr.measure.common.MeasureDef;
import org.opencds.cqf.fhir.cr.measure.common.PopulationBasisValidator;
Expand Down Expand Up @@ -127,16 +128,22 @@ private void validateExpressionResultType(
}

var resultClasses = StratifierUtils.extractClassesFromSingleOrListResult(expressionResult.value());
var groupPopulationBasisCode = groupDef.getPopulationBasis().code();
var groupPopulationBasis = groupDef.getPopulationBasis();

if (MeasureStratifierType.CRITERIA == stratifierDef.getStratifierType()) {
// LUKETODO: refine this error handling because we may get an empty expression result, as opposed to a
// non-empty wrong expression result
if (resultClasses.stream()
.map(Class::getSimpleName)
.noneMatch(simpleName -> simpleName.equals(groupPopulationBasisCode))) {
.noneMatch(
resourceClass -> doesResourceMatchPopulationBasis(resourceClass, groupPopulationBasis))) {

throw new InvalidRequestException(
"criteria-based stratifier is invalid for expression: [%s] due to mismatch between population basis: [%s] and result types: %s for measure URL: %s"
.formatted(expression, groupPopulationBasisCode, prettyClassNames(resultClasses), url));
.formatted(
expression, groupPopulationBasis.code(), prettyClassNames(resultClasses), url));

// LUKETODO: add validation for component criteria stratifiers, which needs the initial population
// resources as well
}

// skip validation below since for criteria-based stratifier, the boolean basis test is irrelevant
Expand All @@ -153,13 +160,18 @@ private void validateExpressionResultType(
"stratifier expression criteria results for expression: [%s] must fall within accepted types for population-basis: [%s] for Measure: [%s] due to mismatch between total result classes: %s and matching result classes: %s"
.formatted(
expression,
groupPopulationBasisCode,
groupPopulationBasis.code(),
url,
prettyClassNames(resultClasses),
prettyClassNames(resultMatchingClasses)));
}
}

// LUKETODO: refine this to deal with all kinds of different basis types
private boolean doesResourceMatchPopulationBasis(Class<?> resourceClass, CodeDef groupPopulationBasisCode) {
return resourceClass.getSimpleName().equalsIgnoreCase(groupPopulationBasisCode.code());
}

private Optional<Class<?>> extractResourceType(String groupPopulationBasisCode) {
if (BOOLEAN_BASIS.equals(groupPopulationBasisCode)) {
return Optional.of(Boolean.class);
Expand Down
Loading
Loading