From e99737ecba18dc831951b07797e448d83dd14749 Mon Sep 17 00:00:00 2001
From: Justin McKelvy <60718638+Capt-Mac@users.noreply.github.com>
Date: Thu, 31 Oct 2024 14:41:37 -0600
Subject: [PATCH] fix not-applicable status for care-gaps (#574)
* fix na status for care-gaps
* remove duplicate identifier
---
.../measure/r4/R4CareGapStatusEvaluator.java | 62 +++++----
.../fhir/cr/measure/r4/R4CareGapsTest.java | 24 ++++
...MinimalProportionDenominatorExclusion.json | 118 ++++++++++++++++++
3 files changed, 172 insertions(+), 32 deletions(-)
create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/resources/measure/MinimalProportionDenominatorExclusion.json
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapStatusEvaluator.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapStatusEvaluator.java
index d19d09a8a..c4b0ae8d9 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapStatusEvaluator.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapStatusEvaluator.java
@@ -23,34 +23,25 @@
*/
public class R4CareGapStatusEvaluator {
/**
- *
- * GapStatus is determined by interpreting a MeasureReport resource of Type Ratio or Proportion that contain the populations: Numerator & Denominator
- *
- *
- *
- * - 'not-applicable': When a subject does not meet the criteria for the Measure scenario, whether by exclusion or exception criteria, or just by not meeting any required criteria, they will not show membership results in the 'Denominator'.
- * - subject is applicable (not a status): When a subject meets the criteria for a Measure they will have membership results in the 'Denominator', indicating they are of the appropriate criteria for the Measure scenario.
- * If in membership of 'Denominator', the subject will be assigned a 'closed-gap' or 'open-gap' status based on membership in 'Numerator' and the 'improvement notation'.
- *
- *
- *
- * Improvement Notation of Scoring Algorithm indicates whether the ratio of Numerator over Denominator populations represents a scenario to increase the Numerator to improve outcomes, or to decrease the Numerator count. If this value is not set on a Measure resource, then it is defaulted to 'Increase' under the IsPositive variable.
- *
- *
- * - ex: 1/10 with improvementNotation "decrease" means that the measureScore is 90%, therefore absence from 'Numerator' means criteria for care was met
- * - ex: 1/10 with improvementNotation "increase" means that the measureScore is 10%, therefore absence from 'Numerator' means criteria for care was NOT met.
- *
- *
- * - 'open-gap': if in 'Denominator' & NOT in 'Numerator', where 'improvement notation' = increase. Then the subject is 'open-gap'
- * - 'open-gap': if in 'Denominator' & in 'Numerator', where 'improvement notation' = decrease. Then the subject is 'open-gap'
- * - 'closed-gap': if in 'Denominator' & NOT in 'Numerator', where 'improvement notation' = decrease. Then the subject is 'closed-gap'
- * - 'closed-gap': if in 'Denominator' & in 'Numerator', where 'improvement notation' = increase. Then the subject is 'closed-gap'
- *
- * 'prospective-gap' is a concept that represents a period of time where a 'care-gap' measure has opportunity to address recommended care in a specific window of time. This 'window of time' we call the 'Date of Compliance' to indicate a range of time that optimally represents when care is meant to be provided.
- *
- * If care has not been provided ('open-gap'), and the date (reportDate) of evaluating for the Measure is before or within the 'Date of Compliance' interval, then the Measure is considered a 'prospective-gap' for the subject evaluated.
+ * Below table is a mapping of expected behavior of the care-gap-status to Measure Report score
+ * | Gap-Status | initial-population | denominator | denominator-exclusion | denominator-exception\*\* | numerator-exclusion | numerator | Improvement Notation\*\*\* |
+ * | ----------------- | ------------------ | ----------- | --------------------- | ------------------------- | ------------------- | --------- | -------------------------- |
+ * | not-applicable | FALSE | N/A | N/A | N/A | N/A | N/A | N/A |
+ * | closed-gap | TRUE | FALSE | FALSE | FALSE | FALSE | FALSE | N/A |
+ * | closed-gap | TRUE | FALSE | TRUE | FALSE | FALSE | FALSE | N/A |
+ * | closed-gap | TRUE | FALSE | FALSE | TRUE | FALSE | FALSE | N/A |
+ * | prospective-gap\* | TRUE | TRUE | FALSE | FALSE | FALSE | FALSE | N/A |
+ * | prospective-gap\* | TRUE | TRUE | FALSE | FALSE | TRUE | FALSE | N/A |
+ * | open-gap | TRUE | TRUE | FALSE | FALSE | TRUE | FALSE | increase |
+ * | open-gap | TRUE | TRUE | FALSE | FALSE | FALSE | FALSE | increase |
+ * | open-gap | TRUE | TRUE | FALSE | FALSE | FALSE | TRUE | decrease |
+ * | closed-gap | TRUE | TRUE | FALSE | FALSE | TRUE | FALSE | decrease |
+ * | closed-gap | TRUE | TRUE | FALSE | FALSE | FALSE | TRUE | increase |
+ * | closed-gap | TRUE | TRUE | FALSE | FALSE | FALSE | FALSE | decrease |
*
- *
+ * *`prospective-gap` status requires additional data points than just population-code values within a MeasureReport in order to determine if ‘prospective-gap’ or just ‘open-gap’.
+ * **denominator-exception: is only for ‘proportion’ scoring type.
+ * ***improvement Notation: is a Measure defined value that tells users how to view results of the Measure Report.
*/
public Map getGroupGapStatus(Measure measure, MeasureReport measureReport) {
Map groupStatus = new HashMap<>();
@@ -67,10 +58,18 @@ public Map getGroupGapStatus(Measure measure, Measur
private CareGapsStatusCode getGapStatus(
Measure measure, MeasureReportGroupComponent measureReportGroup, DateTimeType reportDate) {
+ Pair inInitialPopulation = new MutablePair<>("initial-population", false);
Pair inNumerator = new MutablePair<>("numerator", false);
Pair inDenominator = new MutablePair<>("denominator", false);
// get Numerator and Denominator membership
measureReportGroup.getPopulation().forEach(population -> {
+ if (population.hasCode()
+ && population
+ .getCode()
+ .hasCoding(MEASUREREPORT_MEASURE_POPULATION_SYSTEM, inInitialPopulation.getKey())
+ && population.getCount() == 1) {
+ inInitialPopulation.setValue(true);
+ }
if (population.hasCode()
&& population.getCode().hasCoding(MEASUREREPORT_MEASURE_POPULATION_SYSTEM, inNumerator.getKey())
&& population.getCount() == 1) {
@@ -83,6 +82,10 @@ private CareGapsStatusCode getGapStatus(
}
});
+ // is subject eligible for measure?
+ if (Boolean.FALSE.equals(inInitialPopulation.getValue())) {
+ return CareGapsStatusCode.NOT_APPLICABLE;
+ }
// default improvementNotation
boolean isPositive = true;
@@ -94,11 +97,6 @@ private CareGapsStatusCode getGapStatus(
.hasCoding(MEASUREREPORT_IMPROVEMENT_NOTATION_SYSTEM, IMPROVEMENT_NOTATION_SYSTEM_INCREASE);
}
- if (Boolean.FALSE.equals(inDenominator.getValue())) {
- // patient is not in eligible population
- return CareGapsStatusCode.NOT_APPLICABLE;
- }
-
if (Boolean.TRUE.equals(inDenominator.getValue())
&& ((isPositive && !inNumerator.getValue()) || (!isPositive && inNumerator.getValue()))) {
return getOpenOrProspectiveStatus(measureReportGroup, reportDate);
diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsTest.java
index dc92e1f2d..6b559fce5 100644
--- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsTest.java
+++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsTest.java
@@ -85,6 +85,7 @@ void exm125_careGaps_openGap() {
@Test
void exm125_careGaps_NA() {
+ // validates initial-population=false returns 'not-applicable' status
given.when()
.subject("Patient/neg-denom-EXM125")
.periodStart(LocalDate.of(2019, Month.JANUARY, 1).atStartOfDay().atZone(ZoneId.systemDefault()))
@@ -759,4 +760,27 @@ void noMeasureSpecified() {
assertTrue(e.getMessage().contains("no measure resolving parameter was specified"));
}
}
+ // MinimalProportionDenominatorExclusion
+ @Test
+ void InInitalPopulationAndDenominatorExclusion() {
+ // when subject is in initial population
+ // not in Denominator
+ // they should produce 'closed-gap'
+ GIVEN_REPO
+ .when()
+ .subject("Patient/female-1988")
+ .periodStart(LocalDate.of(2019, Month.JANUARY, 1).atStartOfDay().atZone(ZoneId.systemDefault()))
+ .periodEnd(LocalDate.of(2019, Month.DECEMBER, 31).atStartOfDay().atZone(ZoneId.systemDefault()))
+ .measureId("MinimalProportionDenominatorExclusion")
+ .measureIdentifier(null)
+ .measureUrl(null)
+ .status("closed-gap")
+ .getCareGapsReport()
+ .then()
+ .hasBundleCount(1)
+ .firstParameter()
+ .detectedIssueCount(1)
+ .detectedIssue()
+ .hasCareGapStatus("closed-gap");
+ }
}
diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/resources/measure/MinimalProportionDenominatorExclusion.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/resources/measure/MinimalProportionDenominatorExclusion.json
new file mode 100644
index 000000000..114d3b216
--- /dev/null
+++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/resources/measure/MinimalProportionDenominatorExclusion.json
@@ -0,0 +1,118 @@
+{
+ "id": "MinimalProportionDenominatorExclusion",
+ "resourceType": "Measure",
+ "url": "http://example.com/Measure/MinimalProportionDenominatorExclusion",
+ "library": [
+ "http://example.com/Library/MinimalProportionBooleanBasisSingleGroup"
+ ],
+ "scoring": {
+ "coding": [
+ {
+ "system": "http://hl7.org/fhir/measure-scoring",
+ "code": "proportion"
+ }
+ ]
+ },
+ "group": [
+ {
+ "population": [
+ {
+ "id": "initial-population",
+ "code": {
+ "coding": [
+ {
+ "system": "http://terminology.hl7.org/CodeSystem/measure-population",
+ "code": "initial-population",
+ "display": "Initial Population"
+ }
+ ]
+ },
+ "criteria": {
+ "language": "text/cql-identifier",
+ "expression": "always true"
+ }
+ },
+ {
+ "id": "denominator",
+ "code": {
+ "coding": [
+ {
+ "system": "http://terminology.hl7.org/CodeSystem/measure-population",
+ "code": "denominator",
+ "display": "Denominator"
+ }
+ ]
+ },
+ "criteria": {
+ "language": "text/cql-identifier",
+ "expression": "always false"
+ }
+ },
+ {
+ "id": "denominator-exclusion",
+ "code": {
+ "coding": [
+ {
+ "system": "http://terminology.hl7.org/CodeSystem/measure-population",
+ "code": "denominator-exclusion",
+ "display": "Denominator-Exclusion"
+ }
+ ]
+ },
+ "criteria": {
+ "language": "text/cql-identifier",
+ "expression": "always true"
+ }
+ },
+ {
+ "id": "denominator-exception",
+ "code": {
+ "coding": [
+ {
+ "system": "http://terminology.hl7.org/CodeSystem/measure-population",
+ "code": "denominator-exception",
+ "display": "Denominator-Exception"
+ }
+ ]
+ },
+ "criteria": {
+ "language": "text/cql-identifier",
+ "expression": "always false"
+ }
+ },
+ {
+ "id": "numerator-exclusion",
+ "code": {
+ "coding": [
+ {
+ "system": "http://terminology.hl7.org/CodeSystem/measure-population",
+ "code": "numerator-exclusion",
+ "display": "Numerator-Exclusion"
+ }
+ ]
+ },
+ "criteria": {
+ "language": "text/cql-identifier",
+ "expression": "always false"
+ }
+ },
+ {
+ "id": "numerator",
+ "code": {
+ "coding": [
+ {
+ "system": "http://terminology.hl7.org/CodeSystem/measure-population",
+ "code": "numerator",
+ "display": "Numerator"
+ }
+ ]
+ },
+ "criteria": {
+ "language": "text/cql-identifier",
+ "expression": "always false"
+ }
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file