diff --git a/src/main/java/gov/cms/madie/madiefhirservice/dto/CqlLibraryDetails.java b/src/main/java/gov/cms/madie/madiefhirservice/dto/CqlLibraryDetails.java new file mode 100644 index 00000000..dbd09715 --- /dev/null +++ b/src/main/java/gov/cms/madie/madiefhirservice/dto/CqlLibraryDetails.java @@ -0,0 +1,14 @@ +package gov.cms.madie.madiefhirservice.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.Set; + +@Data +@Builder +public class CqlLibraryDetails { + private String cql; + private String libraryName; + private Set expressions; +} diff --git a/src/main/java/gov/cms/madie/madiefhirservice/services/ElmTranslatorClient.java b/src/main/java/gov/cms/madie/madiefhirservice/services/ElmTranslatorClient.java index 0e0efd59..4b9d5c37 100644 --- a/src/main/java/gov/cms/madie/madiefhirservice/services/ElmTranslatorClient.java +++ b/src/main/java/gov/cms/madie/madiefhirservice/services/ElmTranslatorClient.java @@ -2,10 +2,10 @@ import ca.uhn.fhir.context.FhirContext; import gov.cms.madie.madiefhirservice.config.ElmTranslatorClientConfig; +import gov.cms.madie.madiefhirservice.dto.CqlLibraryDetails; import gov.cms.madie.madiefhirservice.exceptions.CqlElmTranslationServiceException; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r5.model.Library; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -23,19 +23,18 @@ public class ElmTranslatorClient { private ElmTranslatorClientConfig elmTranslatorClientConfig; private RestTemplate elmTranslatorRestTemplate; - private final FhirContext fhirContext; private final FhirContext fhirContextForR5; - public Library getEffectiveDataRequirements( - Bundle bundleResource, String libraryName, String measureId, String accessToken) { + public Library getModuleDefinitionLibrary( + CqlLibraryDetails libraryDetails, boolean recursive, String accessToken) { try { - log.info("Getting data requirements for measure: {}", measureId); + log.info( + "Getting Module Definition Library for library: {}", libraryDetails.getLibraryName()); URI uri = UriComponentsBuilder.fromHttpUrl( elmTranslatorClientConfig.getCqlElmServiceBaseUrl() + elmTranslatorClientConfig.getEffectiveDataRequirementsDataUri()) - .queryParam("libraryName", libraryName) - .queryParam("measureId", measureId) + .queryParam("recursive", recursive) .build() .encode() .toUri(); @@ -43,19 +42,12 @@ public Library getEffectiveDataRequirements( HttpHeaders headers = new HttpHeaders(); headers.set(HttpHeaders.AUTHORIZATION, accessToken); - String bundleStr = - fhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundleResource); - - HttpEntity bundleEntity = new HttpEntity<>(bundleStr, headers); + HttpEntity bundleEntity = new HttpEntity<>(libraryDetails, headers); String effectiveDrJson = elmTranslatorRestTemplate .exchange(uri, HttpMethod.PUT, bundleEntity, String.class) .getBody(); - Library effectiveDataRequirements = - fhirContextForR5.newJsonParser().parseResource(Library.class, effectiveDrJson); - // effectiveDataRequirements needs to have fixed id: effective-data-requirements - effectiveDataRequirements.setId("effective-data-requirements"); - return effectiveDataRequirements; + return fhirContextForR5.newJsonParser().parseResource(Library.class, effectiveDrJson); } catch (Exception ex) { log.error( "An error occurred getting effective data requirements " @@ -66,4 +58,13 @@ public Library getEffectiveDataRequirements( ex); } } + + public Library getEffectiveDataRequirements( + CqlLibraryDetails libraryDetails, boolean recursive, String accessToken) { + Library effectiveDataRequirements = + getModuleDefinitionLibrary(libraryDetails, recursive, accessToken); + // effectiveDataRequirements needs to have fixed id: effective-data-requirements + effectiveDataRequirements.setId("effective-data-requirements"); + return effectiveDataRequirements; + } } diff --git a/src/main/java/gov/cms/madie/madiefhirservice/services/MeasureBundleService.java b/src/main/java/gov/cms/madie/madiefhirservice/services/MeasureBundleService.java index 34ca853f..eb5c2bbf 100644 --- a/src/main/java/gov/cms/madie/madiefhirservice/services/MeasureBundleService.java +++ b/src/main/java/gov/cms/madie/madiefhirservice/services/MeasureBundleService.java @@ -1,6 +1,7 @@ package gov.cms.madie.madiefhirservice.services; import gov.cms.madie.madiefhirservice.constants.UriConstants; +import gov.cms.madie.madiefhirservice.dto.CqlLibraryDetails; import gov.cms.madie.madiefhirservice.utils.BundleUtil; import gov.cms.madie.madiefhirservice.utils.FhirResourceHelpers; import gov.cms.madie.madiefhirservice.utils.ResourceUtils; @@ -22,8 +23,10 @@ import org.springframework.stereotype.Service; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; @Slf4j @@ -47,6 +50,7 @@ public Bundle createMeasureBundle( org.hl7.fhir.r4.model.Measure measure = measureTranslatorService.createFhirMeasureForMadieMeasure(madieMeasure); + Set expressions = getExpressions(measure); // Bundle entry for Measure resource Bundle.BundleEntryComponent measureEntryComponent = @@ -55,14 +59,21 @@ public Bundle createMeasureBundle( new Bundle().setType(Bundle.BundleType.TRANSACTION).addEntry(measureEntryComponent); // Bundle entries for all the library resources of a MADiE Measure List libraryEntryComponents = - createBundleComponentsForLibrariesOfMadieMeasure(madieMeasure, bundleType, accessToken); + createBundleComponentsForLibrariesOfMadieMeasure( + expressions, madieMeasure, bundleType, accessToken); libraryEntryComponents.forEach(bundle::addEntry); if (BundleUtil.MEASURE_BUNDLE_TYPE_EXPORT.equals(bundleType)) { + CqlLibraryDetails libraryDetails = + CqlLibraryDetails.builder() + .libraryName(madieMeasure.getCqlLibraryName()) + .cql(madieMeasure.getCql()) + .expressions(expressions) + .build(); // get effective DataRequirements + log.info("Getting effective data requirements for measure: {}", measure.getId()); org.hl7.fhir.r5.model.Library effectiveDataRequirements = - elmTranslatorClient.getEffectiveDataRequirements( - bundle, madieMeasure.getCqlLibraryName(), madieMeasure.getId(), accessToken); + elmTranslatorClient.getEffectiveDataRequirements(libraryDetails, true, accessToken); // get human-readable for measure String humanReadable = humanReadableService.generateMeasureHumanReadable( @@ -87,8 +98,12 @@ public Bundle createMeasureBundle( * @return list of Library BundleEntryComponents */ public List createBundleComponentsForLibrariesOfMadieMeasure( - Measure madieMeasure, final String bundleType, final String accessToken) { - Library library = getMeasureLibraryResourceForMadieMeasure(madieMeasure); + Set expressions, + Measure madieMeasure, + final String bundleType, + final String accessToken) { + Library library = + getMeasureLibraryResourceForMadieMeasure(expressions, madieMeasure, accessToken); Bundle.BundleEntryComponent mainLibraryBundleComponent = FhirResourceHelpers.getBundleEntryComponent(library, "Transaction"); Map includedLibraryMap = new HashMap<>(); @@ -104,14 +119,28 @@ public List createBundleComponentsForLibrariesOfMad } /** - * Creates Library resource for main library of MADiE Measure + * Creates a Library resource for main library of MADiE Measure * - * @param madieMeasure instance of MADiE Measure - * @return Library + * @param expressions- measure populations, SDEs, Stratification + * @param madieMeasure + * @param accessToken + * @return library- r4 library */ - public Library getMeasureLibraryResourceForMadieMeasure(Measure madieMeasure) { + public Library getMeasureLibraryResourceForMadieMeasure( + Set expressions, Measure madieMeasure, String accessToken) { + log.info("Preparing Measure library resource for measure: {}", madieMeasure.getId()); CqlLibrary cqlLibrary = createCqlLibraryForMadieMeasure(madieMeasure); - return libraryTranslatorService.convertToFhirLibrary(cqlLibrary); + CqlLibraryDetails libraryDetails = + CqlLibraryDetails.builder() + .libraryName(cqlLibrary.getCqlLibraryName()) + .cql(cqlLibrary.getCql()) + .expressions(expressions) + .build(); + Library library = libraryTranslatorService.convertToFhirLibrary(cqlLibrary); + org.hl7.fhir.r5.model.Library r5moduleDefinition = + elmTranslatorClient.getModuleDefinitionLibrary(libraryDetails, false, accessToken); + updateLibraryDataRequirements(library, r5moduleDefinition); + return library; } /** @@ -157,4 +186,35 @@ private void addEffectiveDataRequirementsToMeasure( .setValue(new Reference().setReference("#effective-data-requirements")); measure.getExtension().add(extension); } + + private Set getExpressions(org.hl7.fhir.r4.model.Measure r5Measure) { + Set expressionSet = new HashSet<>(); + r5Measure + .getSupplementalData() + .forEach(supData -> expressionSet.add(supData.getCriteria().getExpression())); + r5Measure + .getGroup() + .forEach( + groupMember -> { + groupMember + .getPopulation() + .forEach( + population -> expressionSet.add(population.getCriteria().getExpression())); + groupMember + .getStratifier() + .forEach( + stratifier -> expressionSet.add(stratifier.getCriteria().getExpression())); + }); + return expressionSet; + } + + private void updateLibraryDataRequirements( + org.hl7.fhir.r4.model.Library library, + org.hl7.fhir.r5.model.Library r5moduleDefinitionLibrary) { + var versionConvertor_40_50 = new VersionConvertor_40_50(new BaseAdvisor_40_50()); + org.hl7.fhir.r4.model.Library r4moduleDefinitionLibrary = + (org.hl7.fhir.r4.model.Library) + versionConvertor_40_50.convertResource(r5moduleDefinitionLibrary); + library.setDataRequirement(r4moduleDefinitionLibrary.getDataRequirement()); + } } diff --git a/src/test/java/gov/cms/madie/madiefhirservice/services/ElmTranslatorClientTest.java b/src/test/java/gov/cms/madie/madiefhirservice/services/ElmTranslatorClientTest.java index 99949cb9..7954f198 100644 --- a/src/test/java/gov/cms/madie/madiefhirservice/services/ElmTranslatorClientTest.java +++ b/src/test/java/gov/cms/madie/madiefhirservice/services/ElmTranslatorClientTest.java @@ -2,8 +2,8 @@ import ca.uhn.fhir.context.FhirContext; import gov.cms.madie.madiefhirservice.config.ElmTranslatorClientConfig; +import gov.cms.madie.madiefhirservice.dto.CqlLibraryDetails; import gov.cms.madie.madiefhirservice.exceptions.CqlElmTranslationServiceException; -import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r5.model.Library; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,24 +36,19 @@ public class ElmTranslatorClientTest { @InjectMocks private ElmTranslatorClient elmTranslatorClient; - private Bundle bundle; - @BeforeEach void beforeEach() { lenient().when(elmTranslatorClientConfig.getCqlElmServiceBaseUrl()).thenReturn("http://test"); lenient() .when(elmTranslatorClientConfig.getEffectiveDataRequirementsDataUri()) .thenReturn("/geteffectivedatarequirements"); - bundle = new Bundle().setType(Bundle.BundleType.TRANSACTION); } @Test public void testGetEffectiveDataRequirementsThrowsException() { assertThrows( CqlElmTranslationServiceException.class, - () -> - elmTranslatorClient.getEffectiveDataRequirements( - bundle, "TEST_LIBRARYNAME", "TEST_TOKEN", "TEST_MEASURE_ID")); + () -> elmTranslatorClient.getEffectiveDataRequirements(null, false, "TEST_TOKEN")); } @Test @@ -70,17 +65,15 @@ public void testGetEffectiveDataRequirementsSuccess() { + " } ]\n" + " }\n" + "}"; + CqlLibraryDetails libraryDetails = CqlLibraryDetails.builder().libraryName("Test").build(); when(restTemplate.exchange( any(URI.class), eq(HttpMethod.PUT), any(HttpEntity.class), any(Class.class))) .thenReturn(ResponseEntity.ok(effectiveDR)); - when(fhirContext.newJsonParser()) - .thenReturn(FhirContext.forR4().newJsonParser()) - .thenReturn(FhirContext.forR5().newJsonParser()); + when(fhirContext.newJsonParser()).thenReturn(FhirContext.forR5().newJsonParser()); Library output = - elmTranslatorClient.getEffectiveDataRequirements( - bundle, "TEST_LIBRARY", "TEST_MEASURE_ID", "TEST_TOKEN"); + elmTranslatorClient.getEffectiveDataRequirements(libraryDetails, false, "TEST_TOKEN"); assertThat(output.getId(), is(equalTo("effective-data-requirements"))); } } diff --git a/src/test/java/gov/cms/madie/madiefhirservice/services/MeasureBundleServiceTest.java b/src/test/java/gov/cms/madie/madiefhirservice/services/MeasureBundleServiceTest.java index 9671ccde..ed210a68 100644 --- a/src/test/java/gov/cms/madie/madiefhirservice/services/MeasureBundleServiceTest.java +++ b/src/test/java/gov/cms/madie/madiefhirservice/services/MeasureBundleServiceTest.java @@ -1,17 +1,15 @@ package gov.cms.madie.madiefhirservice.services; -import com.fasterxml.jackson.core.JsonProcessingException; - import ca.uhn.fhir.rest.api.MethodOutcome; +import com.fasterxml.jackson.core.JsonProcessingException; import gov.cms.madie.madiefhirservice.constants.UriConstants; +import gov.cms.madie.madiefhirservice.dto.CqlLibraryDetails; import gov.cms.madie.madiefhirservice.exceptions.CqlLibraryNotFoundException; import gov.cms.madie.madiefhirservice.utils.BundleUtil; import gov.cms.madie.madiefhirservice.utils.MeasureTestHelper; import gov.cms.madie.madiefhirservice.utils.ResourceFileUtil; import gov.cms.madie.models.library.CqlLibrary; import gov.cms.madie.models.measure.Measure; - -import java.security.Principal; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Library; @@ -23,6 +21,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.security.Principal; import java.util.Map; import static org.hamcrest.CoreMatchers.equalTo; @@ -30,6 +29,7 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; @@ -84,7 +84,9 @@ public void testCreateMeasureBundle() { .thenReturn(measure); when(libraryTranslatorService.convertToFhirLibrary(any(CqlLibrary.class))).thenReturn(library); - + when(elmTranslatorClient.getModuleDefinitionLibrary( + any(CqlLibraryDetails.class), anyBoolean(), anyString())) + .thenReturn(effectiveDataRequirements); doAnswer( invocation -> { Object[] args = invocation.getArguments(); @@ -127,7 +129,7 @@ public void testCreateMeasureBundle() { } @Test - public void testCreateMeasureBundleWhenIncludedLibraryNotFoundInHapi() { + public void testCreateMeasureBundleWhenIncludedLibraryNotFound() { when(measureTranslatorService.createFhirMeasureForMadieMeasure(madieMeasure)) .thenReturn(measure); @@ -136,6 +138,9 @@ public void testCreateMeasureBundleWhenIncludedLibraryNotFoundInHapi() { doThrow(new CqlLibraryNotFoundException("FHIRHelpers", "4.0.001")) .when(libraryService) .getIncludedLibraries(anyString(), any(), anyString(), anyString()); + when(elmTranslatorClient.getModuleDefinitionLibrary( + any(CqlLibraryDetails.class), anyBoolean(), anyString())) + .thenReturn(effectiveDataRequirements); Exception exception = Assertions.assertThrows( CqlLibraryNotFoundException.class, @@ -169,8 +174,12 @@ public void testCreateMeasureBundleForExport() { .getIncludedLibraries(anyString(), anyMap(), anyString(), anyString()); when(elmTranslatorClient.getEffectiveDataRequirements( - any(Bundle.class), anyString(), anyString(), anyString())) + any(CqlLibraryDetails.class), anyBoolean(), anyString())) .thenReturn(effectiveDataRequirements); + when(elmTranslatorClient.getModuleDefinitionLibrary( + any(CqlLibraryDetails.class), anyBoolean(), anyString())) + .thenReturn(effectiveDataRequirements); + when(humanReadableService.generateMeasureHumanReadable( any(Measure.class), any(Bundle.class), any(org.hl7.fhir.r5.model.Library.class))) .thenReturn(humanReadable);