From 10821d8b6773f1d23d76853803ba4a66d83ce9ce Mon Sep 17 00:00:00 2001 From: Greg Akins Date: Thu, 21 Nov 2024 06:45:42 -0500 Subject: [PATCH] MAT-7897: Adding line number to definition so they can be ordered when displayed --- pom.xml | 20 ++- .../dto/CqlBuilderLookup.java | 1 + .../utils/cql/CQLTools.java | 7 +- .../utils/cql/parsing/Cql2ElmListener.java | 7 +- .../cql/parsing/model/CQLDefinition.java | 10 +- .../cql/parsing/model/DefinitionContent.java | 1 + .../utils/cql/CQLToolsTest.java | 62 ++++++++ src/test/resources/tooling_test.cql | 146 ++++++++++++++++++ 8 files changed, 242 insertions(+), 12 deletions(-) create mode 100644 src/test/java/gov/cms/madie/cql_elm_translator/utils/cql/CQLToolsTest.java create mode 100644 src/test/resources/tooling_test.cql diff --git a/pom.xml b/pom.xml index 16eaad5..0e53b6c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 gov.cms.madie madie-translator-commons - 1.0.1 + 1.0.2-SNAPSHOT UTF-8 17 @@ -68,11 +68,11 @@ cql-to-elm ${cqframework.version} - - gov.cms.madie - madie-rest-commons - ${madie.rest.commons.version} - + + gov.cms.madie + madie-rest-commons + ${madie.rest.commons.version} + javax.ws.rs javax.ws.rs-api @@ -92,6 +92,14 @@ ${hamcrest.version} test + + + + org.eclipse.persistence + org.eclipse.persistence.moxy + 4.0.2 + test + org.mockito diff --git a/src/main/java/gov/cms/madie/cql_elm_translator/dto/CqlBuilderLookup.java b/src/main/java/gov/cms/madie/cql_elm_translator/dto/CqlBuilderLookup.java index 55e3386..59985c4 100644 --- a/src/main/java/gov/cms/madie/cql_elm_translator/dto/CqlBuilderLookup.java +++ b/src/main/java/gov/cms/madie/cql_elm_translator/dto/CqlBuilderLookup.java @@ -20,5 +20,6 @@ public static class Lookup { private String libraryName; private String libraryAlias; private String logic; + private int startLine; } } 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 6fa74ab..40bfddc 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 @@ -131,9 +131,10 @@ public void generate() throws IOException { TranslationResource translationResource = TranslationResource.getInstance( - usingProperties.getLibraryType() == "FHIR" - || usingProperties.getLibraryType() - == "QICore"); // <-- BADDDDD!!!! Defaults to fhir + usingProperties.getLibraryType().equals("FHIR") + || usingProperties + .getLibraryType() + .equals("QICore")); // <-- BADDDDD!!!! Defaults to fhir CqlPreprocessorElmCommonVisitor preprocessor = new CqlPreprocessorElmCommonVisitor( diff --git a/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/Cql2ElmListener.java b/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/Cql2ElmListener.java index 897bb7b..3dbda2b 100644 --- a/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/Cql2ElmListener.java +++ b/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/Cql2ElmListener.java @@ -272,7 +272,12 @@ public void enterExpressionDefinition(@NotNull cqlParser.ExpressionDefinitionCon .getInputStream() .getText(new Interval(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex())); definitionContents.add( - DefinitionContent.builder().name(currentContext).content(content).build()); + // add line the def starts on to DefinitionContent + DefinitionContent.builder() + .startLine(ctx.getStart().getLine()) + .name(currentContext) + .content(content) + .build()); graph.addNode(currentContext); } diff --git a/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/model/CQLDefinition.java b/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/model/CQLDefinition.java index f59df5a..b886f98 100644 --- a/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/model/CQLDefinition.java +++ b/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/model/CQLDefinition.java @@ -17,15 +17,16 @@ public class CQLDefinition implements CQLExpression { private String uuid; private String definitionName; private String definitionLogic; - private String context = "Patient"; + @Builder.Default private String context = "Patient"; private boolean supplDataElement; private boolean popDefinition; - private String commentString = ""; + @Builder.Default private String commentString = ""; private String returnType; private String parentLibrary; private String libraryDisplayName; private String libraryVersion; private boolean isFunction; + private int startLine; private List functionArguments; public static class Comparator implements java.util.Comparator { @@ -85,4 +86,9 @@ public boolean equals(Object o) { CQLDefinition that = (CQLDefinition) o; return Objects.equals(definitionName, that.definitionName); } + + @Override + public int hashCode() { + return Objects.hash(uuid); + } } diff --git a/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/model/DefinitionContent.java b/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/model/DefinitionContent.java index c310dce..0df005b 100644 --- a/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/model/DefinitionContent.java +++ b/src/main/java/gov/cms/madie/cql_elm_translator/utils/cql/parsing/model/DefinitionContent.java @@ -12,4 +12,5 @@ public class DefinitionContent { private String content; private List functionArguments; private boolean function; // MAT-7450 + private int startLine; // MAT-7897 } 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 new file mode 100644 index 0000000..d5a2b9d --- /dev/null +++ b/src/test/java/gov/cms/madie/cql_elm_translator/utils/cql/CQLToolsTest.java @@ -0,0 +1,62 @@ +package gov.cms.madie.cql_elm_translator.utils.cql; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.cqframework.cql.cql2elm.CqlTranslator; +import org.cqframework.cql.cql2elm.model.CompiledLibrary; +import org.hl7.elm.r1.Element; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import gov.cms.madie.cql_elm_translator.utils.ResourceUtils; +import gov.cms.madie.cql_elm_translator.utils.cql.parsing.model.DefinitionContent; +import org.hl7.elm.r1.Library.Statements; +import org.hl7.elm.r1.VersionedIdentifier; +import org.hl7.elm.r1.Library; +import org.hl7.elm.r1.ExpressionDef; + +@ExtendWith(MockitoExtension.class) +class CQLToolsTest { + + @Mock private CqlTranslator cqlTranslator; + @Mock private CompiledLibrary compiledLibrary; + @Mock private Library library; + @Mock private Element element; + @Mock private Statements statements; + + VersionedIdentifier versionedIdentifier = new VersionedIdentifier(); + + List expressionDefinitions = new ArrayList<>(); + + @Test + void testGenerate() { + versionedIdentifier.setId("local"); + String cqlData = ResourceUtils.getData("/tooling_test.cql"); + doReturn(statements).when(library).getStatements(); + doReturn(expressionDefinitions).when(statements).getDef(); + doReturn(compiledLibrary).when(cqlTranslator).getTranslatedLibrary(); + doReturn(library).when(compiledLibrary).getLibrary(); + doReturn(versionedIdentifier).when(compiledLibrary).getIdentifier(); + doReturn(element).when(compiledLibrary).resolve(any(String.class)); + Set parentExpressions = new HashSet<>(); + CQLTools cqlTools = new CQLTools(cqlData, null, parentExpressions, cqlTranslator, null); + assertNotNull(cqlTools); + try { + cqlTools.generate(); + Set contents = cqlTools.getDefinitionContents(); + assertNotNull(contents); + } catch (IOException e) { + fail(); + } + } +} diff --git a/src/test/resources/tooling_test.cql b/src/test/resources/tooling_test.cql new file mode 100644 index 0000000..1c37df6 --- /dev/null +++ b/src/test/resources/tooling_test.cql @@ -0,0 +1,146 @@ +library CMS986v4 version '1.0.000' + +using QDM version '5.6' + +include MATGlobalCommonFunctionsQDM version '8.0.000' called Global + +codesystem "ICD10CM": 'urn:oid:2.16.840.1.113883.6.90' +codesystem "SNOMEDCT": 'urn:oid:2.16.840.1.113883.6.96' +codesystem "LOINC": 'urn:oid:2.16.840.1.113883.6.1' + +valueset "Encounter Inpatient": 'urn:oid:2.16.840.1.113883.3.666.5.307' +valueset "Ethnicity": 'urn:oid:2.16.840.1.114222.4.11.837' +valueset "Hospital Dietitian Referral": 'urn:oid:2.16.840.1.113762.1.4.1095.91' +valueset "Malnutrition Diagnosis": 'urn:oid:2.16.840.1.113762.1.4.1095.55' +valueset "Malnutrition Risk Screening": 'urn:oid:2.16.840.1.113762.1.4.1095.92' +valueset "Malnutrition Screening Finding of At Risk Result": 'urn:oid:2.16.840.1.113762.1.4.1095.89' +valueset "Malnutrition Screening Finding of Not At Risk Result": 'urn:oid:2.16.840.1.113762.1.4.1095.34' +valueset "Nutrition Assessment": 'urn:oid:2.16.840.1.113762.1.4.1095.21' +valueset "Nutrition Assessment Status Finding of Moderately Malnourished": 'urn:oid:2.16.840.1.113762.1.4.1095.47' +valueset "Nutrition Assessment Status Finding of Severely Malnourished": 'urn:oid:2.16.840.1.113762.1.4.1095.43' +valueset "Nutrition Assessment Status Finding of Well Nourished or Not Malnourished or Mildly Malnourished": 'urn:oid:2.16.840.1.113762.1.4.1095.96' +valueset "Nutrition Care Plan": 'urn:oid:2.16.840.1.113762.1.4.1095.93' +valueset "ONC Administrative Sex": 'urn:oid:2.16.840.1.113762.1.4.1' +valueset "Payer Type": 'urn:oid:2.16.840.1.114222.4.11.3591' +valueset "Race": 'urn:oid:2.16.840.1.114222.4.11.836' + +code "Status post administration of tPA (rtPA) in a different facility within the last 24 hours prior to admission to current facility": 'Z92.82' from "ICD10CM" display 'Status post administration of tPA (rtPA) in a different facility within the last 24 hours prior to admission to current facility' +code "Angina pectoris with documented spasm": 'I20.1' from "ICD10CM" display 'Angina pectoris with documented spasm' +code "Takotsubo syndrome": 'I51.81' from "ICD10CM" display 'Takotsubo syndrome' + + +parameter "Measurement Muggle" Interval + +parameter "Measurement Period" Interval + +context Patient + +define "SDE Payer": + ["Patient Characteristic Payer": "Payer Type"] + +define "SDE Ethnicity": + ["Patient Characteristic Ethnicity": "Ethnicity"] + +define "SDE Race": + ["Patient Characteristic Race": "Race"] + +define "SDE Sex": + ["Patient Characteristic Sex": "ONC Administrative Sex"] + +define "Measure Population": + "Initial Population" + +define "Initial Population": + ["Encounter, Performed": "Encounter Inpatient"] EncounterInpatient + where AgeInYearsAt(date from start of EncounterInpatient.relevantPeriod) >= 65 + and duration in hours of EncounterInpatient.relevantPeriod >= 24 + and EncounterInpatient.relevantPeriod during day of "Measurement Period" + +define "Encounter with Hospital Dietitian Referral": + "Initial Population" QualifyingEncounter + with ["Intervention, Order": "Hospital Dietitian Referral"] HospitalDietitianReferral + such that HospitalDietitianReferral.authorDatetime during Global."HospitalizationWithObservation" ( QualifyingEncounter ) + +define "Encounter with Malnutrition Risk Screening and Identified Result or with Hospital Dietitian Referral": + "Initial Population" QualifyingEncounter + with ["Assessment, Performed": "Malnutrition Risk Screening"] MalnutritionRiskScreening + such that Coalesce(start of Global."NormalizeInterval"(MalnutritionRiskScreening.relevantDatetime, MalnutritionRiskScreening.relevantPeriod), MalnutritionRiskScreening.authorDatetime) during Global."HospitalizationWithObservation" ( QualifyingEncounter ) + and ( ( MalnutritionRiskScreening.result in "Malnutrition Screening Finding of At Risk Result" ) + or ( MalnutritionRiskScreening.result in "Malnutrition Screening Finding of Not At Risk Result" ) + ) + or exists ( "Encounter with Hospital Dietitian Referral" ) + +define "Encounter with Malnutrition Screening Not At Risk Result": + "Initial Population" QualifyingEncounter + with ["Assessment, Performed": "Malnutrition Risk Screening"] MalnutritionRiskScreening + such that Coalesce(start of Global."NormalizeInterval"(MalnutritionRiskScreening.relevantDatetime, MalnutritionRiskScreening.relevantPeriod), MalnutritionRiskScreening.authorDatetime) during Global."HospitalizationWithObservation" ( QualifyingEncounter ) + and MalnutritionRiskScreening.result in "Malnutrition Screening Finding of Not At Risk Result" + +define "Encounter with Malnutrition Not at Risk Result and without Hospital Dietitian Referral": + "Initial Population" QualifyingEncounter + with ["Assessment, Performed": "Malnutrition Risk Screening"] MalnutritionRiskScreening + such that exists ( "Encounter with Malnutrition Screening Not At Risk Result" ) + and not exists ( "Encounter with Hospital Dietitian Referral" ) + +define "Encounter with Malnutrition Screening At Risk Result": + "Initial Population" QualifyingEncounter + with ["Assessment, Performed": "Malnutrition Risk Screening"] MalnutritionRiskScreening + such that Coalesce(start of Global."NormalizeInterval"(MalnutritionRiskScreening.relevantDatetime, MalnutritionRiskScreening.relevantPeriod), MalnutritionRiskScreening.authorDatetime) during Global."HospitalizationWithObservation" ( QualifyingEncounter ) + and MalnutritionRiskScreening.result in "Malnutrition Screening Finding of At Risk Result" + and not ( exists "Encounter with Malnutrition Screening Not At Risk Result" ) + +define "Encounter with Nutrition Assessment and Identified Status": + "Initial Population" QualifyingEncounter + with ["Assessment, Performed": "Nutrition Assessment"] NutritionAssessment + such that Coalesce(start of Global."NormalizeInterval"(NutritionAssessment.relevantDatetime, NutritionAssessment.relevantPeriod), NutritionAssessment.authorDatetime) during Global."HospitalizationWithObservation" ( QualifyingEncounter ) + and ( NutritionAssessment.result in "Nutrition Assessment Status Finding of Well Nourished or Not Malnourished or Mildly Malnourished" + or ( NutritionAssessment.result in "Nutrition Assessment Status Finding of Moderately Malnourished" ) + or ( NutritionAssessment.result in "Nutrition Assessment Status Finding of Severely Malnourished" ) + ) + and ( exists "Encounter with Malnutrition Screening At Risk Result" + or exists ( "Encounter with Hospital Dietitian Referral" ) + ) + +define "Encounter with Nutrition Assessment Status Not or Mildly Malnourished": + "Initial Population" QualifyingEncounter + with ["Assessment, Performed": "Nutrition Assessment"] NutritionAssessment + such that Coalesce(start of Global."NormalizeInterval"(NutritionAssessment.relevantDatetime, NutritionAssessment.relevantPeriod), NutritionAssessment.authorDatetime) during Global."HospitalizationWithObservation" ( QualifyingEncounter ) + and NutritionAssessment.result in "Nutrition Assessment Status Finding of Well Nourished or Not Malnourished or Mildly Malnourished" + +define "Encounter with Nutrition Assessment Status Finding of Moderately Malnourished": + "Initial Population" QualifyingEncounter + with ["Assessment, Performed": "Nutrition Assessment"] NutritionAssessment + such that Coalesce(start of Global."NormalizeInterval"(NutritionAssessment.relevantDatetime, NutritionAssessment.relevantPeriod), NutritionAssessment.authorDatetime) during Global."HospitalizationWithObservation" ( QualifyingEncounter ) + and NutritionAssessment.result in "Nutrition Assessment Status Finding of Moderately Malnourished" + +define "Encounter with Nutrition Assessment Status Finding of Severely Malnourished": + "Initial Population" QualifyingEncounter + with ["Assessment, Performed": "Nutrition Assessment"] NutritionAssessment + such that Coalesce(start of Global."NormalizeInterval"(NutritionAssessment.relevantDatetime, NutritionAssessment.relevantPeriod), NutritionAssessment.authorDatetime) during Global."HospitalizationWithObservation" ( QualifyingEncounter ) + and NutritionAssessment.result in "Nutrition Assessment Status Finding of Severely Malnourished" + +define "Encounter with Malnutrition Diagnosis": + "Initial Population" QualifyingEncounter + with ["Diagnosis": "Malnutrition Diagnosis"] MalnutritionDiagnosis + such that MalnutritionDiagnosis.prevalencePeriod overlaps Global."HospitalizationWithObservation" ( QualifyingEncounter ) + +define "Encounter with Nutrition Care Plan": + "Initial Population" QualifyingEncounter + with ["Intervention, Performed": "Nutrition Care Plan"] NutritionCarePlan + such that Coalesce(start of Global."NormalizeInterval"(NutritionCarePlan.relevantDatetime, NutritionCarePlan.relevantPeriod), NutritionCarePlan.authorDatetime) during Global."HospitalizationWithObservation" ( QualifyingEncounter ) + +define function "Measure Observation 1"(Encounter "Encounter, Performed"): + if ( "Encounter with Malnutrition Risk Screening and Identified Result or with Hospital Dietitian Referral" contains Encounter + and ( "Encounter with Malnutrition Screening Not At Risk Result" contains Encounter + or ( "Encounter with Malnutrition Screening At Risk Result" contains Encounter ) + ) + or exists ( "Encounter with Hospital Dietitian Referral" ) + ) then 1 + else 0 + +define function "Measure Observation 2"(Encounter "Encounter, Performed"): + if ( "Encounter with Malnutrition Not at Risk Result and without Hospital Dietitian Referral" ) contains Encounter then 0 + else if ( "Encounter with Nutrition Assessment and Identified Status" contains Encounter + and ( ( "Encounter with Nutrition Assessment Status Not or Mildly Malnourished" contains Encounter + or "Encounter with Nutrition Assessment Status Finding of Moderately Malnourished" contains Encounter + or "Encounter with Nutrition Assessment St... \ No newline at end of file