From c1da4313390476049e17c7376517d236df04b753 Mon Sep 17 00:00:00 2001 From: JP Date: Fri, 19 Jan 2024 14:50:41 -0700 Subject: [PATCH] Release 3.6.0, HAPI 6.10.2, fixes for build warnings (#1318) * Prep for release 3.6.0, fix some build warnings * Fixup formatting * Fixes for identifier hiding * Fixes for Android not having all the 11 APIs --- Src/java/.vscode/settings.json | 5 + .../main/groovy/cql.sca-conventions.gradle | 1 + .../cql/cql2elm/Cql2ElmVisitor.java | 644 +++++++++++------- ...ierContext.java => IdentifierContext.java} | 8 +- .../cql/cql2elm/LibraryBuilder.java | 107 ++- .../cql/cql2elm/fhir/r4/BaseTest.java | 9 + .../cql/cql2elm/fhir/r4/exm108/EXM108.cql | 424 ++++++++++++ .../cql2elm/fhir/r4/exm108/FHIRHelpers.cql | 301 ++++++++ .../r4/exm108/MATGlobalCommonFunctions.cql | 283 ++++++++ .../r4/exm108/SupplementalDataElements.cql | 46 ++ .../cql/cql2elm/fhir/r4/exm108/TJCOverall.cql | 74 ++ .../cql/cql2elm/fhir/r4/exm108/VTEICU.cql | 40 ++ .../engine/execution/EvaluationVisitor.java | 17 +- .../terminology/TerminologyProvider.java | 3 - .../CachingModelResolverDecoratorTest.java | 3 +- Src/java/gradle.properties | 4 +- ...mparer.java => ModelInfoComparerTest.java} | 41 +- 17 files changed, 1693 insertions(+), 317 deletions(-) rename Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/{HidingIdentifierContext.java => IdentifierContext.java} (80%) create mode 100644 Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/EXM108.cql create mode 100644 Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/FHIRHelpers.cql create mode 100644 Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/MATGlobalCommonFunctions.cql create mode 100644 Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/SupplementalDataElements.cql create mode 100644 Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/TJCOverall.cql create mode 100644 Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/VTEICU.cql rename Src/java/model/src/test/java/org/hl7/cql/model/{ModelInfoComparer.java => ModelInfoComparerTest.java} (94%) diff --git a/Src/java/.vscode/settings.json b/Src/java/.vscode/settings.json index a028b736d..c232ae24d 100644 --- a/Src/java/.vscode/settings.json +++ b/Src/java/.vscode/settings.json @@ -9,6 +9,8 @@ "bools", "Bryn", "checkstyle", + "Codeable", + "Codesystem", "cqframework", "datumedge", "fhir", @@ -16,11 +18,14 @@ "hamcrest", "Inferencing", "Instancio", + "JAXB", + "modelinfo", "Objenesis", "opdef", "opencds", "qicore", "Randomizer", + "redeclaration", "testng", "tngtech", "trackback", diff --git a/Src/java/buildSrc/src/main/groovy/cql.sca-conventions.gradle b/Src/java/buildSrc/src/main/groovy/cql.sca-conventions.gradle index 5d0dc652e..d23a15b6b 100644 --- a/Src/java/buildSrc/src/main/groovy/cql.sca-conventions.gradle +++ b/Src/java/buildSrc/src/main/groovy/cql.sca-conventions.gradle @@ -27,6 +27,7 @@ tasks.withType(JavaCompile).configureEach { tasks { compileTestJava { + // TODO: Talk to the team about warnings in tests options.errorprone.disableAllWarnings = true options.errorprone.disableWarningsInGeneratedCode = true } diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java index 69779b7ff..3e2d89e69 100755 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/Cql2ElmVisitor.java @@ -9,6 +9,7 @@ import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; +import org.cqframework.cql.cql2elm.LibraryBuilder.IdentifierScope; import org.cqframework.cql.cql2elm.model.*; import org.cqframework.cql.cql2elm.model.invocation.*; import org.cqframework.cql.cql2elm.preprocessor.*; @@ -67,7 +68,8 @@ public Object visitLibrary(cqlParser.LibraryContext ctx) { } } - // Return last result (consistent with super implementation and helps w/ testing) + // Return last result (consistent with super implementation and helps w/ + // testing) return lastResult; } @@ -121,7 +123,7 @@ public UsingDef visitUsingDefinition(cqlParser.UsingDefinitionContext ctx) { // The model was already calculated by CqlPreprocessorVisitor final UsingDef usingDef = libraryBuilder.resolveUsingRef(localIdentifier); - libraryBuilder.pushIdentifierForHiding(localIdentifier, usingDef); + libraryBuilder.pushIdentifier(localIdentifier, usingDef, IdentifierScope.GLOBAL); return usingDef; } @@ -173,9 +175,11 @@ public Object visitIncludeDefinition(cqlParser.IncludeDefinitionContext ctx) { .withPath(path) .withVersion(parseString(ctx.versionSpecifier())); - // TODO: This isn't great because it complicates the loading process (and results in the source being loaded + // TODO: This isn't great because it complicates the loading process (and + // results in the source being loaded // twice in the general case) - // But the full fix is to introduce source resolution/caching to enable this layer to determine whether the + // But the full fix is to introduce source resolution/caching to enable this + // layer to determine whether the // library identifier resolved // with the namespace if (!libraryBuilder.canResolveLibrary(library)) { @@ -195,7 +199,7 @@ public Object visitIncludeDefinition(cqlParser.IncludeDefinitionContext ctx) { } libraryBuilder.addInclude(library); - libraryBuilder.pushIdentifierForHiding(library.getLocalIdentifier(), library); + libraryBuilder.pushIdentifier(library.getLocalIdentifier(), library, IdentifierScope.GLOBAL); return library; } @@ -232,7 +236,7 @@ public ParameterDef visitParameterDefinition(cqlParser.ParameterDefinitionContex } libraryBuilder.addParameter(param); - libraryBuilder.pushIdentifierForHiding(param.getName(), param); + libraryBuilder.pushIdentifier(param.getName(), param, IdentifierScope.GLOBAL); return param; } @@ -278,7 +282,7 @@ public CodeSystemDef visitCodesystemDefinition(cqlParser.CodesystemDefinitionCon } libraryBuilder.addCodeSystem(cs); - libraryBuilder.pushIdentifierForHiding(cs.getName(), cs); + libraryBuilder.pushIdentifier(cs.getName(), cs, IdentifierScope.GLOBAL); return cs; } @@ -352,7 +356,7 @@ public ValueSetDef visitValuesetDefinition(cqlParser.ValuesetDefinitionContext c vs.setResultType(new ListType(libraryBuilder.resolveTypeName("System", "Code"))); } libraryBuilder.addValueSet(vs); - libraryBuilder.pushIdentifierForHiding(vs.getName(), vs); + libraryBuilder.pushIdentifier(vs.getName(), vs, IdentifierScope.GLOBAL); return vs; } @@ -374,7 +378,7 @@ public CodeDef visitCodeDefinition(cqlParser.CodeDefinitionContext ctx) { cd.setResultType(libraryBuilder.resolveTypeName("Code")); libraryBuilder.addCode(cd); - libraryBuilder.pushIdentifierForHiding(cd.getName(), cd); + libraryBuilder.pushIdentifier(cd.getName(), cd, IdentifierScope.GLOBAL); return cd; } @@ -446,7 +450,8 @@ public Object visitContextDefinition(cqlParser.ContextDefinitionContext ctx) { if (!isUnfilteredContext(unqualifiedIdentifier)) { ModelContext modelContext = libraryBuilder.resolveContextName(modelIdentifier, unqualifiedIdentifier); - // If this is the first time a context definition is encountered, construct a context definition: + // If this is the first time a context definition is encountered, construct a + // context definition: // define = element of [] Element modelContextDefinition = contextDefinitions.get(modelContext.getName()); if (modelContextDefinition == null) { @@ -457,7 +462,8 @@ public Object visitContextDefinition(cqlParser.ContextDefinitionContext ctx) { .getModelInfo() : libraryBuilder.getModel(modelIdentifier).getModelInfo(); // String contextTypeName = modelContext.getName(); - // DataType contextType = libraryBuilder.resolveTypeName(modelInfo.getName(), contextTypeName); + // DataType contextType = libraryBuilder.resolveTypeName(modelInfo.getName(), + // contextTypeName); DataType contextType = modelContext.getType(); modelContextDefinition = libraryBuilder.resolveParameterRef(modelContext.getName()); if (modelContextDefinition != null) { @@ -532,10 +538,16 @@ public ExpressionDef internalVisitExpressionDefinition(cqlParser.ExpressionDefin String identifier = parseString(ctx.identifier()); ExpressionDef def = libraryBuilder.resolveExpressionRef(identifier); - // lightweight ExpressionDef to be used to output a hiding warning message - final ExpressionDef hollowExpressionDef = - of.createExpressionDef().withName(identifier).withContext(getCurrentContext()); - libraryBuilder.pushIdentifierForHiding(identifier, hollowExpressionDef); + // First time visiting this expression definition, create a lightweight ExpressionDef to be used to output a + // hiding warning message + // If it's the second time around, we'll be able to resolve it and we can assume it's already on the + // hiding stack. + if (def == null) { + final ExpressionDef hollowExpressionDef = + of.createExpressionDef().withName(identifier).withContext(getCurrentContext()); + libraryBuilder.pushIdentifier(identifier, hollowExpressionDef, IdentifierScope.GLOBAL); + } + if (def == null || isImplicitContextExpressionDef(def)) { if (def != null && isImplicitContextExpressionDef(def)) { libraryBuilder.removeExpression(def); @@ -568,26 +580,35 @@ public ExpressionDef internalVisitExpressionDefinition(cqlParser.ExpressionDefin @Override public ExpressionDef visitExpressionDefinition(cqlParser.ExpressionDefinitionContext ctx) { - ExpressionDef expressionDef = internalVisitExpressionDefinition(ctx); - if (forwards.isEmpty() || !forwards.peek().getName().equals(expressionDef.getName())) { - if (definedExpressionDefinitions.contains(expressionDef.getName())) { - // ERROR: - throw new IllegalArgumentException( - String.format("Identifier %s is already in use in this library.", expressionDef.getName())); + this.libraryBuilder.pushIdentifierScope(); + try { + ExpressionDef expressionDef = internalVisitExpressionDefinition(ctx); + if (forwards.isEmpty() || !forwards.peek().getName().equals(expressionDef.getName())) { + if (definedExpressionDefinitions.contains(expressionDef.getName())) { + // ERROR: + throw new IllegalArgumentException( + String.format("Identifier %s is already in use in this library.", expressionDef.getName())); + } + + // Track defined expression definitions locally, otherwise duplicate expression + // definitions will be missed + // because they are + // overwritten by name when they are encountered by the preprocessor. + definedExpressionDefinitions.add(expressionDef.getName()); } + return expressionDef; - // Track defined expression definitions locally, otherwise duplicate expression definitions will be missed - // because they are - // overwritten by name when they are encountered by the preprocessor. - definedExpressionDefinitions.add(expressionDef.getName()); + } finally { + this.libraryBuilder.popIdentifierScope(); } - return expressionDef; } @Override public Literal visitStringLiteral(cqlParser.StringLiteralContext ctx) { final Literal stringLiteral = libraryBuilder.createLiteral(parseString(ctx.STRING())); - libraryBuilder.pushIdentifierForHiding(stringLiteral.getValue(), stringLiteral); + // Literals are never actually pushed to the stack. This just emits a warning if + // the literal is hiding something + libraryBuilder.pushIdentifier(stringLiteral.getValue(), stringLiteral); return stringLiteral; } @@ -823,25 +844,25 @@ public Object visitTimeLiteral(cqlParser.TimeLiteralContext ctx) { private Expression parseDateTimeLiteral(String input) { /* - DATETIME - : '@' - [0-9][0-9][0-9][0-9] // year - ( - ( - '-'[0-9][0-9] // month - ( - ( - '-'[0-9][0-9] // day - ('T' TIMEFORMAT?)? - ) - | 'T' - )? - ) - | 'T' - )? - ('Z' | ('+' | '-') [0-9][0-9]':'[0-9][0-9])? // timezone offset - ; - */ + * DATETIME + * : '@' + * [0-9][0-9][0-9][0-9] // year + * ( + * ( + * '-'[0-9][0-9] // month + * ( + * ( + * '-'[0-9][0-9] // day + * ('T' TIMEFORMAT?)? + * ) + * | 'T' + * )? + * ) + * | 'T' + * )? + * ('Z' | ('+' | '-') [0-9][0-9]':'[0-9][0-9])? // timezone offset + * ; + */ Pattern dateTimePattern = Pattern.compile( "(\\d{4})(((-(\\d{2}))(((-(\\d{2}))((T)((\\d{2})(\\:(\\d{2})(\\:(\\d{2})(\\.(\\d+))?)?)?)?)?)|(T))?)|(T))?((Z)|(([+-])(\\d{2})(\\:(\\d{2}))))?"); @@ -849,28 +870,32 @@ private Expression parseDateTimeLiteral(String input) { // ----------------------------------01--23-------4---5-------6---7-------8---9-----------------0------1----23---45-----6-------7---8----------- /* - year - group 1 - month - group 5 - day - group 9 - day dateTime indicator - group 11 - hour - group 13 - minute - group 15 - second - group 17 - millisecond - group 19 - month dateTime indicator - group 20 - year dateTime indicator - group 21 - utc indicator - group 23 - timezone offset polarity - group 25 - timezone offset hour - group 26 - timezone offset minute - group 28 - */ + * year - group 1 + * month - group 5 + * day - group 9 + * day dateTime indicator - group 11 + * hour - group 13 + * minute - group 15 + * second - group 17 + * millisecond - group 19 + * month dateTime indicator - group 20 + * year dateTime indicator - group 21 + * utc indicator - group 23 + * timezone offset polarity - group 25 + * timezone offset hour - group 26 + * timezone offset minute - group 28 + */ /* - Pattern dateTimePattern = - Pattern.compile("(\\d{4})(-(\\d{2}))?(-(\\d{2}))?((Z)|(T((\\d{2})(\\:(\\d{2})(\\:(\\d{2})(\\.(\\d+))?)?)?)?((Z)|(([+-])(\\d{2})(\\:?(\\d{2}))?))?))?"); - //1-------2-3---------4-5---------67---8-91-------1---1-------1---1-------1---1-------------11---12-----2-------2----2--------------- - //----------------------------------------0-------1---2-------3---4-------5---6-------------78---90-----1-------2----3--------------- - */ + * Pattern dateTimePattern = + * Pattern.compile( + * "(\\d{4})(-(\\d{2}))?(-(\\d{2}))?((Z)|(T((\\d{2})(\\:(\\d{2})(\\:(\\d{2})(\\.(\\d+))?)?)?)?((Z)|(([+-])(\\d{2})(\\:?(\\d{2}))?))?))?" + * ); + * //1-------2-3---------4-5---------67---8-91-------1---1-------1---1-------1-- + * -1-------------11---12-----2-------2----2--------------- + * //----------------------------------------0-------1---2-------3---4-------5-- + * -6-------------78---90-----1-------2----3--------------- + */ Matcher matcher = dateTimePattern.matcher(input); if (matcher.matches()) { @@ -1416,7 +1441,8 @@ public Object visitDurationExpressionTerm(cqlParser.DurationExpressionTermContex @Override public Object visitDifferenceExpressionTerm(cqlParser.DifferenceExpressionTermContext ctx) { - // difference in days of X <=> difference in days between start of X and end of X + // difference in days of X <=> difference in days between start of X and end of + // X Expression operand = parseExpression(ctx.expressionTerm()); Start start = of.createStart().withOperand(operand); @@ -1533,14 +1559,19 @@ public Object visitMembershipExpression(cqlParser.MembershipExpressionContext ct Expression left = parseExpression(ctx.expression(0)); Expression right = parseExpression(ctx.expression(1)); if (left instanceof ValueSetRef) { - InValueSet in = of.createInValueSet().withCode(right).withValueset((ValueSetRef) left); + InValueSet in = of.createInValueSet() + .withCode(right) + .withValueset((ValueSetRef) left) + .withValuesetExpression(left); libraryBuilder.resolveCall("System", "InValueSet", new InValueSetInvocation(in)); return in; } if (left instanceof CodeSystemRef) { - InCodeSystem in = - of.createInCodeSystem().withCode(right).withCodesystem((CodeSystemRef) left); + InCodeSystem in = of.createInCodeSystem() + .withCode(right) + .withCodesystem((CodeSystemRef) left) + .withCodesystemExpression(left); libraryBuilder.resolveCall("System", "InCodeSystem", new InCodeSystemInvocation(in)); return in; } @@ -1671,7 +1702,8 @@ public BinaryExpression visitInequalityExpression(cqlParser.InequalityExpression @Override public List visitQualifiedIdentifier(cqlParser.QualifiedIdentifierContext ctx) { - // Return the list of qualified identifiers for resolution by the containing element + // Return the list of qualified identifiers for resolution by the containing + // element List identifiers = new ArrayList<>(); for (cqlParser.QualifierContext qualifierContext : ctx.qualifier()) { String qualifier = parseString(qualifierContext); @@ -1685,7 +1717,8 @@ public List visitQualifiedIdentifier(cqlParser.QualifiedIdentifierContex @Override public List visitQualifiedIdentifierExpression(cqlParser.QualifiedIdentifierExpressionContext ctx) { - // Return the list of qualified identifiers for resolution by the containing element + // Return the list of qualified identifiers for resolution by the containing + // element List identifiers = new ArrayList<>(); for (cqlParser.QualifierExpressionContext qualifierContext : ctx.qualifierExpression()) { String qualifier = parseString(qualifierContext); @@ -1741,13 +1774,14 @@ public Object visitTerminal(TerminalNode node) { // chop off leading and trailing ', ", or ` text = text.substring(1, text.length() - 1); - // This is an alternate style of escaping that was removed when we switched to industry-standard escape + // This is an alternate style of escaping that was removed when we switched to + // industry-standard escape // sequences // if (cqlLexer.STRING == tokenType) { - // text = text.replace("''", "'"); + // text = text.replace("''", "'"); // } // else { - // text = text.replace("\"\"", "\""); + // text = text.replace("\"\"", "\""); // } } @@ -1787,7 +1821,8 @@ public Object visitConversionExpressionTerm(cqlParser.ConversionExpressionTermCo @Override public Object visitTypeExpression(cqlParser.TypeExpressionContext ctx) { - // NOTE: These don't use the buildIs or buildAs because those start with a DataType, rather than a TypeSpecifier + // NOTE: These don't use the buildIs or buildAs because those start with a + // DataType, rather than a TypeSpecifier if (ctx.getChild(1).getText().equals("is")) { Is is = of.createIs() .withOperand(parseExpression(ctx.expression())) @@ -1808,7 +1843,8 @@ public Object visitTypeExpression(cqlParser.TypeExpressionContext ctx) { @Override public Object visitCastExpression(cqlParser.CastExpressionContext ctx) { - // NOTE: This doesn't use buildAs because it starts with a DataType, rather than a TypeSpecifier + // NOTE: This doesn't use buildAs because it starts with a DataType, rather than + // a TypeSpecifier As as = of.createAs() .withOperand(parseExpression(ctx.expression())) .withAsTypeSpecifier(parseTypeSpecifier(ctx.typeSpecifier())) @@ -1869,7 +1905,8 @@ public Object visitTimingExpression(cqlParser.TimingExpressionContext ctx) { @Override public Object visitConcurrentWithIntervalOperatorPhrase(cqlParser.ConcurrentWithIntervalOperatorPhraseContext ctx) { - // ('starts' | 'ends' | 'occurs')? 'same' dateTimePrecision? (relativeQualifier | 'as') ('start' | 'end')? + // ('starts' | 'ends' | 'occurs')? 'same' dateTimePrecision? (relativeQualifier + // | 'as') ('start' | 'end')? TimingOperatorContext timingOperator = timingOperators.peek(); ParseTree firstChild = ctx.getChild(0); if ("starts".equals(firstChild.getText())) { @@ -1991,9 +2028,9 @@ public Object visitIncludesIntervalOperatorPhrase(cqlParser.IncludesIntervalOper // If the right is not convertible to an interval or list // if (!isRightPoint && - // !(timingOperator.getRight().getResultType() instanceof IntervalType - // || timingOperator.getRight().getResultType() instanceof ListType)) { - // isRightPoint = true; + // !(timingOperator.getRight().getResultType() instanceof IntervalType + // || timingOperator.getRight().getResultType() instanceof ListType)) { + // isRightPoint = true; // } if (isRightPoint) { @@ -2025,7 +2062,8 @@ public Object visitIncludesIntervalOperatorPhrase(cqlParser.IncludesIntervalOper @Override public Object visitIncludedInIntervalOperatorPhrase(cqlParser.IncludedInIntervalOperatorPhraseContext ctx) { - // ('starts' | 'ends' | 'occurs')? 'properly'? ('during' | 'included in') dateTimePrecisionSpecifier? + // ('starts' | 'ends' | 'occurs')? 'properly'? ('during' | 'included in') + // dateTimePrecisionSpecifier? boolean isProper = false; boolean isLeftPoint = false; TimingOperatorContext timingOperator = timingOperators.peek(); @@ -2060,9 +2098,9 @@ public Object visitIncludedInIntervalOperatorPhrase(cqlParser.IncludedInInterval // If the left is not convertible to an interval or list // if (!isLeftPoint && - // !(timingOperator.getLeft().getResultType() instanceof IntervalType - // || timingOperator.getLeft().getResultType() instanceof ListType)) { - // isLeftPoint = true; + // !(timingOperator.getLeft().getResultType() instanceof IntervalType + // || timingOperator.getLeft().getResultType() instanceof ListType)) { + // isLeftPoint = true; // } if (isLeftPoint) { @@ -2094,7 +2132,8 @@ public Object visitIncludedInIntervalOperatorPhrase(cqlParser.IncludedInInterval @Override public Object visitBeforeOrAfterIntervalOperatorPhrase(cqlParser.BeforeOrAfterIntervalOperatorPhraseContext ctx) { - // ('starts' | 'ends' | 'occurs')? quantityOffset? ('before' | 'after') dateTimePrecisionSpecifier? ('start' | + // ('starts' | 'ends' | 'occurs')? quantityOffset? ('before' | 'after') + // dateTimePrecisionSpecifier? ('start' | // 'end')? // duration before/after @@ -2364,7 +2403,8 @@ public Object visitBeforeOrAfterIntervalOperatorPhrase(cqlParser.BeforeOrAfterIn track(in, ctx.quantityOffset()); libraryBuilder.resolveBinaryCall("System", "In", in); - // if the offset or comparison is inclusive, add a null check for B to ensure correct + // if the offset or comparison is inclusive, add a null check for B to ensure + // correct // interpretation if (isOffsetInclusive || isInclusive) { IsNull nullTest = of.createIsNull().withOperand(right); @@ -2402,9 +2442,11 @@ private BinaryExpression resolveBetweenOperator(String unit, Expression left, Ex @Override public Object visitWithinIntervalOperatorPhrase(cqlParser.WithinIntervalOperatorPhraseContext ctx) { - // ('starts' | 'ends' | 'occurs')? 'properly'? 'within' quantityLiteral 'of' ('start' | 'end')? + // ('starts' | 'ends' | 'occurs')? 'properly'? 'within' quantityLiteral 'of' + // ('start' | 'end')? // A starts within 3 days of start B - // * start of A in [start of B - 3 days, start of B + 3 days] and start B is not null + // * start of A in [start of B - 3 days, start of B + 3 days] and start B is not + // null // A starts within 3 days of B // * start of A in [start of B - 3 days, end of B + 3 days] @@ -2480,7 +2522,8 @@ public Object visitWithinIntervalOperatorPhrase(cqlParser.WithinIntervalOperator In in = of.createIn().withOperand(timingOperator.getLeft(), interval); libraryBuilder.resolveBinaryCall("System", "In", in); - // if the within is not proper and the interval is being constructed from a single point, add a null check for + // if the within is not proper and the interval is being constructed from a + // single point, add a null check for // that point to ensure correct interpretation if (!isProper && (initialBound != null)) { IsNull nullTest = of.createIsNull().withOperand(initialBound); @@ -2718,19 +2761,19 @@ public Object visitSetAggregateExpressionTerm(cqlParser.SetAggregateExpressionTe per = libraryBuilder.buildNull(libraryBuilder.resolveTypeName("System", "Quantity")); // TODO: Test this... - // // Successor(MinValue) - MinValue - // MinValue minimum = libraryBuilder.buildMinimum(pointType); - // track(minimum, ctx); + // // Successor(MinValue) - MinValue + // MinValue minimum = libraryBuilder.buildMinimum(pointType); + // track(minimum, ctx); // - // Expression successor = libraryBuilder.buildSuccessor(minimum); - // track(successor, ctx); + // Expression successor = libraryBuilder.buildSuccessor(minimum); + // track(successor, ctx); // - // minimum = libraryBuilder.buildMinimum(pointType); - // track(minimum, ctx); + // minimum = libraryBuilder.buildMinimum(pointType); + // track(minimum, ctx); // - // Subtract subtract = of.createSubtract().withOperand(successor, minimum); - // libraryBuilder.resolveBinaryCall("System", "Subtract", subtract); - // per = subtract; + // Subtract subtract = of.createSubtract().withOperand(successor, minimum); + // libraryBuilder.resolveBinaryCall("System", "Subtract", subtract); + // per = subtract; } } else { per = libraryBuilder.buildNull(libraryBuilder.resolveTypeName("System", "Quantity")); @@ -2774,16 +2817,23 @@ public Expression visitRetrieve(cqlParser.RetrieveContext ctx) { } ClassType classType = (ClassType) dataType; - // BTR -> The original intent of this code was to have the retrieve return the base type, and use the + // BTR -> The original intent of this code was to have the retrieve return the + // base type, and use the // "templateId" - // element of the retrieve to communicate the "positive" or "negative" profile to the data access layer. - // However, because this notion of carrying the "profile" through a type is not general, it causes + // element of the retrieve to communicate the "positive" or "negative" profile + // to the data access layer. + // However, because this notion of carrying the "profile" through a type is not + // general, it causes // inconsistencies - // when using retrieve results with functions defined in terms of the same type (see GitHub Issue #131). - // Based on the discussion there, the retrieve will now return the declared type, whether it is a profile or + // when using retrieve results with functions defined in terms of the same type + // (see GitHub Issue #131). + // Based on the discussion there, the retrieve will now return the declared + // type, whether it is a profile or // not. - // ProfileType profileType = dataType instanceof ProfileType ? (ProfileType)dataType : null; - // NamedType namedType = profileType == null ? classType : (NamedType)classType.getBaseType(); + // ProfileType profileType = dataType instanceof ProfileType ? + // (ProfileType)dataType : null; + // NamedType namedType = profileType == null ? classType : + // (NamedType)classType.getBaseType(); NamedType namedType = classType; ModelInfo modelInfo = libraryBuilder.getModel(namedType.getNamespace()).getModelInfo(); @@ -2857,10 +2907,13 @@ public Expression visitRetrieve(cqlParser.RetrieveContext ctx) { && ((NamedType) propertyType).getSimpleName().equals("Reference") && namedType.getSimpleName().equals("MedicationRequest")) { // TODO: This is a model-specific special case to support QICore - // This functionality needs to be generalized to a retrieve mapping in the model info - // But that requires a model info change (to represent references, right now the model info only + // This functionality needs to be generalized to a retrieve mapping in the model + // info + // But that requires a model info change (to represent references, right now the + // model info only // includes context relationships) - // The reference expands to [MedicationRequest] MR with [Medication] M such that M.id = + // The reference expands to [MedicationRequest] MR with [Medication] M such that + // M.id = // Last(Split(MR.medication.reference, '/')) and M.code in Retrieve mrRetrieve = buildRetrieve( ctx, useStrictRetrieveTyping, namedType, classType, null, null, null, null, null, null); @@ -2952,9 +3005,11 @@ public Expression visitRetrieve(cqlParser.RetrieveContext ctx) { if (result == null) { result = retrieve; } else { - // Should only include the result if it resolved appropriately with the codeComparator + // Should only include the result if it resolved appropriately with the + // codeComparator // Allowing it to go through for now - // if (retrieve.getCodeProperty() != null && retrieve.getCodeComparator() != null && + // if (retrieve.getCodeProperty() != null && retrieve.getCodeComparator() != + // null && // retrieve.getCodes() != null) { track(retrieve, ctx); result = libraryBuilder.resolveUnion(result, retrieve); @@ -3071,7 +3126,8 @@ private Retrieve buildRetrieve( retrieve.setCodes( ((Contains) contains).getOperand().get(1)); } - // TODO: Introduce support for the contains operator to make this possible to support with a + // TODO: Introduce support for the contains operator to make this possible to + // support with a // retrieve (direct-reference code negation) // ERROR: libraryBuilder.recordParsingException(new CqlSemanticException( @@ -3142,9 +3198,11 @@ private Retrieve buildRetrieve( retrieve.setCodeComparator(codeComparator); // Verify that the type of the terminology target is a List - // Due to implicit conversion defined by specific models, the resolution path above may result in a + // Due to implicit conversion defined by specific models, the resolution path + // above may result in a // List - // In that case, convert to a list of code (Union the Code elements of the Concepts in the list) + // In that case, convert to a list of code (Union the Code elements of the + // Concepts in the list) if (retrieve.getCodes() != null && retrieve.getCodes().getResultType() != null && retrieve.getCodes().getResultType() instanceof ListType @@ -3154,7 +3212,8 @@ private Retrieve buildRetrieve( if (retrieve.getCodes() instanceof ToList) { // ToList will always have a single argument ToList toList = (ToList) retrieve.getCodes(); - // If that argument is a ToConcept, replace the ToList argument with the code (skip the implicit + // If that argument is a ToConcept, replace the ToList argument with the code + // (skip the implicit // conversion, the data access layer is responsible for it) if (toList.getOperand() instanceof ToConcept) { toList.setOperand(((ToConcept) toList.getOperand()).getOperand()); @@ -3176,8 +3235,10 @@ private Retrieve buildRetrieve( } } } catch (Exception e) { - // If something goes wrong attempting to resolve, just set to the expression and report it as a warning, - // it shouldn't prevent translation unless the modelinfo indicates strict retrieve typing + // If something goes wrong attempting to resolve, just set to the expression and + // report it as a warning, + // it shouldn't prevent translation unless the modelinfo indicates strict + // retrieve typing if ((libraryBuilder.isCompatibleWith("1.5") && !(terminology .getResultType() @@ -3239,26 +3300,31 @@ public Object visitQuery(cqlParser.QueryContext ctx) { queryContext.addPrimaryQuerySources(sources); for (AliasedQuerySource source : sources) { - libraryBuilder.pushIdentifierForHiding(source.getAlias(), source); + libraryBuilder.pushIdentifier(source.getAlias(), source); } - // If we are evaluating a population-level query whose source ranges over any patient-context expressions, - // then references to patient context expressions within the iteration clauses of the query can be accessed + // If we are evaluating a population-level query whose source ranges over any + // patient-context expressions, + // then references to patient context expressions within the iteration clauses + // of the query can be accessed // at the patient, rather than the population, context. boolean expressionContextPushed = false; - /* TODO: Address the issue of referencing multiple context expressions within a query (or even expression in general) - if (libraryBuilder.inUnfilteredContext() && queryContext.referencesSpecificContext()) { - libraryBuilder.pushExpressionContext("Patient"); - expressionContextPushed = true; - } - */ + /* + * TODO: Address the issue of referencing multiple context expressions within a + * query (or even expression in general) + * if (libraryBuilder.inUnfilteredContext() && + * queryContext.referencesSpecificContext()) { + * libraryBuilder.pushExpressionContext("Patient"); + * expressionContextPushed = true; + * } + */ List dfcx = null; try { dfcx = ctx.letClause() != null ? (List) visit(ctx.letClause()) : null; if (dfcx != null) { for (LetClause letClause : dfcx) { - libraryBuilder.pushIdentifierForHiding(letClause.getIdentifier(), letClause); + libraryBuilder.pushIdentifier(letClause.getIdentifier(), letClause); } } @@ -3330,11 +3396,13 @@ public Object visitQuery(cqlParser.QueryContext ctx) { queryContext.enterSortClause(); try { sort = (SortClause) visit(ctx.sortClause()); - // Validate that the sort can be performed based on the existence of comparison operators + // Validate that the sort can be performed based on the existence of comparison + // operators // for all types involved for (SortByItem sortByItem : sort.getBy()) { if (sortByItem instanceof ByDirection) { - // validate that there is a comparison operator defined for the result element type + // validate that there is a comparison operator defined for the result element + // type // of the query context libraryBuilder.verifyComparable(queryContext.getResultElementType()); } else { @@ -3369,7 +3437,7 @@ public Object visitQuery(cqlParser.QueryContext ctx) { } if (dfcx != null) { for (LetClause letClause : dfcx) { - libraryBuilder.popIdentifierForHiding(); + libraryBuilder.popIdentifier(); } } } @@ -3378,25 +3446,33 @@ public Object visitQuery(cqlParser.QueryContext ctx) { libraryBuilder.popQueryContext(); if (sources != null) { for (AliasedQuerySource source : sources) { - libraryBuilder.popIdentifierForHiding(); + libraryBuilder.popIdentifier(); } } } } - // TODO: Expand this optimization to work the DateLow/DateHigh property attributes + // TODO: Expand this optimization to work the DateLow/DateHigh property + // attributes /** - * Some systems may wish to optimize performance by restricting retrieves with available date ranges. Specifying - * date ranges in a retrieve was removed from the CQL grammar, but it is still possible to extract date ranges from - * the where clause and put them in the Retrieve in ELM. The optimizeDateRangeInQuery method - * attempts to do this automatically. If optimization is possible, it will remove the corresponding "during" from + * Some systems may wish to optimize performance by restricting retrieves with + * available date ranges. Specifying + * date ranges in a retrieve was removed from the CQL grammar, but it is still + * possible to extract date ranges from + * the where clause and put them in the Retrieve in ELM. The + * optimizeDateRangeInQuery method + * attempts to do this automatically. If optimization is possible, it will + * remove the corresponding "during" from * the where clause and insert the date range into the Retrieve. * - * @param aqs the AliasedQuerySource containing the ClinicalRequest to possibly refactor a date range into. - * @param where the Where clause to search for potential date range optimizations - * @return the where clause with optimized "durings" removed, or null if there is no longer a Where - * clause after optimization. + * @param aqs the AliasedQuerySource containing the ClinicalRequest to + * possibly refactor a date range into. + * @param where the Where clause to search for potential date range + * optimizations + * @return the where clause with optimized "durings" removed, or + * null if there is no longer a Where + * clause after optimization. */ public Expression optimizeDateRangeInQuery(Expression where, AliasedQuerySource aqs) { if (aqs.getExpression() instanceof Retrieve) { @@ -3414,15 +3490,20 @@ && attemptDateRangeOptimization((BinaryExpression) where, retrieve, alias)) { } /** - * Test a BinaryExpression expression and determine if it is suitable to be refactored into the - * Retrieve as a date range restriction. If so, adjust the Retrieve + * Test a BinaryExpression expression and determine if it is + * suitable to be refactored into the + * Retrieve as a date range restriction. If so, adjust the + * Retrieve * accordingly and return true. * - * @param during the BinaryExpression expression to potentially refactor into the Retrieve - * @param retrieve the Retrieve to add qualifying date ranges to (if applicable) + * @param during the BinaryExpression expression to potentially + * refactor into the Retrieve + * @param retrieve the Retrieve to add qualifying date ranges to + * (if applicable) * @param alias the alias of the Retrieve in the query. - * @return true if the date range was set in the Retrieve; false - * otherwise. + * @return true if the date range was set in the + * Retrieve; false + * otherwise. */ private boolean attemptDateRangeOptimization(BinaryExpression during, Retrieve retrieve, String alias) { if (retrieve.getDateProperty() != null || retrieve.getDateRange() != null) { @@ -3443,12 +3524,14 @@ private boolean attemptDateRangeOptimization(BinaryExpression during, Retrieve r } /** - * Collapse a property path expression back to it's qualified form for use as the path attribute of the retrieve. + * Collapse a property path expression back to it's qualified form for use as + * the path attribute of the retrieve. * * @param reference the Expression to collapse - * @param alias the alias of the Retrieve in the query. + * @param alias the alias of the Retrieve in the query. * @return The collapsed path - * operands (or sub-operands) were modified; false otherwise. + * operands (or sub-operands) were modified; false + * otherwise. */ private String getPropertyPath(Expression reference, String alias) { reference = getConversionReference(reference); @@ -3469,11 +3552,13 @@ private String getPropertyPath(Expression reference, String alias) { } /** - * If this is a conversion operator, return the argument of the conversion, on the grounds that the date range optimization + * If this is a conversion operator, return the argument of the conversion, on + * the grounds that the date range optimization * should apply through a conversion (i.e. it is an order-preserving conversion) * * @param reference the Expression to examine - * @return The argument to the conversion operator if there was one, otherwise, the given reference + * @return The argument to the conversion operator if there was one, otherwise, + * the given reference */ private Expression getConversionReference(Expression reference) { if (reference instanceof FunctionRef) { @@ -3499,11 +3584,13 @@ private Expression getConversionReference(Expression reference) { } /** - * If this is a choice selection, return the argument of the choice selection, on the grounds that the date range optimization + * If this is a choice selection, return the argument of the choice selection, + * on the grounds that the date range optimization * should apply through the cast (i.e. it is an order-preserving cast) * * @param reference the Expression to examine - * @return The argument to the choice selection (i.e. As) if there was one, otherwise, the given reference + * @return The argument to the choice selection (i.e. As) if there was one, + * otherwise, the given reference */ private Expression getChoiceSelection(Expression reference) { if (reference instanceof As) { @@ -3517,18 +3604,26 @@ private Expression getChoiceSelection(Expression reference) { } /** - * Test an And expression and determine if it contains any operands (first-level or nested deeper) - * than are IncludedIn expressions that can be refactored into a Retrieve. If so, - * adjust the Retrieve accordingly and reset the corresponding operand to a literal - * true. This and branch containing a true can be further consolidated + * Test an And expression and determine if it contains any operands + * (first-level or nested deeper) + * than are IncludedIn expressions that can be refactored into a + * Retrieve. If so, + * adjust the Retrieve accordingly and reset the corresponding + * operand to a literal + * true. This and branch containing a + * true can be further consolidated * later. * - * @param and the And expression containing operands to potentially refactor into the + * @param and the And expression containing operands to + * potentially refactor into the * Retrieve - * @param retrieve the Retrieve to add qualifying date ranges to (if applicable) + * @param retrieve the Retrieve to add qualifying date ranges to + * (if applicable) * @param alias the alias of the Retrieve in the query. - * @return true if the date range was set in the Retrieve and the And - * operands (or sub-operands) were modified; false otherwise. + * @return true if the date range was set in the + * Retrieve and the And + * operands (or sub-operands) were modified; false + * otherwise. */ private boolean attemptDateRangeOptimization(And and, Retrieve retrieve, String alias) { if (retrieve.getDateProperty() != null || retrieve.getDateRange() != null) { @@ -3551,7 +3646,8 @@ && attemptDateRangeOptimization((BinaryExpression) operand, retrieve, alias)) { } /** - * If any branches in the And tree contain a true, refactor it out. + * If any branches in the And tree contain a true, + * refactor it out. * * @param and the And tree to attempt to consolidate * @return the potentially consolidated And @@ -3574,60 +3670,69 @@ private Expression consolidateAnd(And and) { } /** - * Determine if the right-hand side of an IncludedIn expression can be refactored into the date range - * of a Retrieve. Currently, refactoring is only supported when the RHS is a literal - * DateTime interval, a literal DateTime, a parameter representing a DateTime interval or a DateTime, or an + * Determine if the right-hand side of an IncludedIn expression can + * be refactored into the date range + * of a Retrieve. Currently, refactoring is only supported when the + * RHS is a literal + * DateTime interval, a literal DateTime, a parameter representing a DateTime + * interval or a DateTime, or an * expression reference representing a DateTime interval or a DateTime. * - * @param rhs the right-hand side of the IncludedIn to test for potential optimization - * @return true if the RHS supports refactoring to a Retrieve, false - * otherwise. + * @param rhs the right-hand side of the IncludedIn to test for + * potential optimization + * @return true if the RHS supports refactoring to a + * Retrieve, false + * otherwise. */ private boolean isRHSEligibleForDateRangeOptimization(Expression rhs) { return rhs.getResultType().isSubTypeOf(libraryBuilder.resolveTypeName("System", "DateTime")) || rhs.getResultType() .isSubTypeOf(new IntervalType(libraryBuilder.resolveTypeName("System", "DateTime"))); - // BTR: The only requirement for the optimization is that the expression be of type DateTime or + // BTR: The only requirement for the optimization is that the expression be of + // type DateTime or // Interval - // Whether or not the expression can be statically evaluated (literal, in the loose sense of the word) is really - // a function of the engine in determining the "initial" data requirements, versus subsequent data requirements - // Element targetElement = rhs; - // if (rhs instanceof ParameterRef) { - // String paramName = ((ParameterRef) rhs).getName(); - // for (ParameterDef def : getLibrary().getParameters().getDef()) { - // if (paramName.equals(def.getName())) { - // targetElement = def.getParameterTypeSpecifier(); - // if (targetElement == null) { - // targetElement = def.getDefault(); - // } - // break; - // } - // } - // } else if (rhs instanceof ExpressionRef && !(rhs instanceof FunctionRef)) { - // // TODO: Support forward declaration, if necessary - // String expName = ((ExpressionRef) rhs).getName(); - // for (ExpressionDef def : getLibrary().getStatements().getDef()) { - // if (expName.equals(def.getName())) { - // targetElement = def.getExpression(); - // } - // } - // } + // Whether or not the expression can be statically evaluated (literal, in the + // loose sense of the word) is really + // a function of the engine in determining the "initial" data requirements, + // versus subsequent data requirements + // Element targetElement = rhs; + // if (rhs instanceof ParameterRef) { + // String paramName = ((ParameterRef) rhs).getName(); + // for (ParameterDef def : getLibrary().getParameters().getDef()) { + // if (paramName.equals(def.getName())) { + // targetElement = def.getParameterTypeSpecifier(); + // if (targetElement == null) { + // targetElement = def.getDefault(); + // } + // break; + // } + // } + // } else if (rhs instanceof ExpressionRef && !(rhs instanceof FunctionRef)) { + // // TODO: Support forward declaration, if necessary + // String expName = ((ExpressionRef) rhs).getName(); + // for (ExpressionDef def : getLibrary().getStatements().getDef()) { + // if (expName.equals(def.getName())) { + // targetElement = def.getExpression(); + // } + // } + // } // - // boolean isEligible = false; - // if (targetElement instanceof DateTime) { - // isEligible = true; - // } else if (targetElement instanceof Interval) { - // Interval ivl = (Interval) targetElement; - // isEligible = (ivl.getLow() != null && ivl.getLow() instanceof DateTime) || (ivl.getHigh() != null + // boolean isEligible = false; + // if (targetElement instanceof DateTime) { + // isEligible = true; + // } else if (targetElement instanceof Interval) { + // Interval ivl = (Interval) targetElement; + // isEligible = (ivl.getLow() != null && ivl.getLow() instanceof DateTime) || + // (ivl.getHigh() != null // && ivl.getHigh() instanceof DateTime); - // } else if (targetElement instanceof IntervalTypeSpecifier) { - // IntervalTypeSpecifier spec = (IntervalTypeSpecifier) targetElement; - // isEligible = isDateTimeTypeSpecifier(spec.getPointType()); - // } else if (targetElement instanceof NamedTypeSpecifier) { - // isEligible = isDateTimeTypeSpecifier(targetElement); - // } - // return isEligible; + // } else if (targetElement instanceof IntervalTypeSpecifier) { + // IntervalTypeSpecifier spec = (IntervalTypeSpecifier) targetElement; + // isEligible = isDateTimeTypeSpecifier(spec.getPointType()); + // } else if (targetElement instanceof NamedTypeSpecifier) { + // isEligible = isDateTimeTypeSpecifier(targetElement); + // } + // return isEligible; } private boolean isDateTimeTypeSpecifier(Element e) { @@ -3765,7 +3870,8 @@ public Object visitAggregateClause(cqlParser.AggregateClauseContext ctx) { } // If there is a starting, that's the type of the var - // If there's not a starting, push an Any and then attempt to evaluate (might need a type hint here) + // If there's not a starting, push an Any and then attempt to evaluate (might + // need a type hint here) aggregateClause.setIdentifier(parseString(ctx.identifier())); Expression accumulator = null; @@ -3921,7 +4027,8 @@ public Expression resolveMemberIdentifier(String identifier) { } private Expression resolveIdentifier(String identifier) { - // If the identifier cannot be resolved in the library builder, check for forward declarations for expressions + // If the identifier cannot be resolved in the library builder, check for + // forward declarations for expressions // and parameters Expression result = libraryBuilder.resolveIdentifier(identifier, false); if (result == null) { @@ -3963,8 +4070,10 @@ private Expression resolveIdentifier(String identifier) { private String ensureSystemFunctionName(String libraryName, String functionName) { if (libraryName == null || libraryName.equals("System")) { - // Because these functions can be both a keyword and the name of a method, they can be resolved by the - // parser as a function, instead of as the keyword-based parser rule. In this case, the function + // Because these functions can be both a keyword and the name of a method, they + // can be resolved by the + // parser as a function, instead of as the keyword-based parser rule. In this + // case, the function // name needs to be translated to the System function name in order to resolve. switch (functionName) { case "contains": @@ -4012,8 +4121,10 @@ public Expression resolveFunction( functionName = ensureSystemFunctionName(libraryName, functionName); // 1. Ensure all overloads of the function are registered with the operator map - // 2. Resolve the function, allowing for the case that operator map is a skeleton - // 3. If the resolution from the operator map is a skeleton, compile the function body to determine the result + // 2. Resolve the function, allowing for the case that operator map is a + // skeleton + // 3. If the resolution from the operator map is a skeleton, compile the + // function body to determine the result // type // Find all functionDefinitionInfo instances with the given name @@ -4068,7 +4179,8 @@ public Expression resolveFunction( } if (mustResolve) { - // Extra internal error handling, these should never be hit if the two-phase operator compile is working as + // Extra internal error handling, these should never be hit if the two-phase + // operator compile is working as // expected if (result == null) { throw new IllegalArgumentException("Internal error: could not resolve function"); @@ -4118,7 +4230,8 @@ public Expression resolveFunctionOrQualifiedFunction(String identifier, cqlParse } } - // If we are in an implicit $this context, the function may be resolved as a method invocation + // If we are in an implicit $this context, the function may be resolved as a + // method invocation Expression thisRef = libraryBuilder.resolveIdentifier("$this", false); if (thisRef != null) { Expression result = systemMethodResolver.resolveMethod(thisRef, identifier, paramListCtx, false); @@ -4127,7 +4240,8 @@ public Expression resolveFunctionOrQualifiedFunction(String identifier, cqlParse } } - // If we are in an implicit context (i.e. a context named the same as a parameter), the function may be resolved + // If we are in an implicit context (i.e. a context named the same as a + // parameter), the function may be resolved // as a method invocation ParameterRef parameterRef = libraryBuilder.resolveImplicitContext(); if (parameterRef != null) { @@ -4208,8 +4322,10 @@ private FunctionDef getFunctionDef(Operator op) { } private FunctionHeader getFunctionHeaderByDef(FunctionDef fd) { - // Shouldn't need to do this, something about the hashCode implementation of FunctionDef is throwing this off, - // Don't have time to investigate right now, this should work fine, could potentially be improved + // Shouldn't need to do this, something about the hashCode implementation of + // FunctionDef is throwing this off, + // Don't have time to investigate right now, this should work fine, could + // potentially be improved for (Map.Entry entry : functionHeadersByDef.entrySet()) { if (entry.getKey() == fd) { return entry.getValue(); @@ -4262,70 +4378,82 @@ public FunctionDef compileFunctionDefinition(cqlParser.FunctionDefinitionContext "Internal error: Could not resolve operator map entry for function header %s", fh.getMangledName())); } - libraryBuilder.pushIdentifierForHiding(fun.getName(), fun); + libraryBuilder.pushIdentifier(fun.getName(), fun, IdentifierScope.GLOBAL); final List operand = op.getFunctionDef().getOperand(); for (OperandDef operandDef : operand) { - libraryBuilder.pushIdentifierForHiding(operandDef.getName(), operandDef); + libraryBuilder.pushIdentifier(operandDef.getName(), operandDef); } - if (ctx.functionBody() != null) { - libraryBuilder.beginFunctionDef(fun); - try { - libraryBuilder.pushExpressionContext(getCurrentContext()); + try { + if (ctx.functionBody() != null) { + libraryBuilder.beginFunctionDef(fun); try { - libraryBuilder.pushExpressionDefinition(fh.getMangledName()); + libraryBuilder.pushExpressionContext(getCurrentContext()); try { - fun.setExpression(parseExpression(ctx.functionBody())); + libraryBuilder.pushExpressionDefinition(fh.getMangledName()); + try { + fun.setExpression(parseExpression(ctx.functionBody())); + } finally { + libraryBuilder.popExpressionDefinition(); + } } finally { - libraryBuilder.popExpressionDefinition(); + libraryBuilder.popExpressionContext(); } } finally { - libraryBuilder.popExpressionContext(); + libraryBuilder.endFunctionDef(); } - } finally { - for (OperandDef operandDef : operand) { - libraryBuilder.popIdentifierForHiding(); + + if (resultType != null + && fun.getExpression() != null + && fun.getExpression().getResultType() != null) { + if (!DataTypes.subTypeOf(fun.getExpression().getResultType(), resultType.getResultType())) { + // ERROR: + throw new IllegalArgumentException(String.format( + "Function %s has declared return type %s but the function body returns incompatible type %s.", + fun.getName(), + resultType.getResultType(), + fun.getExpression().getResultType())); + } } - libraryBuilder.endFunctionDef(); - } - if (resultType != null - && fun.getExpression() != null - && fun.getExpression().getResultType() != null) { - if (!DataTypes.subTypeOf(fun.getExpression().getResultType(), resultType.getResultType())) { + fun.setResultType(fun.getExpression().getResultType()); + op.setResultType(fun.getResultType()); + } else { + fun.setExternal(true); + if (resultType == null) { // ERROR: throw new IllegalArgumentException(String.format( - "Function %s has declared return type %s but the function body returns incompatible type %s.", - fun.getName(), - resultType.getResultType(), - fun.getExpression().getResultType())); + "Function %s is marked external but does not declare a return type.", fun.getName())); } + fun.setResultType(resultType.getResultType()); + op.setResultType(fun.getResultType()); } - fun.setResultType(fun.getExpression().getResultType()); - op.setResultType(fun.getResultType()); - } else { - fun.setExternal(true); - if (resultType == null) { - // ERROR: - throw new IllegalArgumentException(String.format( - "Function %s is marked external but does not declare a return type.", fun.getName())); + fun.setContext(getCurrentContext()); + fh.setIsCompiled(); + + return fun; + } finally { + for (OperandDef operandDef : operand) { + try { + libraryBuilder.popIdentifier(); + } catch (Exception e) { + e.printStackTrace(); + } } - fun.setResultType(resultType.getResultType()); - op.setResultType(fun.getResultType()); + // Intentionally do _not_ pop the function name, it needs to remain in global scope! } - - fun.setContext(getCurrentContext()); - fh.setIsCompiled(); - - return fun; } @Override public Object visitFunctionDefinition(cqlParser.FunctionDefinitionContext ctx) { - registerFunctionDefinition(ctx); - FunctionDef fun = compileFunctionDefinition(ctx); - return fun; + libraryBuilder.pushIdentifierScope(); + try { + registerFunctionDefinition(ctx); + return compileFunctionDefinition(ctx); + } finally { + libraryBuilder.popIdentifierScope(); + } } private Expression parseLiteralExpression(ParseTree pt) { diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/HidingIdentifierContext.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/IdentifierContext.java similarity index 80% rename from Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/HidingIdentifierContext.java rename to Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/IdentifierContext.java index 34a1fc402..351660a75 100644 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/HidingIdentifierContext.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/IdentifierContext.java @@ -7,11 +7,11 @@ /** * Simple POJO using for identifier hider that maintains the identifier and Trackable type of the construct being evaluated. */ -public class HidingIdentifierContext { +public class IdentifierContext { private final String identifier; private final Class elementSubclass; - public HidingIdentifierContext(String identifier, Class elementSubclass) { + public IdentifierContext(String identifier, Class elementSubclass) { this.identifier = identifier; this.elementSubclass = elementSubclass; } @@ -32,7 +32,7 @@ public boolean equals(Object other) { if (other == null || getClass() != other.getClass()) { return false; } - HidingIdentifierContext that = (HidingIdentifierContext) other; + IdentifierContext that = (IdentifierContext) other; return Objects.equals(identifier, that.identifier) && Objects.equals(elementSubclass, that.elementSubclass); } @@ -43,7 +43,7 @@ public int hashCode() { @Override public String toString() { - return new StringJoiner(", ", HidingIdentifierContext.class.getSimpleName() + "[", "]") + return new StringJoiner(", ", IdentifierContext.class.getSimpleName() + "[", "]") .add("identifier='" + identifier + "'") .add("elementSubclass=" + elementSubclass) .toString(); diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java index 72701aa85..ed779e8cf 100644 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/LibraryBuilder.java @@ -20,7 +20,7 @@ * Created by Bryn on 12/29/2016. */ public class LibraryBuilder { - public static enum SignatureLevel { + public enum SignatureLevel { /* Indicates signatures will never be included in operator invocations */ @@ -116,7 +116,8 @@ public LibraryManager getLibraryManager() { private final Stack expressionContext = new Stack<>(); private final ExpressionDefinitionContextStack expressionDefinitions = new ExpressionDefinitionContextStack(); private final Stack functionDefs = new Stack<>(); - private final Deque hidingIdentifiersContexts = new ArrayDeque<>(); + private final Deque globalIdentifiers = new ArrayDeque<>(); + private final Stack> localIdentifierStack = new Stack<>(); private int literalContext = 0; private int typeSpecifierContext = 0; private final NamespaceInfo namespaceInfo; @@ -3127,6 +3128,11 @@ private DataType getExpressionDefResultType(ExpressionDef expressionDef) { currentExpressionContext(), expressionDef.getContext())); } + public enum IdentifierScope { + GLOBAL, + LOCAL + } + /** * Add an identifier to the deque to indicate that we are considering it for consideration for identifier hiding and * adding a compiler warning if this is the case. @@ -3140,46 +3146,93 @@ private DataType getExpressionDefResultType(ExpressionDef expressionDef) { *

* Also, special case function overloads so that only a single overloaded function name is taken into account. * + * Default scope is {@link IdentifierScope#LOCAL} + * * @param identifier The identifier belonging to the parameter, expression, function, alias, etc, to be evaluated. * @param trackable The construct trackable, for example {@link ExpressionRef}. */ - void pushIdentifierForHiding(String identifier, Trackable trackable) { - final boolean hasRelevantMatch = hidingIdentifiersContexts.stream() - .filter(innerContext -> innerContext.getIdentifier().equals(identifier)) - .peek(matchedContext -> { - // If we are passing an overloaded function definition, do not issue a warning - if (trackable instanceof FunctionDef - && FunctionDef.class == matchedContext.getTrackableSubclass()) { - return; - } + void pushIdentifier(String identifier, Trackable trackable) { + pushIdentifier(identifier, trackable, IdentifierScope.LOCAL); + } - reportWarning( - resolveWarningMessage(matchedContext.getIdentifier(), identifier, trackable), trackable); - }) - .findAny() - .isPresent(); + /** + * Add an identifier to the deque to indicate that we are considering it for consideration for identifier hiding and + * adding a compiler warning if this is the case. + *

+ * For example, if an alias within an expression body has the same name as a parameter, execution would have + * added the parameter identifier and the next execution would consider an alias with the same name, thus resulting + * in a warning. + *

+ * Exact case matching as well as case-insensitive matching are considered. If known, the type of the structure + * in question will be considered in crafting the warning message, as per the {@link Element} parameter. + *

+ * Also, special case function overloads so that only a single overloaded function name is taken into account. + * + * @param identifier The identifier belonging to the parameter, expression, function, alias, etc, to be evaluated. + * @param trackable The construct trackable, for example {@link ExpressionRef}. + * @param scope the scope of the current identifier + */ + void pushIdentifier(String identifier, Trackable trackable, IdentifierScope scope) { + var localMatch = !localIdentifierStack.isEmpty() + ? findMatchingIdentifierContext(localIdentifierStack.peek(), identifier) + : Optional.empty(); + var globalMatch = findMatchingIdentifierContext(globalIdentifiers, identifier); + + if (globalMatch.isPresent() || localMatch.isPresent()) { + var matchedContext = globalMatch.isPresent() ? globalMatch.get() : localMatch.get(); + + boolean matchedOnFunctionOverloads = + matchedContext.getTrackableSubclass().equals(FunctionDef.class) && trackable instanceof FunctionDef; + + if (!matchedOnFunctionOverloads) { + reportWarning(resolveWarningMessage(matchedContext.getIdentifier(), identifier, trackable), trackable); + } + } - // We will only add function definitions once - if (!(trackable instanceof FunctionDef) || !hasRelevantMatch) { - if (shouldPushHidingContext(trackable)) { - final Class trackableOrNull = trackable != null ? trackable.getClass() : null; - // Sometimes the underlying Trackable doesn't resolve in the calling code - hidingIdentifiersContexts.push( - new HidingIdentifierContext(identifier, trackable != null ? trackable.getClass() : null)); + if (shouldAddIdentifierContext(trackable)) { + final Class trackableOrNull = trackable != null ? trackable.getClass() : null; + // Sometimes the underlying Trackable doesn't resolve in the calling code + if (scope == IdentifierScope.GLOBAL) { + globalIdentifiers.push(new IdentifierContext(identifier, trackableOrNull)); + } else { + localIdentifierStack.peek().push(new IdentifierContext(identifier, trackableOrNull)); } } } + private Optional findMatchingIdentifierContext( + Collection identifierContext, String identifier) { + return identifierContext.stream() + .filter(innerContext -> innerContext.getIdentifier().equals(identifier)) + .findFirst(); + } + /** * Pop the last resolved identifier off the deque. This is needed in case of a context in which an identifier - * falls out of scope, for an example, an alias within an expression or function body + * falls out of scope, for an example, an alias within an expression or function body. */ - void popIdentifierForHiding() { - hidingIdentifiersContexts.pop(); + void popIdentifier() { + popIdentifier(IdentifierScope.LOCAL); + } + + void popIdentifier(IdentifierScope scope) { + if (scope == IdentifierScope.GLOBAL) { + globalIdentifiers.pop(); + } else { + localIdentifierStack.peek().pop(); + } + } + + void pushIdentifierScope() { + localIdentifierStack.push(new ArrayDeque<>()); + } + + void popIdentifierScope() { + localIdentifierStack.pop(); } // TODO: consider other structures that should only trigger a readonly check of identifier hiding - private boolean shouldPushHidingContext(Trackable trackable) { + private boolean shouldAddIdentifierContext(Trackable trackable) { return !(trackable instanceof Literal); } diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r4/BaseTest.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r4/BaseTest.java index f6148aaa9..d2fcc1865 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r4/BaseTest.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/fhir/r4/BaseTest.java @@ -5,12 +5,14 @@ import static org.cqframework.cql.cql2elm.matchers.QuickDataType.quickDataType; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertEquals; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.cqframework.cql.cql2elm.CqlCompilerOptions; import org.cqframework.cql.cql2elm.CqlTranslator; +import org.cqframework.cql.cql2elm.LibraryBuilder.SignatureLevel; import org.cqframework.cql.cql2elm.TestUtils; import org.cqframework.cql.cql2elm.model.CompiledLibrary; import org.hl7.elm.r1.*; @@ -323,4 +325,11 @@ public void testRetrieveWithConcept() throws IOException { ToList toList = (ToList) retrieve.getCodes(); assertThat(toList.getOperand(), instanceOf(CodeRef.class)); } + + @Test + public void testExm108IdentifierHiding() throws IOException { + var translator = TestUtils.runSemanticTest("fhir/r4/exm108/EXM108.cql", 0, SignatureLevel.All); + // Should only be one identifier being hid after fixes, "Warafin" + assertEquals(1, translator.getExceptions().size()); + } } diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/EXM108.cql b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/EXM108.cql new file mode 100644 index 000000000..bb9f5ee43 --- /dev/null +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/EXM108.cql @@ -0,0 +1,424 @@ +library EXM108 version '8.3.000' +//for 2020 reporting year. QDM to QI-4 +//Venous Thromboembolism Prophylaxis +//Author: TJC + +using FHIR version '4.0.1' + +include FHIRHelpers called FHIRHelpers +include MATGlobalCommonFunctions called Global +include SupplementalDataElements called SDE +include VTEICU called VTEICU +include TJCOverall called TJC + +codesystem "SNOMEDCT": 'http://snomed.info/sct/731000124108' +codesystem "LOINC": 'http://loinc.org' +// NOTE: R4 code system URLs changed to be based on terminology.hl7.org... +codesystem "RequestIntent": 'http://terminology.hl7.org/CodeSystem/request-intent' +//NOTE: Created for ActPriority for Encounter.category which is codeableConcept +codesystem "ActPriority": 'http://terminology.hl7.org/ValueSet/v3-ActPriority' + +valueset "Device Application": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1110.48' +valueset "Atrial Fibrillation/Flutter": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.202' +valueset "Comfort Measures": 'http://cts.nlm.nih.gov/fhir/ValueSet/1.3.6.1.4.1.33895.1.3.0.45' +valueset "Direct Thrombin Inhibitor": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.205' +valueset "Emergency Department Visit": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.292' +valueset "General or Neuraxial Anesthesia": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.666.5.1743' +valueset "General Surgery": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.255' +valueset "Glycoprotein IIb/IIIa Inhibitors": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1045.41' +valueset "Graduated compression stockings (GCS)": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.256' +valueset "Gynecological Surgery": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.257' +valueset "Hemorrhagic Stroke": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.212' +valueset "Hip Fracture Surgery": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.258' +valueset "Hip Replacement Surgery": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.259' +valueset "Injectable Factor Xa Inhibitor for VTE Prophylaxis": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.211' +valueset "INR": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.213' +valueset "Intermittent pneumatic compression devices (IPC)": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.214' +valueset "Intracranial Neurosurgery": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.260' +valueset "Intravenous route": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.222' +valueset "Ischemic Stroke": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.247' +valueset "Knee Replacement Surgery": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.261' +valueset "Low Dose Unfractionated Heparin for VTE Prophylaxis": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1045.39' +valueset "Low Molecular Weight Heparin for VTE Prophylaxis": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.219' +valueset "Low Risk": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.400' +valueset "Medical Reason": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.473' +valueset "Mental Health Diagnoses": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.105.12.1004' +valueset "Obstetrics": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.263' +valueset "Obstetrics VTE": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.264' +valueset "Oral Factor Xa Inhibitor for VTE Prophylaxis or VTE Treatment": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.134' +valueset "Patient Refusal": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.93' +valueset "Subcutaneous route": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.223' +valueset "Unfractionated Heparin": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.218' +valueset "Urological Surgery": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.272' +valueset "Venous foot pumps (VFP)": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.230' +valueset "Venous Thromboembolism": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.279' +valueset "Warfarin": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.232' +valueset "Intensive Care Unit": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1110.23' + +code "Risk for venous thromboembolism": '72136-5' from "LOINC" display 'Risk for venous thromboembolism' + +context Patient + +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 "Initial Population": + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" + +// Note: added FHIRHelpers.ToDate() in R4 +define "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions": + ( Global."Inpatient Encounter" InpatientEncounter + with ["Patient"] BirthDate + such that Global."CalendarAgeInYearsAt"(FHIRHelpers.ToDate(BirthDate.birthDate), start of InpatientEncounter.period) >= 18 + ) + intersect "Admission Without VTE or Obstetrical Conditions" + + define "Admission Without VTE or Obstetrical Conditions": + Global."Inpatient Encounter" InpatientEncounter + where not (exists (Global.EncounterDiagnosis(InpatientEncounter)) EncounterDiagnosis + where (EncounterDiagnosis.code in "Obstetrics" + or EncounterDiagnosis.code in "Venous Thromboembolism" + or EncounterDiagnosis.code in "Obstetrics VTE" + ) + ) + +define "Denominator": + "Initial Population" + +define "Denominator Exclusion": + "Encounter Less Than 2 Days" + union "Encounter With ICU Location Stay 1 Day or More" + union "Encounter With Principal Diagnosis of Mental Disorder or Stroke" + union "Encounter With Principal Procedure of SCIP VTE Selected Surgery" + union "Encounter With Intervention Comfort Measures From Day of Start of Hospitalization To Day After Admission" + union "Encounter With Intervention Comfort Measures on Day of or Day After Procedure" + +define "Encounter Less Than 2 Days": + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + where Global."LengthInDays"(QualifyingEncounter.period)< 2 + +define "Encounter With ICU Location Stay 1 Day or More": + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + where exists ( QualifyingEncounter.location Location + where Global.GetLocation(Location.location).type in "Intensive Care Unit" + and Global."LengthInDays"(Location.period)>= 1 + and Location.period starts during TJC."CalendarDayOfOrDayAfter"(start of QualifyingEncounter.period) + ) + +define "Encounter With Principal Diagnosis of Mental Disorder or Stroke": + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + where Global."PrincipalDiagnosis"(QualifyingEncounter).code in "Mental Health Diagnoses" + or Global."PrincipalDiagnosis"(QualifyingEncounter).code in "Hemorrhagic Stroke" + or Global."PrincipalDiagnosis"(QualifyingEncounter).code in "Ischemic Stroke" + +/* NOTE: 2 options to express Principal Procedure to retrive "encounter Procedure as an url" and proedure "rank as extension" in R4 + VTE-1 uses Option 1 - generic extension funtion + VTE-2 uses Option 2 - Specific extension functions + TODO: Mapping Table needs revision on using "Encounter.procedure.code", "Encounter.procedure.sequence" +*/ + +define "Encounter With Principal Procedure of SCIP VTE Selected Surgery": + from + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter, + "SCIP VTE Selected Surgery" SelectedProcedure + let EncounterProcedure: Global.GetExtension(QualifyingEncounter, 'qicore-encounter-procedure') + where FHIRHelpers.ToInteger(Global.GetExtension(EncounterProcedure, 'rank').value as FHIR.positiveInt) = 1 + and Global.GetId(FHIRHelpers.ToString((Global.GetExtension(EncounterProcedure, 'procedure').value as FHIR.Reference).reference)) = SelectedProcedure.id + and Global."Normalize Interval"(SelectedProcedure.performed) during QualifyingEncounter.period + +define "SCIP VTE Selected Surgery": + ( ["Procedure": "General Surgery"] + union ["Procedure": "Gynecological Surgery"] + union ["Procedure": "Hip Fracture Surgery"] + union ["Procedure": "Hip Replacement Surgery"] + union ["Procedure": "Intracranial Neurosurgery"] + union ["Procedure": "Knee Replacement Surgery"] + union ["Procedure": "Urological Surgery"] ) Procedure + where Procedure.status = 'completed' + +define "Encounter With Intervention Comfort Measures From Day of Start of Hospitalization To Day After Admission": + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + with "Intervention Comfort Measures" ComfortMeasure + such that Coalesce(start of Global."Normalize Interval"(ComfortMeasure.performed), ComfortMeasure.authoredOn) during VTEICU."FromDayOfStartOfHospitalizationToDayAfterAdmission"(QualifyingEncounter) + +define "Intervention Comfort Measures": + (["ServiceRequest": "Comfort Measures"] P + where P.intent = 'order' + ) + union + (["Procedure": "Comfort Measures"] InterventionPerformed + where InterventionPerformed.status in {'completed', 'in-progress'}) + +define "Encounter With Intervention Comfort Measures on Day of or Day After Procedure": + from + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter, + ["Procedure": "General or Neuraxial Anesthesia"] AnesthesiaProcedure, + "Intervention Comfort Measures" ComfortMeasure + where AnesthesiaProcedure.status = 'completed' + and Global."Normalize Interval"(AnesthesiaProcedure.performed) ends 1 day after day of start of QualifyingEncounter.period + and Coalesce(start of Global."Normalize Interval"(ComfortMeasure.performed), ComfortMeasure.authoredOn) during TJC."CalendarDayOfOrDayAfter"(end of Global."Normalize Interval"(AnesthesiaProcedure.performed)) + return QualifyingEncounter + +define "Numerator": + "Encounter With VTE Prophylaxis Received on Day of or Day After Admission or Procedure" + union ( "Encounter With Medication Oral Factor Xa Inhibitor Administered on Day of or Day After Admission or Procedure" + intersect ( "Encounter With Prior or Present Diagnosis of Atrial Fibrillation or VTE" + union "Encounter With Prior or Present Procedure of Hip or Knee Replacement Surgery" + ) + ) + union "Encounter With Low Risk for VTE or Anticoagulant Administered" + union "Encounter With No VTE Prophylaxis Due to Medical Reason" + union "Encounter With No VTE Prophylaxis Due to Patient Refusal" + +define "Encounter With VTE Prophylaxis Received on Day of or Day After Admission or Procedure": + ( from + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter, + "VTE Prophylaxis by Medication Administered or Device Applied" VTEProphylaxis + where Coalesce(Global."Normalize Interval"(VTEProphylaxis.effective), Global."Normalize Interval"(VTEProphylaxis.performed)) starts during TJC."CalendarDayOfOrDayAfter"(start of QualifyingEncounter.period) + return QualifyingEncounter + ) + union ( + from + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter, + ["Procedure": "General or Neuraxial Anesthesia"] AnesthesiaProcedure, + "VTE Prophylaxis by Medication Administered or Device Applied" VTEProphylaxis + where Global."Normalize Interval"(AnesthesiaProcedure.performed) ends 1 day after day of start of QualifyingEncounter.period + and Coalesce(Global."Normalize Interval"(VTEProphylaxis.effective), Global."Normalize Interval"(VTEProphylaxis.performed)) starts during TJC."CalendarDayOfOrDayAfter"(end of Global."Normalize Interval"(AnesthesiaProcedure.performed)) + return QualifyingEncounter + ) + +// 10/29: Replaced DeviceUseStatement with Procedure with a new value set and procedure.usdCode for specifying devices +define "VTE Prophylaxis by Medication Administered or Device Applied": + ( ["MedicationAdministration": medication in "Low Dose Unfractionated Heparin for VTE Prophylaxis"] VTEMedication + where VTEMedication.status ='completed' + and VTEMedication.dosage.route in "Subcutaneous route" + ) + union (["MedicationAdministration": medication in "Low Molecular Weight Heparin for VTE Prophylaxis"] LMWH where LMWH.status = 'completed') + union (["MedicationAdministration": medication in "Injectable Factor Xa Inhibitor for VTE Prophylaxis"] FactorXa where FactorXa.status = 'completed') + union (["MedicationAdministration": medication in "Warfarin"] Warfarin where Warfarin.status = 'completed') + union ( + ["Procedure": "Device Application"] DeviceApplied + where DeviceApplied.status = 'complete' + and (DeviceApplied.usedCode in "Intermittent pneumatic compression devices (IPC)" + or DeviceApplied.usedCode in"Venous foot pumps (VFP)" + or DeviceApplied.usedCode in "Graduated compression stockings (GCS)" + ) + ) + +define "Encounter With Medication Oral Factor Xa Inhibitor Administered on Day of or Day After Admission or Procedure": + ( from + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter, + ["MedicationAdministration": medication in "Oral Factor Xa Inhibitor for VTE Prophylaxis or VTE Treatment"] FactorXaMedication + where FactorXaMedication.status = 'completed' + and Global."Normalize Interval"(FactorXaMedication.effective) starts during TJC."CalendarDayOfOrDayAfter"(start of QualifyingEncounter.period) + return QualifyingEncounter + ) + union ( from + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter, + ["Procedure": "General or Neuraxial Anesthesia"] AnesthesiaProcedure, + ["MedicationAdministration": medication in "Oral Factor Xa Inhibitor for VTE Prophylaxis or VTE Treatment"] FactorXaMedication + where FactorXaMedication.status = 'completed' + and AnesthesiaProcedure.status = 'completed' + and Global."Normalize Interval"(AnesthesiaProcedure.performed) ends 1 day after day of start of QualifyingEncounter.period + and Global."Normalize Interval"(FactorXaMedication.effective) starts during TJC."CalendarDayOfOrDayAfter"(end of Global."Normalize Interval"(AnesthesiaProcedure.performed)) + return QualifyingEncounter + ) + +/*NOTE: Feedback at Digitial Quality Summit 2019 indicates verificationStatus may be difficult for implementers to retrieve + both condition.clinicalStatus and condition.verificationStatus = 'confirmed' +*/ +define "Encounter With Prior or Present Diagnosis of Atrial Fibrillation or VTE": + ( "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + with ["Condition": code in "Atrial Fibrillation/Flutter"] AtrialFibrillation + such that FHIRHelpers.ToConcept(AtrialFibrillation.clinicalStatus) in { Global."active", Global."recurrence", Global."relapse" } + and Global."Normalize Interval"(AtrialFibrillation.onset) starts on or before end of QualifyingEncounter.period + ) + union ( "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + where Global.EncounterDiagnosis(QualifyingEncounter).code in "Atrial Fibrillation/Flutter" + ) + union ( "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + with ["Condition": code in "Venous Thromboembolism"] VTEDiagnosis + such that FHIRHelpers.ToConcept(VTEDiagnosis.clinicalStatus) in { Global."inactive", Global."remission", Global."resolved" } + //and VTEDiagnosis.verificationStatus = 'confirmed' + and Global."Normalize Interval"(VTEDiagnosis.onset) before start of QualifyingEncounter.period + ) + +define "Encounter With Prior or Present Procedure of Hip or Knee Replacement Surgery": + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + with ( ["Procedure": "Hip Replacement Surgery"] + union ["Procedure": "Knee Replacement Surgery"] ) HipKneeProcedure + such that HipKneeProcedure.status = 'completed' + and Global."Normalize Interval"(HipKneeProcedure.performed) starts on or before end of QualifyingEncounter.period + + +define "Encounter With Low Risk for VTE or Anticoagulant Administered": + "Low Risk for VTE or Anticoagulant Administered From Day of Start of Hospitalization To Day After Admission" + union "Low Risk for VTE or Anticoagulant Administered on Day of or Day After Procedure" + +define "Low Risk for VTE or Anticoagulant Administered From Day of Start of Hospitalization To Day After Admission": + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + with "Is In Low Risk for VTE or On Anticoagulant" VTERiskAssessment + such that Global."Normalize Interval"(VTERiskAssessment.effective) starts during VTEICU."FromDayOfStartOfHospitalizationToDayAfterAdmission"(QualifyingEncounter) + +define "Is In Low Risk for VTE or On Anticoagulant": + ( ["Observation": "Risk for venous thromboembolism"] VTERiskAssessment + where VTERiskAssessment.value in "Low Risk" + and VTERiskAssessment.status in {'final','amended', 'corrected'} + ) + union ( ["Observation": "INR"] INRLabTest + where INRLabTest.value as Quantity > 3.0 + and INRLabTest.status in {'final','amended', 'corrected'} + return "Observation" { id: INRLabTest.id, effective: INRLabTest.issued } + ) + union ((( ["MedicationAdministration": "Unfractionated Heparin"] UnfractionatedHeparin + where UnfractionatedHeparin.dosage.route in "Intravenous route" + ) + union ["MedicationAdministration": "Direct Thrombin Inhibitor"] + union ["MedicationAdministration": "Glycoprotein IIb/IIIa Inhibitors"] ) AnticoagulantMedication + where AnticoagulantMedication.status = 'complete' + return "Observation" { id: AnticoagulantMedication.id, effective: AnticoagulantMedication.effective } + ) + +define "Low Risk for VTE or Anticoagulant Administered on Day of or Day After Procedure": + from + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter, + ["Procedure": "General or Neuraxial Anesthesia"] AnesthesiaProcedure, + "Is In Low Risk for VTE or On Anticoagulant" VTERiskAssessment + where Global."Normalize Interval"(AnesthesiaProcedure.performed) ends 1 day after day of start of QualifyingEncounter.period + and Global."Normalize Interval"(VTERiskAssessment.effective) starts during TJC."CalendarDayOfOrDayAfter"(end of Global."Normalize Interval"(AnesthesiaProcedure.performed)) + return QualifyingEncounter + +define "Encounter With No VTE Prophylaxis Due to Medical Reason": + ( "No VTE Prophylaxis Medication Due to Medical Reason From Day of Start of Hospitalization To Day After Admission" + intersect "No VTE Prophylaxis Device Due to Medical Reason From Day of Start of Hospitalization To Day After Admission" + ) + union ( "No VTE Prophylaxis Medication Due to Medical Reason on Day of or Day After Procedure" + intersect "No VTE Prophylaxis Device Due to Medical Reason on Day of or Day After Procedure" + ) + +define "No VTE Prophylaxis Medication Due to Medical Reason From Day of Start of Hospitalization To Day After Admission": + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + with "No VTE Prophylaxis Medication Administered or Ordered" NoVTEMedication + such that Coalesce(NoVTEMedication.statusReason,NoVTEMedication.reasonCode) in "Medical Reason" + and Coalesce(NoVTEMedication.authoredOn, start of Global."Normalize Interval"(NoVTEMedication.effective)) during VTEICU."FromDayOfStartOfHospitalizationToDayAfterAdmission"(QualifyingEncounter) + +define "No VTE Prophylaxis Medication Administered or Ordered": + (( ["MedicationAdministration": medication in "Low Dose Unfractionated Heparin for VTE Prophylaxis"] + union + ["MedicationAdministration": medication in "Low Molecular Weight Heparin for VTE Prophylaxis"] + union + ["MedicationAdministration": medication in "Injectable Factor Xa Inhibitor for VTE Prophylaxis"] + union + ["MedicationAdministration": medication in "Warfarin"] + ) MedicationAdm + where MedicationAdm.status = 'not-done' + ) + union + (( ["MedicationRequest": medication in "Low Dose Unfractionated Heparin for VTE Prophylaxis"] + union + ["MedicationRequest": medication in "Low Molecular Weight Heparin for VTE Prophylaxis"] + union + ["MedicationRequest": medication in "Injectable Factor Xa Inhibitor for VTE Prophylaxis"] + union + ["MedicationRequest": medication in "Warfarin"] + ) MedicationOrder + where MedicationOrder.doNotPerform is true + and MedicationOrder.status in {'completed', 'cancelled'} + //11/5 discussion: all expressions for QI-Core must include a status. + //Therefore, we need ServiceRequest.status = completed; and MedicationRequest.status = cancelled (or completed, since cancelled indicates it was once ordered but intentionally never filled). + //So the not done issue is handled by ServiceRequest.doNotPerform = True Or MedicationRequest.doNotPerform = True + ) + +define "No VTE Prophylaxis Medication Due to Medical Reason on Day of or Day After Procedure": + from + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter, + ["Procedure": "General or Neuraxial Anesthesia"] AnesthesiaProcedure, + "No VTE Prophylaxis Medication Administered or Ordered" NoVTEMedication + where Coalesce(NoVTEMedication.statusReason,NoVTEMedication.reasonCode) in "Medical Reason" + and AnesthesiaProcedure.status = 'completed' + and Global."Normalize Interval"(AnesthesiaProcedure.performed) ends 1 day after day of start of QualifyingEncounter.period + and Coalesce(NoVTEMedication.authoredOn, start of Global."Normalize Interval"(NoVTEMedication.effective)) during TJC."CalendarDayOfOrDayAfter"(end of Global."Normalize Interval"(AnesthesiaProcedure.performed)) + return QualifyingEncounter + +define "No VTE Prophylaxis Device Due to Medical Reason From Day of Start of Hospitalization To Day After Admission": + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + with "No VTE Prophylaxis Device Applied or Ordered" NoVTEDevice + such that NoVTEDevice.requestStatusReason in "Medical Reason" + and NoVTEDevice.authoredOn during VTEICU."FromDayOfStartOfHospitalizationToDayAfterAdmission"(QualifyingEncounter) + +//11/5: ServiceRequest not done reason is an extension in QI-Core R4 as ServiceRequest.extension:statusReason +define function GetStatusReason(requestReason ServiceRequest): + Global.GetBaseExtension(requestReason, 'request-statusReason') + +define "No VTE Prophylaxis Device Applied or Ordered": + (( + ["ServiceRequest": "Venous foot pumps (VFP)"] + union ["ServiceRequest": "Intermittent pneumatic compression devices (IPC)"] + union ["ServiceRequest": "Graduated compression stockings (GCS)"] + ) DeviceOrder + where DeviceOrder.status = 'completed' + //11/5 discussion: all expressions for QI-Core must include a status + //ServiceRequest.status - draft | active | suspended | completed | entered-in-error | cancelled + and DeviceOrder.doNotPerform is true + return {id: DeviceOrder.id, requestStatusReason: GetStatusReason(DeviceOrder), authoredOn: DeviceOrder.authoredOn} + ) + union + ( + ["Procedure": "Device Application"] DeviceApplied + let DeviceNotDoneTiming: Global.GetExtension(DeviceApplied, 'qicore-recorded').value + where (DeviceApplied.usedCode in "Intermittent pneumatic compression devices (IPC)" + or DeviceApplied.usedCode in "Venous foot pumps (VFP)" + or DeviceApplied.usedCode in "Graduated compression stockings (GCS)" + ) + and DeviceApplied.status = 'not-done' + return {id: DeviceApplied.id, requestStatusReason: DeviceApplied.statusReason, authoredOn: DeviceNotDoneTiming} + + ) + +define "No VTE Prophylaxis Device Due to Medical Reason on Day of or Day After Procedure": + from + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter, + ["Procedure": "General or Neuraxial Anesthesia"] AnesthesiaProcedure, + "No VTE Prophylaxis Device Applied or Ordered" NoVTEDevice + where NoVTEDevice.requestStatusReason in "Medical Reason" + and AnesthesiaProcedure.status = 'completed' + and Global."Normalize Interval"(AnesthesiaProcedure.performed) ends 1 day after day of start of QualifyingEncounter.period + and NoVTEDevice.authoredOn during TJC."CalendarDayOfOrDayAfter"(end of Global."Normalize Interval"(AnesthesiaProcedure.performed)) + return QualifyingEncounter + +define "Encounter With No VTE Prophylaxis Due to Patient Refusal": + "No VTE Prophylaxis Due to Patient Refusal From Day of Start of Hospitalization To Day After Admission" + union "No VTE Prophylaxis Due to Patient Refusal on Day of or Day After Procedure" + +define "No VTE Prophylaxis Due to Patient Refusal From Day of Start of Hospitalization To Day After Admission": + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter + with "No VTE Prophylaxis Medication or Device Due to Patient Refusal" PatientRefusal + such that Coalesce(start of Global."Normalize Interval"(PatientRefusal.effective), PatientRefusal.authoredOn) + during VTEICU."FromDayOfStartOfHospitalizationToDayAfterAdmission"(QualifyingEncounter) + +define "No VTE Prophylaxis Medication or Device Due to Patient Refusal": + ( "No VTE Prophylaxis Medication Administered or Ordered" NoVTEMedication + where Coalesce(NoVTEMedication.statusReason,NoVTEMedication.reasonCode) in "Patient Refusal" + ) + union ("No VTE Prophylaxis Device Applied or Ordered" NoVTEDevice + where NoVTEDevice.requestStatusReason in "Patient Refusal" + ) + +define "No VTE Prophylaxis Due to Patient Refusal on Day of or Day After Procedure": + from + "Encounter With Age Range and Without VTE Diagnosis or Obstetrical Conditions" QualifyingEncounter, + ["Procedure": "General or Neuraxial Anesthesia"] AnesthesiaProcedure, + "No VTE Prophylaxis Medication or Device Due to Patient Refusal" PatientRefusal + where Global."Normalize Interval"(AnesthesiaProcedure.performed) ends 1 day after day of start of QualifyingEncounter.period + and Coalesce(start of Global."Normalize Interval"(PatientRefusal.effective), PatientRefusal.authoredOn) + during TJC."CalendarDayOfOrDayAfter"(end of Global."Normalize Interval"(AnesthesiaProcedure.performed)) + return QualifyingEncounter diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/FHIRHelpers.cql b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/FHIRHelpers.cql new file mode 100644 index 000000000..3c3b8f656 --- /dev/null +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/FHIRHelpers.cql @@ -0,0 +1,301 @@ +library FHIRHelpers version '4.0.1' + +using FHIR version '4.0.1' + +define function ToInterval(period FHIR.Period): + if period is null then + null + else + Interval[period."start".value, period."end".value] + +define function ToQuantity(quantity FHIR.Quantity): + if quantity is null then + null + else + System.Quantity { value: quantity.value.value, unit: quantity.unit.value } + +define function ToInterval(range FHIR.Range): + if range is null then + null + else + Interval[ToQuantity(range.low), ToQuantity(range.high)] + +define function ToCode(coding FHIR.Coding): + if coding is null then + null + else + System.Code { + code: coding.code.value, + system: coding.system.value, + version: coding.version.value, + display: coding.display.value + } + +define function ToConcept(concept FHIR.CodeableConcept): + if concept is null then + null + else + System.Concept { + codes: concept.coding C return ToCode(C), + display: concept.text.value + } + + +define function ToString(value AccountStatus): value.value +define function ToString(value ActionCardinalityBehavior): value.value +define function ToString(value ActionConditionKind): value.value +define function ToString(value ActionGroupingBehavior): value.value +define function ToString(value ActionParticipantType): value.value +define function ToString(value ActionPrecheckBehavior): value.value +define function ToString(value ActionRelationshipType): value.value +define function ToString(value ActionRequiredBehavior): value.value +define function ToString(value ActionSelectionBehavior): value.value +define function ToString(value ActivityDefinitionKind): value.value +define function ToString(value ActivityParticipantType): value.value +define function ToString(value AddressType): value.value +define function ToString(value AddressUse): value.value +define function ToString(value AdministrativeGender): value.value +define function ToString(value AdverseEventActuality): value.value +define function ToString(value AggregationMode): value.value +define function ToString(value AllergyIntoleranceCategory): value.value +define function ToString(value AllergyIntoleranceCriticality): value.value +define function ToString(value AllergyIntoleranceSeverity): value.value +define function ToString(value AllergyIntoleranceType): value.value +define function ToString(value AppointmentStatus): value.value +define function ToString(value AssertionDirectionType): value.value +define function ToString(value AssertionOperatorType): value.value +define function ToString(value AssertionResponseTypes): value.value +define function ToString(value AuditEventAction): value.value +define function ToString(value AuditEventAgentNetworkType): value.value +define function ToString(value AuditEventOutcome): value.value +define function ToString(value BindingStrength): value.value +define function ToString(value BiologicallyDerivedProductCategory): value.value +define function ToString(value BiologicallyDerivedProductStatus): value.value +define function ToString(value BiologicallyDerivedProductStorageScale): value.value +define function ToString(value BundleType): value.value +define function ToString(value CapabilityStatementKind): value.value +define function ToString(value CarePlanActivityKind): value.value +define function ToString(value CarePlanActivityStatus): value.value +define function ToString(value CarePlanIntent): value.value +define function ToString(value CarePlanStatus): value.value +define function ToString(value CareTeamStatus): value.value +define function ToString(value CatalogEntryRelationType): value.value +define function ToString(value ChargeItemDefinitionPriceComponentType): value.value +define function ToString(value ChargeItemStatus): value.value +define function ToString(value ClaimResponseStatus): value.value +define function ToString(value ClaimStatus): value.value +define function ToString(value ClinicalImpressionStatus): value.value +define function ToString(value CodeSearchSupport): value.value +define function ToString(value CodeSystemContentMode): value.value +define function ToString(value CodeSystemHierarchyMeaning): value.value +define function ToString(value CommunicationPriority): value.value +define function ToString(value CommunicationRequestStatus): value.value +define function ToString(value CommunicationStatus): value.value +define function ToString(value CompartmentCode): value.value +define function ToString(value CompartmentType): value.value +define function ToString(value CompositionAttestationMode): value.value +define function ToString(value CompositionStatus): value.value +define function ToString(value ConceptMapEquivalence): value.value +define function ToString(value ConceptMapGroupUnmappedMode): value.value +define function ToString(value ConditionalDeleteStatus): value.value +define function ToString(value ConditionalReadStatus): value.value +define function ToString(value ConsentDataMeaning): value.value +define function ToString(value ConsentProvisionType): value.value +define function ToString(value ConsentState): value.value +define function ToString(value ConstraintSeverity): value.value +define function ToString(value ContactPointSystem): value.value +define function ToString(value ContactPointUse): value.value +define function ToString(value ContractPublicationStatus): value.value +define function ToString(value ContractStatus): value.value +define function ToString(value ContributorType): value.value +define function ToString(value CoverageStatus): value.value +define function ToString(value CurrencyCode): value.value +define function ToString(value DayOfWeek): value.value +define function ToString(value DaysOfWeek): value.value +define function ToString(value DetectedIssueSeverity): value.value +define function ToString(value DetectedIssueStatus): value.value +define function ToString(value DeviceMetricCalibrationState): value.value +define function ToString(value DeviceMetricCalibrationType): value.value +define function ToString(value DeviceMetricCategory): value.value +define function ToString(value DeviceMetricColor): value.value +define function ToString(value DeviceMetricOperationalStatus): value.value +define function ToString(value DeviceNameType): value.value +define function ToString(value DeviceRequestStatus): value.value +define function ToString(value DeviceUseStatementStatus): value.value +define function ToString(value DiagnosticReportStatus): value.value +define function ToString(value DiscriminatorType): value.value +define function ToString(value DocumentConfidentiality): value.value +define function ToString(value DocumentMode): value.value +define function ToString(value DocumentReferenceStatus): value.value +define function ToString(value DocumentRelationshipType): value.value +define function ToString(value EligibilityRequestPurpose): value.value +define function ToString(value EligibilityRequestStatus): value.value +define function ToString(value EligibilityResponsePurpose): value.value +define function ToString(value EligibilityResponseStatus): value.value +define function ToString(value EnableWhenBehavior): value.value +define function ToString(value EncounterLocationStatus): value.value +define function ToString(value EncounterStatus): value.value +define function ToString(value EndpointStatus): value.value +define function ToString(value EnrollmentRequestStatus): value.value +define function ToString(value EnrollmentResponseStatus): value.value +define function ToString(value EpisodeOfCareStatus): value.value +define function ToString(value EventCapabilityMode): value.value +define function ToString(value EventTiming): value.value +define function ToString(value EvidenceVariableType): value.value +define function ToString(value ExampleScenarioActorType): value.value +define function ToString(value ExplanationOfBenefitStatus): value.value +define function ToString(value ExposureState): value.value +define function ToString(value ExtensionContextType): value.value +define function ToString(value FHIRAllTypes): value.value +define function ToString(value FHIRDefinedType): value.value +define function ToString(value FHIRDeviceStatus): value.value +define function ToString(value FHIRResourceType): value.value +define function ToString(value FHIRSubstanceStatus): value.value +define function ToString(value FHIRVersion): value.value +define function ToString(value FamilyHistoryStatus): value.value +define function ToString(value FilterOperator): value.value +define function ToString(value FlagStatus): value.value +define function ToString(value GoalLifecycleStatus): value.value +define function ToString(value GraphCompartmentRule): value.value +define function ToString(value GraphCompartmentUse): value.value +define function ToString(value GroupMeasure): value.value +define function ToString(value GroupType): value.value +define function ToString(value GuidanceResponseStatus): value.value +define function ToString(value GuidePageGeneration): value.value +define function ToString(value GuideParameterCode): value.value +define function ToString(value HTTPVerb): value.value +define function ToString(value IdentifierUse): value.value +define function ToString(value IdentityAssuranceLevel): value.value +define function ToString(value ImagingStudyStatus): value.value +define function ToString(value ImmunizationEvaluationStatus): value.value +define function ToString(value ImmunizationStatus): value.value +define function ToString(value InvoicePriceComponentType): value.value +define function ToString(value InvoiceStatus): value.value +define function ToString(value IssueSeverity): value.value +define function ToString(value IssueType): value.value +define function ToString(value LinkType): value.value +define function ToString(value LinkageType): value.value +define function ToString(value ListMode): value.value +define function ToString(value ListStatus): value.value +define function ToString(value LocationMode): value.value +define function ToString(value LocationStatus): value.value +define function ToString(value MeasureReportStatus): value.value +define function ToString(value MeasureReportType): value.value +define function ToString(value MediaStatus): value.value +define function ToString(value MedicationAdministrationStatus): value.value +define function ToString(value MedicationDispenseStatus): value.value +define function ToString(value MedicationKnowledgeStatus): value.value +define function ToString(value MedicationRequestIntent): value.value +define function ToString(value MedicationRequestPriority): value.value +define function ToString(value MedicationRequestStatus): value.value +define function ToString(value MedicationStatementStatus): value.value +define function ToString(value MedicationStatus): value.value +define function ToString(value MessageSignificanceCategory): value.value +define function ToString(value Messageheader_Response_Request): value.value +define function ToString(value MimeType): value.value +define function ToString(value NameUse): value.value +define function ToString(value NamingSystemIdentifierType): value.value +define function ToString(value NamingSystemType): value.value +define function ToString(value NarrativeStatus): value.value +define function ToString(value NoteType): value.value +define function ToString(value NutritiionOrderIntent): value.value +define function ToString(value NutritionOrderStatus): value.value +define function ToString(value ObservationDataType): value.value +define function ToString(value ObservationRangeCategory): value.value +define function ToString(value ObservationStatus): value.value +define function ToString(value OperationKind): value.value +define function ToString(value OperationParameterUse): value.value +define function ToString(value OrientationType): value.value +define function ToString(value ParameterUse): value.value +define function ToString(value ParticipantRequired): value.value +define function ToString(value ParticipantStatus): value.value +define function ToString(value ParticipationStatus): value.value +define function ToString(value PaymentNoticeStatus): value.value +define function ToString(value PaymentReconciliationStatus): value.value +define function ToString(value ProcedureStatus): value.value +define function ToString(value PropertyRepresentation): value.value +define function ToString(value PropertyType): value.value +define function ToString(value ProvenanceEntityRole): value.value +define function ToString(value PublicationStatus): value.value +define function ToString(value QualityType): value.value +define function ToString(value QuantityComparator): value.value +define function ToString(value QuestionnaireItemOperator): value.value +define function ToString(value QuestionnaireItemType): value.value +define function ToString(value QuestionnaireResponseStatus): value.value +define function ToString(value ReferenceHandlingPolicy): value.value +define function ToString(value ReferenceVersionRules): value.value +define function ToString(value ReferredDocumentStatus): value.value +define function ToString(value RelatedArtifactType): value.value +define function ToString(value RemittanceOutcome): value.value +define function ToString(value RepositoryType): value.value +define function ToString(value RequestIntent): value.value +define function ToString(value RequestPriority): value.value +define function ToString(value RequestStatus): value.value +define function ToString(value ResearchElementType): value.value +define function ToString(value ResearchStudyStatus): value.value +define function ToString(value ResearchSubjectStatus): value.value +define function ToString(value ResourceType): value.value +define function ToString(value ResourceVersionPolicy): value.value +define function ToString(value ResponseType): value.value +define function ToString(value RestfulCapabilityMode): value.value +define function ToString(value RiskAssessmentStatus): value.value +define function ToString(value SPDXLicense): value.value +define function ToString(value SearchComparator): value.value +define function ToString(value SearchEntryMode): value.value +define function ToString(value SearchModifierCode): value.value +define function ToString(value SearchParamType): value.value +define function ToString(value SectionMode): value.value +define function ToString(value SequenceType): value.value +define function ToString(value ServiceRequestIntent): value.value +define function ToString(value ServiceRequestPriority): value.value +define function ToString(value ServiceRequestStatus): value.value +define function ToString(value SlicingRules): value.value +define function ToString(value SlotStatus): value.value +define function ToString(value SortDirection): value.value +define function ToString(value SpecimenContainedPreference): value.value +define function ToString(value SpecimenStatus): value.value +define function ToString(value Status): value.value +define function ToString(value StrandType): value.value +define function ToString(value StructureDefinitionKind): value.value +define function ToString(value StructureMapContextType): value.value +define function ToString(value StructureMapGroupTypeMode): value.value +define function ToString(value StructureMapInputMode): value.value +define function ToString(value StructureMapModelMode): value.value +define function ToString(value StructureMapSourceListMode): value.value +define function ToString(value StructureMapTargetListMode): value.value +define function ToString(value StructureMapTransform): value.value +define function ToString(value SubscriptionChannelType): value.value +define function ToString(value SubscriptionStatus): value.value +define function ToString(value SupplyDeliveryStatus): value.value +define function ToString(value SupplyRequestStatus): value.value +define function ToString(value SystemRestfulInteraction): value.value +define function ToString(value TaskIntent): value.value +define function ToString(value TaskPriority): value.value +define function ToString(value TaskStatus): value.value +define function ToString(value TestReportActionResult): value.value +define function ToString(value TestReportParticipantType): value.value +define function ToString(value TestReportResult): value.value +define function ToString(value TestReportStatus): value.value +define function ToString(value TestScriptRequestMethodCode): value.value +define function ToString(value TriggerType): value.value +define function ToString(value TypeDerivationRule): value.value +define function ToString(value TypeRestfulInteraction): value.value +define function ToString(value UDIEntryType): value.value +define function ToString(value UnitsOfTime): value.value +define function ToString(value Use): value.value +define function ToString(value VariableType): value.value +define function ToString(value VisionBase): value.value +define function ToString(value VisionEyes): value.value +define function ToString(value VisionStatus): value.value +define function ToString(value XPathUsageType): value.value +define function ToString(value base64Binary): value.value +define function ToBoolean(value boolean): value.value +define function ToDate(value date): value.value +define function ToDateTime(value dateTime): value.value +define function ToDecimal(value decimal): value.value +define function ToDateTime(value instant): value.value +define function ToInteger(value integer): value.value +define function ToString(value string): value.value +define function ToTime(value time): value.value +define function ToString(value uri): value.value +define function ToString(value xhtml): value.value \ No newline at end of file diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/MATGlobalCommonFunctions.cql b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/MATGlobalCommonFunctions.cql new file mode 100644 index 000000000..39880ee59 --- /dev/null +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/MATGlobalCommonFunctions.cql @@ -0,0 +1,283 @@ +library MATGlobalCommonFunctions version '5.0.000' + +using FHIR version '4.0.1' + +include FHIRHelpers called FHIRHelpers + +codesystem "LOINC": 'http://loinc.org' +codesystem "SNOMEDCT": 'http://snomed.info/sct/731000124108' +codesystem "RoleCode": 'http://hl7.org/fhir/v3/RoleCode' +codesystem "Diagnosis Role": 'http://terminology.hl7.org/CodeSystem/diagnosis-role' +codesystem "RequestIntent": 'http://terminology.hl7.org/CodeSystem/request-intent' +codesystem "MedicationRequestCategory": 'http://terminology.hl7.org/CodeSystem/medicationrequest-category' +codesystem "ConditionClinicalStatusCodes": 'http://terminology.hl7.org/CodeSystem/condition-clinical' +codesystem "ConditionVerificationStatusCodes": 'http://terminology.hl7.org/CodeSystem/condition-verification' +codesystem "AllergyIntoleranceClinicalStatusCodes": 'http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical' +codesystem "AllergyIntoleranceVerificationStatusCodes": 'http://terminology.hl7.org/CodeSystem/allergyintolerance-verification' + +valueset "Encounter Inpatient": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.666.5.307' +valueset "Emergency Department Visit": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.292' +valueset "Observation Services": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1111.143' + +code "Birthdate": '21112-8' from "LOINC" display 'Birth date' +code "Dead": '419099009' from "SNOMEDCT" display 'Dead' +code "ER": 'ER' from "RoleCode" display 'Emergency room' +code "ICU": 'ICU' from "RoleCode" display 'Intensive care unit' +code "Billing": 'billing' from "Diagnosis Role" display 'Billing' + +// Condition Clinical Status Codes - Consider value sets for these +code "active": 'active' from "ConditionClinicalStatusCodes" +code "recurrence": 'recurrence' from "ConditionClinicalStatusCodes" +code "relapse": 'relapse' from "ConditionClinicalStatusCodes" +code "inactive": 'inactive' from "ConditionClinicalStatusCodes" +code "remission": 'remission' from "ConditionClinicalStatusCodes" +code "resolved": 'resolved' from "ConditionClinicalStatusCodes" + +// Condition Verification Status Codes - Consider value sets for these +code "unconfirmed": 'unconfirmed' from ConditionVerificationStatusCodes +code "provisional": 'provisional' from ConditionVerificationStatusCodes +code "differential": 'differential' from ConditionVerificationStatusCodes +code "confirmed": 'confirmed' from ConditionVerificationStatusCodes +code "refuted": 'refuted' from ConditionVerificationStatusCodes +code "entered-in-error": 'entered-in-error' from ConditionVerificationStatusCodes + +code "allergy-active": 'active' from "AllergyIntoleranceClinicalStatusCodes" +code "allergy-inactive": 'inactive' from "AllergyIntoleranceClinicalStatusCodes" +code "allergy-resolved": 'resolved' from "AllergyIntoleranceClinicalStatusCodes" + +// Allergy/Intolerance Verification Status Codes - Consider value sets for these +code "allergy-unconfirmed": 'unconfirmed' from AllergyIntoleranceVerificationStatusCodes +code "allergy-confirmed": 'confirmed' from AllergyIntoleranceVerificationStatusCodes +code "allergy-refuted": 'refuted' from AllergyIntoleranceVerificationStatusCodes + +// MedicationRequest Category Codes +code "Community": 'community' from "MedicationRequestCategory" display 'Community' +code "Discharge": 'discharge' from "MedicationRequestCategory" display 'Discharge' + +parameter "Measurement Period" Interval + default Interval[@2019-01-01T00:00:00.0, @2020-01-01T00:00:00.0) + +context Patient + +define "Inpatient Encounter": + [Encounter: "Encounter Inpatient"] EncounterInpatient + where EncounterInpatient.status = 'finished' + and "LengthInDays"(EncounterInpatient.period) <= 120 + and EncounterInpatient.period ends during "Measurement Period" + +define function "ToDate"(Value DateTime): + DateTime(year from Value, month from Value, day from Value, 0, 0, 0, 0, timezoneoffset from Value) + +define function "CalendarAgeInDaysAt"(BirthDateTime DateTime, AsOf DateTime): + days between ToDate(BirthDateTime)and ToDate(AsOf) + +define function "CalendarAgeInDays"(BirthDateTime DateTime): + CalendarAgeInDaysAt(BirthDateTime, Today()) + +define function "CalendarAgeInMonthsAt"(BirthDateTime DateTime, AsOf DateTime): + months between ToDate(BirthDateTime)and ToDate(AsOf) + +define function "CalendarAgeInMonths"(BirthDateTime DateTime): + CalendarAgeInMonthsAt(BirthDateTime, Today()) + +define function "CalendarAgeInYearsAt"(BirthDateTime DateTime, AsOf DateTime): + years between ToDate(BirthDateTime)and ToDate(AsOf) + +define function "CalendarAgeInYears"(BirthDateTime DateTime): + CalendarAgeInYearsAt(BirthDateTime, Today()) + +define function "LengthInDays"(Value Interval): + difference in days between start of Value and end of Value + +define function "ED Visit"(TheEncounter FHIR.Encounter): + singleton from ( + [Encounter: "Emergency Department Visit"] EDVisit + where EDVisit.status = 'finished' + and EDVisit.period ends 1 hour or less on or before start of FHIRHelpers.ToInterval(TheEncounter.period) + ) + +define function "Hospitalization"(TheEncounter FHIR.Encounter): + ( "ED Visit"(TheEncounter) ) X + return + if X is null then TheEncounter.period + else Interval[start of FHIRHelpers.ToInterval(X.period), end of FHIRHelpers.ToInterval(TheEncounter.period)] + +define function "Hospitalization Locations"(TheEncounter FHIR.Encounter): + ( "ED Visit"(TheEncounter) ) EDEncounter + return + if EDEncounter is null then TheEncounter.location + else flatten { EDEncounter.location, TheEncounter.location } + +define function "Hospitalization Length of Stay"(TheEncounter FHIR.Encounter): + LengthInDays("Hospitalization"(TheEncounter)) + +define function "Hospital Admission Time"(TheEncounter FHIR.Encounter): + start of "Hospitalization"(TheEncounter) + +define function "Hospital Discharge Time"(TheEncounter FHIR.Encounter): + end of FHIRHelpers.ToInterval(TheEncounter.period) + +define function "Hospital Arrival Time"(TheEncounter FHIR.Encounter): + start of FHIRHelpers.ToInterval(First( + ( "Hospitalization Locations"(TheEncounter) ) HospitalLocation + sort by start of FHIRHelpers.ToInterval(period) + ).period) + +define function "HospitalizationWithObservation"(TheEncounter FHIR.Encounter): + TheEncounter Visit + let ObsVisit: Last([Encounter: "Observation Services"] LastObs + where LastObs.period ends 1 hour or less on or before start of Visit.period + sort by end of period + ), + VisitStart: Coalesce(start of ObsVisit.period, start of Visit.period), + EDVisit: Last([Encounter: "Emergency Department Visit"] LastED + where LastED.period ends 1 hour or less on or before VisitStart + sort by end of period + ) + return Interval[Coalesce(start of EDVisit.period, VisitStart), end of Visit.period] + +define function "HospitalizationWithObservationLengthofStay"(Encounter FHIR.Encounter): + "LengthInDays"("HospitalizationWithObservation"(Encounter)) + +// TODO - fix these (must fetch Location resources and compare id to reference) +/*define function "Hospital Departure Time"(TheEncounter FHIR.Encounter): + end of FHIRHelpers.ToInterval(Last( + ( "Hospitalization Locations"(TheEncounter) ) HospitalLocation + sort by start of FHIRHelpers.ToInterval(period) + ).period) + +define function "Emergency Department Arrival Time"(TheEncounter FHIR.Encounter): + start of FHIRHelpers.ToInterval(( + singleton from ( + ( "Hospitalization Locations"(TheEncounter) ) HospitalLocation + where HospitalLocation.type ~ "ER" + ) + ).period) + +define function "First Inpatient Intensive Care Unit"(TheEncounter FHIR.Encounter): + First( + ( TheEncounter.location ) HospitalLocation + where HospitalLocation.type ~ "ICU" + and HospitalLocation.period during TheEncounter.period + sort by start of FHIRHelpers.ToInterval(period) + )*/ + +/* +* +* CQFMeasures Common Logic +* +*/ + +define function "Normalize Interval"(choice Choice): + case + when choice is FHIR.dateTime then + Interval[FHIRHelpers.ToDateTime(choice as FHIR.dateTime), FHIRHelpers.ToDateTime(choice as FHIR.dateTime)] + when choice is FHIR.Period then + FHIRHelpers.ToInterval(choice as FHIR.Period) + when choice is FHIR.instant then + Interval[FHIRHelpers.ToDateTime(choice as FHIR.instant), FHIRHelpers.ToDateTime(choice as FHIR.instant)] + when choice is FHIR.Age then + Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age), + FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(choice as FHIR.Age) + 1 year) + when choice is FHIR.Range then + Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as FHIR.Range).low), + FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((choice as FHIR.Range).high) + 1 year) + when choice is FHIR.Timing then + Message(null as Interval, true, '1', 'Error', 'Cannot compute a single interval from a Timing type') + when choice is FHIR.string then + Message(null as Interval, true, '1', 'Error', 'Cannot compute an interval from a String value') + else + null as Interval + end + +define function "Normalize Abatement"(condition Condition): + if condition.abatement is FHIR.dateTime then + Interval[FHIRHelpers.ToDateTime(condition.abatement as FHIR.dateTime), FHIRHelpers.ToDateTime(condition.abatement as FHIR.dateTime)] + else if condition.abatement is FHIR.Period then + FHIRHelpers.ToInterval(condition.abatement as FHIR.Period) + else if condition.abatement is FHIR.string then + Message(null as Interval, true, '1', 'Error', 'Cannot compute an interval from a String value') + else if condition.abatement is FHIR.Age then + Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(condition.abatement as FHIR.Age), + FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity(condition.abatement as FHIR.Age) + 1 year) + else if condition.abatement is FHIR.Range then + Interval[FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((condition.abatement as FHIR.Range).low), + FHIRHelpers.ToDate(Patient.birthDate) + FHIRHelpers.ToQuantity((condition.abatement as FHIR.Range).high) + 1 year) + else if condition.abatement is FHIR.boolean then + Interval[end of "Normalize Interval"(condition.onset), condition.recordedDate) + else null + +define function "Prevalence Period"(condition Condition): + Interval[start of "Normalize Interval"(condition.onset), end of "Normalize Abatement"(condition)) + +define function "GetId"(uri String): + Last(Split(uri, '/')) + + +define function "EncounterDiagnosis"(Encounter Encounter): + Encounter.diagnosis D + return singleton from ([Condition] C where C.id = "GetId"(D.condition.reference)) + +// Returns the condition that is specified as the principal diagnosis for the encounter +// TODO: BTR 2019-07-30: Shouldn't need the FHIRHelpers reference here, investigate +define function "PrincipalDiagnosis"(Encounter Encounter): + (singleton from (Encounter.diagnosis D where FHIRHelpers.ToInteger(D.rank) = 1)) PD + return singleton from ([Condition] C where C.id = "GetId"(PD.condition.reference)) + +// Returns the location for the given location reference +define function GetLocation(reference Reference): + singleton from ( + [Location] L where L.id = GetId(reference.reference) + ) + +/* +NOTE: Extensions are not the preferred approach, but are used as a way to access +content that is defined by extensions but not yet surfaced in the +CQL model info. +*/ +define function "GetExtensions"(domainResource DomainResource, url String): + domainResource.extension E + where E.url = ('http://hl7.org/fhir/us/qicore/StructureDefinition/' + url) + return E + +define function "GetExtension"(domainResource DomainResource, url String): + singleton from "GetExtensions"(domainResource, url) + +/* +NOTE: Extensions are not the preferred approach, but are used as a way to access +content that is defined by extensions but not yet surfaced in the +CQL model info. +*/ +define function "GetExtensions"(element Element, url String): + element.extension E + where E.url = (url) + return E + +define function "GetExtension"(element Element, url String): + singleton from "GetExtensions"(element, url) + +/* +NOTE: Extensions are not the preferred approach, but are used as a way to access +content that is defined by extensions but not yet surfaced in the +CQL model info. +*/ +define function "GetBaseExtensions"(domainResource DomainResource, url String): + domainResource.extension E + where E.url = ('http://hl7.org/fhir/StructureDefinition/' + url) + return E + +define function "GetBaseExtension"(domainResource DomainResource, url String): + singleton from "GetBaseExtensions"(domainResource, url) + +/* +NOTE: Provenance is not the preferred approach, this is provided only as an illustration +for what using Provenance could look like, and is not a tested pattern +*/ +define function "GetProvenance"(resource Resource): + singleton from ([Provenance: target in resource.id]) + +define function "GetMedicationCode"(request MedicationRequest): + if request.medication is CodeableConcept then + request.medication as CodeableConcept + else + (singleton from ([Medication] M where M.id = GetId((request.medication as Reference).reference))).code diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/SupplementalDataElements.cql b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/SupplementalDataElements.cql new file mode 100644 index 000000000..57a61e243 --- /dev/null +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/SupplementalDataElements.cql @@ -0,0 +1,46 @@ +library SupplementalDataElements version '2.0.0' + +using FHIR version '4.0.1' + +include FHIRHelpers called FHIRHelpers + +valueset "ONC Administrative Sex": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1' +valueset "Race": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.836' +valueset "Ethnicity": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.837' +valueset "Payer": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591' + +context Patient + +define "SDE Ethnicity": + (flatten ( + Patient.extension Extension + where Extension.url = 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity' + return Extension.extension + )) E + where E.url = 'ombCategory' + or E.url = 'detailed' + return E.value as Coding + +define "SDE Payer": + [Coverage: type in "Payer"] Payer + return { + code: Payer.type, + period: Payer.period + } + +define "SDE Race": + (flatten ( + Patient.extension Extension + where Extension.url = 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race' + return Extension.extension + )) E + where E.url = 'ombCategory' + or E.url = 'detailed' + return E.value as Coding + +define "SDE Sex": + case + when Patient.gender = 'male' then Code { code: 'M', system: 'http://hl7.org/fhir/v3/AdministrativeGender', display: 'Male' } + when Patient.gender = 'female' then Code { code: 'F', system: 'http://hl7.org/fhir/v3/AdministrativeGender', display: 'Female' } + else null + end diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/TJCOverall.cql b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/TJCOverall.cql new file mode 100644 index 000000000..af076f7b2 --- /dev/null +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/TJCOverall.cql @@ -0,0 +1,74 @@ +library TJCOverall version '5.0.000' + +using FHIR version '4.0.1' + +include FHIRHelpers called FHIRHelpers +include MATGlobalCommonFunctions called Global +include SupplementalDataElements called SDE + +codesystem "SNOMEDCT": 'http://snomed.info/sct/731000124108' +codesystem "LOINC": 'http://loinc.org' + +valueset "Comfort Measures": 'http://cts.nlm.nih.gov/fhir/ValueSet/1.3.6.1.4.1.33895.1.3.0.45' +valueset "Discharge To Acute Care Facility": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.87' +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 "Emergency Department Visit": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.292' +valueset "Encounter Inpatient": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.666.5.307' +valueset "Hemorrhagic Stroke": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.212' +valueset "Ischemic Stroke": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.247' +valueset "Left Against Medical Advice": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.308' +valueset "Non-Elective Inpatient Encounter": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.424' +valueset "Patient Expired": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.309' +valueset "Ticagrelor Therapy": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1110.39' +valueset "Observation Services": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1111.143' + +code "Birth date": '21112-8' from "LOINC" display 'Birth date' + +parameter "Measurement Period" Interval + default Interval[@2019-01-01T00:00:00.0, @2020-01-01T00:00:00.0) + +context Patient + +//Changed "ProcedureRequest" to "ServiceRequest" +define "Intervention Comfort Measures": + (["ServiceRequest": "Comfort Measures"] P + where P.intent = 'order') + union + (["Procedure": "Comfort Measures"] InterventionPerformed + where InterventionPerformed.status = 'completed') + +define "All Stroke Encounter": + "Non Elective Inpatient Encounter" NonElectiveEncounter + where Global.PrincipalDiagnosis(NonElectiveEncounter).code in "Hemorrhagic Stroke" + or Global.PrincipalDiagnosis(NonElectiveEncounter).code in "Ischemic Stroke" + +define "Ischemic Stroke Encounter": + "Encounter with Principal Diagnosis and Age" EncounterWithAge + where Global.PrincipalDiagnosis(EncounterWithAge).code in "Ischemic Stroke" + +define "Non Elective Inpatient Encounter": + ["Encounter": "Non-Elective Inpatient Encounter"] NonElectiveEncounter + where Global."LengthInDays"(NonElectiveEncounter.period) <= 120 + and NonElectiveEncounter.period ends during "Measurement Period" + +define "Ischemic Stroke Encounters with Discharge Disposition": + "Ischemic Stroke Encounter" IschemicStrokeEncounter + where IschemicStrokeEncounter.hospitalization.dischargeDisposition in "Discharge To Acute Care Facility" + or IschemicStrokeEncounter.hospitalization.dischargeDisposition in "Left Against Medical Advice" + or IschemicStrokeEncounter.hospitalization.dischargeDisposition in "Patient Expired" + or IschemicStrokeEncounter.hospitalization.dischargeDisposition in "Discharged to Home for Hospice Care" + or IschemicStrokeEncounter.hospitalization.dischargeDisposition in "Discharged to Health Care Facility for Hospice Care" + +define "Comfort Measures during Hospitalization": + "Ischemic Stroke Encounter" IschemicStrokeEncounter + with "Intervention Comfort Measures" ComfortMeasure + such that FHIRHelpers.ToDateTime(Coalesce(ComfortMeasure.performed as dateTime,ComfortMeasure.authoredOn)) during Global."HospitalizationWithObservation"(IschemicStrokeEncounter) + +define "Encounter with Principal Diagnosis and Age": + "All Stroke Encounter" AllStrokeEncounter + with ["Patient"] BirthDate + such that Global."CalendarAgeInYearsAt"(FHIRHelpers.ToDate(BirthDate.birthDate), start of FHIRHelpers.ToInterval(AllStrokeEncounter.period)) >= 18 + +define function "CalendarDayOfOrDayAfter"(StartValue DateTime ): + Interval(Global."ToDate"(StartValue), Global."ToDate"(StartValue + 2 days)) diff --git a/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/VTEICU.cql b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/VTEICU.cql new file mode 100644 index 000000000..51fab5f3d --- /dev/null +++ b/Src/java/cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/fhir/r4/exm108/VTEICU.cql @@ -0,0 +1,40 @@ +library VTEICU version '5.0.000' + +/* +@update: @@BTR 2020-03-31 -> +Incremented version to 5.0.000 +Updated FHIR version to 4.0.1 +@@ +*/ + +using FHIR version '4.0.1' + +include FHIRHelpers called FHIRHelpers +// NOTE: BTR 2019-07-30: Updated dependencies +include MATGlobalCommonFunctions called Global + +valueset "Intensive Care Unit": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1110.23' + +parameter "Measurement Period" Interval + default Interval[@2019-01-01T00:00:00.0, @2020-01-01T00:00:00.0) + +context Patient + +define function "FromDayOfStartOfHospitalizationToDayAfterAdmission"(Encounter FHIR.Encounter ): + Interval[Global."ToDate"(start of Global."HospitalizationWithObservation"(Encounter)), Global."ToDate"(start of Encounter.period + 2 days)) + +define function "StartOfFirstICU"(Encounter FHIR.Encounter ): + start of "FirstICULocationPeriod"(Encounter) + +define function "FromDayOfStartOfHospitalizationToDayAfterFirstICU"(Encounter FHIR.Encounter ): + Interval[Global."ToDate"(start of Global."HospitalizationWithObservation"(Encounter)), Global."ToDate"(StartOfFirstICU(Encounter)+ 2 days)) + +define function "FirstICULocationPeriod"(Encounter FHIR.Encounter ): + "FirstInpatientIntensiveCareUnit"(Encounter).period + +define function "FirstInpatientIntensiveCareUnit"(Encounter FHIR.Encounter ): + First((Encounter.location)HospitalLocation + where Global.GetLocation(HospitalLocation.location).type in "Intensive Care Unit" + and HospitalLocation.period during Encounter.period + sort by start of period + ) diff --git a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/execution/EvaluationVisitor.java b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/execution/EvaluationVisitor.java index 5ff4a9ab2..e6491ba84 100644 --- a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/execution/EvaluationVisitor.java +++ b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/execution/EvaluationVisitor.java @@ -74,7 +74,13 @@ public Object visitAnd(And and, State state) { @Override public Object visitAnyInCodeSystem(AnyInCodeSystem anyInCodeSystem, State state) { Object codes = visitExpression(anyInCodeSystem.getCodes(), state); - Object codeSystem = visitExpression(anyInCodeSystem.getCodesystemExpression(), state); + Object codeSystem = null; + if (anyInCodeSystem.getCodesystem() != null) { + codeSystem = CodeSystemRefEvaluator.toCodeSystem(anyInCodeSystem.getCodesystem(), state); + } else { + codeSystem = visitExpression(anyInCodeSystem.getCodesystemExpression(), state); + } + return AnyInCodeSystemEvaluator.internalEvaluate(codes, anyInCodeSystem.getCodesystem(), codeSystem, state); } @@ -94,9 +100,14 @@ public Object visitInCodeSystem(InCodeSystem inCodeSystem, State state) { @Override public Object visitAnyInValueSet(AnyInValueSet anyInValueSet, State state) { Object codes = visitExpression(anyInValueSet.getCodes(), state); - Object valueset = visitExpression(anyInValueSet.getValuesetExpression(), state); + Object valueSet = null; + if (anyInValueSet.getValueset() != null) { + valueSet = ValueSetRefEvaluator.toValueSet(state, anyInValueSet.getValueset()); + } else { + valueSet = visitExpression(anyInValueSet.getValuesetExpression(), state); + } - return AnyInValueSetEvaluator.internalEvaluate(codes, anyInValueSet.getValueset(), valueset, state); + return AnyInValueSetEvaluator.internalEvaluate(codes, anyInValueSet.getValueset(), valueSet, state); } @Override diff --git a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/terminology/TerminologyProvider.java b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/terminology/TerminologyProvider.java index dc1a0b8d0..28ac1c9b3 100644 --- a/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/terminology/TerminologyProvider.java +++ b/Src/java/engine/src/main/java/org/opencds/cqf/cql/engine/terminology/TerminologyProvider.java @@ -8,7 +8,6 @@ public interface TerminologyProvider { * @param code the code to check * @param valueSet the valueSet to check * @return true if code is a member of the ValueSet - * @throws TerminologyProviderException if there's an exception during the membership check */ boolean in(Code code, ValueSetInfo valueSet); @@ -16,7 +15,6 @@ public interface TerminologyProvider { * Expands the set of Codes for a given ValueSetInfo * @param valueSet the ValueSetInfo to expand * @return the set of Codes - * @throws TerminologyProviderException if there's an error during expansion */ Iterable expand(ValueSetInfo valueSet); @@ -25,7 +23,6 @@ public interface TerminologyProvider { * @param code the Code to look up * @param codeSystem the CodeSystemInfo to look up from * @return the Code with the display value filled - * @throws TerminologyProviderException if there's an error during lookup */ Code lookup(Code code, CodeSystemInfo codeSystem); } diff --git a/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/model/CachingModelResolverDecoratorTest.java b/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/model/CachingModelResolverDecoratorTest.java index 981701435..855e9f660 100644 --- a/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/model/CachingModelResolverDecoratorTest.java +++ b/Src/java/engine/src/test/java/org/opencds/cqf/cql/engine/model/CachingModelResolverDecoratorTest.java @@ -38,6 +38,7 @@ void after() throws Exception { } @Test + @SuppressWarnings("deprecation") public void context_path_resolved_only_once() { var m = mock(ModelResolver.class); when(m.getPackageName()).thenReturn("test.package"); @@ -52,7 +53,7 @@ public void context_path_resolved_only_once() { } @Test - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "deprecation"}) public void type_resolved_only_once() { var m = mock(ModelResolver.class); when(m.getPackageName()).thenReturn("test.package"); diff --git a/Src/java/gradle.properties b/Src/java/gradle.properties index 107d814d1..5ae866eaa 100644 --- a/Src/java/gradle.properties +++ b/Src/java/gradle.properties @@ -2,9 +2,9 @@ org.gradle.caching=true org.gradle.parallel=true group=info.cqframework -version=3.6.0-SNAPSHOT +version=3.6.0 specification.version=1.5.2 -hapi.version=6.10.0 +hapi.version=6.10.2 fhir-core.version=6.1.2.2 antlr.version=4.13.1 android.api.level=28 diff --git a/Src/java/model/src/test/java/org/hl7/cql/model/ModelInfoComparer.java b/Src/java/model/src/test/java/org/hl7/cql/model/ModelInfoComparerTest.java similarity index 94% rename from Src/java/model/src/test/java/org/hl7/cql/model/ModelInfoComparer.java rename to Src/java/model/src/test/java/org/hl7/cql/model/ModelInfoComparerTest.java index 1c17ad016..a5a9f71fa 100644 --- a/Src/java/model/src/test/java/org/hl7/cql/model/ModelInfoComparer.java +++ b/Src/java/model/src/test/java/org/hl7/cql/model/ModelInfoComparerTest.java @@ -1,7 +1,7 @@ package org.hl7.cql.model; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.*; import jakarta.xml.bind.JAXB; import java.util.*; @@ -9,12 +9,14 @@ import org.hl7.elm_modelinfo.r1.*; import org.testng.annotations.Test; -public class ModelInfoComparer { +public class ModelInfoComparerTest { @Test public void compareModelInfo() { - ModelInfo a = JAXB.unmarshal(ModelInfoComparer.class.getResourceAsStream("a-modelinfo.xml"), ModelInfo.class); - ModelInfo b = JAXB.unmarshal(ModelInfoComparer.class.getResourceAsStream("b-modelinfo.xml"), ModelInfo.class); + ModelInfo a = + JAXB.unmarshal(ModelInfoComparerTest.class.getResourceAsStream("a-modelinfo.xml"), ModelInfo.class); + ModelInfo b = + JAXB.unmarshal(ModelInfoComparerTest.class.getResourceAsStream("b-modelinfo.xml"), ModelInfo.class); ModelInfoCompareContext differences = new ModelInfoCompareContext(); compareModelInfo(differences, a, b); @@ -35,9 +37,9 @@ public void compareModelInfo() { // Not an actual test, Used to determine differences between current and updated model info from the MAT team public void compareMATModelInfo() { ModelInfo a = JAXB.unmarshal( - ModelInfoComparer.class.getResourceAsStream("fhir-modelinfo-4.0.1.xml"), ModelInfo.class); + ModelInfoComparerTest.class.getResourceAsStream("fhir-modelinfo-4.0.1.xml"), ModelInfo.class); ModelInfo b = JAXB.unmarshal( - ModelInfoComparer.class.getResourceAsStream("mat-fhir-modelinfo-4.0.1.xml"), ModelInfo.class); + ModelInfoComparerTest.class.getResourceAsStream("mat-fhir-modelinfo-4.0.1.xml"), ModelInfo.class); ModelInfoCompareContext differences = new ModelInfoCompareContext(); compareModelInfo(differences, a, b); @@ -47,9 +49,9 @@ public void compareMATModelInfo() { @Test public void compareNewModelInfo() { ModelInfo a = JAXB.unmarshal( - ModelInfoComparer.class.getResourceAsStream("fhir-modelinfo-4.0.1.xml"), ModelInfo.class); + ModelInfoComparerTest.class.getResourceAsStream("fhir-modelinfo-4.0.1.xml"), ModelInfo.class); ModelInfo b = JAXB.unmarshal( - ModelInfoComparer.class.getResourceAsStream("new-fhir-modelinfo-4.0.1.xml"), ModelInfo.class); + ModelInfoComparerTest.class.getResourceAsStream("new-fhir-modelinfo-4.0.1.xml"), ModelInfo.class); ModelInfoCompareContext differences = new ModelInfoCompareContext(); compareModelInfo(differences, a, b); @@ -68,9 +70,10 @@ public void compareNewModelInfo() { @Test public void compareMetadataModelInfo() { ModelInfo a = JAXB.unmarshal( - ModelInfoComparer.class.getResourceAsStream("fhir-modelinfo-4.0.1-1.5.1.xml"), ModelInfo.class); + ModelInfoComparerTest.class.getResourceAsStream("fhir-modelinfo-4.0.1-1.5.1.xml"), ModelInfo.class); ModelInfo b = JAXB.unmarshal( - ModelInfoComparer.class.getResourceAsStream("fhir-modelinfo-4.0.1-with-metadata.xml"), ModelInfo.class); + ModelInfoComparerTest.class.getResourceAsStream("fhir-modelinfo-4.0.1-with-metadata.xml"), + ModelInfo.class); ModelInfoCompareContext differences = new ModelInfoCompareContext(); compareModelInfo(differences, a, b); @@ -103,7 +106,7 @@ public void compareMetadataModelInfo() { "ModelInfo.MoneyQuantity.Element code in left only%n" + // ditto "ModelInfo.uuid.Element value in right only%n" - + // redeclartion for metadata + + // redeclaration for metadata "ModelInfo.ElementDefinition.Type.targetProfile.name: targetProfile <> profile%n" + // backwards compatible, but more accurate ElementDefinition.Type "ModelInfo.ElementDefinition.Type.versioning.name: versioning <> targetProfile%n" @@ -115,18 +118,18 @@ public void compareMetadataModelInfo() { "ModelInfo.ElementDefinition.Type.Element versioning in right only%n" + // ditto "ModelInfo.unsignedInt.Element value in right only%n" - + // redeclaration for metdata + + // redeclaration for metadata "ModelInfo.id.Element value in right only%n" - + // redeclaration for metdata + + // redeclaration for metadata "ModelInfo.url.Element value in right only%n" - + // redeclaration for metdata + + // redeclaration for metadata "ModelInfo.canonical.Element value in right only%n" - + // redeclaration for metdata + + // redeclaration for metadata "ModelInfo.code.Element value in right only%n" - + // redeclaration for metdata + + // redeclaration for metadata "ModelInfo.oid.Element value in right only%n" - + // redeclaration for metdata - "ModelInfo.positiveInt.Element value in right only%n"))); // redeclaration for metdata + + // redeclaration for metadata + "ModelInfo.positiveInt.Element value in right only%n"))); // redeclaration for metadata } public class ModelInfoCompareContext { @@ -283,7 +286,7 @@ public static void compareModelSpecifier(ModelInfoCompareContext context, ModelS if (a == null) { context.append(String.format("Model specifier %s|%s in right only", b.getName(), b.getVersion())); } else if (b == null) { - context.append(String.format("Model specifier %s|%s in left only", a.getName(), b.getVersion())); + context.append(String.format("Model specifier %s|%s in left only", a.getName(), a.getVersion())); } else { compareAttribute(context, "version", a.getVersion(), b.getVersion()); }