Skip to content

Commit 1d72fcd

Browse files
authored
Merge pull request #448 from CMSgov/feature/QPPCT-328_CPC_ecqm_breakdown
QPPCT-328: cpc ecqm breakdown
2 parents 7c70e1a + 311d247 commit 1d72fcd

File tree

12 files changed

+89093
-703
lines changed

12 files changed

+89093
-703
lines changed

ERROR_MESSAGES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,5 @@ Any text in the following format `(Example)` are considered variables to be fill
7272
* 61 : A Performance Rate must contain a single Performance Rate UUID
7373
* 62 : The Alternative Payment Model (APM) Entity Identifier must not be empty
7474
* 63 : The Alternative Payment Model (APM) Entity Identifier is not valid
75-
75+
* 64 : CPC+ Submissions must have at least `(CPC+ measure group minimum)` of the following `(CPC+ measure group label)` measures: `(Listing of valid measure ids)`
76+
* 65 : CPC+ Submissions must have at least `(Overall CPC+ measure minimum)` of the following measures: `(Listing of all CPC+ measure ids)`.

converter/src/main/java/gov/cms/qpp/conversion/model/error/ErrorCode.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,12 @@ public enum ErrorCode implements LocalizedError {
9797
QUALITY_MEASURE_ID_INCORRECT_PERFORMANCE_UUID(60, "The eCQM (electronic measure id: %s) has a %s with an "
9898
+ "incorrect UUID of %s", true),
9999
QUALITY_MEASURE_ID_MISSING_SINGLE_PERFORMANCE_RATE(61, "A Performance Rate must contain a single "
100-
+ "Performance Rate UUID");
100+
+ "Performance Rate UUID"),
101+
CPC_PLUS_TOO_FEW_QUALITY_MEASURE_CATEGORY(64,
102+
"CPC+ Submissions must have at least %d of the following %s measures: %s.", true),
103+
CPC_PLUS_TOO_FEW_QUALITY_MEASURES(65,
104+
"CPC+ Submissions must have at least %d of the following measures: %s.", true);
105+
101106

