Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

535 measure evaluation should respect improvement notation specified on group #540

Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,25 @@ public class GroupDef {
private final ConceptDef code;
private final List<StratifierDef> stratifiers;
private final List<PopulationDef> populations;
private final MeasureScoring measureScoring;
private final boolean isPositiveImprovementNotation;

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

public GroupDef(String id, ConceptDef code, List<StratifierDef> stratifiers, List<PopulationDef> populations) {
public GroupDef(
String id,
ConceptDef code,
List<StratifierDef> stratifiers,
List<PopulationDef> populations,
MeasureScoring measureScoring,
boolean isPositiveImprovementNotation) {
this.id = id;
this.code = code;
this.stratifiers = stratifiers;
this.populations = populations;
this.populationIndex = index(populations);
this.measureScoring = measureScoring;
this.isPositiveImprovementNotation = isPositiveImprovementNotation;
}

public String id() {
Expand Down Expand Up @@ -58,4 +68,12 @@ public List<PopulationDef> get(MeasurePopulationType type) {
private Map<MeasurePopulationType, List<PopulationDef>> index(List<PopulationDef> populations) {
return populations.stream().collect(Collectors.groupingBy(PopulationDef::type));
}

public MeasureScoring measureScoring() {
return this.measureScoring;
}

public boolean isPositiveImprovementNotation() {
return this.isPositiveImprovementNotation;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.opencds.cqf.fhir.cr.measure.common;

import java.util.List;
import java.util.Map;
import org.opencds.cqf.cql.engine.runtime.Interval;

public class MeasureDef {
Expand All @@ -10,26 +9,20 @@ public class MeasureDef {
private final String url;
private final String version;
private Interval defaultMeasurementPeriod;
private final Map<GroupDef, MeasureScoring> scoring;
private final List<GroupDef> groups;
private final List<SdeDef> sdes;
private final boolean isBooleanBasis;
private final boolean useMeasureImpNotation;

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

public String id() {
Expand All @@ -56,11 +49,16 @@ public List<GroupDef> groups() {
return this.groups;
}

public Map<GroupDef, MeasureScoring> scoring() {
return this.scoring;
}

public boolean isBooleanBasis() {
return this.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 @@ -451,13 +451,12 @@ protected void evaluateProportion(
throw new NullPointerException("`" + INITIALPOPULATION.getDisplay() + "`, `" + NUMERATOR.getDisplay()
+ "`, `" + DENOMINATOR.getDisplay()
+ "` are required Population Definitions for Measure Scoring Type: "
+ measureDef.scoring().get(groupDef).toCode());
+ groupDef.measureScoring().toCode());
}
// Ratio Populations Check
if (measureDef.scoring().get(groupDef).toCode().equals("ratio") && denominatorException != null) {
throw new IllegalArgumentException(
"`" + DENOMINATOREXCEPTION.getDisplay() + "` are not permitted " + "for MeasureScoring type: "
+ measureDef.scoring().get(groupDef).toCode());
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);
Expand Down Expand Up @@ -548,7 +547,7 @@ protected void evaluateContinuousVariable(
throw new NullPointerException(
"`" + INITIALPOPULATION.getDisplay() + "` & `" + MEASUREPOPULATION.getDisplay()
+ "` are required Population Definitions for Measure Scoring Type: "
+ measureDef.scoring().get(groupDef).toCode());
+ groupDef.measureScoring().toCode());
}

initialPopulation = evaluatePopulationMembership(subjectType, subjectId, initialPopulation, evaluationResult);
Expand Down Expand Up @@ -597,7 +596,7 @@ protected void evaluateCohort(
if (initialPopulation == null) {
throw new NullPointerException("`" + INITIALPOPULATION.getDisplay()
+ "` is a required Population Definition for Measure Scoring Type: "
+ measureDef.scoring().get(groupDef).toCode());
Capt-Mac marked this conversation as resolved.
Show resolved Hide resolved
+ groupDef.measureScoring().toCode());
}
// Evaluate Population
evaluatePopulationMembership(subjectType, subjectId, initialPopulation, evaluationResult);
Expand All @@ -613,7 +612,7 @@ protected void evaluateGroup(
EvaluationResult evaluationResult) {
evaluateStratifiers(subjectId, groupDef.stratifiers(), evaluationResult);

var scoring = measureDef.scoring().get(groupDef);
var scoring = groupDef.measureScoring();
switch (scoring) {
case PROPORTION:
case RATIO:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.opencds.cqf.fhir.cr.measure.common;

import java.util.Map;

public interface MeasureReportScorer<MeasureReportT> {
public void score(Map<GroupDef, MeasureScoring> measureScoring, MeasureReportT measureReport);
public void score(MeasureDef measureDef, MeasureReportT measureReport);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
public class MeasureReportConstants {
private MeasureReportConstants() {}

public static final String IMPROVEMENT_NOTATION_SYSTEM_INCREASE = "increase";
public static final String IMPROVEMENT_NOTATION_SYSTEM_INCREASE_DISPLAY = "Increase";
public static final String IMPROVEMENT_NOTATION_SYSTEM_DECREASE = "decrease";
public static final String IMPROVEMENT_NOTATION_SYSTEM_DECREASE_DISPLAY = "Decrease";
public static final String MEASUREREPORT_IMPROVEMENT_NOTATION_SYSTEM =
"http://terminology.hl7.org/CodeSystem/measure-improvement-notation";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

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_INCREASE;
import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.MEASUREREPORT_IMPROVEMENT_NOTATION_EXTENSION;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
Expand Down Expand Up @@ -56,11 +56,11 @@ 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<>();
Map<GroupDef, MeasureScoring> groupMeasureScoring = new HashMap<>();
for (MeasureGroupComponent group : measure.getGroup()) {
// Ids are not required on groups in dstu3
// checkId(group);
// group improvement notation
var groupIsIncreaseImprovementNotation = groupIsIncreaseImprovementNotation(measureLevelImpNotation, group);

// Populations
List<PopulationDef> populations = new ArrayList<>();
Expand Down Expand Up @@ -101,9 +101,11 @@ public MeasureDef build(Measure measure) {
group.getId(),
null, // No code on group in dstu3
stratifiers,
populations);
populations,
groupMeasureScoringCode,
groupIsIncreaseImprovementNotation);
groups.add(groupDef);
groupMeasureScoring.put(groupDef, groupMeasureScoringCode);
// groupMeasureScoring.put(groupDef, groupMeasureScoringCode);
Capt-Mac marked this conversation as resolved.
Show resolved Hide resolved
}
// define basis of measure
Dstu3MeasureBasisDef measureBasisDef = new Dstu3MeasureBasisDef();
Expand All @@ -112,7 +114,6 @@ public MeasureDef build(Measure measure) {
measure.getId(),
measure.getUrl(),
measure.getVersion(),
groupMeasureScoring,
groups,
sdes,
measureBasisDef.isBooleanBasis(measure));
Expand Down Expand Up @@ -165,4 +166,27 @@ private void checkId(Resource r) {
private MeasureScoring getMeasureScoring(Measure measure) {
return MeasureScoring.fromCode(measure.getScoring().getCodingFirstRep().getCode());
}

private boolean isIncreaseImprovementNotation(String 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;
}
}

public boolean groupIsIncreaseImprovementNotation(
boolean measureImprovementNotationIsPositive, MeasureGroupComponent group) {
var improvementNotationExt = group.getExtensionByUrl(MEASUREREPORT_IMPROVEMENT_NOTATION_EXTENSION);
if (improvementNotationExt != null) {
return isIncreaseImprovementNotation(
improvementNotationExt.getValue().toString());
}
return measureImprovementNotationIsPositive;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public MeasureReport build(
buildGroups(measure, measureDef);
processSdes(measure, measureDef, subjectIds);

this.measureReportScorer.score(measureDef.scoring(), this.report);
this.measureReportScorer.score(measureDef, this.report);

// Only add evaluated resources to individual reports
if (measureReportType == MeasureReportType.INDIVIDUAL) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.opencds.cqf.fhir.cr.measure.dstu3;

import java.util.Map;
import java.util.Optional;
import org.hl7.fhir.dstu3.model.MeasureReport;
import org.hl7.fhir.dstu3.model.MeasureReport.MeasureReportGroupComponent;
Expand All @@ -9,27 +8,33 @@
import org.hl7.fhir.dstu3.model.MeasureReport.StratifierGroupComponent;
import org.hl7.fhir.dstu3.model.MeasureReport.StratifierGroupPopulationComponent;
import org.opencds.cqf.fhir.cr.measure.common.BaseMeasureReportScorer;
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.MeasurePopulationType;
import org.opencds.cqf.fhir.cr.measure.common.MeasureScoring;

public class Dstu3MeasureReportScorer extends BaseMeasureReportScorer<MeasureReport> {

@Override
public void score(Map<GroupDef, MeasureScoring> measureScoring, MeasureReport measureReport) {
public void score(MeasureDef measureDef, MeasureReport measureReport) {
// Measure Def Check
if (measureDef == null) {
throw new IllegalArgumentException("MeasureDef is required in order to score a Measure.");
}
// No groups, nothing to score
if (measureReport.getGroup().isEmpty()) {
return;
}
// Dstu3 doesn't have scoring on group level, extract first assigned groupDef measureScore
MeasureScoring measureScoring = measureDef.groups().get(0).measureScoring();

if (measureScoring == null || measureScoring.isEmpty()) {
// validate scoring
if (measureScoring == null) {
throw new IllegalArgumentException(
"Measure does not have a scoring methodology defined. Add a \"scoring\" property to the measure definition or the group definition.");
}

var scoring = measureScoring.values().iterator().next();
for (MeasureReportGroupComponent mrgc : measureReport.getGroup()) {
scoreGroup(scoring, mrgc);
scoreGroup(measureScoring, mrgc);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package org.opencds.cqf.fhir.cr.measure.r4;

import static org.opencds.cqf.fhir.cr.measure.constant.MeasureConstants.CQFM_CARE_GAP_DATE_OF_COMPLIANCE_EXT_URL;
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;
import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.MEASUREREPORT_IMPROVEMENT_NOTATION_SYSTEM;
import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.MEASUREREPORT_MEASURE_POPULATION_SYSTEM;

import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Measure;
import org.hl7.fhir.r4.model.MeasureReport;
Expand Down Expand Up @@ -83,13 +86,12 @@ private CareGapsStatusCode getGapStatus(
// default improvementNotation
boolean isPositive = true;

// TODO: look for group specified 'improvement notation', if missing, then look on measure
/*if (groupHasImprovementNotation(measureReportGroup)) {
// look for group specified 'improvement notation', if missing, then look on measure
if (groupHasImprovementNotation(measureReportGroup)) {
isPositive = groupImprovementNotationIsPositive(measureReportGroup);
} else if (measure.hasImprovementNotation()) {*/
if (measure.hasImprovementNotation()) {
isPositive =
measure.getImprovementNotation().hasCoding(MEASUREREPORT_IMPROVEMENT_NOTATION_SYSTEM, "increase");
} else if (measure.hasImprovementNotation()) {
isPositive = measure.getImprovementNotation()
.hasCoding(MEASUREREPORT_IMPROVEMENT_NOTATION_SYSTEM, IMPROVEMENT_NOTATION_SYSTEM_INCREASE);
}

if (Boolean.FALSE.equals(inDenominator.getValue())) {
Expand Down Expand Up @@ -134,8 +136,7 @@ private Period getDateOfComplianceExt(MeasureReportGroupComponent measureReportG
return (Period) extValue;
}

/*
// TODO implement Measure Group Level improvement notation extension
// Measure Group Level improvement notation extension
private boolean groupHasImprovementNotation(MeasureReportGroupComponent groupComponent) {
return groupComponent.getExtensionByUrl(MEASUREREPORT_IMPROVEMENT_NOTATION_EXTENSION) != null;
}
Expand All @@ -144,6 +145,6 @@ private boolean groupImprovementNotationIsPositive(MeasureReportGroupComponent g
var code = (CodeableConcept) groupComponent
.getExtensionByUrl(MEASUREREPORT_IMPROVEMENT_NOTATION_EXTENSION)
.getValue();
return code.hasCoding(MEASUREREPORT_IMPROVEMENT_NOTATION_SYSTEM, "increase");
}*/
return code.hasCoding(MEASUREREPORT_IMPROVEMENT_NOTATION_SYSTEM, IMPROVEMENT_NOTATION_SYSTEM_INCREASE);
}
}
Loading