Skip to content

Commit

Permalink
Merge pull request #759 from CMSgov/story-867
Browse files Browse the repository at this point in the history
Mips Virtual Group
  • Loading branch information
saquino0827 committed Sep 18, 2018
2 parents 57bd0f2 + 4e2a62a commit de2616e
Show file tree
Hide file tree
Showing 16 changed files with 133 additions and 27 deletions.
1 change: 1 addition & 0 deletions ERROR_MESSAGES.md
Expand Up @@ -66,3 +66,4 @@ Any text in the following format `(Example)` are considered variables to be fill
* 70 : CT - The measure section measure reference and results has an incorrect number of measure GUID supplied. Please ensure that only one measure GUID is provided per measure.
* 71 : CT - Two or more different measure section measure reference and results have the same measure GUID. Please ensure that each measure section measure reference and results do not have the same measure GUID.
* 72 : CT - The Performance Rate is missing
* 78 : CT - The Program 'Mips Virtual Group' was found. The required entity id for this program name was missing. Please provide a virtual group identifier with the 'Mips Virtual Group' program name.
Expand Up @@ -155,7 +155,9 @@ public enum ErrorCode implements LocalizedError {
MEASURES_RNR_WITH_DUPLICATED_MEASURE_GUID(71, "Two or more different measure section measure reference and results have "
+ "the same measure GUID. Please ensure that each measure section measure reference and results do not have "
+ "the same measure GUID."),
PERFORMANCE_RATE_MISSING(72, "The Performance Rate is missing");
PERFORMANCE_RATE_MISSING(72, "The Performance Rate is missing"),
VIRTUAL_GROUP_ID_REQUIRED(78, "The Program 'Mips Virtual Group' was found. The required entity id for this "
+ "program name was missing. Please provide a virtual group identifier with the 'Mips Virtual Group' program name.");

private static final Map<Integer, ErrorCode> CODE_TO_VALUE = Arrays.stream(values())
.collect(Collectors.toMap(ErrorCode::getCode, Function.identity()));
Expand Down Expand Up @@ -239,4 +241,5 @@ private static final class ServiceCenter {
static final String MESSAGE = "Please contact the Service Center for assistance via phone at "
+ "1-866-288-8292 or TTY: 1-877-715-6222, or by emailing QPP@cms.hhs.gov";
}

}
Expand Up @@ -29,13 +29,16 @@ public class ClinicalDocumentDecoder extends QrdaDecoder {
public static final String ENTITY_TYPE = "entityType";
public static final String MIPS_PROGRAM_NAME = "mips";
public static final String CPCPLUS_PROGRAM_NAME = "cpcPlus";
public static final String ENTITY_ID = "practiceId";
public static final String PRACTICE_ID = "practiceId";
public static final String PRACTICE_SITE_ADDR = "practiceSiteAddr";
public static final String MIPS = "MIPS";
private static final String MIPS_GROUP = "MIPS_GROUP";
private static final String MIPS_INDIVIDUAL = "MIPS_INDIV";
public static final String MIPS_VIRTUAL_GROUP = "MIPS_VIRTUALGROUP";
static final String ENTITY_GROUP = "group";
static final String ENTITY_INDIVIDUAL = "individual";
public static final String ENTITY_VIRTUAL_GROUP = "virtualGroup";
public static final String ENTITY_ID = "entityId";
public static final String CPCPLUS = "CPCPLUS";

public ClinicalDocumentDecoder(Context context) {
Expand All @@ -56,6 +59,8 @@ protected DecodeResult decode(Element element, Node thisNode) {
setPracticeSiteAddress(element, thisNode);
if (ENTITY_INDIVIDUAL.equals(thisNode.getValue(ENTITY_TYPE))) {
setNationalProviderIdOnNode(element, thisNode);
} else if (ENTITY_VIRTUAL_GROUP.equals(thisNode.getValue(ENTITY_TYPE))) {
setVirtualGroupOnNode(element, thisNode);
}
setTaxProviderTaxIdOnNode(element, thisNode);
return DecodeResult.TREE_CONTINUE;
Expand All @@ -71,8 +76,8 @@ protected DecodeResult decode(Element element, Node thisNode) {
private void setEntityIdOnNode(Element element, Node thisNode) {
if (Program.isCpc(thisNode)) {
Consumer<Attribute> consumer = id ->
thisNode.putValue(ENTITY_ID, id.getValue(), false);
setOnNode(element, getXpath(ENTITY_ID), consumer, Filters.attribute(), false);
thisNode.putValue(PRACTICE_ID, id.getValue(), false);
setOnNode(element, getXpath(PRACTICE_ID), consumer, Filters.attribute(), false);
}
}

Expand Down Expand Up @@ -134,6 +139,14 @@ private void setTaxProviderTaxIdOnNode(Element element, Node thisNode) {
consumer, Filters.attribute(), true);
}

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

/**
* decodes the program name and entity type from the name
*
Expand All @@ -155,6 +168,10 @@ private Pair<String, String> getProgramNameEntityPair(String name) {
pair = new ImmutablePair<>(CPCPLUS_PROGRAM_NAME, ENTITY_INDIVIDUAL);
break;

case MIPS_VIRTUAL_GROUP:
pair = new ImmutablePair<>(MIPS_PROGRAM_NAME, ENTITY_VIRTUAL_GROUP);
break;

default:
pair = new ImmutablePair<>(name.toLowerCase(Locale.ENGLISH), ENTITY_INDIVIDUAL);
break;
Expand Down
Expand Up @@ -52,13 +52,18 @@ public void internalEncode(JsonWrapper wrapper, Node thisNode) {
* @param thisNode holds the decoded node sections of clinical document
*/
private void encodeToplevel(JsonWrapper wrapper, Node thisNode) {
String entityType = thisNode.getValue(ClinicalDocumentDecoder.ENTITY_TYPE);

encodePerformanceYear(wrapper, thisNode);
wrapper.putString(ClinicalDocumentDecoder.ENTITY_TYPE,
thisNode.getValue(ClinicalDocumentDecoder.ENTITY_TYPE));
wrapper.putString(ClinicalDocumentDecoder.ENTITY_TYPE, entityType);
wrapper.putString(ClinicalDocumentDecoder.TAX_PAYER_IDENTIFICATION_NUMBER,
thisNode.getValue(ClinicalDocumentDecoder.TAX_PAYER_IDENTIFICATION_NUMBER));
wrapper.putString(ClinicalDocumentDecoder.NATIONAL_PROVIDER_IDENTIFIER,
thisNode.getValue(ClinicalDocumentDecoder.NATIONAL_PROVIDER_IDENTIFIER));
if (ClinicalDocumentDecoder.ENTITY_VIRTUAL_GROUP.equals(entityType)) {
wrapper.putString(ClinicalDocumentDecoder.ENTITY_ID,
thisNode.getValue(ClinicalDocumentDecoder.ENTITY_ID));
}
}

/**
Expand Down
Expand Up @@ -99,9 +99,9 @@ private void pilferParent(JsonWrapper wrapper, Node parent) {
* @param parent holds the decoded node sections of clinical document
*/
private void encodeEntityId(JsonWrapper wrapper, Node parent) {
String entityId = parent.getValue(ClinicalDocumentDecoder.ENTITY_ID);
String entityId = parent.getValue(ClinicalDocumentDecoder.PRACTICE_ID);
if (!StringUtils.isEmpty(entityId)) {
wrapper.putString(ClinicalDocumentDecoder.ENTITY_ID, entityId);
wrapper.putString(ClinicalDocumentDecoder.PRACTICE_ID, entityId);
}
}

Expand Down
Expand Up @@ -13,7 +13,7 @@
* Construct that helps categorize submissions by program name.
*/
public enum Program {
MIPS("MIPS_GROUP", "MIPS_INDIV"),
MIPS("MIPS_GROUP", "MIPS_INDIV", "MIPS_VIRTUALGROUP"),
CPC("CPCPLUS"),
ALL;

Expand Down
Expand Up @@ -50,10 +50,15 @@ protected void internalValidateSingleNode(final Node node) {
if (!getDetails().contains(
Detail.forErrorAndNode(ErrorCode.CLINICAL_DOCUMENT_MISSING_PROGRAM_NAME.format(VALID_PROGRAM_NAMES), node))) {
String programName = Optional.ofNullable(node.getValue(ClinicalDocumentDecoder.PROGRAM_NAME)).orElse("<missing>");
String entityType = Optional.ofNullable(node.getValue(ClinicalDocumentDecoder.ENTITY_TYPE)).orElse("<missing>");

thoroughlyCheck(node).valueIn(ErrorCode.CLINICAL_DOCUMENT_INCORRECT_PROGRAM_NAME.format(programName, VALID_PROGRAM_NAMES),
ClinicalDocumentDecoder.PROGRAM_NAME, ClinicalDocumentDecoder.MIPS_PROGRAM_NAME,
ClinicalDocumentDecoder.CPCPLUS_PROGRAM_NAME);

if (ClinicalDocumentDecoder.ENTITY_VIRTUAL_GROUP.equals(entityType)) {
thoroughlyCheck(node).value(ErrorCode.VIRTUAL_GROUP_ID_REQUIRED, ClinicalDocumentDecoder.ENTITY_ID);
}
}
}
}
Expand Up @@ -48,8 +48,8 @@ protected void internalValidateSingleNode(Node node) {
check(node)
.valueIsNotEmpty(addressError, ClinicalDocumentDecoder.PRACTICE_SITE_ADDR)
.singleValue(ErrorCode.CPC_CLINICAL_DOCUMENT_ONLY_ONE_APM_ALLOWED,
ClinicalDocumentDecoder.ENTITY_ID)
.valueIsNotEmpty(ErrorCode.CPC_CLINICAL_DOCUMENT_EMPTY_APM, ClinicalDocumentDecoder.ENTITY_ID)
ClinicalDocumentDecoder.PRACTICE_ID)
.valueIsNotEmpty(ErrorCode.CPC_CLINICAL_DOCUMENT_EMPTY_APM, ClinicalDocumentDecoder.PRACTICE_ID)
.childMinimum(ErrorCode.CPC_CLINICAL_DOCUMENT_ONE_MEASURE_SECTION_REQUIRED,
1, TemplateId.MEASURE_SECTION_V2);

Expand All @@ -64,7 +64,7 @@ protected void internalValidateSingleNode(Node node) {
* @param node The node to validate
*/
private void validateApmEntityId(Node node) {
String apmEntityId = node.getValue(ClinicalDocumentDecoder.ENTITY_ID);
String apmEntityId = node.getValue(ClinicalDocumentDecoder.PRACTICE_ID);

if (StringUtils.isEmpty(apmEntityId)) {
return;
Expand Down
10 changes: 10 additions & 0 deletions converter/src/main/resources/pathing/path-correlation.json
Expand Up @@ -120,6 +120,16 @@
"relativeXPath": "./*[local-name() = 'effectiveTime' and namespace-uri() = '<nsuri>']/@value",
"xmltype": "attribute"
}
},
{
"decodeLabel": "entityId",
"encodeLabels": [
"entityId"
],
"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.2']/@extension",
"xmltype": "attribute"
}
}
]
},
Expand Down
Expand Up @@ -47,8 +47,9 @@ static void before() throws NoSuchFieldException, IllegalAccessException {
//MultipleTinsDecoder maps multiple tin/npi combination
ClinicalDocumentDecoder.TAX_PAYER_IDENTIFICATION_NUMBER,
ClinicalDocumentDecoder.NATIONAL_PROVIDER_IDENTIFIER,
//There are no validations currently for entity type
ClinicalDocumentDecoder.ENTITY_ID,
//There are no validations currently for entity type
ClinicalDocumentDecoder.PRACTICE_ID,
ClinicalDocumentDecoder.PRACTICE_SITE_ADDR,
PerformanceRateProportionMeasureDecoder.PERFORMANCE_RATE,
PerformanceRateProportionMeasureDecoder.NULL_PERFORMANCE_RATE,
Expand Down
Expand Up @@ -252,7 +252,7 @@ void decodeCpcPlusEntityIdTest() {
objectUnderTest.setNamespace(clinicalDocument.getNamespace());
objectUnderTest.decode(clinicalDocument, testParentNode);
assertWithMessage("Clinical Document contains the Entity Id")
.that(testParentNode.getValue(ClinicalDocumentDecoder.ENTITY_ID))
.that(testParentNode.getValue(ClinicalDocumentDecoder.PRACTICE_ID))
.isEqualTo(ENTITY_ID_VALUE);
}

Expand All @@ -269,6 +269,21 @@ void decodeCpcPracticeSiteAddressTest() {
.isEqualTo("testing123");
}

@Test
void decodeMipsVirtualGroup() {
Element clinicalDocument = makeClinicalDocument(ClinicalDocumentDecoder.MIPS_VIRTUAL_GROUP);
Node testParentNode = new Node();

ClinicalDocumentDecoder objectUnderTest = new ClinicalDocumentDecoder(new Context());
objectUnderTest.setNamespace(clinicalDocument.getNamespace());
objectUnderTest.decode(clinicalDocument, testParentNode);

assertThat(testParentNode.getValue(ClinicalDocumentDecoder.ENTITY_TYPE))
.isEqualTo(ClinicalDocumentDecoder.ENTITY_VIRTUAL_GROUP);
assertThat(testParentNode.getValue(ClinicalDocumentDecoder.ENTITY_ID))
.isEqualTo("x12345");
}

private Element makeClinicalDocument(String programName) {
Namespace rootns = Namespace.getNamespace("urn:hl7-org:v3");
Namespace ns = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
Expand Down Expand Up @@ -321,8 +336,12 @@ private Element prepareDocumentationElement(Namespace rootns) {
Element nationalProviderIdentifier = new Element("id", rootns)
.setAttribute("root", "2.16.840.1.113883.4.6")
.setAttribute("extension", "2567891421");
Element virtualGroup = new Element("id", rootns)
.setAttribute("root", "2.16.840.1.113883.3.249.5.2")
.setAttribute("extension", "x12345");

Element representedOrganization = prepareRepOrgWithTaxPayerId(rootns);
representedOrganization.addContent(virtualGroup);
assignedEntity.addContent(representedOrganization);
assignedEntity.addContent(nationalProviderIdentifier);
performer.addContent(assignedEntity);
Expand Down
Expand Up @@ -114,7 +114,8 @@ void createNode() {
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_TYPE, "individual");
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.TAX_PAYER_IDENTIFICATION_NUMBER, "123456789");
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.NATIONAL_PROVIDER_IDENTIFIER, "2567891421");
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_ID, "AR000000" );
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.PRACTICE_ID, "AR000000" );
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_ID, "x12345" );
clinicalDocumentNode.addChildNode(aciSectionNode);

