diff --git a/src/main/java/gov/cms/madie/madiefhirservice/services/TestCaseBundleService.java b/src/main/java/gov/cms/madie/madiefhirservice/services/TestCaseBundleService.java index 84aef4ca..053fc8ee 100644 --- a/src/main/java/gov/cms/madie/madiefhirservice/services/TestCaseBundleService.java +++ b/src/main/java/gov/cms/madie/madiefhirservice/services/TestCaseBundleService.java @@ -23,19 +23,14 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import gov.cms.madie.models.dto.TestCaseExportMetaData; +import gov.cms.madie.models.measure.*; +import gov.cms.madie.models.measure.Group; +import gov.cms.madie.models.measure.Measure; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateFormatUtils; -import org.hl7.fhir.r4.model.BooleanType; -import org.hl7.fhir.r4.model.Bundle; - -import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.r4.model.MarkdownType; -import org.hl7.fhir.r4.model.MeasureReport; -import org.hl7.fhir.r4.model.Meta; -import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.*; + import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.Resource; -import org.hl7.fhir.r4.model.StringType; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.web.client.RestClientException; @@ -51,8 +46,6 @@ import gov.cms.madie.madiefhirservice.utils.FhirResourceHelpers; import gov.cms.madie.models.common.BundleType; import gov.cms.madie.models.dto.ExportDTO; -import gov.cms.madie.models.measure.Measure; -import gov.cms.madie.models.measure.TestCase; import gov.cms.madie.packaging.utils.PackagingUtility; import gov.cms.madie.packaging.utils.PackagingUtilityFactory; import lombok.RequiredArgsConstructor; @@ -179,7 +172,7 @@ private MeasureReport buildMeasureReport( getUTCDates(measure.getMeasurementPeriodStart()), getUTCDates(measure.getMeasurementPeriodEnd()))); - measureReport.setGroup(buildMeasureReportGroupComponents(testCase)); + measureReport.setGroup(buildMeasureReportGroupComponents(testCase, measure.getGroups())); measureReport.setEvaluatedResource(buildEvaluatedResource(testCaseBundle)); return measureReport; } @@ -241,7 +234,7 @@ private List buildModifierExtension() { } private List buildMeasureReportGroupComponents( - TestCase testCase) { + TestCase testCase, List groups) { if (CollectionUtils.isEmpty(testCase.getGroupPopulations())) { return List.of(); } @@ -249,45 +242,133 @@ private List buildMeasureReportGroupC .map( population -> { var measureReportGroupComponent = new MeasureReport.MeasureReportGroupComponent(); - + measureReportGroupComponent.setId(population.getGroupId()); + // adding populations if (population.getPopulationValues() != null) { var measureReportGroupPopulationComponents = population.getPopulationValues().stream() .map( testCasePopulationValue -> { - String populationCode = testCasePopulationValue.getName().toCode(); - String populationDisplay = - testCasePopulationValue.getName().getDisplay(); - int expectedValue = - getExpectedValue(testCasePopulationValue.getExpected()); - return (new MeasureReport.MeasureReportGroupPopulationComponent()) - .setCode( - FhirResourceHelpers.buildCodeableConcept( - populationCode, - UriConstants.POPULATION_SYSTEM_URI, - populationDisplay)) - .setCount(expectedValue); + var groupComponent = + (new MeasureReport.MeasureReportGroupPopulationComponent()) + .setCode( + FhirResourceHelpers.buildCodeableConcept( + testCasePopulationValue.getName().toCode(), + UriConstants.POPULATION_SYSTEM_URI, + testCasePopulationValue.getName().getDisplay())) + .setCount( + FhirResourceHelpers.getExpectedValue( + testCasePopulationValue.getExpected())); + groupComponent.setId(testCasePopulationValue.getId()); + return groupComponent; }) .collect(Collectors.toList()); measureReportGroupComponent.setPopulation(measureReportGroupPopulationComponents); } + // adding measure score + measureReportGroupComponent.setMeasureScore( + new Quantity() + .setValue( + StringUtils.equalsIgnoreCase("boolean", population.getPopulationBasis()) + ? 1.0 + : 0)); + // adding stratification for patient basis + if (population.getStratificationValues() != null) { + if (StringUtils.equalsIgnoreCase("boolean", population.getPopulationBasis())) { + measureReportGroupComponent.setStratifier( + buildGroupStratifierComponent( + population, groups, population.getGroupId(), true)); + } else { + measureReportGroupComponent.setStratifier( + buildGroupStratifierComponent(population, null, null, false)); + } + } return measureReportGroupComponent; }) .collect(Collectors.toList()); } - /** - * @param expectedValue expected value for a population, can be a string or boolean - * @return an equivalent integer - */ - private int getExpectedValue(Object expectedValue) { - if (expectedValue == null || StringUtils.isBlank(expectedValue.toString())) { - return 0; - } else if (expectedValue instanceof Boolean) { - return (Boolean) expectedValue ? 1 : 0; + private List buildGroupStratifierComponent( + TestCaseGroupPopulation population, + List groups, + String populationGroupId, + boolean isPatientBased) { + return population.getStratificationValues().stream() + .map( + testCaseStratificationValue -> { + List code = new ArrayList(); + var codeText = + isPatientBased + ? getStratificationDefinition( + groups, populationGroupId, testCaseStratificationValue.getId()) + : testCaseStratificationValue.getName(); + code.add(new CodeableConcept().setText(codeText)); + + var stratifierComponent = + new MeasureReport.MeasureReportGroupStratifierComponent() + .setCode(code) + .setStratum( + buildStratum( + testCaseStratificationValue, + isPatientBased, + testCaseStratificationValue.getName())); + stratifierComponent.setId(testCaseStratificationValue.getId()); + return stratifierComponent; + }) + .collect(Collectors.toList()); + } + + private String getStratificationDefinition( + List groups, String populationId, String testCaseStratificationId) { + Group filteredGroup = + groups.stream() + .filter(group -> group.getId().equals(populationId)) + .findFirst() + .orElse(null); + if (filteredGroup != null) { + return filteredGroup.getStratifications().stream() + .filter(stratification -> stratification.getId().equals(testCaseStratificationId)) + .map(selectedStratification -> selectedStratification.getCqlDefinition()) + .findFirst() + .orElse(null); + } + return null; + } + + private List buildStratum( + TestCaseStratificationValue testCaseStratificationValue, + boolean isPatientBased, + String testCaseStratificationName) { + + // stratum + List stratum = + new ArrayList(); + + MeasureReport.StratifierGroupComponent stratifierGroupComponent = + new MeasureReport.StratifierGroupComponent(); + + if (isPatientBased) { + // when value is true + stratifierGroupComponent.setValue(new CodeableConcept().setText("true")); + stratifierGroupComponent.setPopulation( + FhirResourceHelpers.buildStratumPopulation(testCaseStratificationValue, true, true)); + stratum.add(stratifierGroupComponent); + + // when value is false (i.e., inverted ) + MeasureReport.StratifierGroupComponent stratifierGroupComponentForInvertedValue = + new MeasureReport.StratifierGroupComponent(); + stratifierGroupComponentForInvertedValue.setValue(new CodeableConcept().setText("false")); + stratifierGroupComponentForInvertedValue.setPopulation( + FhirResourceHelpers.buildStratumPopulation(testCaseStratificationValue, false, true)); + stratum.add(stratifierGroupComponentForInvertedValue); } else { - return Integer.parseInt(expectedValue.toString()); + // Non-patient based measures + stratifierGroupComponent.setValue(new CodeableConcept().setText(testCaseStratificationName)); + stratifierGroupComponent.setPopulation( + FhirResourceHelpers.buildStratumPopulation(testCaseStratificationValue, null, false)); + stratum.add(stratifierGroupComponent); } + return stratum; } /** diff --git a/src/main/java/gov/cms/madie/madiefhirservice/utils/FhirResourceHelpers.java b/src/main/java/gov/cms/madie/madiefhirservice/utils/FhirResourceHelpers.java index c56b1dd8..21a1f327 100644 --- a/src/main/java/gov/cms/madie/madiefhirservice/utils/FhirResourceHelpers.java +++ b/src/main/java/gov/cms/madie/madiefhirservice/utils/FhirResourceHelpers.java @@ -1,16 +1,17 @@ package gov.cms.madie.madiefhirservice.utils; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Period; -import org.hl7.fhir.r4.model.Resource; +import gov.cms.madie.madiefhirservice.constants.UriConstants; +import gov.cms.madie.models.measure.TestCaseStratificationValue; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.r4.model.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; @Component public class FhirResourceHelpers { @@ -62,6 +63,59 @@ public static CodeableConcept buildCodeableConcept(String code, String system, S return codeableConcept; } + /** + * @param expectedValue expected value for a population, can be a string or boolean + * @return an equivalent integer + */ + public static int getExpectedValue(Object expectedValue) { + if (expectedValue == null || StringUtils.isBlank(expectedValue.toString())) { + return 0; + } else if (expectedValue instanceof Boolean) { + return (Boolean) expectedValue ? 1 : 0; + } else { + return Integer.parseInt(expectedValue.toString()); + } + } + + public static int getExpectedInverseValue(int expectedValue) { + if (expectedValue == 0) { + return 1; + } + return 0; + } + + public static List buildStratumPopulation( + TestCaseStratificationValue testCaseStratificationValue, + Boolean valueIndex, + boolean isPatientBased) { + var measureTestCaseStratificationComponents = + testCaseStratificationValue.getPopulationValues().stream() + .map( + populationValue -> { + MeasureReport.StratifierGroupPopulationComponent + stratifierGroupPopulationComponent = + new MeasureReport.StratifierGroupPopulationComponent(); + + stratifierGroupPopulationComponent.setId(populationValue.getId()); + stratifierGroupPopulationComponent.setCode( + buildCodeableConcept( + populationValue.getName().toCode(), + UriConstants.POPULATION_SYSTEM_URI, + populationValue.getName().getDisplay())); + + stratifierGroupPopulationComponent.setCount( + isPatientBased + ? (valueIndex + ? getExpectedValue(populationValue.getExpected()) + : getExpectedInverseValue( + getExpectedValue(populationValue.getExpected()))) + : Integer.parseInt(populationValue.getExpected().toString())); + return stratifierGroupPopulationComponent; + }) + .collect(Collectors.toList()); + return measureTestCaseStratificationComponents; + } + public static Coding buildCoding(String code, String system, String display) { return new Coding().setCode(code).setSystem(system).setDisplay(display); } diff --git a/src/test/java/gov/cms/madie/madiefhirservice/utils/FhirResourceHelpersTest.java b/src/test/java/gov/cms/madie/madiefhirservice/utils/FhirResourceHelpersTest.java new file mode 100644 index 00000000..d1f56f7c --- /dev/null +++ b/src/test/java/gov/cms/madie/madiefhirservice/utils/FhirResourceHelpersTest.java @@ -0,0 +1,139 @@ +package gov.cms.madie.madiefhirservice.utils; + +import gov.cms.madie.models.measure.PopulationType; +import gov.cms.madie.models.measure.TestCasePopulationValue; +import gov.cms.madie.models.measure.TestCaseStratificationValue; +import org.hl7.fhir.r4.model.MeasureReport; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FhirResourceHelpersTest { + @Test + void testexpectedInverseValue() { + assertEquals(1, FhirResourceHelpers.getExpectedInverseValue(0)); + assertEquals(0, FhirResourceHelpers.getExpectedInverseValue(1)); + } + + @Test + void testBuildStratumPopulationForValueIndexOfTrue() { + TestCaseStratificationValue stratValue1 = + TestCaseStratificationValue.builder().name("Strata-1").expected(1).build(); + stratValue1.setPopulationValues( + List.of( + TestCasePopulationValue.builder() + .id("1") + .name(PopulationType.INITIAL_POPULATION) + .expected(1) + .build(), + TestCasePopulationValue.builder() + .id("2") + .name(PopulationType.DENOMINATOR) + .expected(1) + .build(), + TestCasePopulationValue.builder() + .id("3") + .name(PopulationType.NUMERATOR) + .expected(0) + .build())); + + List stratifierGroupPopulationComponents = + FhirResourceHelpers.buildStratumPopulation(stratValue1, true, true); + + assertEquals(stratifierGroupPopulationComponents.size(), 3); + assertEquals( + stratifierGroupPopulationComponents.get(0).getCode().getCoding().get(0).getCode(), + "initial-population"); + assertEquals(stratifierGroupPopulationComponents.get(0).getCount(), 1); + assertEquals( + stratifierGroupPopulationComponents.get(1).getCode().getCoding().get(0).getCode(), + "denominator"); + assertEquals(stratifierGroupPopulationComponents.get(1).getCount(), 1); + assertEquals( + stratifierGroupPopulationComponents.get(2).getCode().getCoding().get(0).getCode(), + "numerator"); + assertEquals(stratifierGroupPopulationComponents.get(2).getCount(), 0); + } + + @Test + void testBuildStratumPopulationForValueIndexOfFalse() { + TestCaseStratificationValue stratValue1 = + TestCaseStratificationValue.builder().name("Strata-1").expected(1).build(); + stratValue1.setPopulationValues( + List.of( + TestCasePopulationValue.builder() + .id("1") + .name(PopulationType.INITIAL_POPULATION) + .expected(1) + .build(), + TestCasePopulationValue.builder() + .id("2") + .name(PopulationType.DENOMINATOR) + .expected(1) + .build(), + TestCasePopulationValue.builder() + .id("3") + .name(PopulationType.NUMERATOR) + .expected(0) + .build())); + + List stratifierGroupPopulationComponents = + FhirResourceHelpers.buildStratumPopulation(stratValue1, false, true); + + assertEquals(stratifierGroupPopulationComponents.size(), 3); + assertEquals( + stratifierGroupPopulationComponents.get(0).getCode().getCoding().get(0).getCode(), + "initial-population"); + assertEquals(stratifierGroupPopulationComponents.get(0).getCount(), 0); + assertEquals( + stratifierGroupPopulationComponents.get(1).getCode().getCoding().get(0).getCode(), + "denominator"); + assertEquals(stratifierGroupPopulationComponents.get(1).getCount(), 0); + assertEquals( + stratifierGroupPopulationComponents.get(2).getCode().getCoding().get(0).getCode(), + "numerator"); + assertEquals(stratifierGroupPopulationComponents.get(2).getCount(), 1); + } + + @Test + void testBuildStratumPopulationForNonPatientBasedMeasures() { + TestCaseStratificationValue stratValue1 = + TestCaseStratificationValue.builder().name("Strata-1").expected(1).build(); + stratValue1.setPopulationValues( + List.of( + TestCasePopulationValue.builder() + .id("1") + .name(PopulationType.INITIAL_POPULATION) + .expected(5) + .build(), + TestCasePopulationValue.builder() + .id("2") + .name(PopulationType.DENOMINATOR) + .expected(4) + .build(), + TestCasePopulationValue.builder() + .id("3") + .name(PopulationType.NUMERATOR) + .expected(2) + .build())); + + List stratifierGroupPopulationComponents = + FhirResourceHelpers.buildStratumPopulation(stratValue1, null, false); + + assertEquals(stratifierGroupPopulationComponents.size(), 3); + assertEquals( + stratifierGroupPopulationComponents.get(0).getCode().getCoding().get(0).getCode(), + "initial-population"); + assertEquals(stratifierGroupPopulationComponents.get(0).getCount(), 5); + assertEquals( + stratifierGroupPopulationComponents.get(1).getCode().getCoding().get(0).getCode(), + "denominator"); + assertEquals(stratifierGroupPopulationComponents.get(1).getCount(), 4); + assertEquals( + stratifierGroupPopulationComponents.get(2).getCode().getCoding().get(0).getCode(), + "numerator"); + assertEquals(stratifierGroupPopulationComponents.get(2).getCount(), 2); + } +}