Skip to content

Commit c85cd9d

Browse files
authored
Merge pull request #1036 from CMSgov/feature/QPPSF-7324_cpc-validation-updates
QPPSF-7324: Cpc Validation Updates
2 parents 3faf9b0 + d8e58a9 commit c85cd9d

File tree

86 files changed

+5937
-82
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+5937
-82
lines changed

ERROR_MESSAGES.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Any text in the following format `(Example)` are considered variables to be fill
1414
* 9 : CT - Aggregate count value must be an integer
1515
* 11 : CT - This PI Reference and Results is missing a required Measure Performed child
1616
* 12 : CT - This PI Measure Performed Reference and Results requires a single Measure ID
17-
* 13 : CT - Denominator count must be less than or equal to Initial Population count for a measure that is a proportion measure
17+
* 13 : CT - Denominator count must be equal to Initial Population count for a measure that is a proportion measure
1818
* 14 : CT - The electronic measure id: `(Current eMeasure ID)` requires `(Number of Subpopulations required)` `(Type of Subpopulation required)`(s) but there are `(Number of Subpopulations existing)`
1919
* 15 : CT - PI Numerator Denominator element should have a PI Section element as a parent
2020
* 16 : CT - PI Numerator Denominator element does not contain a measure name ID
@@ -82,3 +82,7 @@ Any text in the following format `(Example)` are considered variables to be fill
8282
* 94 : CT - The denominator exclusion id `(denexUuid)` has a count value that is greater than the denominator. The Denominator exclusion cannot be a greater value than the denominator.
8383
* 95 : CT - The Clinical Document must contain one Measure Section v4 with the extension 2017-06-01
8484
* 96 : CT - The APM to TIN/NPI Combination file is missing.
85+
* 97 : CT - CPC+ QRDA-III Submissions require a valid Cehrt ID (Valid Format: XX15EXXXXXXXXXX)
86+
* 98 : CT - The performance rate cannot have a value of 0 and must be of value Null Attribute (NA).
87+
* 99 : CT - The measure id `(Measure Id)` has a duplicate & invalid supplemental data of type `(Supplemental Type)`
88+
* 100 : CT - More than one Cehrt ID was found. Please submit with only one Cehrt id.