nodes = new ArrayList<>();
Expand Down Expand Up @@ -166,29 +167,30 @@ void testInternalEncodeWithoutMeasures() throws EncodeException {
@Test
void testInternalEncodeEmptyEntityId() throws EncodeException {
clinicalDocumentNode.getChildNodes().remove(aciSectionNode);
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_ID,"");
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.PRACTICE_ID,"");
JsonWrapper testJsonWrapper = new JsonWrapper();

ClinicalDocumentEncoder clinicalDocumentEncoder = new ClinicalDocumentEncoder(new Context());
clinicalDocumentEncoder.internalEncode(testJsonWrapper, clinicalDocumentNode);

Map<?, ?> clinicalDocMap = ((Map<?, ?>) testJsonWrapper.getObject());

assertThat(clinicalDocMap.get(ClinicalDocumentDecoder.ENTITY_ID))
assertThat(clinicalDocMap.get(ClinicalDocumentDecoder.PRACTICE_ID))
.isNull();
}

@Test
void testInternalEncodeNullEntityId() throws EncodeException {
clinicalDocumentNode.getChildNodes().remove(aciSectionNode);
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_ID,null);
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.PRACTICE_ID,null);
JsonWrapper testJsonWrapper = new JsonWrapper();

ClinicalDocumentEncoder clinicalDocumentEncoder = new ClinicalDocumentEncoder(new Context());
clinicalDocumentEncoder.internalEncode(testJsonWrapper, clinicalDocumentNode);

