Skip to content

Commit

Permalink
Measure scoring, reportType, and improvementNotation testing (#579)
Browse files Browse the repository at this point in the history
* testing enhancements for measure evaluation

* add enums to manage populations per scoring

* remove redundant population check

* rename test files with resource pre-fix

* code review edits

* finalize test repository varible
  • Loading branch information
Capt-Mac authored Nov 12, 2024
1 parent c6e417f commit 4ca56c7
Show file tree
Hide file tree
Showing 83 changed files with 5,789 additions and 1,548 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public class GroupDef {
private final List<StratifierDef> stratifiers;
private final List<PopulationDef> populations;
private final MeasureScoring measureScoring;
private final boolean isPositiveImprovementNotation;
private final boolean useGroupDefImprovementNotation;
private final boolean isIncreaseImprovementNotation;

private final Map<MeasurePopulationType, List<PopulationDef>> populationIndex;

Expand All @@ -22,14 +23,16 @@ public GroupDef(
List<StratifierDef> stratifiers,
List<PopulationDef> populations,
MeasureScoring measureScoring,
boolean isPositiveImprovementNotation) {
boolean isIncreaseImprovementNotation,
boolean useGroupDefImprovementNotation) {
this.id = id;
this.code = code;
this.stratifiers = stratifiers;
this.populations = populations;
this.populationIndex = index(populations);
this.measureScoring = measureScoring;
this.isPositiveImprovementNotation = isPositiveImprovementNotation;
this.isIncreaseImprovementNotation = isIncreaseImprovementNotation;
this.useGroupDefImprovementNotation = useGroupDefImprovementNotation;
}

public String id() {
Expand Down Expand Up @@ -73,7 +76,11 @@ public MeasureScoring measureScoring() {
return this.measureScoring;
}

public boolean isPositiveImprovementNotation() {
return this.isPositiveImprovementNotation;
public boolean isIncreaseImprovementNotation() {
return this.isIncreaseImprovementNotation;
}

public boolean useGroupDefImprovementNotation() {
return this.useGroupDefImprovementNotation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ public class MeasureDef {
private final boolean useMeasureImpNotation;

public MeasureDef(
String id, String url, String version, List<GroupDef> groups, List<SdeDef> sdes, boolean isBooleanBasis) {
String id,
String url,
String version,
List<GroupDef> groups,
List<SdeDef> sdes,
boolean isBooleanBasis,
boolean useMeasureImprovementNotation) {
this.id = id;
this.url = url;
this.version = version;
this.groups = groups;
this.sdes = sdes;
this.isBooleanBasis = isBooleanBasis;
this.useMeasureImpNotation = groupDefAllSameImpNotation(groups);
this.useMeasureImpNotation = useMeasureImprovementNotation;
}

public String id() {
Expand Down Expand Up @@ -56,9 +62,4 @@ public boolean isBooleanBasis() {
public boolean useMeasureImpNotation() {
return this.useMeasureImpNotation;
}

public boolean groupDefAllSameImpNotation(List<GroupDef> groupDefs) {
// if single rate, then always true
return groupDefs.stream().allMatch(GroupDef::isPositiveImprovementNotation);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ public enum MeasureEvalType {

// This method can be used for reverse lookup purposes
public static Optional<MeasureEvalType> fromCode(String code) {
// checkNotNull(code);
// checkArgument(!code.isEmpty());

return Optional.ofNullable(lookup.get(code));
MeasureEvalType evalType = lookup.get(code);
if (code != null && evalType == null) {
throw new UnsupportedOperationException(
String.format("ReportType: %s, is not an accepted EvalType value.", code));
}
return Optional.ofNullable(evalType);
}

public String getSystem() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.elm.r1.FunctionDef;
Expand All @@ -39,6 +40,7 @@
import org.opencds.cqf.cql.engine.runtime.DateTime;
import org.opencds.cqf.cql.engine.runtime.Interval;
import org.opencds.cqf.fhir.cql.LibraryEngine;
import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureScoringTypePopulations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -291,7 +293,7 @@ protected MeasureDef evaluate(

for (String subjectId : subjectIds) {
if (subjectId == null) {
throw new RuntimeException("SubjectId is required in order to calculate.");
throw new NullPointerException("SubjectId is required in order to calculate.");
}
Pair<String, String> subjectInfo = this.getSubjectTypeAndId(subjectId);
String subjectTypePart = subjectInfo.getLeft();
Expand Down Expand Up @@ -410,6 +412,11 @@ protected void evaluateProportion(
int populationSize,
MeasureReportType reportType,
EvaluationResult evaluationResult) {
// check populations
R4MeasureScoringTypePopulations.validateScoringTypePopulations(
groupDef.populations().stream().map(PopulationDef::type).collect(Collectors.toList()),
groupDef.measureScoring());

PopulationDef initialPopulation = groupDef.getSingle(INITIALPOPULATION);
PopulationDef numerator = groupDef.getSingle(NUMERATOR);
PopulationDef denominator = groupDef.getSingle(DENOMINATOR);
Expand All @@ -419,23 +426,9 @@ protected void evaluateProportion(
PopulationDef dateOfCompliance = groupDef.getSingle(DATEOFCOMPLIANCE);

// Retrieve intersection of populations and results

// add resources
// add subject

// Validate Required Populations are Present
if (initialPopulation == null || denominator == null || numerator == null) {
throw new NullPointerException("`" + INITIALPOPULATION.getDisplay() + "`, `" + NUMERATOR.getDisplay()
+ "`, `" + DENOMINATOR.getDisplay()
+ "` are required Population Definitions for Measure Scoring Type: "
+ groupDef.measureScoring().toCode());
}
// Ratio Populations Check
if (groupDef.measureScoring().toCode().equals("ratio") && denominatorException != null) {
throw new IllegalArgumentException("`" + DENOMINATOREXCEPTION.getDisplay() + "` are not permitted "
+ "for MeasureScoring type: " + groupDef.measureScoring().toCode());
}

initialPopulation = evaluatePopulationMembership(subjectType, subjectId, initialPopulation, evaluationResult);

if (initialPopulation.getSubjects().contains(subjectId)) {
Expand Down Expand Up @@ -520,12 +513,9 @@ protected void evaluateContinuousVariable(
PopulationDef measureObservation = groupDef.getSingle(MEASUREOBSERVATION);
PopulationDef measurePopulationExclusion = groupDef.getSingle(MEASUREPOPULATIONEXCLUSION);
// Validate Required Populations are Present
if (initialPopulation == null || measurePopulation == null) {
throw new NullPointerException(
"`" + INITIALPOPULATION.getDisplay() + "` & `" + MEASUREPOPULATION.getDisplay()
+ "` are required Population Definitions for Measure Scoring Type: "
+ groupDef.measureScoring().toCode());
}
R4MeasureScoringTypePopulations.validateScoringTypePopulations(
groupDef.populations().stream().map(PopulationDef::type).collect(Collectors.toList()),
MeasureScoring.CONTINUOUSVARIABLE);

initialPopulation = evaluatePopulationMembership(subjectType, subjectId, initialPopulation, evaluationResult);
if (initialPopulation.getSubjects().contains(subjectId)) {
Expand Down Expand Up @@ -566,11 +556,9 @@ protected void evaluateCohort(
GroupDef groupDef, String subjectType, String subjectId, EvaluationResult evaluationResult) {
PopulationDef initialPopulation = groupDef.getSingle(INITIALPOPULATION);
// Validate Required Populations are Present
if (initialPopulation == null) {
throw new NullPointerException("`" + INITIALPOPULATION.getDisplay()
+ "` is a required Population Definition for Measure Scoring Type: "
+ groupDef.measureScoring().toCode());
}
R4MeasureScoringTypePopulations.validateScoringTypePopulations(
groupDef.populations().stream().map(PopulationDef::type).collect(Collectors.toList()),
MeasureScoring.COHORT);
// Evaluate Population
evaluatePopulationMembership(subjectType, subjectId, initialPopulation, evaluationResult);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.opencds.cqf.fhir.cr.measure.common.MeasurePopulationType.TOTALDENOMINATOR;
import static org.opencds.cqf.fhir.cr.measure.common.MeasurePopulationType.TOTALNUMERATOR;
import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.IMPROVEMENT_NOTATION_SYSTEM_DECREASE;
import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.IMPROVEMENT_NOTATION_SYSTEM_INCREASE;
import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.MEASUREREPORT_IMPROVEMENT_NOTATION_EXTENSION;

Expand All @@ -18,6 +19,7 @@
import org.hl7.fhir.dstu3.model.Measure.MeasureGroupStratifierComponent;
import org.hl7.fhir.dstu3.model.Measure.MeasureSupplementalDataComponent;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.StringType;
import org.opencds.cqf.fhir.cr.measure.common.CodeDef;
import org.opencds.cqf.fhir.cr.measure.common.ConceptDef;
import org.opencds.cqf.fhir.cr.measure.common.GroupDef;
Expand Down Expand Up @@ -56,11 +58,8 @@ public MeasureDef build(Measure measure) {
if (!measure.getGroup().isEmpty() && groupMeasureScoringCode == null) {
throw new IllegalArgumentException("MeasureScoring must be specified on Measure");
}
var measureLevelImpNotation = measureIsIncreaseImprovementNotation(measure);
List<GroupDef> groups = new ArrayList<>();
for (MeasureGroupComponent group : measure.getGroup()) {
// group improvement notation
var groupIsIncreaseImprovementNotation = groupIsIncreaseImprovementNotation(measureLevelImpNotation, group);

// Populations
List<PopulationDef> populations = new ArrayList<>();
Expand Down Expand Up @@ -103,7 +102,8 @@ public MeasureDef build(Measure measure) {
stratifiers,
populations,
groupMeasureScoringCode,
groupIsIncreaseImprovementNotation);
isGroupIncreaseImprovementNotation(measure, group),
groupHasImprovementNotationExt(group));
groups.add(groupDef);
}
// define basis of measure
Expand All @@ -115,7 +115,8 @@ public MeasureDef build(Measure measure) {
measure.getVersion(),
groups,
sdes,
measureBasisDef.isBooleanBasis(measure));
measureBasisDef.isBooleanBasis(measure),
useMeasureImprovementNotation(groups));
}

private PopulationDef checkPopulationForCode(
Expand Down Expand Up @@ -167,25 +168,53 @@ private MeasureScoring getMeasureScoring(Measure measure) {
}

private boolean isIncreaseImprovementNotation(String improvementNotationValue) {
validateImprovementNotationCode(improvementNotationValue);
return improvementNotationValue.equals(IMPROVEMENT_NOTATION_SYSTEM_INCREASE);
}

public boolean measureIsIncreaseImprovementNotation(Measure measure) {
if (measure.hasImprovementNotation()) {
return isIncreaseImprovementNotation(measure.getImprovementNotation());
} else {
// default ImprovementNotation behavior
return true;
private void validateImprovementNotationCode(String improvementNotationValue) {
boolean hasValidCode = IMPROVEMENT_NOTATION_SYSTEM_INCREASE.equals(improvementNotationValue)
|| IMPROVEMENT_NOTATION_SYSTEM_DECREASE.equals(improvementNotationValue);
if (!hasValidCode) {
throw new IllegalArgumentException(String.format(
"ImprovementNotation Coding has invalid code: %s, combination for Measure.",
improvementNotationValue));
}
}

public String getGroupImprovementNotationExt(Measure.MeasureGroupComponent group) {
var ext = group.getExtensionByUrl(MEASUREREPORT_IMPROVEMENT_NOTATION_EXTENSION)
.getValue();
assert ext instanceof StringType;
StringType value = (StringType) ext;
return value.getValue();
}

private boolean groupHasImprovementNotationExt(MeasureGroupComponent group) {
return group.getExtensionByUrl(MEASUREREPORT_IMPROVEMENT_NOTATION_EXTENSION) != null;
}

private boolean isGroupIncreaseImprovementNotation(Measure measure, MeasureGroupComponent group) {
// default improvement Notation
boolean isIncreaseImpNotation = true;
boolean useGroupImpNotation = groupHasImprovementNotationExt(group);
if (useGroupImpNotation) {
isIncreaseImpNotation = isIncreaseImprovementNotation(getGroupImprovementNotationExt(group));
} else if (measure.hasImprovementNotation()) {
isIncreaseImpNotation = isIncreaseImprovementNotation(measure.getImprovementNotation());
}

return isIncreaseImpNotation;
}

public boolean groupIsIncreaseImprovementNotation(
boolean measureImprovementNotationIsPositive, MeasureGroupComponent group) {
var improvementNotationExt = group.getExtensionByUrl(MEASUREREPORT_IMPROVEMENT_NOTATION_EXTENSION);
if (improvementNotationExt != null) {
return isIncreaseImprovementNotation(
improvementNotationExt.getValue().toString());
private boolean useMeasureImprovementNotation(List<GroupDef> groups) {
// if no groups are present then useMeasure
if (groups == null || groups.isEmpty()) {
return true;
} else {
boolean useGroupImpNotation = groups.stream().allMatch(GroupDef::useGroupDefImprovementNotation)
&& groups.get(0).useGroupDefImprovementNotation();
return !useGroupImpNotation;
}
return measureImprovementNotationIsPositive;
}
}
Loading

0 comments on commit 4ca56c7

Please sign in to comment.