diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/ConceptDef.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/ConceptDef.java index 09514a1da..7cbb9c140 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/ConceptDef.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/ConceptDef.java @@ -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 codes; - private final String text; - - public ConceptDef(List codes, String text) { - this.codes = codes; - this.text = text; - } - - public List codes() { - return this.codes; - } +public record ConceptDef(List codes, String text) { public boolean isEmpty() { return this.codes.isEmpty(); @@ -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(); } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEvaluationResultHandler.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEvaluationResultHandler.java index daa3456ee..b35401429 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEvaluationResultHandler.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEvaluationResultHandler.java @@ -69,7 +69,7 @@ public static void processResults( } } - evaluator.postEvaluation(measureDef); + MeasureMultiSubjectEvaluator.postEvaluationMultiSubject(measureDef); } /** @@ -126,6 +126,7 @@ public static 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); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEvaluator.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEvaluator.java index 5928d3948..395b3a3d9 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEvaluator.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEvaluator.java @@ -12,27 +12,18 @@ import static org.opencds.cqf.fhir.cr.measure.common.MeasurePopulationType.NUMERATOREXCLUSION; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.Sets; -import com.google.common.collect.Table; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Collector; -import java.util.stream.Collectors; -import org.hl7.fhir.r4.model.CodeableConcept; import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.cql.engine.execution.ExpressionResult; -import org.opencds.cqf.fhir.cr.measure.MeasureStratifierType; import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureScoringTypePopulations; -import org.opencds.cqf.fhir.cr.measure.r4.utils.R4ResourceIdUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class implements the core Measure evaluation logic that's defined in the @@ -54,6 +45,9 @@ */ @SuppressWarnings({"squid:S1135", "squid:S3776"}) public class MeasureEvaluator { + + private static final Logger logger = LoggerFactory.getLogger(MeasureEvaluator.class); + private final PopulationBasisValidator populationBasisValidator; public MeasureEvaluator(PopulationBasisValidator populationBasisValidator) { @@ -342,7 +336,6 @@ protected void evaluateContinuousVariable( // only measureObservations that intersect with finalized measure-population results should be retained pruneObservationResources( measurePopulationObservation.getResources(), measurePopulation, measurePopulationObservation); - // what about subjects? if (measurePopulation != null) { pruneObservationSubjectResources( measurePopulation.subjectResources, measurePopulationObservation.getSubjectResources()); @@ -441,6 +434,9 @@ protected void evaluateGroup( evaluateStratifiers(subjectId, groupDef.stratifiers(), evaluationResult); + // LUKETODO: do we want to validate criteria stratifiers versus scoring? for example, a ratio scoring seems + // incompatible with criteria stratifiers + var scoring = groupDef.measureScoring(); switch (scoring) { case PROPORTION, RATIO: @@ -493,9 +489,15 @@ private void addStratifierComponentResult( for (StratifierComponentDef component : components) { var expressionResult = evaluationResult.forExpression(component.expression()); - Optional.ofNullable(expressionResult.value()) - .ifPresent(nonNullValue -> - component.putResult(subjectId, nonNullValue, expressionResult.evaluatedResources())); + Optional.ofNullable(expressionResult) + .ifPresentOrElse( + nonNullExpressionResult -> component.putResult( + subjectId, + nonNullExpressionResult.value(), + nonNullExpressionResult.evaluatedResources()), + () -> logger.warn( + "Could not find CQL expression result for stratifier component expression: {}", + component.expression())); } } @@ -503,242 +505,29 @@ private void addStratifierNonComponentResult( String subjectId, EvaluationResult evaluationResult, StratifierDef stratifierDef) { var expressionResult = evaluationResult.forExpression(stratifierDef.expression()); - Optional.ofNullable(expressionResult) - .map(ExpressionResult::value) - .ifPresent(nonNullValue -> stratifierDef.putResult( - subjectId, // context of CQL expression ex: Patient based - nonNullValue, - expressionResult.evaluatedResources())); - } - - /** - * Take the accumulated subject-by-subject evaluation results and use it to build StratumDefs - * and StratumPopulationDefs - * - * @param measureDef to mutate post-evaluation with results of initial stratifier - * subject-by-subject accumulations. - * - */ - public void postEvaluation(MeasureDef measureDef) { - - for (GroupDef groupDef : measureDef.groups()) { - for (StratifierDef stratifierDef : groupDef.stratifiers()) { - final List stratumDefs; - - if (!stratifierDef.components().isEmpty()) { - stratumDefs = componentStratumPlural(stratifierDef, groupDef.populations()); - } else { - stratumDefs = nonComponentStratumPlural(stratifierDef, groupDef.populations()); - } - - stratifierDef.addAllStratum(stratumDefs); - } + if (expressionResult != null) { + logger.info( + "expressionResult: stratifierDef.expression(): {}, value: {}, evaluatedResources: {}", + stratifierDef.expression(), + expressionResult.value(), + expressionResult.evaluatedResources()); } - } - - private StratumDef buildStratumDef( - StratifierDef stratifierDef, - Set values, - List subjectIds, - List populationDefs) { - - boolean isComponent = values.size() > 1; - String stratumText = null; - - for (StratumValueDef valuePair : values) { - StratumValueWrapper value = valuePair.value(); - var componentDef = valuePair.def(); - // Set Stratum value to indicate which value is displaying results - // ex. for Gender stratifier, code 'Male' - if (value.getValueClass().equals(CodeableConcept.class)) { - if (isComponent) { - // component stratifier example: code: "gender", value: 'M' - // value being stratified: 'M' - stratumText = componentDef.code().text(); - } else { - // non-component stratifiers only set stratified value, code is set on stratifier object - // value being stratified: 'M' - if (value.getValue() instanceof CodeableConcept codeableConcept) { - stratumText = codeableConcept.getText(); - } - } - } else if (isComponent) { - stratumText = expressionResultToCodableConcept(value).getText(); - } else if (MeasureStratifierType.VALUE == stratifierDef.getStratifierType()) { - // non-component stratifiers only set stratified value, code is set on stratifier object - // value being stratified: 'M' - stratumText = expressionResultToCodableConcept(value).getText(); - } - } - - return new StratumDef( - stratumText, - populationDefs.stream() - .map(popDef -> buildStratumPopulationDef(popDef, subjectIds)) - .toList(), - values, - subjectIds); - } - - private static StratumPopulationDef buildStratumPopulationDef( - PopulationDef populationDef, List subjectIds) { - - var popSubjectIds = populationDef.getSubjects().stream() - .map(R4ResourceIdUtils::addPatientQualifier) - .collect(Collectors.toUnmodifiableSet()); - var qualifiedSubjectIdsCommonToPopulation = Sets.intersection(new HashSet<>(subjectIds), popSubjectIds); + if (expressionResult == null) { + logger.warn( + "Could not find CQL expression result for stratifier expression: {}", stratifierDef.expression()); - return new StratumPopulationDef(populationDef.id(), qualifiedSubjectIdsCommonToPopulation); - } - - private List componentStratumPlural(StratifierDef stratifierDef, List populationDefs) { - - final Table subjectResultTable = - buildSubjectResultsTable(stratifierDef.components()); - - // Stratifiers should be of the same basis as population - // Split subjects by result values - // ex. all Male Patients and all Female Patients - - var componentSubjects = groupSubjectsByValueDefSet(subjectResultTable); - - var stratumDefs = new ArrayList(); - - componentSubjects.forEach((valueSet, subjects) -> { - // converts table into component value combinations - // | Stratum | Set | List | - // | --------- | ----------------------- | ---------------------- | - // | Stratum-1 | <'M','White> | [subject-a] | - // | Stratum-2 | <'F','hispanic/latino'> | [subject-b] | - // | Stratum-3 | <'M','hispanic/latino'> | [subject-c] | - // | Stratum-4 | <'F','black'> | [subject-d, subject-e] | - - var stratumDef = buildStratumDef(stratifierDef, valueSet, subjects, populationDefs); - - stratumDefs.add(stratumDef); - }); - - return stratumDefs; - } - - private List nonComponentStratumPlural( - StratifierDef stratifierDef, List populationDefs) { - // standard Stratifier - // one criteria expression defined, one set of criteria results - - // standard Stratifier - // one criteria expression defined, one set of criteria results - final Map subjectValues = stratifierDef.getResults(); - - // nonComponent stratifiers will have a single expression that can generate results, instead of grouping - // combinations of results - // example: 'gender' expression could produce values of 'M', 'F' - // subject1: 'gender'--> 'M' - // subject2: 'gender'--> 'F' - // stratifier criteria results are: 'M', 'F' - - if (MeasureStratifierType.CRITERIA == stratifierDef.getStratifierType()) { - // Seems to be irrelevant for criteria based stratifiers - var stratValues = Set.of(); - // Seems to be irrelevant for criteria based stratifiers - var patients = List.of(); - - var stratum = buildStratumDef(stratifierDef, stratValues, patients, populationDefs); - return List.of(stratum); - } - - Map> subjectsByValue = subjectValues.keySet().stream() - .collect(Collectors.groupingBy( - x -> new StratumValueWrapper(subjectValues.get(x).rawValue()))); - - var stratumMultiple = new ArrayList(); - - // Stratum 1 - // Value: 'M'--> subjects: subject1 - // Stratum 2 - // Value: 'F'--> subjects: subject2 - // loop through each value key - for (Map.Entry> stratValue : subjectsByValue.entrySet()) { - // patch Patient values with prefix of ResourceType to match with incoming population subjects for stratum - // TODO: should match context of CQL, not only Patient - var patientsSubjects = stratValue.getValue().stream() - .map(R4ResourceIdUtils::addPatientQualifier) - .toList(); - // build the stratum for each unique value - // non-component stratifiers will populate a 'null' for componentStratifierDef, since it doesn't have - // multiple criteria - // TODO: build out nonComponent stratum method - Set stratValues = Set.of(new StratumValueDef(stratValue.getKey(), null)); - var stratum = buildStratumDef(stratifierDef, stratValues, patientsSubjects, populationDefs); - stratumMultiple.add(stratum); + return; } - return stratumMultiple; - } - - private Table buildSubjectResultsTable( - List componentDefs) { - - final Table subjectResultTable = HashBasedTable.create(); + final Object expressionResultValue = expressionResult.value(); - // Component Stratifier - // one or more criteria expression defined, one set of criteria results per component specified - // results of component stratifier are an intersection of membership to both component result sets + if (expressionResultValue == null) { + logger.warn("CQL expression result is null for stratifier expression: {}", stratifierDef.expression()); - componentDefs.forEach(componentDef -> componentDef.getResults().forEach((subject, result) -> { - StratumValueWrapper stratumValueWrapper = new StratumValueWrapper(result.rawValue()); - subjectResultTable.put(R4ResourceIdUtils.addPatientQualifier(subject), stratumValueWrapper, componentDef); - })); - - return subjectResultTable; - } - - private static Map, List> groupSubjectsByValueDefSet( - Table table) { - // input format - // | Subject (String) | CriteriaResult (ValueWrapper) | StratifierComponentDef | - // | ---------------- | ----------------------------- | ---------------------- | - // | subject-a | M | gender | - // | subject-b | F | gender | - // | subject-c | M | gender | - // | subject-d | F | gender | - // | subject-e | F | gender | - // | subject-a | white | race | - // | subject-b | hispanic/latino | race | - // | subject-c | hispanic/latino | race | - // | subject-d | black | race | - // | subject-e | black | race | - - // Step 1: Build Map> - final Map> subjectToValueDefs = new HashMap<>(); - - for (Table.Cell cell : table.cellSet()) { - subjectToValueDefs - .computeIfAbsent(cell.getRowKey(), k -> new HashSet<>()) - .add(new StratumValueDef(cell.getColumnKey(), cell.getValue())); + return; } - // output format: - // | Set | List | - // | ----------------------- | ---------------------- | - // | <'M','White> | [subject-a] | - // | <'F','hispanic/latino'> | [subject-b] | - // | <'M','hispanic/latino'> | [subject-c] | - // | <'F','black'> | [subject-d, subject-e] | - - // Step 2: Invert to Map, List> - return subjectToValueDefs.entrySet().stream() - .collect(Collectors.groupingBy( - Map.Entry::getValue, - Collector.of(ArrayList::new, (list, e) -> list.add(e.getKey()), (l1, l2) -> { - l1.addAll(l2); - return l1; - }))); - } - // This is weird pattern where we have multiple qualifying values within a single stratum, - // which was previously unsupported. So for now, comma-delim the first five values. - private static CodeableConcept expressionResultToCodableConcept(StratumValueWrapper value) { - return new CodeableConcept().setText(value.getValueAsString()); + stratifierDef.putResult(subjectId, expressionResultValue, expressionResult.evaluatedResources()); } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureMultiSubjectEvaluator.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureMultiSubjectEvaluator.java new file mode 100644 index 000000000..197732c29 --- /dev/null +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureMultiSubjectEvaluator.java @@ -0,0 +1,364 @@ +package org.opencds.cqf.fhir.cr.measure.common; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Sets; +import com.google.common.collect.Sets.SetView; +import com.google.common.collect.Table; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.ResourceType; +import org.opencds.cqf.fhir.cr.measure.MeasureStratifierType; +import org.opencds.cqf.fhir.cr.measure.r4.utils.R4ResourceIdUtils; + +// LUKETODO: javadoc +public class MeasureMultiSubjectEvaluator { + + /** + * Take the accumulated subject-by-subject evaluation results and use it to build StratumDefs + * and StratumPopulationDefs + * + * @param measureDef to mutate post-evaluation with results of initial stratifier + * subject-by-subject accumulations. + * + */ + public static void postEvaluationMultiSubject(MeasureDef measureDef) { + + for (GroupDef groupDef : measureDef.groups()) { + for (StratifierDef stratifierDef : groupDef.stratifiers()) { + final List stratumDefs; + + if (!stratifierDef.components().isEmpty()) { + stratumDefs = componentStratumPlural( + stratifierDef, groupDef.getPopulationBasis(), groupDef.populations()); + } else { + stratumDefs = nonComponentStratumPlural( + stratifierDef, groupDef.getPopulationBasis(), groupDef.populations()); + } + + stratifierDef.addAllStratum(stratumDefs); + } + } + } + + private static StratumDef buildStratumDef( + StratifierDef stratifierDef, + Set values, + List subjectIds, + CodeDef populationBasis, + List populationDefs) { + + boolean isComponent = values.size() > 1; + String stratumText = null; + + for (StratumValueDef valuePair : values) { + StratumValueWrapper value = valuePair.value(); + var componentDef = valuePair.def(); + // Set Stratum value to indicate which value is displaying results + // ex. for Gender stratifier, code 'Male' + if (value.getValueClass().equals(CodeableConcept.class)) { + if (isComponent) { + // component stratifier example: code: "gender", value: 'M' + // value being stratified: 'M' + stratumText = componentDef.code().text(); + } else { + // non-component stratifiers only set stratified value, code is set on stratifier object + // value being stratified: 'M' + if (value.getValue() instanceof CodeableConcept codeableConcept) { + stratumText = codeableConcept.getText(); + } + } + } else if (isComponent) { + stratumText = expressionResultToCodableConcept(value).getText(); + } else if (MeasureStratifierType.VALUE == stratifierDef.getStratifierType()) { + // non-component stratifiers only set stratified value, code is set on stratifier object + // value being stratified: 'M' + stratumText = expressionResultToCodableConcept(value).getText(); + } + } + + return new StratumDef( + stratumText, + populationDefs.stream() + .map(popDef -> buildStratumPopulationDef( + stratifierDef.getStratifierType(), + stratifierDef.getAllCriteriaResultValues(), + populationBasis, + popDef, + subjectIds)) + .toList(), + values, + subjectIds); + } + + private static StratumPopulationDef buildStratumPopulationDef( + MeasureStratifierType measureStratifierType, + Set evaluationResultsForStratifier, + CodeDef groupPopulationBasis, + PopulationDef populationDef, + List subjectIds) { + + var popSubjectIds = populationDef.getSubjects().stream() + .map(R4ResourceIdUtils::addPatientQualifier) + .collect(Collectors.toUnmodifiableSet()); + + var qualifiedSubjectIdsCommonToPopulation = Sets.intersection(new HashSet<>(subjectIds), popSubjectIds); + + final StratumPopulationDef stratumPopulationDef = + new StratumPopulationDef(populationDef.id(), qualifiedSubjectIdsCommonToPopulation); + + final List resourceIds = getResourceIds(subjectIds, groupPopulationBasis, populationDef); + + final int stratumCount = + getStratumCountUpper(measureStratifierType, evaluationResultsForStratifier, populationDef, resourceIds); + + stratumPopulationDef.setCount(stratumCount); + stratumPopulationDef.addAllResourceIds(resourceIds); + + return stratumPopulationDef; + } + + private static List componentStratumPlural( + StratifierDef stratifierDef, CodeDef populationBasis, List populationDefs) { + + final Table subjectResultTable = + buildSubjectResultsTable(stratifierDef.components()); + + // Stratifiers should be of the same basis as population + // Split subjects by result values + // ex. all Male Patients and all Female Patients + + var componentSubjects = groupSubjectsByValueDefSet(subjectResultTable); + + var stratumDefs = new ArrayList(); + + componentSubjects.forEach((valueSet, subjects) -> { + // converts table into component value combinations + // | Stratum | Set | List | + // | --------- | ----------------------- | ---------------------- | + // | Stratum-1 | <'M','White> | [subject-a] | + // | Stratum-2 | <'F','hispanic/latino'> | [subject-b] | + // | Stratum-3 | <'M','hispanic/latino'> | [subject-c] | + // | Stratum-4 | <'F','black'> | [subject-d, subject-e] | + + var stratumDef = buildStratumDef(stratifierDef, valueSet, subjects, populationBasis, populationDefs); + + stratumDefs.add(stratumDef); + }); + + return stratumDefs; + } + + private static List nonComponentStratumPlural( + StratifierDef stratifierDef, CodeDef populationBasis, List populationDefs) { + // standard Stratifier + // one criteria expression defined, one set of criteria results + + // standard Stratifier + // one criteria expression defined, one set of criteria results + final Map subjectValues = stratifierDef.getResults(); + + // nonComponent stratifiers will have a single expression that can generate results, instead of grouping + // combinations of results + // example: 'gender' expression could produce values of 'M', 'F' + // subject1: 'gender'--> 'M' + // subject2: 'gender'--> 'F' + // stratifier criteria results are: 'M', 'F' + + if (MeasureStratifierType.CRITERIA == stratifierDef.getStratifierType()) { + // Seems to be irrelevant for criteria based stratifiers + var stratValues = Set.of(); + // Seems to be irrelevant for criteria based stratifiers + var patients = List.of(); + + var stratum = buildStratumDef(stratifierDef, stratValues, patients, populationBasis, populationDefs); + return List.of(stratum); + } + + final Map> subjectsByValue = subjectValues.entrySet().stream() + .filter(entry -> entry.getValue() != null) + .filter(entry -> entry.getValue().rawValue() != null) + .collect(Collectors.groupingBy( + entry -> new StratumValueWrapper(entry.getValue().rawValue()), + Collectors.mapping(Entry::getKey, Collectors.toList()))); + + var stratumMultiple = new ArrayList(); + + // Stratum 1 + // Value: 'M'--> subjects: subject1 + // Stratum 2 + // Value: 'F'--> subjects: subject2 + // loop through each value key + for (Map.Entry> stratValue : subjectsByValue.entrySet()) { + // patch Patient values with prefix of ResourceType to match with incoming population subjects for stratum + // TODO: should match context of CQL, not only Patient + var patientsSubjects = stratValue.getValue().stream() + .map(R4ResourceIdUtils::addPatientQualifier) + .toList(); + // build the stratum for each unique value + // non-component stratifiers will populate a 'null' for componentStratifierDef, since it doesn't have + // multiple criteria + // TODO: build out nonComponent stratum method + Set stratValues = Set.of(new StratumValueDef(stratValue.getKey(), null)); + var stratum = + buildStratumDef(stratifierDef, stratValues, patientsSubjects, populationBasis, populationDefs); + stratumMultiple.add(stratum); + } + + return stratumMultiple; + } + + private static Table buildSubjectResultsTable( + List componentDefs) { + + final Table subjectResultTable = HashBasedTable.create(); + + // Component Stratifier + // one or more criteria expression defined, one set of criteria results per component specified + // results of component stratifier are an intersection of membership to both component result sets + + componentDefs.forEach(componentDef -> componentDef.getResults().forEach((subject, result) -> { + StratumValueWrapper stratumValueWrapper = new StratumValueWrapper(result.rawValue()); + subjectResultTable.put(R4ResourceIdUtils.addPatientQualifier(subject), stratumValueWrapper, componentDef); + })); + + return subjectResultTable; + } + + private static Map, List> groupSubjectsByValueDefSet( + Table table) { + // input format + // | Subject (String) | CriteriaResult (ValueWrapper) | StratifierComponentDef | + // | ---------------- | ----------------------------- | ---------------------- | + // | subject-a | M | gender | + // | subject-b | F | gender | + // | subject-c | M | gender | + // | subject-d | F | gender | + // | subject-e | F | gender | + // | subject-a | white | race | + // | subject-b | hispanic/latino | race | + // | subject-c | hispanic/latino | race | + // | subject-d | black | race | + // | subject-e | black | race | + + // Step 1: Build Map> + final Map> subjectToValueDefs = new HashMap<>(); + + for (Table.Cell cell : table.cellSet()) { + subjectToValueDefs + .computeIfAbsent(cell.getRowKey(), k -> new HashSet<>()) + .add(new StratumValueDef(cell.getColumnKey(), cell.getValue())); + } + // output format: + // | Set | List | + // | ----------------------- | ---------------------- | + // | <'M','White> | [subject-a] | + // | <'F','hispanic/latino'> | [subject-b] | + // | <'M','hispanic/latino'> | [subject-c] | + // | <'F','black'> | [subject-d, subject-e] | + + // Step 2: Invert to Map, List> + return subjectToValueDefs.entrySet().stream() + .collect(Collectors.groupingBy( + Map.Entry::getValue, + Collector.of(ArrayList::new, (list, e) -> list.add(e.getKey()), (l1, l2) -> { + l1.addAll(l2); + return l1; + }))); + } + + // This is weird pattern where we have multiple qualifying values within a single stratum, + // which was previously unsupported. So for now, comma-delim the first five values. + private static CodeableConcept expressionResultToCodableConcept(StratumValueWrapper value) { + return new CodeableConcept().setText(value.getValueAsString()); + } + + private static int getStratumCountUpper( + MeasureStratifierType measureStratifierType, + Set evaluationResults, + PopulationDef populationDef, + List resourceIds) { + + if (MeasureStratifierType.CRITERIA == measureStratifierType) { + final Set resources = populationDef.getResources(); + // LUKETODO: for the component criteria scenario, we don't add the results directly to the stratifierDef, + // but to each of the component defs, which is why this is empty + if (resources.isEmpty() || evaluationResults.isEmpty()) { + // There's no intersection, so no point in going further. + return 0; + } + + final Class resourcesClassFirst = resources.iterator().next().getClass(); + final Class resultClassFirst = + evaluationResults.iterator().next().getClass(); + + // Sanity check: isCriteriaBasedStratifier() should have filtered this out + if (resourcesClassFirst != resultClassFirst) { + // Different classes, so no point in going further. + return 0; + } + + final SetView intersection = Sets.intersection(resources, evaluationResults); + return intersection.size(); + } + + if (resourceIds.isEmpty()) { + return 0; + } + + return resourceIds.size(); + } + + @Nonnull + private static List getResourceIds( + List subjectIds, CodeDef populationBasis, PopulationDef populationDef) { + String resourceType; + try { + // when this method is checked with a primitive value and not ResourceType it returns an error + // this try/catch is to prevent the exception thrown from setting the correct value + resourceType = ResourceType.fromCode(populationBasis.code()).toString(); + } catch (FHIRException e) { + resourceType = null; + } + + // only ResourceType fhirType should return true here + boolean isResourceType = resourceType != null; + List resourceIds = new ArrayList<>(); + assert populationDef != null; + if (populationDef.getSubjectResources() != null) { + for (String subjectId : subjectIds) { + // retrieve criteria results by subject Key + var resources = + populationDef.getSubjectResources().get(R4ResourceIdUtils.stripPatientQualifier(subjectId)); + if (resources != null) { + if (isResourceType) { + resourceIds.addAll(resources.stream() + .map(MeasureMultiSubjectEvaluator::getPopulationResourceIds) // get resource id + .toList()); + } else { + resourceIds.addAll( + resources.stream().map(Object::toString).toList()); + } + } + } + } + return resourceIds; + } + + private static String getPopulationResourceIds(Object resourceObject) { + if (resourceObject instanceof IBaseResource resource) { + return resource.getIdElement().toVersionless().getValueAsString(); + } + return null; + } +} diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/PopulationDef.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/PopulationDef.java index 4f6f56ce1..c4f710c2f 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/PopulationDef.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/PopulationDef.java @@ -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 { @@ -62,6 +63,12 @@ public Set getSubjects() { return this.getSubjectResources().keySet(); } + public Set getSubjectsWithPatientQualifier() { + return getSubjects().stream() + .map(R4ResourceIdUtils::addPatientQualifier) + .collect(Collectors.toUnmodifiableSet()); + } + public void retainAllResources(Set resourcesToRetain) { getSubjectResources().forEach((key, value) -> value.retainAll(resourcesToRetain)); } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratifierComponentDef.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratifierComponentDef.java index 53a7a0159..5f78312f5 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratifierComponentDef.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratifierComponentDef.java @@ -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; @@ -40,4 +41,14 @@ public Map 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(); + } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratumDef.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratumDef.java index e49be71da..376083be2 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratumDef.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratumDef.java @@ -43,4 +43,6 @@ public Set getValueDefs() { public List getSubjectIds() { return subjectIds; } + + // LUKETODO: toString } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratumPopulationDef.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratumPopulationDef.java index 8f5c547ba..1a7ea523f 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratumPopulationDef.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/StratumPopulationDef.java @@ -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; @@ -14,6 +16,10 @@ public class StratumPopulationDef { private final String id; private final Set 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 resourceIds = new ArrayList<>(); public StratumPopulationDef(String id, Set subjectsQualifiedOrUnqualified) { this.id = id; @@ -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 getSubjectsQualifiedOrUnqualified() { return subjectsQualifiedOrUnqualified; } - // LUKETODO: javadoc + /** + * @return The subjectIds without a FHIR resource qualifier, whether they previously had a + * qualifier or not + */ public Set 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 getResourceIds() { + return resourceIds; + } + + public void addAllResourceIds(List resourceIds) { + this.resourceIds.addAll(resourceIds); + } + + // LUKETODO: toString } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportScorer.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportScorer.java index deaba8cd4..82e32207f 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportScorer.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportScorer.java @@ -84,6 +84,9 @@ *

(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

*/ @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 { private static final Logger logger = LoggerFactory.getLogger(R4MeasureReportScorer.class); @@ -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), diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java index 399280a4a..e05830d60 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java @@ -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; @@ -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 @@ -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> extractResourceType(String groupPopulationBasisCode) { if (BOOLEAN_BASIS.equals(groupPopulationBasisCode)) { return Optional.of(Boolean.class); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4StratifierBuilder.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4StratifierBuilder.java index de748e706..1aca04a73 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4StratifierBuilder.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4StratifierBuilder.java @@ -2,12 +2,12 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import com.google.common.collect.Sets; -import com.google.common.collect.Sets.SetView; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -37,6 +37,8 @@ import org.opencds.cqf.fhir.cr.measure.constant.MeasureConstants; import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureReportBuilder.BuilderContext; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4ResourceIdUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Convenience class with functionality split out from {@link R4MeasureReportBuilder} to @@ -44,6 +46,7 @@ */ @SuppressWarnings({"squid:S1135", "squid:S107"}) class R4StratifierBuilder { + private static final Logger logger = LoggerFactory.getLogger(R4StratifierBuilder.class); private R4StratifierBuilder() { // static class @@ -94,14 +97,7 @@ private static void componentStratifier( var reportStratum = reportStratifier.addStratum(); buildStratum( - bc, - stratifierDef, - stratumDef, - reportStratum, - stratumDef.getValueDefs(), - stratumDef.getSubjectIds(), - populations, - groupDef); + bc, stratifierDef, stratumDef, reportStratum, stratumDef.getValueDefs(), populations, groupDef); }); } @@ -132,7 +128,6 @@ private static void nonComponentStratifier( getOnlyStratumDef(stratifierDef), reportStratum, stratValues, - patients, populations, groupDef); return; // short-circuit so we don't process non-criteria logic @@ -158,15 +153,7 @@ private static void buildStratumOuter( var reportStratum = reportStratifier.addStratum(); - buildStratum( - bc, - stratifierDef, - stratumDef, - reportStratum, - stratumDef.getValueDefs(), - stratumDef.getSubjectIds(), - populations, - groupDef); + buildStratum(bc, stratifierDef, stratumDef, reportStratum, stratumDef.getValueDefs(), populations, groupDef); } private static void buildStratum( @@ -175,7 +162,6 @@ private static void buildStratum( StratumDef stratumDef, StratifierGroupComponent stratum, Set values, - List subjectIds, List populations, GroupDef groupDef) { boolean isComponent = values.size() > 1; @@ -213,6 +199,9 @@ private static void buildStratum( // non-component stratifiers only set stratified value, code is set on stratifier object // value being stratified: 'M' stratum.setValue(expressionResultToCodableConcept(value)); + // stratumDefText = stratum.getValue().getText(); + } else if (MeasureStratifierType.CRITERIA == stratifierDef.getStratifierType()) { + // stratumDefText = value.getValueAsString(); } } @@ -236,8 +225,7 @@ private static void buildStratum( throw new InternalErrorException("could not find MeasureGroupPopulationComponent"); } var stratumPopulation = stratum.addPopulation(); - buildStratumPopulation( - bc, stratifierDef, stratumPopulationDef, stratumPopulation, subjectIds, optMgpc.get(), groupDef); + buildStratumPopulation(bc, stratumPopulationDef, stratumPopulation, optMgpc.get(), groupDef); } } @@ -263,10 +251,8 @@ private static CodeableConcept expressionResultToCodableConcept(StratumValueWrap // the provided list of subjectIds private static void buildStratumPopulation( BuilderContext bc, - StratifierDef stratifierDef, StratumPopulationDef stratumPopulationDef, StratifierGroupPopulationComponent sgpc, - List subjectIds, MeasureGroupPopulationComponent population, GroupDef groupDef) { @@ -294,61 +280,56 @@ private static void buildStratumPopulation( population.getCode().getCodingFirstRep().getCode())); } - final Set subjectsQualifiedOrUnqualified = stratumPopulationDef.getSubjectsQualifiedOrUnqualified(); - if (groupDef.isBooleanBasis()) { - buildBooleanBasisStratumPopulation(bc, sgpc, populationDef, subjectsQualifiedOrUnqualified); + buildBooleanBasisStratumPopulation(bc, sgpc, stratumPopulationDef, populationDef); } else { - buildResourceBasisStratumPopulation(bc, stratifierDef, sgpc, subjectIds, populationDef, groupDef); + buildResourceBasisStratumPopulation(bc, stratumPopulationDef, sgpc); } } private static void buildBooleanBasisStratumPopulation( BuilderContext bc, StratifierGroupPopulationComponent sgpc, - PopulationDef populationDef, - Set subjectIdsCommonToPopulation) { + StratumPopulationDef stratumPopulationDef, + PopulationDef populationDef) { + + final Set subjectsCommonToPopulation = stratumPopulationDef.getSubjectsQualifiedOrUnqualified(); + + var popSubjectIds = populationDef.getSubjectsWithPatientQualifier(); - var popSubjectIds = populationDef.getSubjects().stream() - .map(R4ResourceIdUtils::addPatientQualifier) - .toList(); if (popSubjectIds.isEmpty()) { sgpc.setCount(0); return; } - sgpc.setCount(subjectIdsCommonToPopulation.size()); + sgpc.setCount(subjectsCommonToPopulation.size()); // subject-list ListResource to match intersection of results - if (!subjectIdsCommonToPopulation.isEmpty() + if (!subjectsCommonToPopulation.isEmpty() && bc.report().getType() == org.hl7.fhir.r4.model.MeasureReport.MeasureReportType.SUBJECTLIST) { ListResource popSubjectList = - R4StratifierBuilder.createIdList(UUID.randomUUID().toString(), subjectIdsCommonToPopulation); + R4StratifierBuilder.createIdList(UUID.randomUUID().toString(), subjectsCommonToPopulation); bc.addContained(popSubjectList); sgpc.setSubjectResults(new Reference("#" + popSubjectList.getId())); } } private static void buildResourceBasisStratumPopulation( - BuilderContext bc, - StratifierDef stratifierDef, - StratifierGroupPopulationComponent sgpc, - List subjectIds, - PopulationDef populationDef, - GroupDef groupDef) { + BuilderContext bc, StratumPopulationDef stratumPopulationDef, StratifierGroupPopulationComponent sgpc) { - final List resourceIds = getResourceIds(subjectIds, groupDef, populationDef); + // LUKETODO: this is wrong for our purposes: + // 1) we are getting non-distinct Date values, one duplicate for each of the 2 dates resolved by the population + // 2) we are doing the computation in the Builder, when we ought to do it in the MeasureEvaluator + // 3) We're conflating the intersection code with the counting, but this needs to be done separately + // 4) So we need to capture the intersection of resources in the MeasureEvaluator, then count them separately + // 5) As a first step, move this code to the MeasureEvaluator and ensure all existing tests pass + sgpc.setCount(stratumPopulationDef.getCount()); - final int stratumCount = getStratumCountUpper(stratifierDef, populationDef, resourceIds); - - sgpc.setCount(stratumCount); - - if (resourceIds.isEmpty()) { - return; - } + final List resourceIds = stratumPopulationDef.getResourceIds(); // subject-list ListResource to match intersection of results - if (bc.report().getType() == org.hl7.fhir.r4.model.MeasureReport.MeasureReportType.SUBJECTLIST) { + if ((!resourceIds.isEmpty()) + && bc.report().getType() == org.hl7.fhir.r4.model.MeasureReport.MeasureReportType.SUBJECTLIST) { ListResource popSubjectList = R4StratifierBuilder.createIdList(UUID.randomUUID().toString(), resourceIds); bc.addContained(popSubjectList); @@ -356,36 +337,40 @@ private static void buildResourceBasisStratumPopulation( } } - private static int getStratumCountUpper( - StratifierDef stratifierDef, PopulationDef populationDef, List resourceIds) { + public static boolean collectionsEqualIgnoringOrder(Collection coll1, Collection coll2) { + if (coll1 == null || coll2 == null) { + return coll1 == coll2; + } - if (MeasureStratifierType.CRITERIA == stratifierDef.getStratifierType()) { - final Set resources = populationDef.getResources(); - final Set results = stratifierDef.getAllCriteriaResultValues(); + if (coll1.size() != coll2.size()) { + return false; + } - if (resources.isEmpty() || results.isEmpty()) { - // There's no intersection, so no point in going further. - return 0; - } + Map frequencies = new HashMap<>(); + + // Build frequency map for the first collection + for (T item : coll1) { + frequencies.put(item, frequencies.getOrDefault(item, 0) + 1); + } - final Class resourcesClassFirst = resources.iterator().next().getClass(); - final Class resultClassFirst = results.iterator().next().getClass(); + // A simple 'Map.equals()' won't work if coll2 has different elements + // that aren't in coll1. We must check coll2 against the map. + for (T item : coll2) { + Integer count = frequencies.get(item); - // Sanity check: isCriteriaBasedStratifier() should have filtered this out - if (resourcesClassFirst != resultClassFirst) { - // Different classes, so no point in going further. - return 0; + // If the item is not in the map or the count is zero, it's a mismatch + if (count == null || count == 0) { + return false; } - final SetView intersection = Sets.intersection(resources, results); - return intersection.size(); - } - - if (resourceIds.isEmpty()) { - return 0; + // Decrement the count for the item + frequencies.put(item, count - 1); } - return resourceIds.size(); + // All counts should be zero, but the size check at the beginning + // combined with the decrement loop already guarantees this. + // If we reached here, the collections are equal. + return true; } @Nonnull diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifierTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifierTest.java new file mode 100644 index 000000000..9275a7866 --- /dev/null +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifierTest.java @@ -0,0 +1,569 @@ +package org.opencds.cqf.fhir.cr.measure.r4; + +import ca.uhn.fhir.context.FhirContext; +import org.junit.jupiter.api.Test; +import org.opencds.cqf.fhir.cr.measure.r4.Measure.Given; +import org.opencds.cqf.fhir.cr.measure.r4.Measure.SelectedReport; + +public class ComponentCriteriaStratifierTest { + + private static final Given GIVEN = Measure.given().repositoryFor("ComponentCriteriaStratifier"); + + /* + population=1/1/2024, 1/2/2024 + Components + criteria stratifier 1 + raw result: 2/1/2024, 1/2/2024 + criteria stratifier 2 + raw result: 2/3/2024, 1/2/2024 + stratum population: 1/2/2024 + */ + @Test + void cohortDateComponentCriteriaStratWithIntersectionScenario1() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierDateBasisWithIntersectionScenario1") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(4) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-feb1-jan2-feb3-jan2") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(1) + .up() + .up() + .up() + .up() + .report(); + } + + /* + population=1/1/2024, 1/2/2024 + Components + criteria stratifier 1 + raw result: 2/1/2024, 3/2/2024 + criteria stratifier 2 + raw result: 2/3/2024, 3/2/2024, 1/2/2024 + stratum population: NONE: lack of intersection between components and population + */ + @Test + void cohortDateComponentCriteriaStratNoIntersectionScenario2() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierDateBasisNoIntersectionScenario2") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(4) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-feb1-mar2-feb1-mar2-jan2") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortDateComponentCriteriaStratNoIntersectionScenario3() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierDateBasisNoIntersectionScenario3") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(4) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-mar1-apr1-jan1-jan2") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortDateComponentCriteriaStratNoIntersectionScenario4() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierDateBasisNoIntersectionScenario4") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(4) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-may1-jun1-may1-jun1") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortDateComponentCriteriaStratNoIntersectionScenario5() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierDateBasisNoIntersectionScenario5") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(4) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-jul1-aug1-sep1-oct1") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortBooleanComponentCriteriaStratWithIntersectionScenario1() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierBooleanBasisWithIntersectionScenario1") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(2) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-encounters-arrived-triaged-arrived-in-progress-boolean") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + // LUKETODO: maybe this is correct? debug to be sure + .hasCount(1) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortBooleanComponentCriteriaStratNoIntersectionScenario2() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario2") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasCount(2) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-encounter-planned-triaged-arrived-cancelled-boolean") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortBooleanComponentCriteriaStratNoIntersectionScenario3() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario3") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasCount(2) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-encounters-cancelled-finished-arrived-planned-boolean") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortBooleanComponentCriteriaStratNoIntersectionScenario4() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario4") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasCount(2) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-encounters-cancelled-triaged-cancelled-triaged-boolean") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortBooleanComponentCriteriaStratNoIntersectionScenario5() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario5") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasCount(2) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-encounters-cancelled-in-progress-finished-triaged-boolean") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortEncounterComponentCriteriaStratWithIntersectionScenario1() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierEncounterBasisWithIntersectionScenario1") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasCount(4) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-encounters-arrived-triaged-arrived-in-progress-resource") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(1) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortEncounterComponentCriteriaStratNoIntersectionScenario2() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario2") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasCount(4) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-encounters-planned-triaged-arrived-cancelled") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortEncounterComponentCriteriaStratNoIntersectionScenario3() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario3") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasCount(4) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-encounters-cancelled-finished-arrived-planned") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortEncounterComponentCriteriaStratNoIntersectionScenario4() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario4") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasCount(4) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-encounters-cancelled-triaged-cancelled-triaged") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + @Test + void cohortEncounterComponentCriteriaStratNoIntersectionScenario5() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario5") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasGroupCount(1) + .firstGroup() + .hasPopulationCount(1) + .firstPopulation() + .hasCount(4) + .up() + .hasStratifierCount(1) + .stratifierById("stratifier-encounters-cancelled-in-progress-finished-triaged") + .hasStratumCount(1) + .firstStratum() + .hasPopulationCount(1) + .firstPopulation() + .hasName("initial-population") + .hasCount(0) + .up() + .up() + .up() + .up() + .report(); + } + + // LUKETODO: test that explicitly handles mismatches and asserts error handling: + + // 9. 1 of n Component stratifier criteria expression has non-compliant population basis (population = Resource, + // Stratifier expression result is "String" or something). Throws error + + @Test + void cohortBooleanComponentCriteriaStratPopulationStratExpressionMismatchEncounter() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierBooleanBasisMismatchEncounter") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasContainedOperationOutcome() + .hasContainedOperationOutcomeMsg("Mismatch between population basis and stratifier criteria expression") + .report(); + } + + @Test + void cohortEncounterComponentCriteriaStratPopulationStratExpressionMismatchDate() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierEncounterBasisMismatchDate") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasContainedOperationOutcome() + .hasContainedOperationOutcomeMsg("Mismatch between population basis and stratifier criteria expression") + .report(); + } + + @Test + void cohortDateComponentCriteriaStratPopulationStratExpressionMismatchBoolean() { + + final SelectedReport then = GIVEN.when() + .measureId("ComponentCriteriaStratifierDateBasisMismatchBoolean") + .evaluate() + .then(); + + System.out.println( + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(then.report())); + + then.hasContainedOperationOutcome() + .hasContainedOperationOutcomeMsg("Mismatch between population basis and stratifier criteria expression") + .report(); + } +} diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java index c124757a7..b3f7d054c 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java @@ -870,17 +870,23 @@ public SelectedGroup hasDateOfCompliance() { } public SelectedPopulation population(String name) { - return this.population(g -> g.getPopulation().stream() + var population = this.population(g -> g.getPopulation().stream() .filter(x -> x.hasCode() && x.getCode().hasCoding() && x.getCode().getCoding().get(0).getCode().equals(name)) .findFirst() - .get()); + .orElse(null)); + + assertNotNull(population); + return population; } public SelectedPopulation population( Selector populationSelector) { var p = populationSelector.select(value()); + if (p == null) { + return null; + } return new SelectedPopulation(p, this); } @@ -898,7 +904,7 @@ public SelectedStratifier firstStratifier() { } public SelectedStratifier stratifierById(String stratId) { - final SelectedStratifier stratifier = this.stratifier(g -> g.getStratifier().stream() + var stratifier = this.stratifier(g -> g.getStratifier().stream() .filter(t -> t.getId().equals(stratId)) .findFirst() .orElse(null)); @@ -911,6 +917,9 @@ public SelectedStratifier stratifierById(String stratId) { public SelectedStratifier stratifier( Selector stratifierSelector) { var s = stratifierSelector.select(value()); + if (s == null) { + return null; + } return new SelectedStratifier(s, this); } @@ -986,6 +995,11 @@ public SelectedPopulation(MeasureReportGroupPopulationComponent value, SelectedG super(value, parent); } + public SelectedPopulation hasName(String name) { + assertEquals(name, value().getCode().getCodingFirstRep().getCode()); + return this; + } + public SelectedPopulation hasCount(int count) { MeasureValidationUtils.validatePopulation(value(), count); return this; @@ -1041,11 +1055,15 @@ public SelectedStratum stratum(CodeableConcept value) { } public SelectedStratum stratum(String textValue) { - return stratum(s -> s.getStratum().stream() + var stratum = stratum(s -> s.getStratum().stream() .filter(x -> x.hasValue() && x.getValue().hasText()) .filter(x -> x.getValue().getText().equals(textValue)) .findFirst() .orElse(null)); + + assertNotNull(stratum); + + return stratum; } public SelectedStratum stratumByComponentValueText(String textValue) { @@ -1068,6 +1086,9 @@ public SelectedStratum stratum( Selector stratumSelector) { var s = stratumSelector.select(value()); + if (s == null) { + return null; + } return new SelectedStratum(s, this); } @@ -1174,5 +1195,10 @@ public SelectedStratumPopulation hasNoStratumPopulationSubjectResults() { assertNull(value().getSubjectResults().getReference()); return this; } + + public SelectedStratumPopulation hasName(String name) { + assertEquals(name, value().getCode().getCodingFirstRep().getCode()); + return this; + } } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureStratifierTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureStratifierTest.java index 2184dd633..c57ab0328 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureStratifierTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureStratifierTest.java @@ -4,8 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.MeasureReport.MeasureReportStatus; import org.junit.jupiter.api.Test; import org.opencds.cqf.fhir.cr.measure.r4.Measure.Given; @@ -212,7 +214,7 @@ void cohortBooleanValueStratDifferentStratTypeFromBasisInvalid() { */ @Test void cohortBooleanValueStratComponentStrat() { - GIVEN_MEASURE_STRATIFIER_TEST + final MeasureReport report = GIVEN_MEASURE_STRATIFIER_TEST .when() .measureId("CohortBooleanStratComponent") .evaluate() @@ -243,6 +245,11 @@ void cohortBooleanValueStratComponentStrat() { .up() .up() .report(); + + final String json = + FhirContext.forR4Cached().newJsonParser().setPrettyPrint(true).encodeResourceToString(report); + + System.out.println("json = " + json); } /** diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidatorTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidatorTest.java index 5466fefeb..6d890e73e 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidatorTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidatorTest.java @@ -467,6 +467,7 @@ private static EvaluationResult buildEvaluationResult(Map expres return evaluationResult; } + // LUKETODO: what's this for? @Nonnull private static EvaluationResult buildEvaluationResult(Object expressionResult) { final EvaluationResult evaluationResult = new EvaluationResult(); diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/cql/ComponentCriteriaStratifier.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/cql/ComponentCriteriaStratifier.cql new file mode 100644 index 000000000..a1f8c0b99 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/cql/ComponentCriteriaStratifier.cql @@ -0,0 +1,149 @@ +library ComponentCriteriaStratifier + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' called FHIRHelpers + +parameter "Measurement Period" Interval default Interval[@2024-01-01T00:00:00, @2024-12-31T23:59:59] + +context Patient + +define "Initial Population Boolean": + exists("Initial Population Resource") + +define "Initial Population Resource": + "Encounters Arrived Planned" + +define "Initial Population Date": + { @2024-01-01, @2024-01-02 } + +define "Encounters Arrived Planned": + [Encounter] E + where E.status.value in { 'arrived', 'planned' } + +// +// Boolean/Resource +// + +//// Scenario 1: Overlap on Arrived + +define "Stratifier Encounters Arrived Triaged Boolean": + exists("Stratifier Encounters Arrived Triaged Resource") + +define "Stratifier Encounters Arrived Triaged Resource": + [Encounter] E + where E.status.value in { 'arrived', 'triaged' } + +define "Stratifier Encounters Arrived In-Progress Boolean": + exists("Stratifier Encounters Arrived In-Progress Resource") + +define "Stratifier Encounters Arrived In-Progress Resource": + [Encounter] E + where E.status.value in { 'arrived', 'in-progress' } + +// Scenario 2: No overlap due to intersection only between initial-pop and strat2 + +define "Stratifier Encounters Planned Triaged Boolean": + exists("Stratifier Encounters Planned Triaged Resource") + +define "Stratifier Encounters Planned Triaged Resource": + [Encounter] E + where E.status.value in { 'planned', 'triaged' } + +define "Stratifier Encounters Arrived Cancelled Boolean": + exists("Stratifier Encounters Arrived Cancelled Resource") + +define "Stratifier Encounters Arrived Cancelled Resource": + [Encounter] E + where E.status.value in { 'arrived', 'cancelled' } + +// Scenario 3: No overlap despite total intersection between initial-pop and strat2 + +define "Stratifier Encounters Cancelled Finished Boolean": + exists("Stratifier Encounters Cancelled Finished Resource") + +define "Stratifier Encounters Cancelled Finished Resource": + [Encounter] E + where E.status.value in { 'cancelled', 'finished' } + +define "Stratifier Encounters Arrived Planned Boolean": + exists("Stratifier Encounters Arrived Planned Resource") + +define "Stratifier Encounters Arrived Planned Resource": + "Encounters Arrived Planned" + +// Scenario 4: No overlap despite total intersection between strat1 and strat2 + +define "Stratifier Encounters Cancelled Triaged 1 Boolean": + exists("Stratifier Encounters Cancelled Triaged 1 Resource") + +define "Stratifier Encounters Cancelled Triaged 2 Boolean": + exists("Stratifier Encounters Cancelled Triaged 2 Resource") + +define "Stratifier Encounters Cancelled Triaged 1 Resource": + [Encounter] E + where E.status.value in { 'cancelled', 'triaged' } + +define "Stratifier Encounters Cancelled Triaged 2 Resource": + [Encounter] E + where E.status.value in { 'cancelled', 'triaged' } + +// Scenario 5: No overlap because init-pop, strat1, and strat2 have zero overlap with all + +define "Stratifier Encounters Cancelled In-Progress Boolean": + exists("Stratifier Encounters Cancelled In-Progress Resource") + +define "Stratifier Encounters Finished Triaged Boolean": + exists("Stratifier Encounters Finished Triaged Resource") + +define "Stratifier Encounters Cancelled In-Progress Resource": + [Encounter] E + where E.status.value in { 'cancelled', 'in-progress' } + +define "Stratifier Encounters Finished Triaged Resource": + [Encounter] E + where E.status.value in { 'finished', 'triaged' } + +// +// Date +// + +// Scenario 1: Overlap on 2024-01-02 + +define "Stratifier Feb1 Jan2": + { @2024-02-01, @2024-01-02 } + +define "Stratifier Feb3 Jan2": + { @2024-02-03, @2024-01-02 } + +// Scenario 2: No overlap due to intersection only between initial-pop and strat2 + +define "Stratifier Feb1 Mar2": + { @2024-02-01, @2024-03-02 } + +define "Stratifier Feb1 Mar2 Jan2": + { @2024-02-03, @2024-03-02, @2024-01-02 } + +// Scenario 3: No overlap despite total intersection between initial-pop and strat2 + +define "Stratifier Mar1 Apr1": + { @2024-03-01, @2024-04-01 } + +define "Stratifier Jan1 Jan2": + { @2024-01-01, @2024-02-01 } + +// Scenario 4: No overlap despite total intersection between strat1 and strat2 + +define "Stratifier May1 Jun1 1": + { @2024-05-01, @2024-06-01 } + +define "Stratifier May1 Jun1 2": + { @2024-05-01, @2024-06-01 } + +// Scenario 5: No overlap because init-pop, strat1, and strat2 have zero overlap with all + +define "Stratifier Jul1 Aug1": + { @2024-07-01, @2024-08-01 } + +define "Stratifier Sep1 Oct1": + { @2024-09-01, @2024-10-01 } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/library/ComponentCriteriaStratifier.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/library/ComponentCriteriaStratifier.json new file mode 100644 index 000000000..adbbb006e --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/library/ComponentCriteriaStratifier.json @@ -0,0 +1,18 @@ +{ + "resourceType": "Library", + "id": "ComponentCriteriaStratifier", + "url": "http://example.com/Library/ComponentCriteriaStratifier", + "name": "ComponentCriteriaStratifier", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/ComponentCriteriaStratifier.cql" + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisMismatchEncounter.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisMismatchEncounter.json new file mode 100644 index 000000000..49002cbea --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisMismatchEncounter.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierBooleanBasisMismatchEncounter", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierBooleanBasisMismatchEncounter", + "url": "http://example.com/Measure/ComponentCriteriaStratifierBooleanBasisMismatchEncounter", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "boolean" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Boolean" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-arrived-triaged-arrived-in-progress-boolean", + "code" : { + "text": "Stratifier Encounters Arrived Triaged Arrived In-Progress Boolean" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounters-arrived-triaged", + "code" : { + "text": "Encounters Arrived Triaged" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Arrived Triaged Boolean" + } + }, + { + "id": "stratifier-encounters-in-progress", + "code" : { + "text": "Encounters Arrived In-Progress" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Arrived In-Progress Boolean" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario2.json new file mode 100644 index 000000000..cb2817bdc --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario2.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario2", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario2", + "url": "http://example.com/Measure/ComponentCriteriaStratifierBooleaBasisNoIntersectionScenario2", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "boolean" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Boolean" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounter-planned-triaged-arrived-cancelled-boolean", + "code" : { + "text": "Stratifier Stratifier Encounters Planned Triaged Arrived Cancelled" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounter-planned-triaged", + "code" : { + "text": "Stratifier Stratifier Encounters Planned Triaged" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Planned Triaged Boolean" + } + }, + { + "id": "stratifier-encounter-arrived-cancelled", + "code" : { + "text": "Stratifier Encounters Arrived Cancelled" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Arrived Cancelled Boolean" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario3.json new file mode 100644 index 000000000..b52c67e70 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario3.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario3", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario3", + "url": "http://example.com/Measure/ComponentCriteriaStratifierBooleaBasisNoIntersectionScenario3", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "boolean" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Boolean" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-cancelled-finished-arrived-planned-boolean", + "code" : { + "text": "Stratifier Stratifier Encounter Cancelled Finished Arrived Planned Boolean" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounter-cancelled-finished", + "code" : { + "text": "Stratifier Encounters Cancelled Finished" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Cancelled Finished Boolean" + } + }, + { + "id": "stratifier-encounter-in-progress", + "code" : { + "text": "Encounter Arrived Planned" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Arrived Planned Boolean" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario4.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario4.json new file mode 100644 index 000000000..54bc262e8 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario4.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario4", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario4", + "url": "http://example.com/Measure/ComponentCriteriaStratifierBooleaBasisNoIntersectionScenario4", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "boolean" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Boolean" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-cancelled-triaged-cancelled-triaged-boolean", + "code" : { + "text": "Stratifier Encounters Cancelled Triaged Cancelled Triaged Boolean" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounters-cancelled-triaged-1", + "code" : { + "text": "Stratifier Encounters Cancelled Triaged 1" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Cancelled Triaged 1 Boolean" + } + }, + { + "id": "stratifier-encounters-cancelled-triaged-2", + "code" : { + "text": "Stratifier Encounters Cancelled Triaged 2" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Cancelled Triaged 2 Boolean" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario5.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario5.json new file mode 100644 index 000000000..8d6073c32 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario5.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario5", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierBooleanBasisNoIntersectionScenario5", + "url": "http://example.com/Measure/ComponentCriteriaStratifierBooleaBasisNoIntersectionScenario5", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "boolean" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Boolean" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-cancelled-in-progress-finished-triaged-boolean", + "code" : { + "text": "Stratifier Encounters Cancelled In-Progress Finished Triaged Boolean" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounters-cancelled-in-progress", + "code" : { + "text": "Stratifier Encounters Cancelled In-Progress" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Cancelled In-Progress Boolean" + } + }, + { + "id": "stratifier-encounters-finished-triaged", + "code" : { + "text": "Stratifier Encounters Finished Triaged" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Finished Triaged Boolean" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisWithIntersectionScenario1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisWithIntersectionScenario1.json new file mode 100644 index 000000000..be1b76ded --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierBooleanBasisWithIntersectionScenario1.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierBooleanBasisWithIntersectionScenario1", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierBooleanBasisWithIntersectionScenario1", + "url": "http://example.com/Measure/ComponentCriteriaStratifierBooleanBasisWithIntersectionScenario1", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "boolean" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Boolean" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-arrived-triaged-arrived-in-progress-boolean", + "code" : { + "text": "Stratifier Encounters Arrived Triaged Arrived In-Progress Boolean" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounters-arrived-triaged", + "code" : { + "text": "Encounters Arrived Triaged" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Arrived Triaged Boolean" + } + }, + { + "id": "stratifier-encounters-in-progress", + "code" : { + "text": "Encounters Arrived In-Progress" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Arrived In-Progress Boolean" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisMismatchBoolean.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisMismatchBoolean.json new file mode 100644 index 000000000..7efcd742c --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisMismatchBoolean.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierDateBasisMismatchBoolean", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierDateBasisMismatchBoolean", + "url": "http://example.com/Measure/ComponentCriteriaStratifierDateBasisMismatchBoolean", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "date" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Date" + } + } + ], + "stratifier": [ + { + "id": "stratifier-feb1-jan2-feb3-jan2", + "code" : { + "text": "Stratifier Feb1 Jan2 + Feb3 Jan2" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-comp-feb1-jan2", + "code" : { + "text": "Stratifier Feb1 Jan2" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Feb1 Jan2" + } + }, + { + "id": "stratifier-comp-feb3-jan2", + "code" : { + "text": "Stratifier Feb3 Jan2" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Feb3 Jan2" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario2.json new file mode 100644 index 000000000..50fb869fb --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario2.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierDateBasisNoIntersectionScenario2", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierDateBasisNoIntersectionScenario2", + "url": "http://example.com/Measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario2", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "date" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Date" + } + } + ], + "stratifier": [ + { + "id": "stratifier-feb1-mar2-feb1-mar2-jan2", + "code" : { + "text": "Stratifier Feb1 Mar2 + Feb1 Mar2 Jan2" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-comp-feb1-mar2", + "code" : { + "text": "Stratifier Feb1 Mar2" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Feb1 Mar2" + } + }, + { + "id": "stratifier-comp-feb1-mar2-jan2", + "code" : { + "text": "Stratifier Feb1 Mar2 Jan2" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Feb1 Mar2 Jan2" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario3.json new file mode 100644 index 000000000..8cb9c9dbd --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario3.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierDateBasisNoIntersectionScenario3", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierDateBasisNoIntersectionScenario3", + "url": "http://example.com/Measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario3", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "date" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Date" + } + } + ], + "stratifier": [ + { + "id": "stratifier-mar1-apr1-jan1-jan2", + "code" : { + "text": "Stratifier Mat1 Apr1 + Jan1 Jan2" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-comp-mar1-apr1", + "code" : { + "text": "Stratifier Mar1 Apr1" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Mar1 Apr1" + } + }, + { + "id": "stratifier-comp-jan1-jan2", + "code" : { + "text": "Stratifier Jan1 Jan2" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Jan1 Jan2" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario4.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario4.json new file mode 100644 index 000000000..dbf1ecd5a --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario4.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierDateBasisNoIntersectionScenario4", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierDateBasisNoIntersectionScenario4", + "url": "http://example.com/Measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario4", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "date" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Date" + } + } + ], + "stratifier": [ + { + "id": "stratifier-may1-jun1-may1-jun1", + "code" : { + "text": "Stratifier May1 Jun1 + May1 Jun1" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-comp-may1-jun1-1", + "code" : { + "text": "Stratifier May1 Jun1 1" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier May1 Jun1 1" + } + }, + { + "id": "stratifier-comp-may1-jun1-2", + "code" : { + "text": "Stratifier May1 Jun1 2" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier May1 Jun1 1 2" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario5.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario5.json new file mode 100644 index 000000000..9eddd5546 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario5.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierDateBasisNoIntersectionScenario5", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierDateBasisNoIntersectionScenario5", + "url": "http://example.com/Measure/ComponentCriteriaStratifierDateBasisNoIntersectionScenario5", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "date" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Date" + } + } + ], + "stratifier": [ + { + "id": "stratifier-jul1-aug1-sep1-oct1", + "code" : { + "text": "Stratifier Jul1 Aug1 + Sep1 Oct1" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-comp-jul1-aug1", + "code" : { + "text": "Stratifier Jul1 Aug1" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Jul1 Aug1" + } + }, + { + "id": "stratifier-comp-sep1-oct1", + "code" : { + "text": "Stratifier Sep1 Oct1" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Sep1 Oct1" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisWithIntersectionScenario1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisWithIntersectionScenario1.json new file mode 100644 index 000000000..cd258c95a --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierDateBasisWithIntersectionScenario1.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierDateBasisWithIntersectionScenario1", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierDateBasisWithIntersectionScenario1", + "url": "http://example.com/Measure/ComponentCriteriaStratifierDateBasisWithIntersectionScenario1", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "date" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Date" + } + } + ], + "stratifier": [ + { + "id": "stratifier-feb1-jan2-feb3-jan2", + "code" : { + "text": "Stratifier Feb1 Jan2 + Feb3 Jan2" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-comp-feb1-jan2", + "code" : { + "text": "Stratifier Feb1 Jan2" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Feb1 Jan2" + } + }, + { + "id": "stratifier-comp-feb3-jan2", + "code" : { + "text": "Stratifier Feb3 Jan2" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Feb3 Jan2" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisMismatchDate.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisMismatchDate.json new file mode 100644 index 000000000..f24b0885d --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisMismatchDate.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierEncounterBasisMismatchDate", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierEncounterBasisMismatchDate", + "url": "http://example.com/Measure/ComponentCriteriaStratifierEncounterBasisMismatchDate", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "Encounter" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Resource" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-arrived-triaged-arrived-in-progress-resource", + "code" : { + "text": "Stratifier Encounters Arrived Triaged Arrived In-Progress Resource" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounters-arrived-triaged", + "code" : { + "text": "Encounters Arrived Triaged" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Arrived Triaged Resource" + } + }, + { + "id": "stratifier-encounter-in-progress", + "code" : { + "text": "Encounters Arrived In-Progress" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Arrived In-Progress Resource" + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario2.json new file mode 100644 index 000000000..1680750a4 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario2.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario2", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario2", + "url": "http://example.com/Measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario2", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "Encounter" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Resource" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-planned-triaged-arrived-cancelled", + "code" : { + "text": "Stratifier Encounters Planned Triaged Arrived Cancelled Resource" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounters-planned-triaged", + "code" : { + "text": "Stratifier Encounters Planned Triaged" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Planned Triaged Resource" + } + }, + { + "id": "stratifier-encounters-arrived-cancelled", + "code" : { + "text": "Stratifier Encounters Arrived Cancelled" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounter Arrived Cancelled Resource" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario3.json new file mode 100644 index 000000000..9495e94db --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario3.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario3", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario3", + "url": "http://example.com/Measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario3", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "Encounter" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Resource" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-cancelled-finished-arrived-planned", + "code" : { + "text": "Stratifier Encounters Cancelled Finished Arrived Planned Resource" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounters-cancelled-finished", + "code" : { + "text": "Stratifier Encounters Cancelled Finished" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Cancelled Finished Resource" + } + }, + { + "id": "stratifier-encounters-arrived-planned", + "code" : { + "text": "Stratifier Encounters Arrived Planned" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounter Arrived Planned Resource" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario4.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario4.json new file mode 100644 index 000000000..fb3858f1a --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario4.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario4", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario4", + "url": "http://example.com/Measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario4", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "Encounter" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Resource" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-cancelled-triaged-cancelled-triaged", + "code" : { + "text": "Stratifier Encounters Cancelled Triaged Cancelled Triaged" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounters-cancelled-triaged-1", + "code" : { + "text": "Stratifier Encounters Cancelled Triaged 1" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Cancelled Triaged 1 Resource" + } + }, + { + "id": "stratifier-encounters-cancelled-triaged-2", + "code" : { + "text": "Stratifier Encounters Cancelled Triaged 2" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Cancelled Triaged 2 Resource" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario5.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario5.json new file mode 100644 index 000000000..ae230f2cf --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario5.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario5", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario5", + "url": "http://example.com/Measure/ComponentCriteriaStratifierEncounterBasisNoIntersectionScenario5", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "Encounter" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Resource" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-cancelled-in-progress-finished-triaged", + "code" : { + "text": "Stratifier Encounters Cancelled In-Progress Finished Triaged" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounters-cancelled-in-progress", + "code" : { + "text": "Stratifier Encounters Cancelled In-Progress" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Cancelled In-Progress Resource" + } + }, + { + "id": "stratifier-encounters-finished-triaged", + "code" : { + "text": "Stratifier Encounters Finished Triaged" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Finished Triaged Resource" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisWithIntersectionScenario1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisWithIntersectionScenario1.json new file mode 100644 index 000000000..8f71d8b95 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/resources/measure/ComponentCriteriaStratifierEncounterBasisWithIntersectionScenario1.json @@ -0,0 +1,82 @@ +{ + "id": "ComponentCriteriaStratifierEncounterBasisWithIntersectionScenario1", + "resourceType": "Measure", + "name": "ComponentCriteriaStratifierEncounterBasisWithIntersectionScenario1", + "url": "http://example.com/Measure/ComponentCriteriaStratifierEncounterBasisWithIntersectionScenario1", + "library": [ + "http://example.com/Library/ComponentCriteriaStratifier" + ], + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "Encounter" + } + ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "id": "group-1", + "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": "Initial Population Resource" + } + } + ], + "stratifier": [ + { + "id": "stratifier-encounters-arrived-triaged-arrived-in-progress-resource", + "code" : { + "text": "Stratifier Encounters Arrived Triaged Arrived In-Progress Resource" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-population-stratifier-type", + "valueCode": "criteria" + } + ], + "component": [ + { + "id": "stratifier-encounters-arrived-triaged", + "code" : { + "text": "Encounters Arrived Triaged" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Arrived Triaged Resource" + } + }, + { + "id": "stratifier-encounter-in-progress", + "code" : { + "text": "Encounters Arrived In-Progress" + }, + "criteria": { + "language": "text/cql.identifier", + "expression": "Stratifier Encounters Arrived In-Progress Resource" + } + } + ] + } + ] + } + ] +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_arrived_pat1_1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_arrived_pat1_1.json new file mode 100644 index 000000000..6e4892d86 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_arrived_pat1_1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_arrived_pat1_1", + "status": "arrived", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_arrived_pat1_2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_arrived_pat1_2.json new file mode 100644 index 000000000..2cf05b37b --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_arrived_pat1_2.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_arrived_pat1_2", + "status": "arrived", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_finished_pat1_1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_finished_pat1_1.json new file mode 100644 index 000000000..533cb9c4c --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_finished_pat1_1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_finished_pat1_1", + "status": "finished", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_finished_pat2_1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_finished_pat2_1.json new file mode 100644 index 000000000..4b041f661 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_finished_pat2_1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_finished_pat2_1", + "status": "finished", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_in_progress_pat1_1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_in_progress_pat1_1.json new file mode 100644 index 000000000..a8ed56744 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_in_progress_pat1_1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_in_progress_pat1_1", + "status": "in-progress", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_in_progress_pat2_1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_in_progress_pat2_1.json new file mode 100644 index 000000000..31ec9ccea --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_in_progress_pat2_1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_in_progress_pat2_1", + "status": "in-progress", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_in_progress_pat2_2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_in_progress_pat2_2.json new file mode 100644 index 000000000..98a28e953 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_in_progress_pat2_2.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_in_progress_pat2_2", + "status": "in-progress", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_planned_pat1_1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_planned_pat1_1.json new file mode 100644 index 000000000..d44d2bdef --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_planned_pat1_1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_planned_pat1_1", + "status": "planned", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_planned_pat2_1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_planned_pat2_1.json new file mode 100644 index 000000000..a8861480a --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_planned_pat2_1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_planned_pat2_1", + "status": "planned", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_triaged_pat1_1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_triaged_pat1_1.json new file mode 100644 index 000000000..cdc0fdf40 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_triaged_pat1_1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_triaged_pat1_1", + "status": "triaged", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_triaged_pat2_1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_triaged_pat2_1.json new file mode 100644 index 000000000..138ed1270 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/encounter/enc_triaged_pat2_1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_triaged_pat2_1", + "status": "triaged", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2024-01-01T00:00:00-05:00", + "end": "2024-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/patient/patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/patient/patient1.json new file mode 100644 index 000000000..179f54afb --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/patient/patient1.json @@ -0,0 +1,6 @@ +{ + "resourceType": "Patient", + "id": "patient1", + "gender": "female", + "birthDate": "1904-01-01" +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/patient/patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/patient/patient2.json new file mode 100644 index 000000000..1ab526f85 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/ComponentCriteriaStratifier/input/tests/patient/patient2.json @@ -0,0 +1,6 @@ +{ + "resourceType": "Patient", + "id": "patient2", + "gender": "male", + "birthDate": "1924-06-01" +} \ No newline at end of file