Map<?, ?> clinicalDocMap = ((Map<?, ?>) testJsonWrapper.getObject());

assertThat(clinicalDocMap.get(ClinicalDocumentDecoder.ENTITY_ID))
assertThat(clinicalDocMap.get(ClinicalDocumentDecoder.PRACTICE_ID))
.isNull();
}

Expand All @@ -211,6 +213,21 @@ void testClinicalDocumentEncoderIgnoresInvalidMeasurementSection() {
assertThat(value).isEqualTo(expectedSection);
}

@Test
void testVirtualGroupIdEncode() {
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_TYPE, ClinicalDocumentDecoder.ENTITY_VIRTUAL_GROUP);

JsonWrapper testJsonWrapper = new JsonWrapper();

ClinicalDocumentEncoder clinicalDocumentEncoder = new ClinicalDocumentEncoder(new Context());
clinicalDocumentEncoder.internalEncode(testJsonWrapper, clinicalDocumentNode);

Map<?, ?> clinicalDocMap = ((Map<?, ?>) testJsonWrapper.getObject());

assertThat(clinicalDocMap.get(ClinicalDocumentDecoder.ENTITY_ID))
.isEqualTo("x12345");
}


@SuppressWarnings("unchecked")
private List<LinkedHashMap<String, Object>> getMeasurementSets(Map clinicalDocumentMap) {
Expand Down
Expand Up @@ -22,6 +22,7 @@
import java.util.List;
import java.util.Set;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

class ClinicalDocumentValidatorTest {
Expand Down Expand Up @@ -254,6 +255,33 @@ void testInvalidProgramName() {
.containsExactly(ErrorCode.CLINICAL_DOCUMENT_INCORRECT_PROGRAM_NAME.format(invalidProgramName, ClinicalDocumentValidator.VALID_PROGRAM_NAMES));
}

@Test
void testMissingVirtualGroupId() {
Node clinicalDocumentNode = createValidClinicalDocumentNode();
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_TYPE, ClinicalDocumentDecoder.ENTITY_VIRTUAL_GROUP);
Node aciSectionNode = createAciSectionNode(clinicalDocumentNode);
clinicalDocumentNode.addChildNode(aciSectionNode);
ClinicalDocumentValidator validator = new ClinicalDocumentValidator();
Set<Detail> errors = validator.validateSingleNode(clinicalDocumentNode);

assertThat(errors).comparingElementsUsing(DetailsErrorEquals.INSTANCE)
.containsExactly(ErrorCode.VIRTUAL_GROUP_ID_REQUIRED);
}

@Test
void testSuccessVirtualGroupId() {
Node clinicalDocumentNode = createValidClinicalDocumentNode();
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_TYPE, ClinicalDocumentDecoder.ENTITY_VIRTUAL_GROUP);
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_ID, "x12345");
Node aciSectionNode = createAciSectionNode(clinicalDocumentNode);
clinicalDocumentNode.addChildNode(aciSectionNode);
ClinicalDocumentValidator validator = new ClinicalDocumentValidator();
Set<Detail> errors = validator.validateSingleNode(clinicalDocumentNode);