102107
private static final Map<Integer, ErrorCode> CODE_TO_VALUE = Arrays.stream(values())
103108
.collect(Collectors.toMap(ErrorCode::getCode, Function.identity()));
@@ -106,7 +111,7 @@ public enum ErrorCode implements LocalizedError {
106111
private final String message;
107112
private final boolean hasFormat;
108113

109-
ErrorCode(int code,String message) {
114+
ErrorCode(int code, String message) {
110115
this(code, message, false);
111116
}
112117

converter/src/main/java/gov/cms/qpp/conversion/model/validation/MeasureConfig.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class MeasureConfig {
3131

3232
@JsonProperty("eMeasureUuid")
3333
private String electronicMeasureVerUuid;
34+
private String cpcPlusGroup;
3435

3536
private List<Strata> strata;
3637

@@ -138,6 +139,14 @@ public void setElectronicMeasureId(String electronicMeasureId) {
138139
this.electronicMeasureId = electronicMeasureId;
139140
}
140141

142+
public String getCpcPlusGroup() {
143+
return cpcPlusGroup;
144+
}
145+
146+
public void setCpcPlusGroup(String cpcPlusGroup) {
147+
this.cpcPlusGroup = cpcPlusGroup;
148+
}
149+
141150
public String getElectronicMeasureVerUuid() {
142151
return electronicMeasureVerUuid;
143152
}

converter/src/main/java/gov/cms/qpp/conversion/model/validation/MeasureConfigs.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import java.io.IOException;
1010
import java.io.InputStream;
11+
import java.util.ArrayList;
12+
import java.util.HashMap;
1113
import java.util.List;
1214
import java.util.Map;
1315
import java.util.function.Function;
@@ -19,6 +21,7 @@ public class MeasureConfigs {
1921

2022
private static String measureDataFileName = DEFAULT_MEASURE_DATA_FILE_NAME;
2123
private static Map<String, MeasureConfig> configurationMap;
24+
private static Map<String, List<MeasureConfig>> cpcPlusGroups;
2225

2326
/**
2427
* Static initialization
@@ -39,6 +42,11 @@ private MeasureConfigs() {
3942
*/
4043
private static void initMeasureConfigs() {
4144
configurationMap = grabConfiguration(measureDataFileName);
45+
cpcPlusGroups = new HashMap<>();
46+
getMeasureConfigs().stream()
47+
.filter(config -> config.getCpcPlusGroup() != null)
48+
.forEach(config -> cpcPlusGroups.computeIfAbsent(
49+
config.getCpcPlusGroup(), key -> new ArrayList<>()).add(config));
4250
}
4351

4452
public static Map<String, MeasureConfig> grabConfiguration(String fileName) {
@@ -94,13 +102,22 @@ public static Map<String, MeasureConfig> getConfigurationMap() {
94102
return configurationMap;
95103
}
96104

105+
/**
106+
* Retrieves a mapping of CPC+ measure groups
107+
*
108+
* @return mapped CPC+ measure groups
109+
*/
110+
public static Map<String, List<MeasureConfig>> getCpcPlusGroups() {
111+
return cpcPlusGroups;
112+
}
113+
97114
/**
98115
* Retrieves a list of required mappings for any given section
99116
*
100117
* @param section Specified section for measures required
101118
* @return The list of required measures
102119
*/
103-
public static List<String> requiredMeasuresForSection(String section) {
120+
static List<String> requiredMeasuresForSection(String section) {
104121

105122
return configurationMap.values().stream()
106123
.filter(measureConfig -> measureConfig.isRequired() && section.equals(measureConfig.getCategory()))

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

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package gov.cms.qpp.conversion.validate;
22

3+
import com.google.common.base.Strings;
4+
import gov.cms.qpp.conversion.model.Node;
5+
import gov.cms.qpp.conversion.model.TemplateId;
6+
import gov.cms.qpp.conversion.model.error.Detail;
7+
import gov.cms.qpp.conversion.model.error.LocalizedError;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
311
import java.util.Arrays;
412
import java.util.EnumMap;
513
import java.util.EnumSet;
@@ -10,16 +18,6 @@
1018
import java.util.Set;
1119
import java.util.concurrent.atomic.AtomicInteger;
1220

13-
import org.slf4j.Logger;
14-
import org.slf4j.LoggerFactory;
15-
16-
import com.google.common.base.Strings;
17-
18-
import gov.cms.qpp.conversion.model.Node;
19-
import gov.cms.qpp.conversion.model.TemplateId;
20-
import gov.cms.qpp.conversion.model.error.Detail;
21-
import gov.cms.qpp.conversion.model.error.LocalizedError;
22-
2321
/**
2422
* Node checker DSL to help abbreviate / simplify single node validations
2523
*/
@@ -77,7 +75,7 @@ private boolean shouldShortcut() {
7775
/**
7876
* checks target node for the existence of a value with the given name key
7977
*
80-
* @param message error message if searched value is not found
78+
* @param code that identifies the error
8179
* @param name key of expected value
8280
* @return The checker, for chaining method calls.
8381
*/
@@ -92,11 +90,11 @@ public Checker value(LocalizedError code, String name) {
9290
/**
9391
* checks target node to ensure a value is retrieved with given name key
9492
*
95-
* @param message error message if value is empty
93+
* @param code that identifies the error
9694
* @param name key of expected value
9795
* @return The checker, for chaining method calls.
9896
*/
99-
public Checker valueIsNotEmpty(LocalizedError code, String name) {
97+
Checker valueIsNotEmpty(LocalizedError code, String name) {
10098
lastAppraised = node.getValue(name);
10199
if (!shouldShortcut() && Strings.isNullOrEmpty((String) lastAppraised)) {
102100
details.add(detail(code));
@@ -107,7 +105,7 @@ public Checker valueIsNotEmpty(LocalizedError code, String name) {
107105
/**
108106
* checks target node for the existence of a single value with the given name key
109107
*
110-
* @param message error message if searched value is not found
108+
* @param code that identifies the error
111109
* @param name key of expected value
112110
* @return The checker, for chaining method calls.
113111
*/
@@ -124,25 +122,25 @@ public Checker singleValue(LocalizedError code, String name) {
124122
* checks target node for the existence of a value with the given name key
125123
* and matches that value with one of the supplied values.
126124
*
127-
* @param message error message if searched value is not found
125+
* @param code that identifies the error
128126
* @param name key of expected value
129127
* @param expected the expected value
130128
* @return The checker, for chaining method calls.
131129
*/
132-
public Checker valueIs(LocalizedError code, String name, String expected) {
130+
Checker valueIs(LocalizedError code, String name, String expected) {
133131
return valueIn(code, name, expected);
134132
}
135133

136134
/**
137135
* checks target node for the existence of a value with the given name key
138136
* and matches that value with one of the supplied values.
139137
*
140-
* @param message error message if searched value is not found
138+
* @param code that identifies the error
141139
* @param name key of expected value
142140
* @param values List of strings to check for the existence of.
143141
* @return The checker, for chaining method calls.
144142
*/
145-
public Checker valueIn(LocalizedError code, String name, String... values) {
143+
Checker valueIn(LocalizedError code, String name, String... values) {
146144
boolean contains = false;
147145
if (name == null) {
148146
details.add(detail(code));
@@ -168,11 +166,11 @@ public Checker valueIn(LocalizedError code, String name, String... values) {
168166
/**
169167
* Checks target node for the existence of an integer value with the given name key.
170168
*
171-
* @param message error message if searched value is not found or is not appropriately typed
169+
* @param code that identifies the error
172170
* @param name key of expected value
173171
* @return The checker, for chaining method calls.
174172
*/
175-
public Checker intValue(LocalizedError code, String name) {
173+
Checker intValue(LocalizedError code, String name) {
176174
if (!shouldShortcut()) {
177175
try {
178176
lastAppraised = Integer.parseInt(node.getValue(name));
@@ -187,12 +185,12 @@ public Checker intValue(LocalizedError code, String name) {
187185
/**
188186
* Allow for compound comparisons of Node values.
189187
*
190-
* @param message error message should comparison fail
188+
* @param code that identifies the error
191189
* @param value to be compared against
192190
* @return The checker, for chaining method calls.
193191
*/
194192
@SuppressWarnings("unchecked")
195-
public Checker greaterThan(LocalizedError code, Comparable<?> value) {
193+
Checker greaterThan(LocalizedError code, Comparable<?> value) {
196194
if (!shouldShortcut() && lastAppraised != null && ((Comparable<Object>) lastAppraised).compareTo(value) <= 0) {
197195
details.add(detail(code));
198196
}
@@ -203,12 +201,12 @@ public Checker greaterThan(LocalizedError code, Comparable<?> value) {
203201
/**
204202
* Allow for compound comparisons of Node values.
205203
*
206-
* @param message error message should comparison fail
204+
* @param code that identifies the error
207205
* @param value to be compared against
208206
* @return The checker, for chaining method calls.
209207
*/
210208
@SuppressWarnings("unchecked")
211-
public Checker lessThanOrEqualTo(LocalizedError code, Comparable<?> value) {
209+
Checker lessThanOrEqualTo(LocalizedError code, Comparable<?> value) {
212210
if (!shouldShortcut() && lastAppraised != null && ((Comparable<Object>) lastAppraised).compareTo(value) > 0) {
213211
details.add(detail(code));
214212
}
@@ -219,14 +217,14 @@ public Checker lessThanOrEqualTo(LocalizedError code, Comparable<?> value) {
219217
/**
220218
* Checks target node value to be between a specific range
221219
*
222-
* @param message error message should comparison fail
220+
* @param code that identifies the error
223221
* @param name key of expected value
224222
* @param startValue starting value for range
225223
* @param endValue ending value for range
226224
* @return The checker, for chaining method calls
227225
*/
228226
@SuppressWarnings("unchecked")
229-
public Checker inDecimalRangeOf(LocalizedError code, String name, float startValue, float endValue) {
227+
Checker inDecimalRangeOf(LocalizedError code, String name, float startValue, float endValue) {
230228
if (!shouldShortcut()) {
231229
try {
232230
lastAppraised = Float.parseFloat(node.getValue(name));
@@ -245,10 +243,10 @@ public Checker inDecimalRangeOf(LocalizedError code, String name, float startVal
245243
/**
246244
* Checks target node for the existence of a specified parent.
247245
*
248-
* @param message validation error message
246+
* @param code that identifies the error
249247
* @return The checker, for chaining method calls.
250248
*/
251-
public Checker hasParent(LocalizedError code, TemplateId type) {
249+
Checker hasParent(LocalizedError code, TemplateId type) {
252250
if (!shouldShortcut()) {
253251
TemplateId parentType = Optional.ofNullable(node.getParent())
254252
.orElse(new Node()).getType();
@@ -262,10 +260,10 @@ public Checker hasParent(LocalizedError code, TemplateId type) {
262260
/**
263261
* Checks target node for the existence of any child nodes.
264262
*
265-
* @param message validation error message
263+
* @param code that identifies the error
266264
* @return The checker, for chaining method calls.
267265
*/
268-
public Checker hasChildren(LocalizedError code) {
266+
Checker hasChildren(LocalizedError code) {
269267
if (!shouldShortcut() && node.getChildNodes().isEmpty()) {
270268
details.add(detail(code));
271269
}
@@ -275,7 +273,7 @@ public Checker hasChildren(LocalizedError code) {
275273
/**
276274
* Verifies that the target node has at least the given minimum or more of the given {@link TemplateId}s.
277275
*
278-
* @param message validation error message
276+
* @param code that identifies the error
279277
* @param minimum minimum required children of specified types
280278
* @param types types of children to filter by
281279
* @return The checker, for chaining method calls.
@@ -293,7 +291,7 @@ public Checker childMinimum(LocalizedError code, int minimum, TemplateId... type
293291
/**
294292
* Verifies that the target node has less than the given maximum of the given {@link TemplateId}s.
295293
*
296-
* @param message validation error message
294+
* @param code that identifies the error
297295
* @param maximum maximum required children of specified types
298296
* @param types types of children to filter by
299297
* @return The checker, for chaining method calls.
@@ -311,13 +309,16 @@ public Checker childMaximum(LocalizedError code, int maximum, TemplateId... type
311309
/**
312310
* Verifies that the measures specified are contained within the current node's children
313311
*
314-
* @param message validation error message
312+
* @param code that identifies the error
315313
* @param measureIds measures specified for given node
316314
* @return The checker, for chaining method calls
317315
*/
318-
public Checker hasMeasures(LocalizedError code, String... measureIds) {
316+
Checker hasMeasures(LocalizedError code, String... measureIds) {
317+
return hasMeasures(code, measureIds.length, measureIds);
318+
}
319+
320+
Checker hasMeasures(LocalizedError code, int numberOfMeasuresRequired, String... measureIds) {
319321
if (!shouldShortcut()) {
320-
int numberOfMeasuresRequired = Arrays.asList(measureIds).size();
321322

322323
long numNodesWithWantedMeasureIds = node.getChildNodes(currentNode -> {
323324
String measureIdOfNode = currentNode.getValue("measureId");
@@ -332,7 +333,7 @@ public Checker hasMeasures(LocalizedError code, String... measureIds) {
332333
return false;
333334
}).count();
334335

335-
if (numberOfMeasuresRequired != numNodesWithWantedMeasureIds) {
336+
if (numNodesWithWantedMeasureIds < numberOfMeasuresRequired) {
336337
details.add(detail(code));
337338
}
338339
}
@@ -342,11 +343,11 @@ public Checker hasMeasures(LocalizedError code, String... measureIds) {
342343
/**
343344
* Verifies that the target node contains only children of specified template ids
344345
*
345-
* @param message validation error message
346+
* @param code that identifies the error
346347
* @param types types of template ids to filter
347348
* @return The checker, for chaining method calls.
348349
*/
349-
public Checker onlyHasChildren(LocalizedError code, TemplateId... types) {
350+
Checker onlyHasChildren(LocalizedError code, TemplateId... types) {
350351
if (!shouldShortcut()) {
351352
Set<TemplateId> templateIds = EnumSet.noneOf(TemplateId.class);
352353
for (TemplateId templateId : types) {
@@ -368,7 +369,7 @@ public Checker onlyHasChildren(LocalizedError code, TemplateId... types) {
368369
*
369370
* @return The checker, for chaining method calls.
370371
*/
371-
public Checker incompleteValidation() {
372+
Checker incompleteValidation() {
372373
node.setValidated(false);
373374
return this;
374375
}

0 commit comments

Comments
 (0)