Skip to content

Commit

Permalink
fix not-applicable status for care-gaps (#574)
Browse files Browse the repository at this point in the history
* fix na status for care-gaps

* remove duplicate identifier
  • Loading branch information
Capt-Mac authored Oct 31, 2024
1 parent 4eae404 commit e99737e
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,25 @@
*/
public class R4CareGapStatusEvaluator {
/**
* <p>
* GapStatus is determined by interpreting a MeasureReport resource of Type Ratio or Proportion that contain the populations: Numerator & Denominator
* </p>
*<p>
* <ul>
* <li>'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'.</li>
* <li> 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.</li>
* 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'.
*</ul>
* </p>
* <p>
* 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.
* </p>
* <ul>
* <li>ex: 1/10 with improvementNotation "decrease" means that the measureScore is 90%, therefore absence from 'Numerator' means criteria for care was met</li>
* <li>ex: 1/10 with improvementNotation "increase" means that the measureScore is 10%, therefore absence from 'Numerator' means criteria for care was NOT met.</li>
* </ul>
* <ul>
* <li>'open-gap': if in 'Denominator' & NOT in 'Numerator', where 'improvement notation' = increase. Then the subject is 'open-gap'</li>
* <li>'open-gap': if in 'Denominator' & in 'Numerator', where 'improvement notation' = decrease. Then the subject is 'open-gap'</li>
* <li>'closed-gap': if in 'Denominator' & NOT in 'Numerator', where 'improvement notation' = decrease. Then the subject is 'closed-gap'</li>
* <li>'closed-gap': if in 'Denominator' & in 'Numerator', where 'improvement notation' = increase. Then the subject is 'closed-gap'</li>
* </ul>
* <p>'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.</p>
*<br/>
* <p>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.</p>
* 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 |
*
* <p></p>
* *`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<String, CareGapsStatusCode> getGroupGapStatus(Measure measure, MeasureReport measureReport) {
Map<String, CareGapsStatusCode> groupStatus = new HashMap<>();
Expand All @@ -67,10 +58,18 @@ public Map<String, CareGapsStatusCode> getGroupGapStatus(Measure measure, Measur

private CareGapsStatusCode getGapStatus(
Measure measure, MeasureReportGroupComponent measureReportGroup, DateTimeType reportDate) {
Pair<String, Boolean> inInitialPopulation = new MutablePair<>("initial-population", false);
Pair<String, Boolean> inNumerator = new MutablePair<>("numerator", false);
Pair<String, Boolean> 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) {
Expand All @@ -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;

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down Expand Up @@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
}
]
}

0 comments on commit e99737e

Please sign in to comment.