Skip to content

Commit

Permalink
Merge pull request #1372 from CMSgov/feature/QPPSE-910_MVP-Subgroup-S…
Browse files Browse the repository at this point in the history
…ubmissions

QPPSE-910: MVP And SubGroup Submissions
  • Loading branch information
saquino0827 authored Oct 12, 2023
2 parents dcfd563 + 0ed295d commit 9a3495f
Show file tree
Hide file tree
Showing 40 changed files with 15,519 additions and 218 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* throughout the conversion.
*/
public class Context {
public static final String REPORTING_YEAR = "2022";
public static final String REPORTING_YEAR = "2023";
private final Map<Class<? extends Annotation>, Registry<?>> registries = new IdentityHashMap<>();
private Program program = Program.ALL;
private boolean historical;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.Consumer;

/**
Expand All @@ -36,6 +37,8 @@ public class ClinicalDocumentDecoder extends QrdaDecoder {
public static final String APM_ENTITY_ID = "apmEntityId";
public static final String VG_ID = "virtualGroupId";
public static final String CEHRT = "cehrtId";
public static final String MVP_ID = "mvpId";
public static final String SUBGROUP_ID = "subgroupId";

//QPP Json value constants for: Node(Identifier, value)
public static final String MIPS_PROGRAM_NAME = "mips";
Expand All @@ -44,18 +47,21 @@ public class ClinicalDocumentDecoder extends QrdaDecoder {
public static final String ENTITY_APM = "apm";
static final String ENTITY_GROUP = "group";
static final String ENTITY_INDIVIDUAL = "individual";
public static final String ENTITY_SUBGROUP = "subgroup";
public static final String ENTITY_VIRTUAL_GROUP = "virtualGroup";
public static final String APP_PROGRAM_NAME = "app1";
public static final String MIPS = "MIPS";
public static final Set<String> MVP_ENTITIES = Set.of(ENTITY_INDIVIDUAL, ENTITY_GROUP, ENTITY_SUBGROUP, ENTITY_APM);

// Program names in XML format
public static final String PCF = "PCF";
public static final String APP = "APP";
public static final String CPCPLUS = "CPCPLUS";
private static final String MIPS_GROUP = "MIPS_GROUP";
private static final String MIPS_INDIVIDUAL = "MIPS_INDIV";
public static final String MIPS_GROUP = "MIPS_GROUP";
public static final String MIPS_INDIVIDUAL = "MIPS_INDIV";
public static final String MIPS_APM = "MIPS_APMENTITY";
public static final String MIPS_VIRTUAL_GROUP = "MIPS_VIRTUALGROUP";
public static final String MIPS_SUBGROUP = "MIPS_SUBGROUP";
private static final String APP_GROUP = "MIPS_APP1_GROUP";
private static final String APP_INDIVIDUAL = "MIPS_APP1_INDIV";
public static final String APP_APM = "MIPS_APP1_APMENTITY";
Expand All @@ -76,7 +82,13 @@ protected DecodeResult decode(Element element, Node thisNode) {
setProgramNameOnNode(element, thisNode);
setPracticeSiteAddress(element, thisNode);
setCehrtOnNode(element, thisNode);
String entityType = thisNode.getValue(ENTITY_TYPE);
String entityType = thisNode.getValueOrDefault(ENTITY_TYPE, "");
if (MVP_ENTITIES.contains(entityType) && Program.isMips(thisNode)) {
setValueOnNode(element, thisNode, MVP_ID);
if (ENTITY_SUBGROUP.equalsIgnoreCase(entityType)) {
setValueOnNode(element, thisNode, SUBGROUP_ID);
}
}
if (ENTITY_APM.equalsIgnoreCase(entityType)) {
setEntityIdOnNode(element, thisNode);
setMultipleNationalProviderIdsOnNode(element, thisNode);
Expand Down Expand Up @@ -143,6 +155,22 @@ private void setCehrtOnNode(Element element, Node thisNode) {
setOnNode(element, getXpath(CEHRT), consumer, Filters.attribute(), false);
}

private void setMvpIdOnNode(Element element, Node thisNode) {
Consumer<? super Attribute> consumer = p -> thisNode.putValue(MVP_ID, p.getValue());
setOnNode(element, getXpath(MVP_ID), consumer, Filters.attribute(), true);
}

/**
* Sets a specific value as an element on the Node class decoder
* @param element current xml element to find the value via xpath
* @param thisNode current node
* @param currentValue to be added to the Node
*/
private void setValueOnNode(Element element, Node thisNode, String currentValue) {
Consumer<? super Attribute> consumer = p -> thisNode.putValue(currentValue, p.getValue());
setOnNode(element, getXpath(currentValue), consumer, Filters.attribute(), true);
}

/**
* Will decode the program name from the xml
*
Expand Down Expand Up @@ -260,6 +288,10 @@ private Pair<String, String> getProgramNameEntityPair(String name) {
pair = new ImmutablePair<>(PCF_PROGRAM_NAME, ENTITY_APM);
break;

case MIPS_SUBGROUP:
pair = new ImmutablePair<>(MIPS_PROGRAM_NAME, ENTITY_SUBGROUP);
break;

default:
pair = new ImmutablePair<>(name.toLowerCase(Locale.ENGLISH), ENTITY_INDIVIDUAL);
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gov.cms.qpp.conversion.encode;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -26,6 +27,7 @@ public class ClinicalDocumentEncoder extends QppOutputEncoder {

private static final Logger DEV_LOG = LoggerFactory.getLogger(ClinicalDocumentEncoder.class);
private static final String MEASUREMENT_SETS = "measurementSets";
private static final String SUBMISSION = "submission";

public ClinicalDocumentEncoder(Context context) {
super(context);
Expand Down Expand Up @@ -55,17 +57,17 @@ public void internalEncode(JsonWrapper wrapper, Node thisNode) {
*/
private void encodeToplevel(JsonWrapper wrapper, Node thisNode) {
String entityType = thisNode.getValue(ClinicalDocumentDecoder.ENTITY_TYPE);

encodePerformanceYear(wrapper, thisNode);
wrapper.put(ClinicalDocumentDecoder.ENTITY_TYPE, entityType);
if (!ClinicalDocumentDecoder.ENTITY_APM.equals(entityType)) {
if (!ClinicalDocumentDecoder.ENTITY_APM.equals(entityType)
&& !ClinicalDocumentDecoder.ENTITY_SUBGROUP.equalsIgnoreCase(entityType)) {
wrapper.put(ClinicalDocumentDecoder.NATIONAL_PROVIDER_IDENTIFIER,
thisNode.getValue(ClinicalDocumentDecoder.NATIONAL_PROVIDER_IDENTIFIER));
wrapper.put(ClinicalDocumentDecoder.TAX_PAYER_IDENTIFICATION_NUMBER,
thisNode.getValue(ClinicalDocumentDecoder.TAX_PAYER_IDENTIFICATION_NUMBER));
}

if(Program.isPcf(thisNode)) {
if (Program.isPcf(thisNode)) {
wrapper.put(ClinicalDocumentDecoder.ENTITY_ID, thisNode.getValue(ClinicalDocumentDecoder.PCF_ENTITY_ID));
}

Expand All @@ -77,6 +79,10 @@ private void encodeToplevel(JsonWrapper wrapper, Node thisNode) {
ClinicalDocumentDecoder.ENTITY_APM.equalsIgnoreCase(entityType))) {
wrapper.put(ClinicalDocumentDecoder.ENTITY_ID, thisNode.getValue(ClinicalDocumentDecoder.ENTITY_ID));
}

if (ClinicalDocumentDecoder.ENTITY_SUBGROUP.equalsIgnoreCase(entityType)) {
wrapper.put(ClinicalDocumentDecoder.ENTITY_ID, thisNode.getValue(ClinicalDocumentDecoder.SUBGROUP_ID));
}
}

/**
Expand Down Expand Up @@ -111,7 +117,6 @@ private JsonWrapper encodeMeasurementSets(Map<TemplateId, Node> childMapByTempla
if (child == null) {
continue;
}

try {
TemplateId childType = child.getType();

Expand All @@ -120,7 +125,12 @@ private JsonWrapper encodeMeasurementSets(Map<TemplateId, Node> childMapByTempla

sectionEncoder.encode(childWrapper, child);
childWrapper.put("source", "qrda3");
String mvpId = currentNode.getValue(ClinicalDocumentDecoder.MVP_ID);
if (TemplateId.MEASURE_SECTION_V5.getRoot().equalsIgnoreCase(childType.getRoot())
&& !StringUtils.isEmpty(mvpId)) {
childWrapper.put(ClinicalDocumentDecoder.PROGRAM_NAME, mvpId);
}
else if (TemplateId.MEASURE_SECTION_V5.getRoot().equalsIgnoreCase(childType.getRoot())
&& ClinicalDocumentDecoder.MIPS_APM.equalsIgnoreCase(
currentNode.getValue(ClinicalDocumentDecoder.RAW_PROGRAM_NAME))) {
childWrapper.put(ClinicalDocumentDecoder.PROGRAM_NAME, ClinicalDocumentDecoder.MIPS.toLowerCase(Locale.getDefault()));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gov.cms.qpp.conversion.encode;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -90,9 +91,16 @@ private void encodeChildren(List<Node> children, JsonWrapper measurementsWrapper
* @param parent the clinical document node
*/
private void pilferParent(JsonWrapper wrapper, Node parent) {
wrapper.put(ClinicalDocumentDecoder.PROGRAM_NAME,
String mvpId = parent.getValue(ClinicalDocumentDecoder.MVP_ID);
if (StringUtils.isEmpty(mvpId)) {
wrapper.put(ClinicalDocumentDecoder.PROGRAM_NAME,
parent.getValue(ClinicalDocumentDecoder.PROGRAM_NAME));
maintainContinuity(wrapper, parent, ClinicalDocumentDecoder.PROGRAM_NAME);
maintainContinuity(wrapper, parent, ClinicalDocumentDecoder.PROGRAM_NAME);
} else {
wrapper.put(ClinicalDocumentDecoder.PROGRAM_NAME,
parent.getValue(ClinicalDocumentDecoder.MVP_ID));
maintainContinuity(wrapper, parent, ClinicalDocumentDecoder.MVP_ID);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* Construct that helps categorize submissions by program name.
*/
public enum Program {
MIPS("MIPS_GROUP", "MIPS_INDIV", "MIPS_VIRTUALGROUP", "MIPS", "MIPS_APMENTITY"),
MIPS("MIPS_GROUP", "MIPS_INDIV", "MIPS_VIRTUALGROUP", "MIPS", "MIPS_APMENTITY", "MIPS_SUBGROUP"),
PCF("PCF"),
APP("MIPS_APP1_INDIV", "MIPS_APP1_GROUP", "MIPS_APP1_APMENTITY"),
ALL;
Expand Down
20 changes: 20 additions & 0 deletions converter/src/main/resources/pathing/path-correlation.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,26 @@
"relativeXPath": "./*[local-name() = 'documentationOf']/*[local-name() = 'serviceEvent']/*[local-name() = 'performer']",
"xmltype": "element"
}
},
{
"decodeLabel": "mvpId",
"encodeLabels": [
"mvpId"
],
"goods": {
"relativeXPath": "./*[local-name() = 'participant' and namespace-uri() = '<nsuri>']/*[local-name() = 'associatedEntity' and namespace-uri() = '<nsuri>']/*[local-name() = 'id' and namespace-uri() = '<nsuri>'][@root='2.16.840.1.113883.3.249.5.6']/@extension",
"xmltype": "attribute"
}
},
{
"decodeLabel": "subgroupId",
"encodeLabels": [
"subgroupId"
],
"goods": {
"relativeXPath": "./*[local-name() = 'documentationOf' and namespace-uri() = '<nsuri>']/*[local-name() = 'serviceEvent' and namespace-uri() = '<nsuri>']/*[local-name() = 'performer' and namespace-uri() = '<nsuri>']/*[local-name() = 'assignedEntity' and namespace-uri() = '<nsuri>']/*[local-name() = 'representedOrganization' and namespace-uri() = '<nsuri>']/*[local-name() = 'id' and namespace-uri() = '<nsuri>'][@root='2.16.840.1.113883.3.249.5.5']/@extension",
"xmltype": "attribute"
}
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ static void before() throws NoSuchFieldException, IllegalAccessException {
ClinicalDocumentDecoder.PCF_ENTITY_ID,
ClinicalDocumentDecoder.VG_ID,
ClinicalDocumentDecoder.APM_ENTITY_ID,
ClinicalDocumentDecoder.MVP_ID,
ClinicalDocumentDecoder.SUBGROUP_ID,
PerformanceRateProportionMeasureDecoder.PERFORMANCE_RATE,
PerformanceRateProportionMeasureDecoder.NULL_PERFORMANCE_RATE,
//There are no validations for performanceYear
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
* Positive Testing Scenarios to check PCF for valid top level attributes and validations
* Ensures:
* - valid apm entity
* - Sample file with valid test tin/npi and measurement sets for 2021 go through without issue.
* - Sample file with valid test tin/npi and measurement sets for 2023 go through without issue.
*/
public class PcfRoundTripTest {
private static JsonWrapper wrapper = new JsonWrapper();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,46 +33,46 @@ void testQualityMeasuresContainsPerformanceStart() {
String performanceStart = JsonHelper.readJsonAtJsonPath(json,
"$.measurementSets[0].performanceStart", new TypeRef<String>() { });

assertThat(performanceStart).isEqualTo("2022-01-01");
assertThat(performanceStart).isEqualTo("2023-01-01");
}

@Test
void testQualityMeasuresContainsPerformanceEnd() {
String performanceStart = JsonHelper.readJsonAtJsonPath(json,
"$.measurementSets[0].performanceEnd", new TypeRef<String>() { });

assertThat(performanceStart).isEqualTo("2022-12-31");
assertThat(performanceStart).isEqualTo("2023-12-31");
}

@Test
void testAciSectionContainsPerformanceStart() {
String performanceStart = JsonHelper.readJsonAtJsonPath(json,
"$.measurementSets[1].performanceStart", new TypeRef<String>() { });

assertThat(performanceStart).isEqualTo("2022-02-01");
assertThat(performanceStart).isEqualTo("2023-02-01");
}

@Test
void testAciSectionContainsPerformanceEnd() {
String performanceStart = JsonHelper.readJsonAtJsonPath(json,
"$.measurementSets[1].performanceEnd", new TypeRef<String>() { });

assertThat(performanceStart).isEqualTo("2022-05-31");
assertThat(performanceStart).isEqualTo("2023-05-31");
}

@Test
void testIaContainsPerformanceStart() {
String performanceStart = JsonHelper.readJsonAtJsonPath(json,
"$.measurementSets[2].performanceStart", new TypeRef<String>() { });

assertThat(performanceStart).isEqualTo("2022-01-01");
assertThat(performanceStart).isEqualTo("2023-01-01");
}

@Test
void testIaContainsPerformanceEnd() {
String performanceStart = JsonHelper.readJsonAtJsonPath(json,
"$.measurementSets[2].performanceEnd", new TypeRef<String>() { });

assertThat(performanceStart).isEqualTo("2022-04-30");
assertThat(performanceStart).isEqualTo("2023-04-30");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ void compareTopLevelAttributeEntityId() throws XmlException {
@Test
void compareAciMeasurePerformanceEnd() throws XmlException {
String jsonPath = "measurementSets[1].performanceEnd";
helper.executeAttributeTest(jsonPath, "value", "20220531");
helper.executeAttributeTest(jsonPath, "value", "20230531");
}

@Test
void compareAciMeasurePerformanceStart() throws XmlException {
String jsonPath = "measurementSets[1].performanceStart";
helper.executeAttributeTest(jsonPath, "value", "20220201");
helper.executeAttributeTest(jsonPath, "value", "20230201");
}

@Test
Expand All @@ -88,13 +88,13 @@ void compareAciMeasurePerformedMeasureIdAciPea1Denominator() throws XmlException
@Test
void compareIaMeasurePerformanceEnd() throws XmlException {
String jsonPath = "measurementSets[2].performanceEnd";
helper.executeAttributeTest(jsonPath, "value", "20220430");
helper.executeAttributeTest(jsonPath, "value", "20230430");
}

@Test
void compareIaMeasurePerformanceStart() throws XmlException {
String jsonPath = "measurementSets[2].performanceStart";
helper.executeAttributeTest(jsonPath, "value", "20220101");
helper.executeAttributeTest(jsonPath, "value", "20230101");
}

@Test
Expand All @@ -113,13 +113,13 @@ void compareIaMeasurePerformedMeasureIdIaEpa1() throws XmlException {
@Test
void compareQualityMeasurePerformanceEnd() throws XmlException {
String jsonPath = "measurementSets[0].performanceEnd";
helper.executeAttributeTest(jsonPath, "value", "20221231");
helper.executeAttributeTest(jsonPath, "value", "20231231");
}

@Test
void compareQualityMeasurePerformanceStart() throws XmlException {
String jsonPath = "measurementSets[0].performanceStart";
helper.executeAttributeTest(jsonPath, "value", "20220101");
helper.executeAttributeTest(jsonPath, "value", "20230101");
}

@Test
Expand Down
Loading

0 comments on commit 9a3495f

Please sign in to comment.