assertThat(errors).isEmpty();
}


private List<Detail> getErrors(AllErrors content) {
return content.getErrors().get(0).getDetails();
}
Expand Down
Expand Up @@ -89,7 +89,7 @@ void testCpcPlusMultipleApm() {
Node clinicalDocumentNode = createValidCpcPlusClinicalDocument();

// extra APM
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_ID, "1234567", false);
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.PRACTICE_ID, "1234567", false);
cpcValidator.internalValidateSingleNode(clinicalDocumentNode);

assertWithMessage("Must validate with the correct error")
Expand All @@ -100,7 +100,7 @@ void testCpcPlusMultipleApm() {
@Test
void testCpcPlusNoApm() {
Node clinicalDocumentNode = createValidCpcPlusClinicalDocument();
clinicalDocumentNode.removeValue(ClinicalDocumentDecoder.ENTITY_ID);
clinicalDocumentNode.removeValue(ClinicalDocumentDecoder.PRACTICE_ID);
cpcValidator.internalValidateSingleNode(clinicalDocumentNode);

assertWithMessage("Must validate with the correct error")
Expand All @@ -111,7 +111,7 @@ void testCpcPlusNoApm() {
@Test
void testCpcPlusEmptyApm() {
Node clinicalDocumentNode = createValidCpcPlusClinicalDocument();
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_ID, "");
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.PRACTICE_ID, "");
cpcValidator.internalValidateSingleNode(clinicalDocumentNode);

assertWithMessage("Must validate with the correct error")
Expand All @@ -122,7 +122,7 @@ void testCpcPlusEmptyApm() {
@Test
void testCpcPlusInvalidApm() {
Node clinicalDocumentNode = createValidCpcPlusClinicalDocument();
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_ID, "PropertyTaxes");
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.PRACTICE_ID, "PropertyTaxes");
cpcValidator.internalValidateSingleNode(clinicalDocumentNode);

assertWithMessage("Must validate with the correct error")
Expand Down Expand Up @@ -179,7 +179,7 @@ private Node createCpcPlusClinicalDocument() {
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.PROGRAM_NAME, ClinicalDocumentDecoder.CPCPLUS_PROGRAM_NAME);
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_TYPE, "");
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.PRACTICE_SITE_ADDR, "test");
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.ENTITY_ID, "DogCow");
clinicalDocumentNode.putValue(ClinicalDocumentDecoder.PRACTICE_ID, "DogCow");

return clinicalDocumentNode;
}
Expand Down
Expand Up @@ -75,7 +75,7 @@ private static String deriveCpcHash(Node node) {
* @return Apm Entity ID value
*/
private static String findApm(Node node) {
return findValue(node, ClinicalDocumentDecoder.ENTITY_ID, TemplateId.CLINICAL_DOCUMENT);
return findValue(node, ClinicalDocumentDecoder.PRACTICE_ID, TemplateId.CLINICAL_DOCUMENT);
}

/**
Expand Down

0 comments on commit de2616e

Please sign in to comment.