commons/src/main/java/gov/cms/qpp/conversion/model/error/ProblemCode.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public enum ProblemCode implements LocalizedProblem {
3939
+ "Measure Performed child"),
4040
PI_MEASURE_PERFORMED_RNR_MEASURE_ID_NOT_SINGULAR(12, "This PI Measure Performed Reference and Results requires "
4141
+ "a single Measure ID"),
42-
DENOMINATOR_COUNT_INVALID(13, "Denominator count must be less than or equal to Initial Population count "
42+
DENOMINATOR_COUNT_INVALID(13, "Denominator count must be equal to Initial Population count "
4343
+ "for a measure that is a proportion measure"),
4444
POPULATION_CRITERIA_COUNT_INCORRECT(14,
4545
"The electronic measure id: `(Current eMeasure ID)` requires `(Number of Subpopulations required)` "
@@ -182,7 +182,12 @@ public enum ProblemCode implements LocalizedProblem {
182182
CPC_PLUS_DENEX_GREATER_THAN_DENOMINATOR(94, "The denominator exclusion id `(denexUuid)` has a count value that is greater than the "
183183
+ "denominator. The Denominator exclusion cannot be a greater value than the denominator.", true),
184184
MEASURE_SECTION_V4_REQUIRED(95, "The Clinical Document must contain one Measure Section v4 with the extension 2017-06-01"),
185-
MISSING_API_TIN_NPI_FILE(96, "The APM to TIN/NPI Combination file is missing.");
185+
MISSING_API_TIN_NPI_FILE(96, "The APM to TIN/NPI Combination file is missing."),
186+
CPC_MISSING_CEHRT_ID(97, "CPC+ QRDA-III Submissions require a valid Cehrt ID (Valid Format: XX15EXXXXXXXXXX)"),
187+
CPC_PLUS_ZERO_PERFORMANCE_RATE(98, "The performance rate cannot have a value of 0 and must be of value Null Attribute (NA)."),
188+
CPC_PLUS_EXTRA_SUPPLEMENTAL_DATA(99, "The measure id `(Measure Id)` has a duplicate & invalid supplemental data of type `(Supplemental Type)`", true),
189+
CPC_PLUS_DUPLICATE_CEHRT(100, "More than one Cehrt ID was found. Please submit with only one Cehrt id.");
190+
186191

187192
private static final Map<Integer, ProblemCode> CODE_TO_VALUE = Arrays.stream(values())
188193
.collect(Collectors.toMap(ProblemCode::getCode, Function.identity()));

converter/src/main/java/gov/cms/qpp/conversion/validate/CpcClinicalDocumentValidator.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.time.ZonedDateTime;
1919
import java.time.format.DateTimeFormatter;
2020
import java.util.Arrays;
21+
import java.util.List;
2122

2223
import org.apache.commons.lang3.StringUtils;
2324

@@ -97,6 +98,7 @@ protected void performValidation(Node node) {
9798
validateNumberOfTinsAndNpis(node);
9899
validateApmNpiCombination(node);
99100
}
101+
validateCehrtId(node);
100102
}
101103

102104
/**
@@ -150,6 +152,21 @@ private void validateApmNpiCombination(Node node) {
150152
context.getPiiValidator().validateApmTinNpiCombination(node, this);
151153
}
152154

155+
private void validateCehrtId(Node node) {
156+
String cehrtId = node.getValue(ClinicalDocumentDecoder.CEHRT);
157+
if(cehrtId == null || cehrtId.length() != 15 || !cehrtFormat(cehrtId.substring(2, 5))) {
158+
addError(Detail.forProblemAndNode(ProblemCode.CPC_MISSING_CEHRT_ID, node));
159+
}
160+
List<String> duplicateCehrts = node.getDuplicateValues(ClinicalDocumentDecoder.CEHRT);
161+
if (duplicateCehrts != null && duplicateCehrts.size() > 0) {
162+
addError(Detail.forProblemAndNode(ProblemCode.CPC_PLUS_DUPLICATE_CEHRT, node));
163+
}
164+
}
165+
166+
private boolean cehrtFormat(String requiredSubstring) {
167+
return requiredSubstring.equalsIgnoreCase("15E");
168+
}
169+
153170
/**
154171
* Validates the submission is not after the set end date
155172
*

converter/src/main/java/gov/cms/qpp/conversion/validate/CpcMeasureDataValidator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import gov.cms.qpp.conversion.model.TemplateId;
77
import gov.cms.qpp.conversion.model.Validator;
88
import gov.cms.qpp.conversion.model.error.Detail;
9-
import gov.cms.qpp.conversion.model.error.ProblemCode;
109
import gov.cms.qpp.conversion.model.error.LocalizedProblem;
10+
import gov.cms.qpp.conversion.model.error.ProblemCode;
1111
import gov.cms.qpp.conversion.model.validation.MeasureConfig;
1212
import gov.cms.qpp.conversion.model.validation.SupplementalData;
1313
import gov.cms.qpp.conversion.model.validation.SupplementalData.SupplementalType;
@@ -67,6 +67,11 @@ private void validateAllSupplementalNodesOfSpecifiedType(
6767
MeasureConfig measureConfig = MeasureConfigHelper.getMeasureConfig(node.getParent());
6868
if (measureConfig != null) {
6969
String electronicMeasureId = measureConfig.getElectronicMeasureId();
70+
if (supplementalDataNodes.size() > codes.size()) {
71+
LocalizedProblem error =
72+
ProblemCode.CPC_PLUS_EXTRA_SUPPLEMENTAL_DATA.format(electronicMeasureId, supplementalDataType);
73+
addError(Detail.forProblemAndNode(error, node));
74+
}
7075
for (SupplementalData supplementalData : codes) {
7176
Node validatedSupplementalNode = filterCorrectNode(supplementalDataNodes, supplementalData);
7277

converter/src/main/java/gov/cms/qpp/conversion/validate/CpcQualityMeasureIdValidator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ protected void performValidation(Node node) {
100100
}
101101
//skip if performance rate is missing
102102
if (null != performanceRateNode) {
103+
String performanceRateValue =
104+
performanceRateNode.getValue(PerformanceRateProportionMeasureDecoder.PERFORMANCE_RATE);
103105
if (PerformanceRateValidator.NULL_ATTRIBUTE.equals(
104106
performanceRateNode.getValue(PerformanceRateProportionMeasureDecoder.NULL_PERFORMANCE_RATE))) {
105107
if (performanceDenominator != 0) {
@@ -108,6 +110,9 @@ protected void performValidation(Node node) {
108110
.format(performanceRateNode
109111
.getValue(PerformanceRateProportionMeasureDecoder.PERFORMANCE_RATE_ID)), node));
110112
}
113+
} else if (performanceRateValue != null && NumberHelper.isNumeric(performanceRateValue)
114+
&& Double.valueOf(performanceRateValue) == 0 && (denominatorValue == 0 || performanceDenominator == 0)) {
115+
addError(Detail.forProblemAndNode(ProblemCode.CPC_PLUS_ZERO_PERFORMANCE_RATE, node));
111116
}
112117
}
113118

converter/src/main/java/gov/cms/qpp/conversion/validate/QualityMeasureIdValidator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,8 @@ private void validateDenominatorCount(Node denomCount, Node ipopCount) {
225225
.incompleteValidation()
226226
.intValue(ProblemCode.AGGREGATE_COUNT_VALUE_NOT_INTEGER,
227227
AggregateCountDecoder.AGGREGATE_COUNT)
228-
.lessThanOrEqualTo(ProblemCode.DENOMINATOR_COUNT_INVALID,
229-
Integer.parseInt(ipopCount.getValue(AggregateCountDecoder.AGGREGATE_COUNT)));
228+
.valueIn(ProblemCode.DENOMINATOR_COUNT_INVALID, AggregateCountDecoder.AGGREGATE_COUNT,
229+
ipopCount.getValue(AggregateCountDecoder.AGGREGATE_COUNT));
230230
}
231231

232232
/**

converter/src/test/java/gov/cms/qpp/acceptance/QualityMeasureIdRoundTripTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class QualityMeasureIdRoundTripTest {
4444
Paths.get("src/test/resources/negative/wrongSubPopulationsMeasure135.xml");
4545
static final Path MISSING_COUNT_FOR_PERF_DENOM =
4646
Paths.get("src/test/resources/negative/perfDenomAggCountMissing.xml");
47+
static final Path ZERO_COUNT_FOR_PERF_DENOM =
48+
Paths.get("src/test/resources/negative/perfRateDenomZero.xml");
4749
static final Path MIPS_APM_FILE = Paths.get("src/test/resources/CpcMipsApm-2020.xml");
4850
ApmEntityIds apmEntityIds;
4951

@@ -209,6 +211,23 @@ void testMissingPerfDenomAggregateCount() {
209211
.contains(error);
210212
}
211213

214+
@Test
215+
void testZeroPerfRateDenom() {
216+
Converter converter = new Converter(new PathSource(ZERO_COUNT_FOR_PERF_DENOM), new Context(apmEntityIds));
217+
List<Detail> details = new ArrayList<>();
218+
219+
try {
220+
converter.transform();
221+
} catch (TransformException exception) {
222+
AllErrors errors = exception.getDetails();
223+
details.addAll(errors.getErrors().get(0).getDetails());
224+
}
225+
226+
LocalizedProblem error = ProblemCode.CPC_PLUS_ZERO_PERFORMANCE_RATE;
227+
assertThat(details).comparingElementsUsing(DetailsErrorEquals.INSTANCE)
228+
.contains(error);
229+
}
230+
212231
@Test
213232
void test2020TopLevelMipsApmSampleFile() {
214233
MeasureConfigs.initMeasureConfigs(MeasureConfigs.DEFAULT_MEASURE_DATA_FILE_NAME);

converter/src/test/java/gov/cms/qpp/conversion/validate/CpcClinicalDocumentValidatorTest.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,17 @@ void testCpcPlusMissingNpi() {
178178
.containsExactly(ProblemCode.CPC_PLUS_NPI_REQUIRED);
179179
}
180180

181+
@Test
182+
void testCpcPlusMissingCehrt() {
183+
Node clinicalDocumentNode = createValidCpcPlusClinicalDocument();
184+
clinicalDocumentNode.removeValue(ClinicalDocumentDecoder.CEHRT);
185+
List<Detail> errors = cpcValidator.validateSingleNode(clinicalDocumentNode).getErrors();
186+
187+
assertWithMessage("Must validate with the correct error")
188+
.that(errors).comparingElementsUsing(DetailsErrorEquals.INSTANCE)
189+
.containsExactly(ProblemCode.CPC_MISSING_CEHRT_ID);
190+
}
191+
181192
@Test
182193
void testWarnWhenContainsIa() {
183194
Node clinicalDocumentNode = createCpcPlusClinicalDocument();
@@ -202,6 +213,17 @@ void testWarnWhenContainsPi() {
202213
.contains(ProblemCode.CPC_PLUS_NO_IA_OR_PI);
203214
}
204215

216+
@Test
217+
void testCpcPlusDuplicateCehrt() {
218+
Node clinicalDocumentNode = createValidCpcPlusClinicalDocument();
219+
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.CEHRT, "XX15EXXXXXXXXXX", false);
220+
List<Detail> errors = cpcValidator.validateSingleNode(clinicalDocumentNode).getErrors();
221+
222+
assertWithMessage("Must validate with the correct error")
223+
.that(errors).comparingElementsUsing(DetailsErrorEquals.INSTANCE)
224+
.containsExactly(ProblemCode.CPC_PLUS_DUPLICATE_CEHRT);
225+
}
226+
205227
private Node createValidCpcPlusClinicalDocument() {
206228
Node clinicalDocumentNode = createCpcPlusClinicalDocument();
207229
addMeasureSectionNode(clinicalDocumentNode);
@@ -216,7 +238,7 @@ private Node createCpcPlusClinicalDocument() {
216238
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.PRACTICE_ID, "DogCow");
217239
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.TAX_PAYER_IDENTIFICATION_NUMBER, "123456789");
218240
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.NATIONAL_PROVIDER_IDENTIFIER, "9900000099");
219-
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.CEHRT, "1234567890x");
241+
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.CEHRT, "XX15EXXXXXXXXXX");
220242

221243
return clinicalDocumentNode;
222244
}

converter/src/test/java/gov/cms/qpp/conversion/validate/CpcMeasureDataValidatorTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ void validateFailureSupplementalDataMissingCountTest() throws Exception {
6666
.contains(expectedError);
6767
}
6868

69+
@Test
70+
void validateFailureExtraSupplementalDataTest() throws Exception {
71+
String failurePayerFile = TestHelper.getFixture("failureExtraSupplementalDataFile.xml");
72+
Node placeholder = new QrdaDecoderEngine(new Context()).decode(XmlUtils.stringToDom(failurePayerFile));
73+
CpcMeasureDataValidator validator = new CpcMeasureDataValidator();
74+
Node underTest = placeholder.findFirstNode(TemplateId.MEASURE_DATA_CMS_V4);
75+
List<Detail> errors = validator.validateSingleNode(underTest).getErrors();
76+
77+
LocalizedProblem expectedError = ProblemCode.CPC_PLUS_EXTRA_SUPPLEMENTAL_DATA.format(MEASURE_ID,
78+
SupplementalData.SupplementalType.SEX);
79+
80+
assertThat(errors).comparingElementsUsing(DetailsErrorEquals.INSTANCE)
81+
.contains(expectedError);
82+
}
83+
6984
@DisplayName("Should fail on absent supplemental race data")
7085
@ParameterizedTest(name = "{index} => Supplemental data=''{0}''")
7186
@EnumSource(value = SupplementalData.class, mode = EnumSource.Mode.INCLUDE,

converter/src/test/java/gov/cms/qpp/conversion/validate/CpcQualityMeasureIdValidatorTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.junit.jupiter.api.Test;
1010

1111
import gov.cms.qpp.conversion.decode.AggregateCountDecoder;
12+
import gov.cms.qpp.conversion.decode.PerformanceRateProportionMeasureDecoder;
1213
import gov.cms.qpp.conversion.model.Node;
1314
import gov.cms.qpp.conversion.model.TemplateId;
1415
import gov.cms.qpp.conversion.model.error.Detail;
@@ -28,7 +29,7 @@ void setUp() {
2829
validator = new CpcQualityMeasureIdValidator();
2930

3031
testNode = new Node(TemplateId.MEASURE_REFERENCE_RESULTS_CMS_V4);
31-
testNode.putValue(MeasureConfigHelper.MEASURE_ID,MEASURE_ID);
32+
testNode.putValue(MeasureConfigHelper.MEASURE_ID, MEASURE_ID);
3233
}
3334

3435
@Test

0 commit comments

Comments
 (0)