diff --git a/src/main/java/gov/cms/madie/madiefhirservice/services/HumanReadableService.java b/src/main/java/gov/cms/madie/madiefhirservice/services/HumanReadableService.java index 07259c94..1e34c185 100644 --- a/src/main/java/gov/cms/madie/madiefhirservice/services/HumanReadableService.java +++ b/src/main/java/gov/cms/madie/madiefhirservice/services/HumanReadableService.java @@ -1,31 +1,41 @@ package gov.cms.madie.madiefhirservice.services; -import gov.cms.madie.madiefhirservice.constants.UriConstants.CqfMeasures; -import gov.cms.madie.madiefhirservice.exceptions.HumanReadableGenerationException; -import gov.cms.madie.madiefhirservice.exceptions.ResourceNotFoundException; -import gov.cms.madie.madiefhirservice.utils.ResourceUtils; -import gov.cms.madie.models.measure.Measure; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import static org.springframework.web.util.HtmlUtils.htmlEscape; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50; import org.hl7.fhir.convertors.conv40_50.VersionConvertor_40_50; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Library; import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r5.model.Enumerations.FHIRTypes; import org.hl7.fhir.r5.model.Expression; import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.ParameterDefinition; import org.hl7.fhir.r5.model.RelatedArtifact; import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.utils.LiquidEngine; import org.springframework.stereotype.Service; -import java.util.List; -import static org.springframework.web.util.HtmlUtils.htmlEscape; + +import gov.cms.madie.madiefhirservice.constants.UriConstants.CqfMeasures; +import gov.cms.madie.madiefhirservice.exceptions.HumanReadableGenerationException; +import gov.cms.madie.madiefhirservice.exceptions.ResourceNotFoundException; +import gov.cms.madie.madiefhirservice.utils.ResourceUtils; +import gov.cms.madie.models.measure.Measure; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Slf4j @Service @RequiredArgsConstructor public class HumanReadableService extends ResourceUtils { + private final LiquidEngine liquidEngine; @@ -49,7 +59,7 @@ private void escapeTopLevelProperties(org.hl7.fhir.r5.model.Measure measure) { } private void escapeSupplementalProperties(org.hl7.fhir.r5.model.Measure measure) { - // supplemental data Elements + // supplemental data Elements measure .getSupplementalData() .forEach( @@ -80,7 +90,7 @@ public void escapeContainedProperties(org.hl7.fhir.r5.model.Measure measure) { escapeStr(innerExtension.getValue().primitiveValue()))); }); }); - // population criteria + // population criteria relatedArtifacts.forEach( relatedArtifact -> { relatedArtifact.setLabel(escapeStr(relatedArtifact.getLabel())); @@ -95,7 +105,7 @@ public org.hl7.fhir.r5.model.Measure escapeMeasure(org.hl7.fhir.r5.model.Measure escapeTopLevelProperties(measure); escapeSupplementalProperties(measure); escapeContainedProperties(measure); - // logic definitions, effective data requirements + // logic definitions, effective data requirements // risk factors and supplemental data guidance measure .getExtension() @@ -153,8 +163,10 @@ public String generateMeasureHumanReadable( var versionConvertor_40_50 = new VersionConvertor_40_50(new BaseAdvisor_40_50()); org.hl7.fhir.r5.model.Measure r5Measure = (org.hl7.fhir.r5.model.Measure) versionConvertor_40_50.convertResource(measureResource); - + // sort effectiveDataRequirements.parameters + sortParameters(madieMeasure, effectiveDataRequirements); r5Measure.addContained(effectiveDataRequirements); + r5Measure.getExtension().add(createEffectiveDataRequirementExtension()); // escape html org.hl7.fhir.r5.model.Measure escapedR5Measure = escapeMeasure(r5Measure); @@ -171,6 +183,75 @@ public String generateMeasureHumanReadable( } } + private void sortParameters( + Measure madieMeasure, org.hl7.fhir.r5.model.Library effectiveDataRequirements) { + List suppDataDefs = + madieMeasure.getSupplementalData().stream() + .map((s) -> s.getDefinition()) + .collect(Collectors.toList()); + List riskAdjDefs = + madieMeasure.getRiskAdjustments().stream() + .map((s) -> s.getDefinition()) + .collect(Collectors.toList()); + List strats = new ArrayList(); + if (madieMeasure.getGroups() != null) { + madieMeasure.getGroups().stream() + .forEach( + (g) -> + strats.addAll( + g.getStratifications().stream() + .map(s -> s.getCqlDefinition()) + .collect(Collectors.toList()))); + } + + Collections.sort( + effectiveDataRequirements.getParameter(), + new Comparator() { + + @Override + public int compare(ParameterDefinition o1, ParameterDefinition o2) { + + int ord1 = determineOrd(o1, suppDataDefs, riskAdjDefs, strats); + int ord2 = determineOrd(o2, suppDataDefs, riskAdjDefs, strats); + + int result = ord1 - ord2; + return result; + } + }); + } + + private int determineOrd( + ParameterDefinition paramDef, + List suppDataDefs, + List riskAdjDefs, + List strats) { + int result = 1; // default = 1 + + // if paramDef is a period, then ord = 0 + if (paramDef != null + && paramDef.getType() != null + && paramDef.getType().toCode().equals(FHIRTypes.PERIOD.toCode())) { + result = 0; + } + // if paramDef is a supp data then ord = 2 + if (paramDef != null && suppDataDefs.contains(paramDef.getName())) { + result = 2; + } + + // if paramDef is a risk adjustment data then ord = 3 + if (paramDef != null && riskAdjDefs.contains(paramDef.getName())) { + result = 3; + } + + // if paramDef is a stratification data then ord = 4 + if (paramDef != null && strats.contains(paramDef.getName())) { + result = 4; + } + + // if paramDef is anything else then ord = 1 + return result; + } + /** * Generate human-readable for a library * diff --git a/src/test/java/gov/cms/madie/madiefhirservice/services/HumanReadableServiceTest.java b/src/test/java/gov/cms/madie/madiefhirservice/services/HumanReadableServiceTest.java index 9c70fbe1..f39cd50a 100644 --- a/src/test/java/gov/cms/madie/madiefhirservice/services/HumanReadableServiceTest.java +++ b/src/test/java/gov/cms/madie/madiefhirservice/services/HumanReadableServiceTest.java @@ -14,6 +14,7 @@ import org.hl7.fhir.r4.model.Library; import org.hl7.fhir.r4.model.Period; import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r5.model.ParameterDefinition; import org.hl7.fhir.r5.utils.LiquidEngine; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -134,6 +136,43 @@ public void generateMeasureHumanReadable() { assertTrue(generatedHumanReadable.contains(hrText)); } + @Test + public void generateMeasureHumanReadableOrdered() { + Bundle.BundleEntryComponent measureBundleEntryComponent = getBundleEntryComponent(measure); + Bundle.BundleEntryComponent libraryBundleEntryComponent = getBundleEntryComponent(library); + Bundle bundle = + new Bundle() + .setType(Bundle.BundleType.TRANSACTION) + .addEntry(measureBundleEntryComponent) + .addEntry(libraryBundleEntryComponent); + + String hrText = "
Human Readable for Measure: " + madieMeasure.getMeasureName() + "
"; + + when(liquidEngine.parse(anyString(), anyString())) + .thenReturn(new LiquidEngine.LiquidDocument()); + + when(liquidEngine.evaluate( + any(LiquidEngine.LiquidDocument.class), + argThat( + (measure) -> { + org.hl7.fhir.r5.model.Measure m = (org.hl7.fhir.r5.model.Measure) measure; + + org.hl7.fhir.r5.model.Library library = + (org.hl7.fhir.r5.model.Library) m.getContained().get(0); + ParameterDefinition paramDef = library.getParameter().get(0); + + return "Period".equals(paramDef.getType().toCode()); + }), + any())) + .thenReturn(hrText); + + String generatedHumanReadable = + humanReadableService.generateMeasureHumanReadable( + madieMeasure, bundle, effectiveDataRequirements); + assertNotNull(generatedHumanReadable); + assertTrue(generatedHumanReadable.contains(hrText)); + } + @Test public void generateHumanReadableThrowsResourceNotFoundExceptionForNoBundle() { assertThrows( diff --git a/src/test/resources/humanReadable/effective-data-requirements.json b/src/test/resources/humanReadable/effective-data-requirements.json index 41d2e001..54564eee 100644 --- a/src/test/resources/humanReadable/effective-data-requirements.json +++ b/src/test/resources/humanReadable/effective-data-requirements.json @@ -134,19 +134,20 @@ "display": "Library FHIRHelpers", "resource": "Library/FHIRHelpers|4.1.000" } ], - "parameter": [ { - "name": "Measurement Period", - "use": "in", - "min": 0, - "max": "1", - "type": "Period" - }, { + "parameter": [ { "name": "boolDenom", "use": "out", "min": 0, "max": "1", "type": "boolean" - }, { + },{ + "name": "Measurement Period", + "use": "in", + "min": 0, + "max": "1", + "type": "Period" + }, + { "name": "ex", "use": "out", "min": 0,