diff --git a/pom.xml b/pom.xml index 6b2166f..055e6e5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 gov.cms.madie madie-translator-commons - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT UTF-8 17 diff --git a/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/CQLTools.java b/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/CQLTools.java index 8da0f57..f7f956d 100644 --- a/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/CQLTools.java +++ b/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/CQLTools.java @@ -398,12 +398,13 @@ private void collectReturnTypeMap() { this.allNamesToReturnTypeMap.put(libraryName + "-" + libraryVersion, new HashMap<>()); for (ExpressionDef expression : statements.getDef()) { + String resultType = + expression.getResultType() == null ? null : expression.getResultType().toString(); this.allNamesToReturnTypeMap .get(libraryName + "-" + libraryVersion) - .put(expression.getName(), expression.getResultType().toString()); - this.nameToReturnTypeMap.put(expression.getName(), expression.getResultType().toString()); - this.expressionToReturnTypeMap.put( - expression.getName(), expression.getResultType().toString()); + .put(expression.getName(), resultType); + this.nameToReturnTypeMap.put(expression.getName(), resultType); + this.expressionToReturnTypeMap.put(expression.getName(), resultType); } if (parameters != null) { diff --git a/src/test/java/gov/cms/madie/cql_elm_translator/utils/cql/CQLToolsTest.java b/src/test/java/gov/cms/madie/cql_elm_translator/utils/cql/CQLToolsTest.java index b337490..e93d9d0 100644 --- a/src/test/java/gov/cms/madie/cql_elm_translator/utils/cql/CQLToolsTest.java +++ b/src/test/java/gov/cms/madie/cql_elm_translator/utils/cql/CQLToolsTest.java @@ -1,18 +1,29 @@ package gov.cms.madie.cql_elm_translator.utils.cql; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import lombok.extern.slf4j.Slf4j; import org.cqframework.cql.cql2elm.CqlTranslator; import org.cqframework.cql.cql2elm.model.CompiledLibrary; import org.cqframework.cql.cql2elm.model.ResolvedIdentifierContext; +import org.hl7.cql.model.DataType; +import org.hl7.elm.r1.IncludeDef; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -26,6 +37,7 @@ import org.hl7.elm.r1.ExpressionDef; @ExtendWith(MockitoExtension.class) +@Slf4j class CQLToolsTest { @Mock private CqlTranslator cqlTranslator; @@ -33,6 +45,8 @@ class CQLToolsTest { @Mock private Library library; @Mock private ResolvedIdentifierContext element; @Mock private Statements statements; + @Mock private Library.Includes includes; + @Mock private Map compiledLibraryMap; VersionedIdentifier versionedIdentifier = new VersionedIdentifier(); @@ -59,4 +73,66 @@ void testGenerate() { fail(); } } + + @Test + void testItHandlesNullExpressionResultType() { + versionedIdentifier.setId("local"); + String cqlData = ResourceUtils.getData("/tooling_test_qicore6.cql"); + doReturn(statements).when(library).getStatements(); + doReturn(compiledLibrary).when(cqlTranslator).getTranslatedLibrary(); + doReturn(library).when(compiledLibrary).getLibrary(); + doReturn(versionedIdentifier).when(compiledLibrary).getIdentifier(); + doReturn(element).when(compiledLibrary).resolve(any(String.class)); + doReturn(includes).when(library).getIncludes(); + + ExpressionDef expression1 = mock(ExpressionDef.class); + DataType booleanDataType = mock(DataType.class); + doReturn(booleanDataType).when(expression1).getResultType(); + doReturn("expression1").when(expression1).getName(); + doReturn("System.Boolean").when(booleanDataType).toString(); + + ExpressionDef expression2 = mock(ExpressionDef.class); + doReturn("expression2").when(expression2).getName(); + doReturn(null).when(expression2).getResultType(); + + List expressionDefs = List.of(expression1, expression2); + + doReturn(expressionDefs).when(statements).getDef(); + + Set parentExpressions = new HashSet<>(); + IncludeDef includeDef1 = mock(IncludeDef.class); + List includeDefs = List.of(includeDef1); + doReturn(includeDefs).when(includes).getDef(); + doReturn("SupplementalDataElements").when(includeDef1).getPath(); + CompiledLibrary sdeCompiledLib = mock(CompiledLibrary.class); + Library sdeLib = mock(Library.class); + doReturn(sdeLib).when(sdeCompiledLib).getLibrary(); + VersionedIdentifier sdeLibVersionedIdentifier = new VersionedIdentifier(); + sdeLibVersionedIdentifier.setId("SupplementalDataElements"); + doReturn(sdeLibVersionedIdentifier).when(sdeCompiledLib).getIdentifier(); + + Library.Statements statements1 = mock(Library.Statements.class); + doReturn(statements1).when(sdeLib).getStatements(); + Library.Parameters parameters1 = mock(Library.Parameters.class); + doReturn(parameters1).when(sdeLib).getParameters(); + + doReturn(sdeCompiledLib).when(compiledLibraryMap).get(anyString()); + + CQLTools cqlTools = + new CQLTools(cqlData, null, parentExpressions, cqlTranslator, compiledLibraryMap); + assertNotNull(cqlTools); + try { + cqlTools.generate(); + Set contents = cqlTools.getDefinitionContents(); + assertNotNull(contents); + Map expressionToReturnTypeMap = cqlTools.getExpressionToReturnTypeMap(); + assertThat(expressionToReturnTypeMap, is(notNullValue())); + assertThat(expressionToReturnTypeMap.containsKey("expression1"), is(true)); + assertThat(expressionToReturnTypeMap.containsKey("expression2"), is(true)); + assertThat(expressionToReturnTypeMap.get("expression1"), is(equalTo("System.Boolean"))); + assertThat(expressionToReturnTypeMap.get("expression2"), is(nullValue())); + } catch (IOException e) { + fail(); + } + } } diff --git a/src/test/resources/tooling_test_qicore6.cql b/src/test/resources/tooling_test_qicore6.cql new file mode 100644 index 0000000..e2aaf9a --- /dev/null +++ b/src/test/resources/tooling_test_qicore6.cql @@ -0,0 +1,61 @@ +library QM61 version '0.0.000' + +using QICore version '6.0.0' + +include QICoreCommon version '3.0.000' called QICoreCommon +include FHIRHelpers version '4.4.000' called FHIRHelpers +include SupplementalDataElements version '4.3.000' called SDE +include Hospice version '7.0.000' called Hospice +include Status version '2.0.000' called Status + +codesystem "LOINC": 'http://loinc.org' +codesystem "SNOMEDCT": 'http://snomed.info/sct' + +valueset "Clinical Oral Evaluation": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.125.12.1003' +valueset "Dental Caries": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.125.12.1004' +valueset "Discharged to Health Care Facility for Hospice Care": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.207' +valueset "Discharged to Home for Hospice Care": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.209' +valueset "Encounter Inpatient": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.666.5.307' + +code "Discharge to healthcare facility for hospice care (procedure)": '428371000124100' from "SNOMEDCT" display 'Discharge to healthcare facility for hospice care (procedure)' +code "Discharge to home for hospice care (procedure)": '428361000124107' from "SNOMEDCT" display 'Discharge to home for hospice care (procedure)' +code "Hospice care [Minimum Data Set]": '45755-6' from "LOINC" display 'Hospice care [Minimum Data Set]' +code "Yes (qualifier value)": '373066001' from "SNOMEDCT" display 'Yes (qualifier value)' + +parameter "Measurement Period" Interval + +context Patient + + +define "Initial Population": + AgeInYearsAt(date from start of "Measurement Period")in Interval[1, 20] + and exists ( "Qualifying Encounters" ) + +define "Qualifying Encounters": + (([Encounter: "Clinical Oral Evaluation"]).isEncounterPerformed()) ValidEncounter + where ValidEncounter.period.toInterval() during day of "Measurement Period" + +define "Denominator": + "Initial Population" + +define "Denominator Exclusions": + Hospice."Has Hospice Services" + +define "Numerator": +exists ["QICore Condition Problems Health Concerns": "Dental Caries"] DentalCaries + where DentalCaries.prevalenceInterval() overlaps "Measurement Period" + +define "SDE Ethnicity": + SDE."SDE Ethnicity" + +define "SDE Payer": + SDE."SDE Payer" + +define "SDE Race": + SDE."SDE Race" + +define "SDE Sex": + SDE."SDE Sex" + +define "SDE Eth": + SDE."SDE Eth"