From 99d99bb46b6d9ddddee74de48f6cf78cf613d832 Mon Sep 17 00:00:00 2001 From: chubert-sb <85900348+chubert-sb@users.noreply.github.com> Date: Mon, 9 Oct 2023 10:07:18 -0400 Subject: [PATCH] Add endpoint for relevant qdm elements (#98) --- .../controllers/CqlToolsController.java | 9 +++ .../service/DataCriteriaService.java | 61 ++++++++++++-- .../controllers/CqlToolsControllerTest.java | 18 +++++ .../service/DataCriteriaServiceTest.java | 80 +++++++++++++++++++ 4 files changed, 162 insertions(+), 6 deletions(-) diff --git a/src/main/java/gov/cms/mat/cql_elm_translation/controllers/CqlToolsController.java b/src/main/java/gov/cms/mat/cql_elm_translation/controllers/CqlToolsController.java index e724c20a..230cd2cf 100644 --- a/src/main/java/gov/cms/mat/cql_elm_translation/controllers/CqlToolsController.java +++ b/src/main/java/gov/cms/mat/cql_elm_translation/controllers/CqlToolsController.java @@ -10,6 +10,7 @@ import lombok.extern.slf4j.Slf4j; import org.cqframework.cql.tools.formatter.CqlFormatterVisitor; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -71,4 +72,12 @@ public ResponseEntity> getLibraryElms( return ResponseEntity.status(HttpStatus.OK) .body(cqlConversionService.getElmForCql(cql, accessToken)); } + + @PutMapping("/qdm/relevant-elements") + public ResponseEntity> getRelevantElements( + @RequestBody Measure measure, @RequestHeader("Authorization") String accessToken) { + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(dataCriteriaService.getRelevantElements(measure, accessToken)); + } } diff --git a/src/main/java/gov/cms/mat/cql_elm_translation/service/DataCriteriaService.java b/src/main/java/gov/cms/mat/cql_elm_translation/service/DataCriteriaService.java index daae01d6..a12d3720 100644 --- a/src/main/java/gov/cms/mat/cql_elm_translation/service/DataCriteriaService.java +++ b/src/main/java/gov/cms/mat/cql_elm_translation/service/DataCriteriaService.java @@ -1,5 +1,6 @@ package gov.cms.mat.cql_elm_translation.service; +import gov.cms.madie.models.measure.Measure; import gov.cms.mat.cql_elm_translation.cql_translator.MadieLibrarySourceProvider; import gov.cms.mat.cql_elm_translation.data.DataCriteria; import gov.cms.mat.cql_elm_translation.data.RequestData; @@ -21,11 +22,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; @Slf4j @@ -36,6 +33,10 @@ public class DataCriteriaService { private final CqlConversionService cqlConversionService; public DataCriteria parseDataCriteriaFromCql(String cql, String accessToken) { + return parseCql(cql, accessToken).getDataCriteria(); + } + + private CQLTools parseCql(String cql, String accessToken) { // Run Translator to compile libraries MadieLibrarySourceProvider librarySourceProvider = new MadieLibrarySourceProvider(); cqlConversionService.setUpLibrarySourceProvider(cql, accessToken); @@ -53,7 +54,55 @@ public DataCriteria parseDataCriteriaFromCql(String cql, String accessToken) { } catch (IOException e) { throw new RuntimeException(e); } - return cqlTools.getDataCriteria(); + return cqlTools; + } + + public List getRelevantElements(Measure measure, String accessToken) { + if (StringUtils.isBlank(measure.getCql())) { + log.info("Data criteria not found as cql is blank"); + return Collections.emptyList(); + } + + List sourceDataCriteria = + getSourceDataCriteria(measure.getCql(), accessToken); + + CQLTools tools = parseCql(measure.getCql(), accessToken); + + Set usedDefinitions = new HashSet<>(); + measure + .getGroups() + .forEach( + group -> { + group + .getPopulations() + .forEach( + population -> { + if (!population.getDefinition().isEmpty()) { + usedDefinitions.add(population.getDefinition()); + } + }); + }); + + Set values = new HashSet<>(); + usedDefinitions.forEach( + def -> { + if (!MapUtils.isEmpty(tools.getExpressionNameToValuesetDataTypeMap())) { + tools + .getExpressionNameToValuesetDataTypeMap() + .get(def) + .forEach((expression, valueSet) -> values.add(expression)); + } + if (!MapUtils.isEmpty(tools.getExpressionNameToCodeDataTypeMap())) { + tools + .getExpressionNameToCodeDataTypeMap() + .get(def) + .forEach((expression, valueSet) -> values.add(expression)); + } + }); + + return sourceDataCriteria.stream() + .filter(sourceDataCriteria1 -> values.contains(sourceDataCriteria1.getTitle())) + .toList(); } public List getSourceDataCriteria(String cql, String accessToken) { diff --git a/src/test/java/gov/cms/mat/cql_elm_translation/controllers/CqlToolsControllerTest.java b/src/test/java/gov/cms/mat/cql_elm_translation/controllers/CqlToolsControllerTest.java index 462ddc5f..de520af1 100644 --- a/src/test/java/gov/cms/mat/cql_elm_translation/controllers/CqlToolsControllerTest.java +++ b/src/test/java/gov/cms/mat/cql_elm_translation/controllers/CqlToolsControllerTest.java @@ -1,6 +1,8 @@ package gov.cms.mat.cql_elm_translation.controllers; +import gov.cms.madie.models.measure.Group; import gov.cms.madie.models.measure.Measure; +import gov.cms.madie.models.measure.Population; import gov.cms.mat.cql_elm_translation.ResourceFileUtil; import gov.cms.mat.cql_elm_translation.dto.SourceDataCriteria; import gov.cms.mat.cql_elm_translation.exceptions.CqlFormatException; @@ -15,6 +17,7 @@ import org.springframework.http.HttpStatus; import java.security.Principal; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -110,6 +113,21 @@ void testGenerateHumanReadable() { assertEquals(result.getStatusCode(), HttpStatus.OK); } + @Test + void testGetRelevantElements() { + String cql = getData("/qdm_data_criteria_retrieval_test.cql"); + Measure measure = Measure.builder().cql(cql).build(); + String token = "john"; + var sdc = SourceDataCriteria.builder().oid("1.2.3").description("EP: Test").title("EP").build(); + when(dataCriteriaService.getRelevantElements(any(Measure.class), anyString())) + .thenReturn(List.of(sdc)); + var result = cqlToolsController.getRelevantElements(measure, token); + SourceDataCriteria sourceDataCriteria = result.getBody().get(0); + assertThat(sourceDataCriteria.getOid(), is(equalTo(sdc.getOid()))); + assertThat(sourceDataCriteria.getDescription(), is(equalTo(sdc.getDescription()))); + assertThat(sourceDataCriteria.getTitle(), is(equalTo(sdc.getTitle()))); + } + private boolean inputMatchesOutput(String input, String output) { return input .replaceAll("[\\s\\u0000\\u00a0]", "") diff --git a/src/test/java/gov/cms/mat/cql_elm_translation/service/DataCriteriaServiceTest.java b/src/test/java/gov/cms/mat/cql_elm_translation/service/DataCriteriaServiceTest.java index 1e912d45..4a7421c6 100644 --- a/src/test/java/gov/cms/mat/cql_elm_translation/service/DataCriteriaServiceTest.java +++ b/src/test/java/gov/cms/mat/cql_elm_translation/service/DataCriteriaServiceTest.java @@ -1,5 +1,8 @@ package gov.cms.mat.cql_elm_translation.service; +import gov.cms.madie.models.measure.Group; +import gov.cms.madie.models.measure.Measure; +import gov.cms.madie.models.measure.Population; import gov.cms.mat.cql_elm_translation.ResourceFileUtil; import gov.cms.mat.cql_elm_translation.cql_translator.TranslationResource; import gov.cms.mat.cql_elm_translation.data.RequestData; @@ -14,6 +17,7 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.Collections; import java.util.List; import static org.hamcrest.CoreMatchers.equalTo; @@ -120,4 +124,80 @@ void testGetSourceDataCriteriaWhenNoCqlProvided() { dataCriteriaService.getSourceDataCriteria("", token); assertThat(sourceDataCriteria.size(), is(equalTo(0))); } + + @Test + void testGetRelevantElements() { + Population population = Population.builder().definition("Qualifying Encounters").build(); + Group group = Group.builder().populations(Collections.singletonList(population)).build(); + Measure measure = Measure.builder().cql(cql).groups(Collections.singletonList(group)).build(); + CqlTranslator translator = + TranslationResource.getInstance(false) + .buildTranslator(requestData.getCqlDataInputStream(), requestData.createMap()); + + Mockito.doNothing() + .when(cqlConversionService) + .setUpLibrarySourceProvider(anyString(), anyString()); + when(cqlConversionService.processCqlData(any(RequestData.class))).thenReturn(translator); + + List relevantElements = + dataCriteriaService.getRelevantElements(measure, token); + + // source data criteria for value set + assertThat(relevantElements.size(), is(equalTo(2))); + assertThat(relevantElements.get(0).getOid(), is(equalTo("2.16.840.1.113883.3.666.5.307"))); + assertThat(relevantElements.get(0).getTitle(), is(equalTo("Encounter Inpatient"))); + assertThat(relevantElements.get(0).getType(), is(equalTo("EncounterPerformed"))); + assertThat( + relevantElements.get(0).getDescription(), + is(equalTo("Encounter, Performed: Encounter Inpatient"))); + assertFalse(relevantElements.get(0).isDrc()); + + // source data criteria for direct reference code + assertTrue(relevantElements.get(1).isDrc()); + assertThat(relevantElements.get(1).getTitle(), is(equalTo("Clinical Examples"))); + assertThat(relevantElements.get(1).getType(), is(equalTo("EncounterPerformed"))); + assertThat( + relevantElements.get(1).getDescription(), + is(equalTo("Encounter, Performed: Clinical Examples"))); + } + + @Test + void testGetRelevantElementsWhenNoSourceCriteriaFound() { + String cql = + "library DataCriteriaRetrivalTest version '0.0.000'\n" + + "using QDM version '5.6'\n" + + "valueset \"Encounter Inpatient\": 'urn:oid:2.16.840.1.113883.3.666.5.307'\n" + + "parameter \"Measurement Period\" Interval\n" + + "context Patient\n" + + "define \"Qualifying Encounters\":\n true"; + + Population population = Population.builder().definition("Qualifying Encounters").build(); + Group group = Group.builder().populations(Collections.singletonList(population)).build(); + Measure measure = Measure.builder().cql(cql).groups(Collections.singletonList(group)).build(); + + RequestData data = requestData.toBuilder().cqlData(cql).build(); + CqlTranslator translator = + TranslationResource.getInstance(false) + .buildTranslator(data.getCqlDataInputStream(), data.createMap()); + + Mockito.doNothing() + .when(cqlConversionService) + .setUpLibrarySourceProvider(anyString(), anyString()); + when(cqlConversionService.processCqlData(any(RequestData.class))).thenReturn(translator); + + List sourceDataCriteria = + dataCriteriaService.getRelevantElements(measure, token); + + assertThat(sourceDataCriteria.size(), is(equalTo(0))); + } + + @Test + void testGetRelevantElementsWhenNoCqlProvided() { + Population population = Population.builder().definition("Qualifying Encounters").build(); + Group group = Group.builder().populations(Collections.singletonList(population)).build(); + Measure measure = Measure.builder().cql("").groups(Collections.singletonList(group)).build(); + List sourceDataCriteria = + dataCriteriaService.getRelevantElements(measure, token); + assertThat(sourceDataCriteria.size(), is(equalTo(0))); + } }