From c53d2ce18734f1b28c9f4bc0adbc02d7f0540547 Mon Sep 17 00:00:00 2001 From: Bryn Rhodes Date: Tue, 24 Oct 2023 18:35:08 -0600 Subject: [PATCH] Br fix datarequirements (#1256) * #1203: Fixed function references missing from logic definitions in data-requirements * #1202: Fixed extension access reporting as a code filter instead of a property access #1202: Fixed property access not being reported correctly when it occurred after the initial query context for a retrieve #1202: Fixed data requirements not being reported correctly when a query source was itself a nested query #1202: Fixed duplicate data requirements being reported for property access not directly related to a retrieve #1202: Fixed enableResultTypes not working in some cases --- .../cql/cql2elm/Cql2ElmVisitor.java | 105 +--- .../CqlPreprocessorElmCommonVisitor.java | 110 ++++ .../elm/requirements/ElmDataRequirement.java | 16 +- .../cql/elm/requirements/ElmRequirements.java | 36 +- .../requirements/ElmRequirementsContext.java | 65 +- .../requirements/ElmRequirementsVisitor.java | 13 +- .../cql/elm/requirements/TypeResolver.java | 59 ++ .../fhir/DataRequirementsProcessor.java | 80 ++- .../fhir/DataRequirementsProcessorTest.java | 186 +++++- .../cql/SupplementalDataElements-3.1.000.cql | 9 +- .../fhir/CMS143/cql/TestUnion.cql | 41 ++ .../Library-EffectiveDataRequirements.json | 189 +++--- ...EncounterMP-EffectiveDataRequirements.json | 108 ++++ ...DEEthnicity-EffectiveDataRequirements.json | 31 + ...ry-SDEPayer-EffectiveDataRequirements.json | 39 ++ ...ary-SDERace-EffectiveDataRequirements.json | 31 + ...rary-SDESex-EffectiveDataRequirements.json | 46 ++ .../Library-EffectiveDataRequirements.json | 79 ++- .../CMS645-ModuleDefinitionLibrary.json | 10 +- ...DeviceOrder-EffectiveDataRequirements.json | 89 +++ .../fhir/DeviceOrder/QICoreCommon.cql | 581 ++++++++++++++++++ .../fhir/DeviceOrder/TestDeviceOrder.cql | 23 + .../Library-EXMLogic-data-requirements.json | 25 +- 23 files changed, 1683 insertions(+), 288 deletions(-) create mode 100644 Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/cql/TestUnion.cql create mode 100644 Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-QualifyingEncounterMP-EffectiveDataRequirements.json create mode 100644 Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDEEthnicity-EffectiveDataRequirements.json create mode 100644 Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDEPayer-EffectiveDataRequirements.json create mode 100644 Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDERace-EffectiveDataRequirements.json create mode 100644 Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDESex-EffectiveDataRequirements.json create mode 100644 Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/Library-TestDeviceOrder-EffectiveDataRequirements.json create mode 100644 Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/QICoreCommon.cql create mode 100644 Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/TestDeviceOrder.cql 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 0f1c82a28..3a13ad5dc 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 @@ -27,14 +27,6 @@ public class Cql2ElmVisitor extends CqlPreprocessorElmCommonVisitor { static final Logger logger = LoggerFactory.getLogger(Cql2ElmVisitor.class); - private boolean locate = false; - private boolean resultTypes = false; - private boolean dateRangeOptimization = false; - private boolean detailedErrors = false; - private boolean methodInvocation = true; - private boolean includeDeprecatedElements = false; - private boolean fromKeywordRequired = false; - private final SystemMethodResolver systemMethodResolver; public void setLibraryInfo(LibraryInfo libraryInfo) { @@ -65,91 +57,6 @@ public Cql2ElmVisitor(LibraryBuilder libraryBuilder) { this.systemMethodResolver = new SystemMethodResolver(this, libraryBuilder); } - public void enableLocators() { - locate = true; - } - - public void disableLocators() { - locate = false; - } - - public void enableResultTypes() { - resultTypes = true; - } - - public void disableResultTypes() { - resultTypes = false; - } - - public void enableDateRangeOptimization() { - dateRangeOptimization = true; - } - - public void disableDateRangeOptimization() { - dateRangeOptimization = false; - } - - public boolean getDateRangeOptimization() { - return dateRangeOptimization; - } - - public void enableDetailedErrors() { - detailedErrors = true; - } - - public void disableDetailedErrors() { - detailedErrors = false; - } - - public boolean isDetailedErrorsEnabled() { - return detailedErrors; - } - - public void enableMethodInvocation() { - methodInvocation = true; - } - - public void disableMethodInvocation() { - methodInvocation = false; - } - - public boolean isFromKeywordRequired() { - return fromKeywordRequired; - } - - public void enableFromKeywordRequired() { - fromKeywordRequired = true; - } - - public void disableFromKeywordRequired() { - fromKeywordRequired = false; - } - - public void setTranslatorOptions(CqlCompilerOptions options) { - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableDateRangeOptimization)) { - this.enableDateRangeOptimization(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableAnnotations)) { - this.enableAnnotations(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableLocators)) { - this.enableLocators(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableResultTypes)) { - this.enableResultTypes(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.EnableDetailedErrors)) { - this.enableDetailedErrors(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.DisableMethodInvocation)) { - this.disableMethodInvocation(); - } - if (options.getOptions().contains(CqlCompilerOptions.Options.RequireFromKeyword)) { - this.enableFromKeywordRequired(); - } - libraryBuilder.setCompatibilityLevel(options.getCompatibilityLevel()); - } - public List getRetrieves() { return retrieves; } @@ -335,7 +242,7 @@ public TupleElementDefinition visitTupleElementDefinition(cqlParser.TupleElement .withName(parseString(ctx.referentialIdentifier())) .withElementType(parseTypeSpecifier(ctx.typeSpecifier())); - if (includeDeprecatedElements) { + if (getIncludeDeprecatedElements()) { result.setType(result.getElementType()); } @@ -3123,7 +3030,7 @@ else if (libraryBuilder.isCompatibleWith("1.5")) { @Override public Object visitSourceClause(cqlParser.SourceClauseContext ctx) { boolean hasFrom = "from".equals(ctx.getChild(0).getText()); - if (!hasFrom && fromKeywordRequired) { + if (!hasFrom && isFromKeywordRequired()) { throw new IllegalArgumentException("The from keyword is required for queries."); } @@ -3177,7 +3084,7 @@ public Object visitQuery(cqlParser.QueryContext ctx) { } Expression where = ctx.whereClause() != null ? (Expression) visit(ctx.whereClause()) : null; - if (dateRangeOptimization && where != null) { + if (getDateRangeOptimization() && where != null) { for (AliasedQuerySource aqs : sources) { where = optimizeDateRangeInQuery(where, aqs); } @@ -3960,7 +3867,7 @@ public Expression resolveFunctionOrQualifiedFunction(String identifier, cqlParse // NOTE: FHIRPath method invocation // If the target is an expression, resolve as a method invocation - if (target instanceof Expression && methodInvocation) { + if (target instanceof Expression && isMethodInvocationEnabled()) { return systemMethodResolver.resolveMethod((Expression)target, identifier, paramListCtx, true); } @@ -4219,11 +4126,11 @@ private TrackBack getTrackBack(ParserRuleContext ctx) { } private void decorate(Element element, TrackBack tb) { - if (locate && tb != null) { + if (locatorsEnabled() && tb != null) { element.setLocator(tb.toLocator()); } - if (resultTypes && element.getResultType() != null) { + if (resultTypesEnabled() && element.getResultType() != null) { if (element.getResultType() instanceof NamedType) { element.setResultTypeName(libraryBuilder.dataTypeToQName(element.getResultType())); } diff --git a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java index 9a49c8c02..6891a5ecf 100644 --- a/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java +++ b/Src/java/cql-to-elm/src/main/java/org/cqframework/cql/cql2elm/preprocessor/CqlPreprocessorElmCommonVisitor.java @@ -45,6 +45,10 @@ public class CqlPreprocessorElmCommonVisitor extends cqlBaseVisitor { private int nextLocalId = 1; private boolean locate = false; private boolean resultTypes = false; + private boolean dateRangeOptimization = false; + private boolean methodInvocation = true; + private boolean fromKeywordRequired = false; + private final List expressions = new ArrayList<>(); private boolean includeDeprecatedElements = false; @@ -783,4 +787,110 @@ public static String normalizeWhitespace(String input) { public static boolean isStartingWithDigit(String header, int index) { return (index < header.length()) && Character.isDigit(header.charAt(index)); } + + + public void enableLocators() { + locate = true; + } + + public boolean locatorsEnabled() { + return locate; + } + + public void disableLocators() { + locate = false; + } + + public void enableResultTypes() { + resultTypes = true; + } + + public void disableResultTypes() { + resultTypes = false; + } + + public boolean resultTypesEnabled() { + return resultTypes; + } + + public void enableDateRangeOptimization() { + dateRangeOptimization = true; + } + + public void disableDateRangeOptimization() { + dateRangeOptimization = false; + } + + public boolean getDateRangeOptimization() { + return dateRangeOptimization; + } + + public void enableDetailedErrors() { + detailedErrors = true; + } + + public void disableDetailedErrors() { + detailedErrors = false; + } + + public boolean isDetailedErrorsEnabled() { + return detailedErrors; + } + + public void enableMethodInvocation() { + methodInvocation = true; + } + + public void disableMethodInvocation() { + methodInvocation = false; + } + + public boolean isMethodInvocationEnabled() { + return methodInvocation; + } + + public boolean isFromKeywordRequired() { + return fromKeywordRequired; + } + + public void enableFromKeywordRequired() { + fromKeywordRequired = true; + } + + public void disableFromKeywordRequired() { + fromKeywordRequired = false; + } + + public boolean getIncludeDeprecatedElements() { + return includeDeprecatedElements; + } + + public void setIncludeDeprecatedElements(boolean includeDeprecatedElements) { + this.includeDeprecatedElements = includeDeprecatedElements; + } + + public void setTranslatorOptions(CqlCompilerOptions options) { + if (options.getOptions().contains(CqlCompilerOptions.Options.EnableDateRangeOptimization)) { + this.enableDateRangeOptimization(); + } + if (options.getOptions().contains(CqlCompilerOptions.Options.EnableAnnotations)) { + this.enableAnnotations(); + } + if (options.getOptions().contains(CqlCompilerOptions.Options.EnableLocators)) { + this.enableLocators(); + } + if (options.getOptions().contains(CqlCompilerOptions.Options.EnableResultTypes)) { + this.enableResultTypes(); + } + if (options.getOptions().contains(CqlCompilerOptions.Options.EnableDetailedErrors)) { + this.enableDetailedErrors(); + } + if (options.getOptions().contains(CqlCompilerOptions.Options.DisableMethodInvocation)) { + this.disableMethodInvocation(); + } + if (options.getOptions().contains(CqlCompilerOptions.Options.RequireFromKeyword)) { + this.enableFromKeywordRequired(); + } + libraryBuilder.setCompatibilityLevel(options.getCompatibilityLevel()); + } } diff --git a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmDataRequirement.java b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmDataRequirement.java index 012d2d68f..7c3384c0f 100644 --- a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmDataRequirement.java +++ b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmDataRequirement.java @@ -79,7 +79,11 @@ else if (querySource instanceof LetClause) { private static ElmDataRequirement inferFrom(ElmDataRequirement requirement) { Retrieve inferredRetrieve = ElmCloner.clone(requirement.getRetrieve()); ElmDataRequirement result = new ElmDataRequirement(requirement.libraryIdentifier, inferredRetrieve, requirement.getRetrieve()); - result.propertySet = requirement.propertySet; + if (requirement.hasProperties()) { + for (Property p : requirement.getProperties()) { + result.addProperty(p); + } + } return result; } @@ -127,6 +131,10 @@ public Iterable getProperties() { return propertySet; } + public boolean hasProperties() { + return propertySet != null; + } + public void addProperty(Property property) { if (propertySet == null) { propertySet = new LinkedHashSet(); @@ -134,6 +142,12 @@ public void addProperty(Property property) { propertySet.add(property); } + public void removeProperty(Property property) { + if (propertySet != null) { + propertySet.remove(property); + } + } + public void reportProperty(ElmPropertyRequirement propertyRequirement) { if (propertySet == null) { propertySet = new LinkedHashSet(); diff --git a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirements.java b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirements.java index 01266b2e6..afa84ae59 100644 --- a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirements.java +++ b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirements.java @@ -78,7 +78,7 @@ public Iterable getRetrieves() { For expressions, unique by qualified name For data requirements, collapse according to the CQL specification: https://cql.hl7.org/05-languagesemantics.html#artifact-data-requirements */ - public ElmRequirements collapse() { + public ElmRequirements collapse(ElmRequirementsContext context) { ElmRequirements result = new ElmRequirements(this.libraryIdentifier, this.element); // UsingDefs @@ -243,6 +243,7 @@ public ElmRequirements collapse() { // Retrieves // Sort retrieves by type/profile to reduce search space LinkedHashMap> retrievesByType = new LinkedHashMap>(); + List unboundRequirements = new ArrayList<>(); for (ElmRequirement r : getRetrieves()) { Retrieve retrieve = (Retrieve)r.getElement(); if (retrieve.getDataType() != null) { @@ -257,9 +258,40 @@ public ElmRequirements collapse() { } typeRetrieves.add(r); } - // TODO: What to do with data requirements that are captured to track unbound element references + else { + unboundRequirements.add(r); + } + } + + // Distribute unbound property requirements + // If an ElmDataRequirement has a retrieve that does not have a dataType (i.e. it is not a direct data access layer retrieve + // but rather is the result of requirements inference), then distribute the property references it contains to + // all data layer-bound retrieves of the same type + // In other words, we can't unambiguously tie the property reference to any particular retrieve of that type, + // so apply it to all of them + for (ElmRequirement requirement : unboundRequirements) { + if (requirement instanceof ElmDataRequirement) { + ElmDataRequirement dataRequirement = (ElmDataRequirement)requirement; + if (dataRequirement.hasProperties()) { + String typeUri = context.getTypeResolver().getTypeUri(dataRequirement.getRetrieve().getResultType()); + if (typeUri != null) { + List typeRequirements = retrievesByType.get(typeUri); + if (typeRequirements != null) { + for (ElmRequirement typeRequirement : typeRequirements) { + if (typeRequirement instanceof ElmDataRequirement) { + ElmDataRequirement typeDataRequirement = (ElmDataRequirement)typeRequirement; + for (Property p : dataRequirement.getProperties()) { + typeDataRequirement.addProperty(p); + } + } + } + } + } + } + } } + // Equivalent // Has the same context, type/profile, code path and date path // If two retrieves are "equivalent" they can be merged diff --git a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirementsContext.java b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirementsContext.java index ca9cff0d9..bab42ac7e 100644 --- a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirementsContext.java +++ b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirementsContext.java @@ -3,7 +3,9 @@ import org.cqframework.cql.cql2elm.*; import org.cqframework.cql.cql2elm.model.LibraryRef; import org.cqframework.cql.cql2elm.model.CompiledLibrary; +import org.hl7.cql.model.ClassType; import org.hl7.cql.model.DataType; +import org.hl7.cql.model.NamedType; import org.hl7.cql.model.NamespaceManager; import org.hl7.elm.r1.*; @@ -467,6 +469,14 @@ else if (requirement instanceof ElmQueryRequirement) { } } } + else if (requirement instanceof ElmOperatorRequirement) { + ElmOperatorRequirement operatorRequirement = (ElmOperatorRequirement)requirement; + for (ElmRequirement r : operatorRequirement.getRequirements()) { + if (inferredRequirements == null || !inferredRequirements.hasRequirement(r)) { + reportRequirements(r, inferredRequirements); + } + } + } else { reportRequirement(requirement); } @@ -485,22 +495,43 @@ else if (expression.getResultTypeSpecifier() instanceof NamedTypeSpecifier) { return null; } + private QName getProfiledType(DataType type) { + return typeResolver.dataTypeToProfileQName(type); + } + private Map unboundDataRequirements = new LinkedHashMap(); - private ElmDataRequirement getDataRequirementForTypeName(QName typeName) { - ElmDataRequirement requirement = unboundDataRequirements.get(typeName); - if (requirement == null) { - Retrieve retrieve = new Retrieve(); - retrieve.setDataType(typeName); - if (typeName.getNamespaceURI() != null && typeName.getLocalPart() != null) { - retrieve.setTemplateId(typeName.getNamespaceURI() + "/" + typeName.getLocalPart()); + private ElmDataRequirement getDataRequirementForTypeName(QName typeName, QName profiledTypeName) { + DataType type = null; + try { + type = typeResolver.resolveTypeName(typeName); + } + catch (Exception e) { + // ignore an exception resolving the type, just don't attempt to build an unbound requirement + // We should only be building unbound requirements for retrievable types, so if we can't determine + // retrievability, ignore the requirement + } + + if (type != null && type instanceof ClassType && ((ClassType)type).isRetrievable()) { + ElmDataRequirement requirement = unboundDataRequirements.get(profiledTypeName != null ? profiledTypeName : typeName); + if (requirement == null) { + Retrieve retrieve = new Retrieve(); + retrieve.setDataType(typeName); + if (profiledTypeName != null && profiledTypeName.getNamespaceURI() != null && profiledTypeName.getLocalPart() != null) { + retrieve.setTemplateId(profiledTypeName.getNamespaceURI() + "/" + profiledTypeName.getLocalPart()); + } + else if (typeName.getNamespaceURI() != null && typeName.getLocalPart() != null) { + retrieve.setTemplateId(typeName.getNamespaceURI() + "/" + typeName.getLocalPart()); + } + requirement = new ElmDataRequirement(getCurrentLibraryIdentifier(), retrieve); + unboundDataRequirements.put(typeName, requirement); + reportRequirement(requirement); } - requirement = new ElmDataRequirement(getCurrentLibraryIdentifier(), retrieve); - unboundDataRequirements.put(typeName, requirement); - reportRequirement(requirement); + + return requirement; } - return requirement; + return null; } public ElmPropertyRequirement reportProperty(Property property) { @@ -556,11 +587,13 @@ public ElmPropertyRequirement reportProperty(Property property) { else { QName typeName = getType(property.getSource()); if (typeName != null) { - ElmDataRequirement requirement = getDataRequirementForTypeName(typeName); - ElmPropertyRequirement propertyRequirement = new ElmPropertyRequirement(getCurrentLibraryIdentifier(), - property, property.getSource(), false); - requirement.reportProperty(propertyRequirement); - return propertyRequirement; + ElmDataRequirement requirement = getDataRequirementForTypeName(typeName, getProfiledType(property.getSource().getResultType())); + if (requirement != null) { + ElmPropertyRequirement propertyRequirement = new ElmPropertyRequirement(getCurrentLibraryIdentifier(), + property, property.getSource(), false); + requirement.reportProperty(propertyRequirement); + return propertyRequirement; + } } } diff --git a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirementsVisitor.java b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirementsVisitor.java index e164538d5..fcd532ec6 100644 --- a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirementsVisitor.java +++ b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/ElmRequirementsVisitor.java @@ -424,14 +424,14 @@ public ElmRequirement visitBinaryExpression(BinaryExpression elm, ElmRequirement @Override public ElmRequirement visitTernaryExpression(TernaryExpression elm, ElmRequirementsContext context) { - super.visitTernaryExpression(elm, context); - return new ElmOperatorRequirement(context.getCurrentLibraryIdentifier(), elm); + ElmRequirement requirements = super.visitTernaryExpression(elm, context); + return new ElmOperatorRequirement(context.getCurrentLibraryIdentifier(), elm).combine(requirements); } @Override public ElmRequirement visitNaryExpression(NaryExpression elm, ElmRequirementsContext context) { - super.visitNaryExpression(elm, context); - return new ElmOperatorRequirement(context.getCurrentLibraryIdentifier(), elm); + ElmRequirement requirements = super.visitNaryExpression(elm, context); + return new ElmOperatorRequirement(context.getCurrentLibraryIdentifier(), elm).combine(requirements); } @Override @@ -1276,6 +1276,11 @@ public ElmRequirement visitChildren(AliasedQuerySource elm, ElmRequirementsConte finally { aliasContext = context.getCurrentQueryContext().exitAliasDefinitionContext(result); } + // If this is an operator requirement, report it directly to the context, otherwise the context it contains will not be reported + // since query requirements are abstracted to an ElmDataRequirement + if (result instanceof ElmOperatorRequirement) { + context.reportRequirements(result, null); + } return aliasContext.getRequirements(); } diff --git a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/TypeResolver.java b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/TypeResolver.java index da088ff57..9cf116b5d 100644 --- a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/TypeResolver.java +++ b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/TypeResolver.java @@ -4,6 +4,7 @@ import org.cqframework.cql.cql2elm.model.Model; import org.hl7.cql.model.*; import org.hl7.elm.r1.*; +import org.hl7.elm_modelinfo.r1.ModelInfo; import javax.xml.namespace.QName; import java.util.ArrayList; @@ -21,6 +22,64 @@ public LibraryManager getLibraryManager() { return libraryManager; } + public String getTypeUri(DataType type) { + if (type instanceof ListType) { + return getTypeUri(((ListType)type).getElementType()); + } + if (type instanceof ClassType) { + ClassType classType = (ClassType)type; + if (classType.getIdentifier() != null) { + return classType.getIdentifier(); + } + } + + if (type instanceof NamedType) { + return dataTypeToQName(type).getLocalPart(); + } + + return null; + } + + public QName dataTypeToProfileQName(DataType type) { + if (type instanceof ClassType) { + ClassType classType = (ClassType)type; + if (classType.getIdentifier() != null) { + int tailIndex = classType.getIdentifier().lastIndexOf('/'); + if (tailIndex > 0) { + String tail = classType.getIdentifier().substring(tailIndex + 1); + String namespace = classType.getIdentifier().substring(0, tailIndex); + return new QName(namespace, tail); + } + } + } + + if (type instanceof NamedType) { + return dataTypeToQName(type); + } + + return null; + } + /** + * Return the QName for the given type (without target mapping) + * This is to preserve data requirements reporting for profiled types when + * reported against unbound data requirements. This will only work when + * the ELM tree has type references (which typically means it came + * straight from the translator, although type resolution could be + * performed by a visitor on an ELM tree). + * @param type The data type to determine a QName for + * @return The QName for the given type (without target mapping) + */ + public QName dataTypeToQName(DataType type) { + if (type instanceof NamedType) { + NamedType namedType = (NamedType)type; + ModelInfo modelInfo = libraryManager.getModelManager().resolveModel(namedType.getNamespace()).getModelInfo(); + return new QName(modelInfo.getUrl(), namedType.getSimpleName()); + } + + // ERROR: + throw new IllegalArgumentException("A named type is required in this context."); + } + public DataType resolveTypeName(QName typeName) { if (typeName == null) { throw new IllegalArgumentException("typeName is required"); diff --git a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/fhir/DataRequirementsProcessor.java b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/fhir/DataRequirementsProcessor.java index 2691dee38..d450d9a9c 100644 --- a/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/fhir/DataRequirementsProcessor.java +++ b/Src/java/elm-fhir/src/main/java/org/cqframework/cql/elm/requirements/fhir/DataRequirementsProcessor.java @@ -31,6 +31,7 @@ import org.opencds.cqf.cql.engine.fhir.converter.FhirTypeConverterFactory; import javax.xml.bind.JAXBElement; +import javax.xml.namespace.QName; import java.io.Serializable; import java.math.BigDecimal; import java.time.ZonedDateTime; @@ -175,12 +176,81 @@ public Library gatherDataRequirements(LibraryManager libraryManager, CompiledLib // Collapse the requirements if (options.getCollapseDataRequirements()) { - requirements = requirements.collapse(); + for (ElmRequirement requirement : requirements.getRequirements()) { + collapseExtensionReference(context, requirement); + } + requirements = requirements.collapse(context); } return createLibrary(context, requirements, translatedLibrary.getIdentifier(), expressionDefs, parameters, evaluationDateTime, includeLogicDefinitions); } + /** + * If the requirement has property references to `url` and `extension`, and a code filter on `url` to a literal extension value, + * replace the `url` and `extension` property references with a new property reference to the tail of the extension + * Also remove `us-core-` and `qicore-` as wellknown prefixes + * TODO: Use the structure definition element slice name as the name of the property, rather than the hard-coded removal + * of well-known prefixes + * @param requirement + */ + private void collapseExtensionReference(ElmRequirementsContext context, ElmRequirement requirement) { + if (requirement instanceof ElmDataRequirement) { + ElmDataRequirement dataRequirement = (ElmDataRequirement)requirement; + if (dataRequirement.hasProperties()) { + Property urlProperty = null; + Property extensionProperty = null; + for (Property p : dataRequirement.getProperties()) { + if (p.getPath().equals("url")) { + urlProperty = p; + continue; + } + + if (p.getPath().equals("extension")) { + extensionProperty = p; + continue; + } + } + + if (urlProperty != null) { + Retrieve r = dataRequirement.getRetrieve(); + if (r != null) { + CodeFilterElement extensionFilterElement = null; + org.hl7.fhir.r5.model.DataRequirement.DataRequirementCodeFilterComponent extensionFilterComponent = null; + for (CodeFilterElement cfe : r.getCodeFilter()) { + org.hl7.fhir.r5.model.DataRequirement.DataRequirementCodeFilterComponent cfc = + toCodeFilterComponent(context, dataRequirement.getLibraryIdentifier(), cfe.getProperty(), cfe.getValue()); + + if (cfc.hasPath() && cfc.hasCode() && "url".equals(cfc.getPath()) && cfc.getCodeFirstRep().hasCode() && cfc.getCodeFirstRep().getCode().startsWith("http://")) { + extensionFilterElement = cfe; + extensionFilterComponent = cfc; + break; + } + } + + if (extensionFilterElement != null && extensionFilterComponent != null) { + String extensionName = extensionFilterComponent.getCodeFirstRep().getCode(); + int tailIndex = extensionName.lastIndexOf("/"); + if (tailIndex > 0) { + extensionName = extensionName.substring(tailIndex + 1); + } + if (extensionName.startsWith("us-core-")) { + extensionName = extensionName.substring(8); + } + if (extensionName.startsWith("qicore-")) { + extensionName = extensionName.substring(7); + } + r.getCodeFilter().remove(extensionFilterElement); + dataRequirement.removeProperty(urlProperty); + if (extensionProperty != null) { + dataRequirement.removeProperty(extensionProperty); + } + dataRequirement.addProperty(new Property().withPath(extensionName)); + } + } + } + } + } + } private Library createLibrary(ElmRequirementsContext context, ElmRequirements requirements, VersionedIdentifier libraryIdentifier, Iterable expressionDefs, Map parameters, ZonedDateTime evaluationDateTime, boolean includeLogicDefinitions) { @@ -343,6 +413,14 @@ private List extractLogicDefinitions(ElmRequirementsContext context, } } + for (ElmRequirement req : requirements.getFunctionDefs()) { + FunctionDef def = (FunctionDef)req.getElement(); + org.hl7.cql_annotations.r1.Annotation a = getAnnotation(def); + if (a != null) { + result.add(toLogicDefinition(req, def, toNarrativeText(a), sequence++)); + } + } + return result; } diff --git a/Src/java/elm-fhir/src/test/java/org/cqframework/cql/elm/requirements/fhir/DataRequirementsProcessorTest.java b/Src/java/elm-fhir/src/test/java/org/cqframework/cql/elm/requirements/fhir/DataRequirementsProcessorTest.java index d1cf0460b..29b5c8638 100644 --- a/Src/java/elm-fhir/src/test/java/org/cqframework/cql/elm/requirements/fhir/DataRequirementsProcessorTest.java +++ b/Src/java/elm-fhir/src/test/java/org/cqframework/cql/elm/requirements/fhir/DataRequirementsProcessorTest.java @@ -521,11 +521,40 @@ private CqlCompilerOptions getCompilerOptions() { return new CqlCompilerOptions(); } + private Setup setupUncollapsedDataRequirementsGather(String fileName, CqlCompilerOptions cqlTranslatorOptions) throws IOException { + cqlTranslatorOptions.setCollapseDataRequirements(false); + cqlTranslatorOptions.setAnalyzeDataRequirements(false); + return setup(fileName, cqlTranslatorOptions); + } + + private Setup setupUncollapsedDataRequirementsGather(NamespaceInfo namespace, String fileName, CqlCompilerOptions cqlTranslatorOptions) throws IOException { + cqlTranslatorOptions.setCollapseDataRequirements(false); + cqlTranslatorOptions.setAnalyzeDataRequirements(false); + return setup(namespace, fileName, cqlTranslatorOptions); + } + + private Setup setupUncollapsedDataRequirementsAnalysis(String fileName, CqlCompilerOptions cqlTranslatorOptions) throws IOException { + cqlTranslatorOptions.setCollapseDataRequirements(false); + cqlTranslatorOptions.setAnalyzeDataRequirements(true); + return setup(fileName, cqlTranslatorOptions); + } + + private Setup setupUncollapsedDataRequirementsAnalysis(NamespaceInfo namespace, String fileName, CqlCompilerOptions cqlTranslatorOptions) throws IOException { + cqlTranslatorOptions.setCollapseDataRequirements(false); + cqlTranslatorOptions.setAnalyzeDataRequirements(true); + return setup(namespace, fileName, cqlTranslatorOptions); + } + private Setup setupDataRequirementsGather(String fileName, CqlCompilerOptions cqlTranslatorOptions) throws IOException { cqlTranslatorOptions.setCollapseDataRequirements(true); cqlTranslatorOptions.setAnalyzeDataRequirements(false); return setup(fileName, cqlTranslatorOptions); + } + private Setup setupDataRequirementsGather(NamespaceInfo namespace, String fileName, CqlCompilerOptions cqlTranslatorOptions) throws IOException { + cqlTranslatorOptions.setCollapseDataRequirements(true); + cqlTranslatorOptions.setAnalyzeDataRequirements(false); + return setup(namespace, fileName, cqlTranslatorOptions); } private Setup setupDataRequirementsAnalysis(String fileName, CqlCompilerOptions cqlTranslatorOptions) throws IOException { @@ -534,6 +563,12 @@ private Setup setupDataRequirementsAnalysis(String fileName, CqlCompilerOptions return setup(fileName, cqlTranslatorOptions); } + private Setup setupDataRequirementsAnalysis(NamespaceInfo namespace, String fileName, CqlCompilerOptions cqlTranslatorOptions) throws IOException { + cqlTranslatorOptions.setCollapseDataRequirements(true); + cqlTranslatorOptions.setAnalyzeDataRequirements(true); + return setup(namespace, fileName, cqlTranslatorOptions); + } + private org.hl7.fhir.r5.model.Library getModuleDefinitionLibrary(Setup setup, CqlCompilerOptions cqlTranslatorOptions, Map parameters) { DataRequirementsProcessor dqReqTrans = new DataRequirementsProcessor(); org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = dqReqTrans.gatherDataRequirements(setup.manager(), setup.library(), cqlTranslatorOptions, null, parameters, false,false); @@ -548,6 +583,13 @@ private org.hl7.fhir.r5.model.Library getModuleDefinitionLibrary(Setup setup, Cq return moduleDefinitionLibrary; } + private org.hl7.fhir.r5.model.Library getModuleDefinitionLibrary(Setup setup, CqlCompilerOptions cqlTranslatorOptions, Map parameters, ZonedDateTime evaluationDateTime, boolean includeLogicDefinitions) { + DataRequirementsProcessor dqReqTrans = new DataRequirementsProcessor(); + org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = dqReqTrans.gatherDataRequirements(setup.manager(), setup.library(), cqlTranslatorOptions, null, parameters, evaluationDateTime, includeLogicDefinitions,false); + assertTrue(moduleDefinitionLibrary.getType().getCode("http://terminology.hl7.org/CodeSystem/library-type").equalsIgnoreCase("module-definition")); + return moduleDefinitionLibrary; + } + private org.hl7.fhir.r5.model.Library getModuleDefinitionLibrary(Setup setup, CqlCompilerOptions cqlTranslatorOptions) { DataRequirementsProcessor dqReqTrans = new DataRequirementsProcessor(); org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = dqReqTrans.gatherDataRequirements(setup.manager(), setup.library(), cqlTranslatorOptions, null, false); @@ -1671,16 +1713,123 @@ public void TestCMS645() throws IOException { @Test public void TestCMS143() throws IOException { - CqlCompilerOptions compilerOptions = getCompilerOptions(); - compilerOptions.setAnalyzeDataRequirements(false); - var manager = setupDataRequirementsAnalysis("CMS143/cql/POAGOpticNerveEvaluationFHIR-0.0.003.cql", compilerOptions); - org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = getModuleDefinitionLibrary(manager, compilerOptions, new HashMap(), ZonedDateTime.of(2023, 1, 16, 0, 0, 0, 0, ZoneId.of("UTC"))); + CqlCompilerOptions compilerOptions = CqlCompilerOptions.defaultOptions(); + compilerOptions.getOptions().add(CqlCompilerOptions.Options.EnableResultTypes); + Set expressions = new HashSet<>(); + //expressions.add("Qualifying Encounter"); + //expressions.add("Qualifying Encounter During Measurement Period"); + //expressions.add("Qualifying Encounter During Measurement Period Expanded"); + expressions.add("Initial Population"); + expressions.add("Denominator"); + expressions.add("Denominator Exception"); + expressions.add("Numerator"); + expressions.add("SDE Ethnicity"); + expressions.add("SDE Race"); + expressions.add("SDE Sex"); + expressions.add("SDE Payer"); + //var manager = setupUncollapsedDataRequirementsAnalysis(new NamespaceInfo("gov.healthit.ecqi.ecqms", "http://ecqi.healthit.gov/ecqms"), "CMS143/cql/TestUnion.cql", compilerOptions); + var manager = setupDataRequirementsAnalysis(new NamespaceInfo("gov.healthit.ecqi.ecqms", "http://ecqi.healthit.gov/ecqms"), "CMS143/cql/POAGOpticNerveEvaluationFHIR-0.0.003.cql", compilerOptions); + org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = getModuleDefinitionLibrary(manager, compilerOptions, expressions); assertNotNull(moduleDefinitionLibrary); assertEqualToExpectedModuleDefinitionLibrary(moduleDefinitionLibrary, "CMS143/resources/Library-EffectiveDataRequirements.json"); //outputModuleDefinitionLibrary(moduleDefinitionLibrary); } + @Test + public void TestSDESex() throws IOException { + CqlCompilerOptions compilerOptions = CqlCompilerOptions.defaultOptions(); + compilerOptions.getOptions().add(CqlCompilerOptions.Options.EnableResultTypes); + Set expressions = new HashSet<>(); + expressions.add("SDE Sex"); + var manager = setupDataRequirementsAnalysis(new NamespaceInfo("gov.healthit.ecqi.ecqms", "http://ecqi.healthit.gov/ecqms"), "CMS143/cql/POAGOpticNerveEvaluationFHIR-0.0.003.cql", compilerOptions); + org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = getModuleDefinitionLibrary(manager, compilerOptions, expressions); + assertNotNull(moduleDefinitionLibrary); + assertEqualToExpectedModuleDefinitionLibrary(moduleDefinitionLibrary, "CMS143/resources/Library-SDESex-EffectiveDataRequirements.json"); + + // Has direct reference codes to M#http://hl7.org/fhir/v3/AdministrativeGender and F#http://hl7.org/fhir/v3/AdministrativeGender + // Has relatedArtifact to code system http://hl7.org/fhir/v3/AdministrativeGender + // Has relatedArtifact to Library SDE + // Has one and only one DataRequirement for Patient with profile QICore Patient and mustSupport gender + + //outputModuleDefinitionLibrary(moduleDefinitionLibrary); + } + + @Test + public void TestSDEPayer() throws IOException { + CqlCompilerOptions compilerOptions = CqlCompilerOptions.defaultOptions(); + compilerOptions.getOptions().add(CqlCompilerOptions.Options.EnableResultTypes); + Set expressions = new HashSet<>(); + expressions.add("SDE Payer"); + var manager = setupDataRequirementsAnalysis(new NamespaceInfo("gov.healthit.ecqi.ecqms", "http://ecqi.healthit.gov/ecqms"), "CMS143/cql/POAGOpticNerveEvaluationFHIR-0.0.003.cql", compilerOptions); + org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = getModuleDefinitionLibrary(manager, compilerOptions, expressions); + assertNotNull(moduleDefinitionLibrary); + assertEqualToExpectedModuleDefinitionLibrary(moduleDefinitionLibrary, "CMS143/resources/Library-SDEPayer-EffectiveDataRequirements.json"); + + // Has relatedArtifact to Library SDE + // Has relatedArtifact to Value Set Payer + // Has one and only one DatRequirement for Coverage with the Payer Type value set and mustSupport type and period + + //outputModuleDefinitionLibrary(moduleDefinitionLibrary); + } + + @Test + public void TestSDEEthnicity() throws IOException { + CqlCompilerOptions compilerOptions = CqlCompilerOptions.defaultOptions(); + compilerOptions.getOptions().add(CqlCompilerOptions.Options.EnableResultTypes); + Set expressions = new HashSet<>(); + expressions.add("SDE Ethnicity"); + var manager = setupDataRequirementsAnalysis(new NamespaceInfo("gov.healthit.ecqi.ecqms", "http://ecqi.healthit.gov/ecqms"), "CMS143/cql/POAGOpticNerveEvaluationFHIR-0.0.003.cql", compilerOptions); + org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = getModuleDefinitionLibrary(manager, compilerOptions, expressions); + assertNotNull(moduleDefinitionLibrary); + assertEqualToExpectedModuleDefinitionLibrary(moduleDefinitionLibrary, "CMS143/resources/Library-SDEEthnicity-EffectiveDataRequirements.json"); + + // Has relatedArtifact to Library SDE + // Has one and only one DatRequirement for Patient with the QICore Profile and mustSupport ethnicity + + //outputModuleDefinitionLibrary(moduleDefinitionLibrary); + } + + @Test + public void TestSDERace() throws IOException { + CqlCompilerOptions compilerOptions = CqlCompilerOptions.defaultOptions(); + compilerOptions.getOptions().add(CqlCompilerOptions.Options.EnableResultTypes); + Set expressions = new HashSet<>(); + expressions.add("SDE Race"); + var manager = setupDataRequirementsAnalysis(new NamespaceInfo("gov.healthit.ecqi.ecqms", "http://ecqi.healthit.gov/ecqms"), "CMS143/cql/POAGOpticNerveEvaluationFHIR-0.0.003.cql", compilerOptions); + org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = getModuleDefinitionLibrary(manager, compilerOptions, expressions); + assertNotNull(moduleDefinitionLibrary); + assertEqualToExpectedModuleDefinitionLibrary(moduleDefinitionLibrary, "CMS143/resources/Library-SDERace-EffectiveDataRequirements.json"); + + // Has relatedArtifact to Library SDE + // Has one and only one DatRequirement for Patient with the QICore Profile and mustSupport race + + //outputModuleDefinitionLibrary(moduleDefinitionLibrary); + } + + @Test + public void TestQualifyingEncounterMP() throws IOException { + CqlCompilerOptions compilerOptions = CqlCompilerOptions.defaultOptions(); + compilerOptions.getOptions().add(CqlCompilerOptions.Options.EnableResultTypes); + Set expressions = new HashSet<>(); + expressions.add("Qualifying Encounter During Measurement Period"); + var manager = setupDataRequirementsAnalysis(new NamespaceInfo("gov.healthit.ecqi.ecqms", "http://ecqi.healthit.gov/ecqms"), "CMS143/cql/POAGOpticNerveEvaluationFHIR-0.0.003.cql", compilerOptions); + org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = getModuleDefinitionLibrary(manager, compilerOptions, expressions); + assertNotNull(moduleDefinitionLibrary); + assertEqualToExpectedModuleDefinitionLibrary(moduleDefinitionLibrary, "CMS143/resources/Library-QualifyingEncounterMP-EffectiveDataRequirements.json"); + + // Has direct reference codes to VR and AMB + // Has relatedArtifact to ActCode code system + // Has relatedArtifact to Office Visit ValueSet + // Has relatedArtifact to Opthalmological Services ValueSet + // Has relatedArtifact to Outpatient Consultation ValueSet + // Has relatedArtifact to Nursing Facility Visit ValueSet + // Has relatedArtifact to Care Services in Long-Term Residentail Facility ValueSet + // Has 5 DataRequirements for Encounter with the QICore Encounter Profile and mustSupport type, period, and class, one for each ValueSet + + //outputModuleDefinitionLibrary(moduleDefinitionLibrary); + } + @Test public void TestCMS149() throws IOException { CqlCompilerOptions compilerOptions = getCompilerOptions(); @@ -1692,6 +1841,35 @@ public void TestCMS149() throws IOException { //outputModuleDefinitionLibrary(moduleDefinitionLibrary); } + + private Extension getLogicDefinitionByName(List logicDefinitions, String libraryName, String name) { + for (Extension ld : logicDefinitions) { + Extension ln = ld.getExtensionByUrl("libraryName"); + assertTrue(ln != null && ln.hasValue()); + Extension n = ld.getExtensionByUrl("name"); + assertTrue(n != null && n.hasValue()); + if (ln.getValueStringType().getValue().equals(libraryName) && n.getValueStringType().getValue().equals(name)) { + return ld; + } + } + return null; + } + + @Test + public void TestDeviceOrder() throws IOException { + CqlCompilerOptions compilerOptions = CqlCompilerOptions.defaultOptions(); + var manager = setupDataRequirementsGather("DeviceOrder/TestDeviceOrder.cql", compilerOptions); + org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = getModuleDefinitionLibrary(manager, compilerOptions, new HashMap(), ZonedDateTime.of(2023, 1, 16, 0, 0, 0, 0, ZoneId.of("UTC")), true); + assertNotNull(moduleDefinitionLibrary); + assertEqualToExpectedModuleDefinitionLibrary(moduleDefinitionLibrary, "DeviceOrder/Library-TestDeviceOrder-EffectiveDataRequirements.json"); + //outputModuleDefinitionLibrary(moduleDefinitionLibrary); + + List logicDefinitions = moduleDefinitionLibrary.getExtensionsByUrl("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-logicDefinition"); + assertTrue(logicDefinitions != null); + assertTrue(logicDefinitions.size() > 0); + Extension logicDefinition = getLogicDefinitionByName(logicDefinitions, "TestDeviceOrder", "isDeviceOrder"); + assertTrue(logicDefinition != null); + } @Test public void TestDataRequirementsProcessorWithPertinence() { diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/cql/SupplementalDataElements-3.1.000.cql b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/cql/SupplementalDataElements-3.1.000.cql index 04be4f8a1..09f156fe8 100644 --- a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/cql/SupplementalDataElements-3.1.000.cql +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/cql/SupplementalDataElements-3.1.000.cql @@ -13,11 +13,16 @@ using QICore version '4.1.1' include FHIRHelpers version '4.1.000' called FHIRHelpers +codesystem "AdministrativeGender": 'http://hl7.org/fhir/v3/AdministrativeGender' + valueset "Ethnicity": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.837' valueset "ONC Administrative Sex": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1' valueset "Payer": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591' valueset "Race": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.836' +code "M": 'M' from "AdministrativeGender" display 'Male' +code "F": 'F' from "AdministrativeGender" display 'Female' + context Patient define "SDE Ethnicity": @@ -43,7 +48,7 @@ define "SDE Race": 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' } + when Patient.gender = 'male' then "M" + when Patient.gender = 'female' then "F" else null end \ No newline at end of file diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/cql/TestUnion.cql b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/cql/TestUnion.cql new file mode 100644 index 000000000..e2ebae52a --- /dev/null +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/cql/TestUnion.cql @@ -0,0 +1,41 @@ +library TestUnion version '0.0.003' + +using QICore version '4.1.1' + +include FHIRHelpers version '4.1.000' called FHIRHelpers +include CQMCommon version '1.0.000' called CQMCommon +include FHIRCommon version '4.1.000' called FHIRCommon +include QICoreCommon version '1.2.000' called QICoreCommon + +codesystem "ActCode": 'http://terminology.hl7.org/CodeSystem/v3-ActCode' + +valueset "Office Visit": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1001' +valueset "Ophthalmological Services": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1285' +valueset "Outpatient Consultation": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1008' + +code "virtual": 'VR' from "ActCode" display 'virtual' +code "AMB": 'AMB' from "ActCode" display 'Ambulatory' + +parameter "Measurement Period" Interval + +context Patient + +define "Qualifying Encounter": + ["Encounter": "Office Visit"] + union ["Encounter": "Ophthalmological Services"] + union ["Encounter": "Outpatient Consultation"] + +define "Qualifying Encounter During Measurement Period": + "Qualifying Encounter" QualifyingEncounter + where QualifyingEncounter.period during "Measurement Period" + and QualifyingEncounter.class !~ "virtual" + and QualifyingEncounter.class ~ "AMB" + +define "Qualifying Encounter During Measurement Period Expanded": + (["Encounter": "Office Visit"] + union ["Encounter": "Ophthalmological Services"] + union ["Encounter": "Outpatient Consultation"] + ) QualifyingEncounter + where QualifyingEncounter.period during "Measurement Period" + and QualifyingEncounter.class !~ "virtual" + and QualifyingEncounter.class ~ "AMB" diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-EffectiveDataRequirements.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-EffectiveDataRequirements.json index 109ba8aef..5c842e31a 100644 --- a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-EffectiveDataRequirements.json +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-EffectiveDataRequirements.json @@ -1,6 +1,20 @@ { "resourceType": "Library", "extension": [ { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode", + "valueCoding": { + "system": "http://hl7.org/fhir/v3/AdministrativeGender", + "code": "M", + "display": "Male" + } + }, { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode", + "valueCoding": { + "system": "http://hl7.org/fhir/v3/AdministrativeGender", + "code": "F", + "display": "Female" + } + }, { "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode", "valueCoding": { "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", @@ -23,73 +37,61 @@ } ] }, "relatedArtifact": [ { - "type": "depends-on", - "display": "QICore model information", - "resource": "http://hl7.org/fhir/Library/QICore-ModelInfo" - }, { - "type": "depends-on", - "display": "Library FHIRHelpers", - "resource": "Library/FHIRHelpers|4.1.000" - }, { "type": "depends-on", "display": "Library SDE", - "resource": "Library/SupplementalDataElements|3.1.000" + "resource": "http://ecqi.healthit.gov/ecqms/Library/SupplementalDataElements|3.1.000" }, { "type": "depends-on", - "display": "Library CQMCommon", - "resource": "Library/CQMCommon|1.0.000" + "display": "Library FHIRHelpers", + "resource": "http://ecqi.healthit.gov/ecqms/Library/FHIRHelpers|4.1.000" }, { "type": "depends-on", - "display": "Library FHIRCommon", - "resource": "Library/FHIRCommon|4.1.000" + "display": "Library QICoreCommon", + "resource": "http://ecqi.healthit.gov/ecqms/Library/QICoreCommon|1.2.000" }, { "type": "depends-on", - "display": "Library QICoreCommon", - "resource": "Library/QICoreCommon|1.2.000" + "display": "Code system AdministrativeGender", + "resource": "http://hl7.org/fhir/v3/AdministrativeGender" }, { "type": "depends-on", "display": "Code system ActCode", "resource": "http://terminology.hl7.org/CodeSystem/v3-ActCode" - }, { - "type": "depends-on", - "display": "Value set Care Services in Long-Term Residential Facility", - "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1014" }, { "type": "depends-on", "display": "Value set Cup to Disc Ratio", "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1333" }, { "type": "depends-on", - "display": "Value set Face-to-Face Interaction", - "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1048" + "display": "Value set Office Visit", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1001" }, { "type": "depends-on", - "display": "Value set Medical Reason", - "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1007" + "display": "Value set Ophthalmological Services", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1285" + }, { + "type": "depends-on", + "display": "Value set Outpatient Consultation", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1008" }, { "type": "depends-on", "display": "Value set Nursing Facility Visit", "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1012" }, { "type": "depends-on", - "display": "Value set Office Visit", - "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1001" + "display": "Value set Care Services in Long-Term Residential Facility", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1014" }, { "type": "depends-on", - "display": "Value set Ophthalmological Services", - "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1285" + "display": "Value set Primary Open-Angle Glaucoma", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.326" }, { "type": "depends-on", "display": "Value set Optic Disc Exam for Structural Abnormalities", "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1334" }, { "type": "depends-on", - "display": "Value set Outpatient Consultation", - "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1008" - }, { - "type": "depends-on", - "display": "Value set Primary Open-Angle Glaucoma", - "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.326" + "display": "Value set Payer", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591" } ], "parameter": [ { "name": "Measurement Period", @@ -97,30 +99,6 @@ "min": 0, "max": "1", "type": "Period" - }, { - "name": "Patient", - "use": "out", - "min": 0, - "max": "1", - "type": "Resource" - }, { - "name": "SDE Ethnicity", - "use": "out", - "min": 0, - "max": "1", - "type": "Resource" - }, { - "name": "SDE Payer", - "use": "out", - "min": 0, - "max": "*", - "type": "Resource" - }, { - "name": "SDE Race", - "use": "out", - "min": 0, - "max": "1", - "type": "Resource" }, { "name": "SDE Sex", "use": "out", @@ -128,19 +106,7 @@ "max": "1", "type": "Coding" }, { - "name": "Qualifying Encounter During Measurement Period", - "use": "out", - "min": 0, - "max": "*", - "type": "Resource" - }, { - "name": "Primary Open Angle Glaucoma Encounter", - "use": "out", - "min": 0, - "max": "*", - "type": "Resource" - }, { - "name": "Initial Population", + "name": "Numerator", "use": "out", "min": 0, "max": "1", @@ -152,72 +118,73 @@ "max": "1", "type": "boolean" }, { - "name": "Medical Reason for Not Performing Cup to Disc Ratio", - "use": "out", - "min": 0, - "max": "*", - "type": "Resource" - }, { - "name": "Medical Reason for Not Performing Optic Disc Exam", + "name": "SDE Payer", "use": "out", "min": 0, "max": "*", "type": "Resource" }, { - "name": "Denominator Exceptions", + "name": "Initial Population", "use": "out", "min": 0, "max": "1", "type": "boolean" }, { - "name": "Cup to Disc Ratio Performed with Result", - "use": "out", - "min": 0, - "max": "*", - "type": "Resource" - }, { - "name": "Optic Disc Exam Performed with Result", + "name": "SDE Ethnicity", "use": "out", "min": 0, - "max": "*", + "max": "1", "type": "Resource" }, { - "name": "Numerator", + "name": "SDE Race", "use": "out", "min": 0, "max": "1", - "type": "boolean" + "type": "Resource" } ], "dataRequirement": [ { - "type": "Patient", - "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ] - }, { "type": "Patient", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ], - "mustSupport": [ "url", "extension" ], + "mustSupport": [ "gender", "gender.value", "birthDate", "birthDate.value", "ethnicity", "race" ] + }, { + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "class" ], "codeFilter": [ { - "path": "url", - "code": [ { - "code": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity" - } ] + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1001" } ] }, { - "type": "Patient", - "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ], - "mustSupport": [ "url", "extension" ], + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "class" ], "codeFilter": [ { - "path": "url", - "code": [ { - "code": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race" - } ] + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1285" } ] }, { - "type": "Coverage", - "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-coverage" ], - "mustSupport": [ "type", "period" ], + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "class" ], "codeFilter": [ { "path": "type", - "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591" + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1008" + } ] + }, { + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "class" ], + "codeFilter": [ { + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1012" + } ] + }, { + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "class" ], + "codeFilter": [ { + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1014" } ] }, { "type": "Condition", @@ -243,5 +210,13 @@ "path": "code", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1334" } ] + }, { + "type": "Coverage", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-coverage" ], + "mustSupport": [ "type", "period" ], + "codeFilter": [ { + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591" + } ] } ] } diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-QualifyingEncounterMP-EffectiveDataRequirements.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-QualifyingEncounterMP-EffectiveDataRequirements.json new file mode 100644 index 000000000..f44ffc722 --- /dev/null +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-QualifyingEncounterMP-EffectiveDataRequirements.json @@ -0,0 +1,108 @@ +{ + "resourceType": "Library", + "extension": [ { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode", + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", + "code": "VR", + "display": "virtual" + } + }, { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode", + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", + "code": "AMB", + "display": "Ambulatory" + } + } ], + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "module-definition" + } ] + }, + "relatedArtifact": [ { + "type": "depends-on", + "display": "Library FHIRHelpers", + "resource": "http://ecqi.healthit.gov/ecqms/Library/FHIRHelpers|4.1.000" + }, { + "type": "depends-on", + "display": "Code system ActCode", + "resource": "http://terminology.hl7.org/CodeSystem/v3-ActCode" + }, { + "type": "depends-on", + "display": "Value set Office Visit", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1001" + }, { + "type": "depends-on", + "display": "Value set Ophthalmological Services", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1285" + }, { + "type": "depends-on", + "display": "Value set Outpatient Consultation", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1008" + }, { + "type": "depends-on", + "display": "Value set Nursing Facility Visit", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1012" + }, { + "type": "depends-on", + "display": "Value set Care Services in Long-Term Residential Facility", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1014" + } ], + "parameter": [ { + "name": "Measurement Period", + "use": "in", + "min": 0, + "max": "1", + "type": "Period" + }, { + "name": "Qualifying Encounter During Measurement Period", + "use": "out", + "min": 0, + "max": "*", + "type": "Resource" + } ], + "dataRequirement": [ { + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "class" ], + "codeFilter": [ { + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1001" + } ] + }, { + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "class" ], + "codeFilter": [ { + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1285" + } ] + }, { + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "class" ], + "codeFilter": [ { + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1008" + } ] + }, { + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "class" ], + "codeFilter": [ { + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1012" + } ] + }, { + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "class" ], + "codeFilter": [ { + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1014" + } ] + } ] +} diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDEEthnicity-EffectiveDataRequirements.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDEEthnicity-EffectiveDataRequirements.json new file mode 100644 index 000000000..80c67c9ff --- /dev/null +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDEEthnicity-EffectiveDataRequirements.json @@ -0,0 +1,31 @@ +{ + "resourceType": "Library", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "module-definition" + } ] + }, + "relatedArtifact": [ { + "type": "depends-on", + "display": "Library SDE", + "resource": "http://ecqi.healthit.gov/ecqms/Library/SupplementalDataElements|3.1.000" + }, { + "type": "depends-on", + "display": "Library FHIRHelpers", + "resource": "http://ecqi.healthit.gov/ecqms/Library/FHIRHelpers|4.1.000" + } ], + "parameter": [ { + "name": "SDE Ethnicity", + "use": "out", + "min": 0, + "max": "1", + "type": "Resource" + } ], + "dataRequirement": [ { + "type": "Patient", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ], + "mustSupport": [ "ethnicity" ] + } ] +} \ No newline at end of file diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDEPayer-EffectiveDataRequirements.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDEPayer-EffectiveDataRequirements.json new file mode 100644 index 000000000..32c4d76e2 --- /dev/null +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDEPayer-EffectiveDataRequirements.json @@ -0,0 +1,39 @@ +{ + "resourceType": "Library", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "module-definition" + } ] + }, + "relatedArtifact": [ { + "type": "depends-on", + "display": "Library SDE", + "resource": "http://ecqi.healthit.gov/ecqms/Library/SupplementalDataElements|3.1.000" + }, { + "type": "depends-on", + "display": "Library FHIRHelpers", + "resource": "http://ecqi.healthit.gov/ecqms/Library/FHIRHelpers|4.1.000" + }, { + "type": "depends-on", + "display": "Value set Payer", + "resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591" + } ], + "parameter": [ { + "name": "SDE Payer", + "use": "out", + "min": 0, + "max": "*", + "type": "Resource" + } ], + "dataRequirement": [ { + "type": "Coverage", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-coverage" ], + "mustSupport": [ "type", "period" ], + "codeFilter": [ { + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591" + } ] + } ] +} \ No newline at end of file diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDERace-EffectiveDataRequirements.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDERace-EffectiveDataRequirements.json new file mode 100644 index 000000000..29cac7c10 --- /dev/null +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDERace-EffectiveDataRequirements.json @@ -0,0 +1,31 @@ +{ + "resourceType": "Library", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "module-definition" + } ] + }, + "relatedArtifact": [ { + "type": "depends-on", + "display": "Library SDE", + "resource": "http://ecqi.healthit.gov/ecqms/Library/SupplementalDataElements|3.1.000" + }, { + "type": "depends-on", + "display": "Library FHIRHelpers", + "resource": "http://ecqi.healthit.gov/ecqms/Library/FHIRHelpers|4.1.000" + } ], + "parameter": [ { + "name": "SDE Race", + "use": "out", + "min": 0, + "max": "1", + "type": "Resource" + } ], + "dataRequirement": [ { + "type": "Patient", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ], + "mustSupport": [ "race" ] + } ] +} diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDESex-EffectiveDataRequirements.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDESex-EffectiveDataRequirements.json new file mode 100644 index 000000000..b52531f3d --- /dev/null +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS143/resources/Library-SDESex-EffectiveDataRequirements.json @@ -0,0 +1,46 @@ +{ + "resourceType": "Library", + "extension": [ { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode", + "valueCoding": { + "system": "http://hl7.org/fhir/v3/AdministrativeGender", + "code": "M", + "display": "Male" + } + }, { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode", + "valueCoding": { + "system": "http://hl7.org/fhir/v3/AdministrativeGender", + "code": "F", + "display": "Female" + } + } ], + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "module-definition" + } ] + }, + "relatedArtifact": [ { + "type": "depends-on", + "display": "Library SDE", + "resource": "http://ecqi.healthit.gov/ecqms/Library/SupplementalDataElements|3.1.000" + }, { + "type": "depends-on", + "display": "Code system AdministrativeGender", + "resource": "http://hl7.org/fhir/v3/AdministrativeGender" + } ], + "parameter": [ { + "name": "SDE Sex", + "use": "out", + "min": 0, + "max": "1", + "type": "Coding" + } ], + "dataRequirement": [ { + "type": "Patient", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ], + "mustSupport": [ "gender", "gender.value" ] + } ] +} \ No newline at end of file diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS149/resources/Library-EffectiveDataRequirements.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS149/resources/Library-EffectiveDataRequirements.json index 056b1c2d9..aebbaf50f 100644 --- a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS149/resources/Library-EffectiveDataRequirements.json +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS149/resources/Library-EffectiveDataRequirements.json @@ -199,28 +199,9 @@ "type": "boolean" } ], "dataRequirement": [ { - "type": "Patient", - "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ] - }, { - "type": "Patient", - "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ], - "mustSupport": [ "url", "extension" ], - "codeFilter": [ { - "path": "url", - "code": [ { - "code": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity" - } ] - } ] - }, { "type": "Patient", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ], - "mustSupport": [ "url", "extension" ], - "codeFilter": [ { - "path": "url", - "code": [ { - "code": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race" - } ] - } ] + "mustSupport": [ "ethnicity", "race" ] }, { "type": "Coverage", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-coverage" ], @@ -232,7 +213,7 @@ }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], - "mustSupport": [ "type" ], + "mustSupport": [ "type", "period", "period.low", "period.lowClosed", "period.high", "period.highClosed", "class", "status", "status.value" ], "codeFilter": [ { "path": "type", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1492" @@ -240,7 +221,7 @@ }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], - "mustSupport": [ "type" ], + "mustSupport": [ "type", "period", "period.low", "period.lowClosed", "period.high", "period.highClosed", "class", "status", "status.value" ], "codeFilter": [ { "path": "type", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1012" @@ -248,7 +229,7 @@ }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], - "mustSupport": [ "type" ], + "mustSupport": [ "type", "period", "period.low", "period.lowClosed", "period.high", "period.highClosed", "class", "status", "status.value" ], "codeFilter": [ { "path": "type", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1014" @@ -256,7 +237,7 @@ }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], - "mustSupport": [ "type" ], + "mustSupport": [ "type", "period", "period.low", "period.lowClosed", "period.high", "period.highClosed", "class", "status", "status.value" ], "codeFilter": [ { "path": "type", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1016" @@ -264,7 +245,7 @@ }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], - "mustSupport": [ "type" ], + "mustSupport": [ "type", "period", "period.low", "period.lowClosed", "period.high", "period.highClosed", "class", "status", "status.value" ], "codeFilter": [ { "path": "type", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1496" @@ -272,7 +253,7 @@ }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], - "mustSupport": [ "type" ], + "mustSupport": [ "type", "period", "period.low", "period.lowClosed", "period.high", "period.highClosed", "class", "status", "status.value" ], "codeFilter": [ { "path": "type", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1023" @@ -280,7 +261,7 @@ }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], - "mustSupport": [ "type" ], + "mustSupport": [ "type", "period", "period.low", "period.lowClosed", "period.high", "period.highClosed", "class", "status", "status.value" ], "codeFilter": [ { "path": "type", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1011" @@ -288,7 +269,7 @@ }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], - "mustSupport": [ "type" ], + "mustSupport": [ "type", "period", "period.low", "period.lowClosed", "period.high", "period.highClosed", "class", "status", "status.value" ], "codeFilter": [ { "path": "type", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1001" @@ -296,11 +277,19 @@ }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], - "mustSupport": [ "type" ], + "mustSupport": [ "type", "period", "period.low", "period.lowClosed", "period.high", "period.highClosed", "class", "status", "status.value" ], "codeFilter": [ { "path": "type", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.464.1003.101.12.1008" } ] + }, { + "type": "Encounter", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter" ], + "mustSupport": [ "type", "period", "period.low", "period.lowClosed", "period.high", "period.highClosed", "class", "status", "status.value" ], + "codeFilter": [ { + "path": "type", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1012" + } ] }, { "type": "Condition", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-condition" ], @@ -309,5 +298,37 @@ "path": "code", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1005" } ] + }, { + "type": "Observation", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-observation" ], + "mustSupport": [ "code", "issued", "issued.value", "value" ], + "codeFilter": [ { + "path": "code", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1006" + } ] + }, { + "type": "Observation", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-observation" ], + "mustSupport": [ "code", "issued", "issued.value", "value" ], + "codeFilter": [ { + "path": "code", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1332" + } ] + }, { + "type": "Observation", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-observationnotdone" ], + "mustSupport": [ "code", "issued", "issued.value", "status", "status.value", "extension" ], + "codeFilter": [ { + "path": "code", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1006" + } ] + }, { + "type": "Observation", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-observationnotdone" ], + "mustSupport": [ "code", "issued", "issued.value", "status", "status.value", "extension" ], + "codeFilter": [ { + "path": "code", + "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1332" + } ] } ] } diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS645/CMS645-ModuleDefinitionLibrary.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS645/CMS645-ModuleDefinitionLibrary.json index da827ef70..f9f0d0ff9 100644 --- a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS645/CMS645-ModuleDefinitionLibrary.json +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/CMS645/CMS645-ModuleDefinitionLibrary.json @@ -54,6 +54,14 @@ "id": "G10002", "type": "MedicationRequest", "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest" ], - "mustSupport": [ "medication.reference" ] + "mustSupport": [ "medication.reference", "status", "status.value", "intent", "intent.value", "doNotPerform", "doNotPerform.value", "dosageInstruction" ] + }, { + "type": "MedicationRequest", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest" ], + "mustSupport": [ "medication", "status", "status.value", "intent", "intent.value", "doNotPerform", "doNotPerform.value", "dosageInstruction" ], + "codeFilter": [ { + "path": "medication", + "valueSet": "tbd" + } ] } ] } diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/Library-TestDeviceOrder-EffectiveDataRequirements.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/Library-TestDeviceOrder-EffectiveDataRequirements.json new file mode 100644 index 000000000..32460cbed --- /dev/null +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/Library-TestDeviceOrder-EffectiveDataRequirements.json @@ -0,0 +1,89 @@ +{ + "resourceType": "Library", + "extension": [ { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-logicDefinition", + "extension": [ { + "url": "libraryName", + "valueString": "TestDeviceOrder" + }, { + "url": "name", + "valueString": "Has Criteria Indicating Frailty" + }, { + "url": "statement", + "valueString": "define \"Has Criteria Indicating Frailty\":\n exists ( (([DeviceRequest: \"Frailty Device\"]).isDeviceOrder()) FrailtyDeviceOrder\n where FrailtyDeviceOrder.doNotPerform() is not true\n and FrailtyDeviceOrder.authoredOn.toInterval() during day of \"Measurement Period\"\n )" + }, { + "url": "displaySequence", + "valueInteger": 0 + } ] + }, { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-logicDefinition", + "extension": [ { + "url": "libraryName", + "valueString": "TestDeviceOrder" + }, { + "url": "name", + "valueString": "isDeviceOrder" + }, { + "url": "statement", + "valueString": "define fluent function isDeviceOrder(DeviceRequest List):\n DeviceRequest D\n where D.status in { 'active', 'completed' }\n and D.intent = 'order'" + }, { + "url": "displaySequence", + "valueInteger": 1 + } ] + } ], + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "module-definition" + } ] + }, + "relatedArtifact": [ { + "type": "depends-on", + "display": "QICore model information", + "resource": "http://hl7.org/fhir/Library/QICore-ModelInfo" + }, { + "type": "depends-on", + "display": "Library FHIRHelpers", + "resource": "Library/FHIRHelpers|4.0.1" + }, { + "type": "depends-on", + "display": "Library QICoreCommon", + "resource": "Library/QICoreCommon|1.5.000" + }, { + "type": "depends-on", + "display": "Value set Frailty Device", + "resource": "http://example.org/fhir/valueset/frailty-device" + } ], + "parameter": [ { + "name": "Measurement Period", + "use": "in", + "min": 0, + "max": "1", + "type": "Period" + }, { + "name": "Patient", + "use": "out", + "min": 0, + "max": "1", + "type": "Resource" + }, { + "name": "Has Criteria Indicating Frailty", + "use": "out", + "min": 0, + "max": "1", + "type": "boolean" + } ], + "dataRequirement": [ { + "type": "Patient", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient" ] + }, { + "type": "DeviceRequest", + "profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-devicerequest" ], + "mustSupport": [ "code", "status", "status.value", "intent", "intent.value", "authoredOn", "authoredOn.value" ], + "codeFilter": [ { + "path": "code", + "valueSet": "http://example.org/fhir/valueset/frailty-device" + } ] + } ] +} diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/QICoreCommon.cql b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/QICoreCommon.cql new file mode 100644 index 000000000..4dda5198b --- /dev/null +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/QICoreCommon.cql @@ -0,0 +1,581 @@ +library QICoreCommon version '1.5.000' + +using QICore version '4.1.1' + +include FHIRHelpers version '4.0.1' + +codesystem "LOINC": 'http://loinc.org' +codesystem "SNOMEDCT": 'http://snomed.info/sct' +codesystem "ActCode": 'http://terminology.hl7.org/CodeSystem/v3-ActCode' +codesystem "RoleCode": 'http://terminology.hl7.org/CodeSystem/v3-RoleCode' +codesystem "Diagnosis Role": 'http://terminology.hl7.org/CodeSystem/diagnosis-role' +codesystem "RequestIntent": 'http://hl7.org/fhir/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-ver-status' +codesystem "AllergyIntoleranceClinicalStatusCodes": 'http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical' +codesystem "AllergyIntoleranceVerificationStatusCodes": 'http://terminology.hl7.org/CodeSystem/allergyintolerance-verification' +codesystem "ObservationCategoryCodes": 'http://terminology.hl7.org/CodeSystem/observation-category' +codesystem "USCoreObservationCategoryExtensionCodes": 'http://hl7.org/fhir/us/core/CodeSystem/us-core-observation-category' +codesystem "ConditionCategory": 'http://terminology.hl7.org/CodeSystem/condition-category' +codesystem "USCoreConditionCategoryExtensionCodes": 'http://hl7.org/fhir/us/core/CodeSystem/condition-category' + +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' + +// Encounter Class Codes +code "ambulatory": 'AMB' from ActCode display 'ambulatory' +code "emergency": 'EMER' from ActCode display 'emergency' +code "field": 'FLD' from ActCode display 'field' +code "home health": 'HH' from ActCode display 'home health' +code "inpatient encounter": 'IMP' from ActCode display 'inpatient encounter' +code "inpatient acute": 'ACUTE' from ActCode display 'inpatient acute' +code "inpatient non-acute": 'NONAC' from ActCode display 'inpatient non-acute' +code "observation encounter": 'OBSENC' from ActCode display 'observation encounter' +code "pre-admission": 'PRENC' from ActCode display 'pre-admission' +code "short stay": 'SS' from ActCode display 'short stay' +code "virtual": 'VR' from ActCode display 'VR' + +// Condition Category Codes +code "problem-list-item": 'problem-list-item' from "ConditionCategory" display 'Problem List Item' +code "encounter-diagnosis": 'encounter-diagnosis' from "ConditionCategory" display 'Encounter Diagnosis' +code "health-concern": 'health-concern' from "USCoreConditionCategoryExtensionCodes" display 'Health Concern' + +// 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 "Inpatient": 'inpatient' from "MedicationRequestCategory" display 'Inpatient' +code "Outpatient": 'outpatient' from "MedicationRequestCategory" display 'Outpatient' +code "Community": 'community' from "MedicationRequestCategory" display 'Community' +code "Discharge": 'discharge' from "MedicationRequestCategory" display 'Discharge' + +// Diagnosis Role Codes +code "AD": 'AD' from "Diagnosis Role" display 'Admission diagnosis' +code "DD": 'DD' from "Diagnosis Role" display 'Discharge diagnosis' +code "CC": 'CC' from "Diagnosis Role" display 'Chief complaint' +code "CM": 'CM' from "Diagnosis Role" display 'Comorbidity diagnosis' +code "pre-op": 'pre-op' from "Diagnosis Role" display 'pre-op diagnosis' +code "post-op": 'post-op' from "Diagnosis Role" display 'post-op diagnosis' +code "billing": 'billing' from "Diagnosis Role" display 'billing diagnosis' + +// Observation Category Codes +code "social-history": 'social-history' from "ObservationCategoryCodes" display 'Social History' +code "vital-signs": 'vital-signs' from "ObservationCategoryCodes" display 'Vital Signs' +code "imaging": 'imaging' from "ObservationCategoryCodes" display 'Imaging' +code "laboratory": 'laboratory' from "ObservationCategoryCodes" display 'Laboratory' +code "procedure": 'procedure' from "ObservationCategoryCodes" display 'Procedure' +code "survey": 'survey' from "ObservationCategoryCodes" display 'Survey' +code "exam": 'exam' from "ObservationCategoryCodes" display 'Exam' +code "therapy": 'therapy' from "ObservationCategoryCodes" display 'Therapy' +code "activity": 'activity' from "ObservationCategoryCodes" display 'Activity' +code "clinical-test": 'clinical-test' from "USCoreObservationCategoryExtensionCodes" display 'Clinical Test' + +context Patient + +/* Candidates for FHIRCommon */ + +/* +@description: Returns true if the given condition has a clinical status of active, recurrence, or relapse +*/ +define fluent function isActive(condition Condition): + condition.clinicalStatus ~ "active" + or condition.clinicalStatus ~ "recurrence" + or condition.clinicalStatus ~ "relapse" + +/* +@description: Returns true if the given condition has the given category +*/ +define fluent function hasCategory(condition Condition, category Code): + exists (condition.category C + where C ~ category + ) + +/* +@description: Returns true if the given condition is a problem list item. +*/ +define fluent function isProblemListItem(condition Condition): + exists (condition.category C + where C ~ "problem-list-item" + ) + +/* +@description: Returns true if the given condition is an encounter diagnosis +*/ +define fluent function isEncounterDiagnosis(condition Condition): + exists (condition.category C + where C ~ "encounter-diagnosis" + ) + +/* +@description: Returns true if the given condition is a health concern +*/ +define fluent function isHealthConcern(condition Condition): + exists (condition.category C + where C ~ "health-concern" + ) + +/* +@description: Returns true if the given observation has the given category +*/ +define fluent function hasCategory(observation Observation, category Code): + exists (observation.category C + where C ~ category + ) + +/* +@description: Returns true if the given observation is a social history observation +*/ +define fluent function isSocialHistory(observation Observation): + exists (observation.category C + where C ~ "social-history" + ) + +/* +@description: Returns true if the given observation is a vital sign +*/ +define fluent function isVitalSign(observation Observation): + exists (observation.category C + where C ~ "vital-signs" + ) + +/* +@description: Returns true if the given observation is an imaging observation +*/ +define fluent function isImaging(observation Observation): + exists (observation.category C + where C ~ "imaging" + ) + +/* +@description: Returns true if the given observation is a laboratory observation +*/ +define fluent function isLaboratory(observation Observation): + exists (observation.category C + where C ~ "laboratory" + ) + +/* +@description: REturns true if the given observation is a procedure observation +*/ +define fluent function isProcedure(observation Observation): + exists (observation.category C + where C ~ "procedure" + ) + +/* +@description: Returns true if the given observation is a survey observation +*/ +define fluent function isSurvey(observation Observation): + exists (observation.category C + where C ~ "survey" + ) + +/* +@description: Returns true if the given observation is an exam observation +*/ +define fluent function isExam(observation Observation): + exists (observation.category C + where C ~ "exam" + ) + +/* +@description: Returns true if the given observation is a therapy observation +*/ +define fluent function isTherapy(observation Observation): + exists (observation.category C + where C ~ "therapy" + ) + +/* +@description: Returns true if the given observation is an activity observation +*/ +define fluent function isActivity(observation Observation): + exists (observation.category C + where C ~ "activity" + ) + +/* +@description: Returns true if the given observation is a clinical test result +*/ +define fluent function isClinicalTest(observation Observation): + exists (observation.category C + where C ~ "clinical-test" + ) + +/* +@description: Returns true if the given MedicationRequest has a category of Community +*/ +define fluent function isCommunity(medicationRequest MedicationRequest): + exists (medicationRequest.category C + where C ~ Community + ) + +/* +@description: Returns true if the given MedicationRequest has a category of Community +*/ +define fluent function isCommunity(medicationRequest MedicationNotRequested): + exists (medicationRequest.category C + where C ~ Community + ) + +/* +@description: Returns true if the given MedicationRequest has a category of Discharge +*/ +define fluent function isDischarge(medicationRequest MedicationRequest): + exists (medicationRequest.category C + where C ~ Discharge + ) + +/* +@description: Returns true if the given MedicationRequest has a category of Discharge +*/ +define fluent function isDischarge(medicationRequest MedicationNotRequested): + exists (medicationRequest.category C + where C ~ Discharge + ) + +/* +@description: Returns true if the given DeviceRequest is a negation (i.e. do not perform this order) +*/ +define fluent function doNotPerform(deviceRequest DeviceRequest): + singleton from ( + deviceRequest.modifierExtension E + where E.url = 'http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-doNotPerform' + return E.value as Boolean + ) + +/* +@description: Normalizes a value that is a choice of timing-valued types to an equivalent interval +@comment: Normalizes a choice type of DateTime, Quanitty, Interval, or Interval types +to an equivalent interval. This selection of choice types is a superset of the majority of choice types that are used as possible +representations for timing-valued elements in QICore, allowing this function to be used across any resource. +The input can be provided as a DateTime, Quantity, Interval or Interval. +The intent of this function is to provide a clear and concise mechanism to treat single +elements that have multiple possible representations as intervals so that logic doesn't have to account +for the variability. More complex calculations (such as medication request period or dispense period +calculation) need specific guidance and consideration. That guidance may make use of this function, but +the focus of this function is on single element calculations where the semantics are unambiguous. +If the input is a DateTime, the result a DateTime Interval beginning and ending on that DateTime. +If the input is a Quantity, the quantity is expected to be a calendar-duration interpreted as an Age, +and the result is a DateTime Interval beginning on the Date the patient turned that age and ending immediately before one year later. +If the input is a DateTime Interval, the result is the input. +If the input is a Quantity Interval, the quantities are expected to be calendar-durations interpreted as an Age, and the result +is a DateTime Interval beginning on the date the patient turned the age given as the start of the quantity interval, and ending +immediately before one year later than the date the patient turned the age given as the end of the quantity interval. +If the input is a Timing, an error will be thrown indicating that Timing calculations are not implemented. +Any other input will reslt in a null DateTime Interval +@deprecated: This function is deprecated. Use the fluent function `toInterval()` instead +*/ +define function ToInterval(choice Choice, Interval, Timing>): + case + when choice is DateTime then + Interval[choice as DateTime, choice as DateTime] + when choice is Interval then + choice as Interval + when choice is Quantity then + Interval[Patient.birthDate + (choice as Quantity), + Patient.birthDate + (choice as Quantity) + 1 year) + when choice is Interval then + Interval[Patient.birthDate + (choice.low as Quantity), + Patient.birthDate + (choice.high as Quantity) + 1 year) + when choice is Timing then + Message(null, true, 'NOT_IMPLEMENTED', 'Error', 'Calculation of an interval from a Timing value is not supported') as Interval + else + null as Interval + end + +/* +@description: Normalizes a value that is a choice of timing-valued types to an equivalent interval +@comment: Normalizes a choice type of DateTime, Quanitty, Interval, or Interval types +to an equivalent interval. This selection of choice types is a superset of the majority of choice types that are used as possible +representations for timing-valued elements in QICore, allowing this function to be used across any resource. +The input can be provided as a DateTime, Quantity, Interval or Interval. +The intent of this function is to provide a clear and concise mechanism to treat single +elements that have multiple possible representations as intervals so that logic doesn't have to account +for the variability. More complex calculations (such as medication request period or dispense period +calculation) need specific guidance and consideration. That guidance may make use of this function, but +the focus of this function is on single element calculations where the semantics are unambiguous. +If the input is a DateTime, the result a DateTime Interval beginning and ending on that DateTime. +If the input is a Quantity, the quantity is expected to be a calendar-duration interpreted as an Age, +and the result is a DateTime Interval beginning on the Date the patient turned that age and ending immediately before one year later. +If the input is a DateTime Interval, the result is the input. +If the input is a Quantity Interval, the quantities are expected to be calendar-durations interpreted as an Age, and the result +is a DateTime Interval beginning on the date the patient turned the age given as the start of the quantity interval, and ending +immediately before one year later than the date the patient turned the age given as the end of the quantity interval. +If the input is a Timing, an error will be thrown indicating that Timing calculations are not implemented. +Any other input will reslt in a null DateTime Interval +*/ +define fluent function toInterval(choice Choice, Interval, Timing>): + case + when choice is DateTime then + Interval[choice as DateTime, choice as DateTime] + when choice is Interval then + choice as Interval + when choice is Quantity then + Interval[Patient.birthDate + (choice as Quantity), + Patient.birthDate + (choice as Quantity) + 1 year) + when choice is Interval then + Interval[Patient.birthDate + (choice.low as Quantity), + Patient.birthDate + (choice.high as Quantity) + 1 year) + when choice is Timing then + Message(null, true, 'NOT_IMPLEMENTED', 'Error', 'Calculation of an interval from a Timing value is not supported') as Interval + else + null as Interval + end + +/* +@description: Returns an interval representing the normalized abatement of a given Condition. +@comment: If the abatement element of the Condition is represented as a DateTime, the result +is an interval beginning and ending on that DateTime. +If the abatement is represented as a Quantity, the quantity is expected to be a calendar-duration and is interpreted as the age of the patient. The +result is an interval from the date the patient turned that age to immediately before one year later. +If the abatement is represented as a Quantity Interval, the quantities are expected to be calendar-durations and are interpreted as an age range during +which the abatement occurred. The result is an interval from the date the patient turned the starting age of the quantity interval, and ending immediately +before one year later than the date the patient turned the ending age of the quantity interval. +@deprecated: This function is deprecated. Use the fluent function `abatementInterval()` instead. +*/ +define function ToAbatementInterval(condition Condition): + if condition.abatement is DateTime then + Interval[condition.abatement as DateTime, condition.abatement as DateTime] + else if condition.abatement is Quantity then + Interval[Patient.birthDate + (condition.abatement as Quantity), + Patient.birthDate + (condition.abatement as Quantity) + 1 year) + else if condition.abatement is Interval then + Interval[Patient.birthDate + (condition.abatement.low as Quantity), + Patient.birthDate + (condition.abatement.high as Quantity) + 1 year) + else if condition.abatement is Interval then + Interval[condition.abatement.low, condition.abatement.high) + else null as Interval + +/* +@description: Returns an interval representing the normalized abatement of a given Condition. +@comment: If the abatement element of the Condition is represented as a DateTime, the result +is an interval beginning and ending on that DateTime. +If the abatement is represented as a Quantity, the quantity is expected to be a calendar-duration and is interpreted as the age of the patient. The +result is an interval from the date the patient turned that age to immediately before one year later. +If the abatement is represented as a Quantity Interval, the quantities are expected to be calendar-durations and are interpreted as an age range during +which the abatement occurred. The result is an interval from the date the patient turned the starting age of the quantity interval, and ending immediately +before one year later than the date the patient turned the ending age of the quantity interval. +*/ +define fluent function abatementInterval(condition Condition): + if condition.abatement is DateTime then + Interval[condition.abatement as DateTime, condition.abatement as DateTime] + else if condition.abatement is Quantity then + Interval[Patient.birthDate + (condition.abatement as Quantity), + Patient.birthDate + (condition.abatement as Quantity) + 1 year) + else if condition.abatement is Interval then + Interval[Patient.birthDate + (condition.abatement.low as Quantity), + Patient.birthDate + (condition.abatement.high as Quantity) + 1 year) + else if condition.abatement is Interval then + Interval[condition.abatement.low, condition.abatement.high) + else null as Interval + +/* +@description: Returns an interval representing the normalized prevalence period of a given Condition. +@comment: Uses the ToInterval and ToAbatementInterval functions to determine the widest potential interval from +onset to abatement as specified in the given Condition. If the condition is active, or has an abatement date the resulting +interval will have a closed ending boundary. Otherwise, the resulting interval will have an open ending boundary. +@deprecated: This function is deprecated. Use the `prevalenceInterval()` fluent function instead +*/ +define function ToPrevalenceInterval(condition Condition): +if condition.clinicalStatus ~ "active" + or condition.clinicalStatus ~ "recurrence" + or condition.clinicalStatus ~ "relapse" then + Interval[start of ToInterval(condition.onset), end of ToAbatementInterval(condition)] +else + (end of ToAbatementInterval(condition)) abatementDate + return if abatementDate is null then + Interval[start of ToInterval(condition.onset), abatementDate) + else + Interval[start of ToInterval(condition.onset), abatementDate] + +/* +@description: Returns an interval representing the normalized prevalence period of a given Condition. +@comment: Uses the ToInterval and ToAbatementInterval functions to determine the widest potential interval from +onset to abatement as specified in the given Condition. If the condition is active, or has an abatement date the resulting +interval will have a closed ending boundary. Otherwise, the resulting interval will have an open ending boundary. +*/ +define fluent function prevalenceInterval(condition Condition): +if condition.clinicalStatus ~ "active" + or condition.clinicalStatus ~ "recurrence" + or condition.clinicalStatus ~ "relapse" then + Interval[start of condition.onset.toInterval(), end of condition.abatementInterval()] +else + (end of ToAbatementInterval(condition)) abatementDate + return if abatementDate is null then + Interval[start of ToInterval(condition.onset), abatementDate) + else + Interval[start of ToInterval(condition.onset), abatementDate] + +/* +@description: Returns the tail of the given uri (i.e. everything after the last slash in the URI). +@comment: This function can be used to determine the logical id of a given resource. It can be used in +a single-server environment to trace references. However, this function does not attempt to resolve +or distinguish the base of the given url, and so cannot be used safely in multi-server environments. +@deprecated: This function is deprecated. Use the fluent function `getId()` instead +*/ +define function GetId(uri String ): + Last(Split(uri, '/')) + +/* +@description: Returns the tail of the given uri (i.e. everything after the last slash in the URI). +@comment: This function can be used to determine the logical id of a given resource. It can be used in +a single-server environment to trace references. However, this function does not attempt to resolve +or distinguish the base of the given url, and so cannot be used safely in multi-server environments. +*/ +define fluent function getId(uri String): + Last(Split(uri, '/')) + +/* +@description: Given an interval, return true if the interval has a starting boundary specified +(i.e. the start of the interval is not null and not the minimum DateTime value) +@deprecated: This function is deprecated. Uee the fluent function `hasStart()` instead +*/ +define function "HasStart"(period Interval ): + not ( start of period is null + or start of period = minimum DateTime + ) + +/* +@description: Given an interval, return true if the interval has a starting boundary specified +(i.e. the start of the interval is not null and not the minimum DateTime value) +*/ +define fluent function hasStart(period Interval ): + not ( start of period is null + or start of period = minimum DateTime + ) + +/* +@description: Given an interval, returns true if the interval has an ending boundary specified +(i.e. the end of the interval is not null and not the maximum DateTime value) +@deprecated: This function is deprecated. Use the fluent function `hasEnd()` instead +*/ +define function "HasEnd"(period Interval ): + not ( + end of period is null + or end of period = maximum DateTime + ) + +/* +@description: Given an interval, returns true if the interval has an ending boundary specified +(i.e. the end of the interval is not null and not the maximum DateTime value) +*/ +define fluent function hasEnd(period Interval ): + not ( + end of period is null + or end of period = maximum DateTime + ) + +/* +@description: Given an interval, returns the ending point if the interval has an ending boundary specified, +otherwise, returns the starting point +@deprecated: This function is deprecated. Use the fluent function `latest()` instead +*/ +define function "Latest"(choice Choice, Interval> ): + (choice.toInterval()) period + return + if (HasEnd(period)) then end of period + else start of period + +/* +@description: Given an interval, returns the ending point if the interval has an ending boundary specified, +otherwise, returns the starting point +*/ +define fluent function latest(choice Choice, Interval> ): + (choice.toInterval()) period + return + if (period."hasEnd"()) then end of period + else start of period + +/* +@description: Given an interval, return the starting point if the interval has a starting boundary specified, +otherwise, return the ending point +@deprecated: This function is deprecated. Use the fluent function `earliest()` instead +*/ +define function "Earliest"(choice Choice, Interval> ): + (choice.toInterval()) period + return + if (HasStart(period)) then start of period + else end of period + +/* +@description: Given an interval, return the starting point if the interval has a starting boundary specified, +otherwise, return the ending point +*/ +define fluent function earliest(choice Choice, Interval> ): + (choice.toInterval()) period + return + if (period."hasStart"()) then start of period + else end of period + +/* +@description: Creates a list of integers from 1 to how many days are in the interval. Note, this wont create an index for +the final day if it is less than 24 hours. This also includes the first 24 hour period. +@deprecated: This function is deprecated. Use the fluent function `toDayNumbers()` instead +*/ +define function "Interval To Day Numbers"(Period Interval): + ( expand { Interval[1, duration in days between start of Period and end of Period]} ) DayNumber + return end of DayNumber + +/* +@description: Creates a list of integers from 1 to how many days are in the interval. Note, this wont create an index for +the final day if it is less than 24 hours. This also includes the first 24 hour period. +*/ +define fluent function toDayNumbers(Period Interval): + ( expand { Interval[1, duration in days between start of Period and end of Period]} ) DayNumber + return end of DayNumber + +/* +@description: Creates a list of 24 hour long intervals in an interval paired with the index (1 indexed) to which 24 hour interval it is. +Note that the result will include intervals that are closed at the beginning and open at the end +@deprecated: This function is deprecated. Use the fluent function `daysInPeriod()` instead +*/ +define function "Days In Period"(Period Interval): + ( "Interval To Day Numbers"(Period)) DayIndex + let startPeriod: start of Period + (24 hours * (DayIndex - 1)), + endPeriod: if (hours between startPeriod and end of Period < 24) then startPeriod + else start of Period + (24 hours * DayIndex) + return Tuple { + dayIndex: DayIndex, + dayPeriod: Interval[startPeriod, endPeriod) + } + +/* +@description: Creates a list of 24 hour long intervals in an interval paired with the index (1 indexed) to which 24 hour interval it is. +Note that the result will include intervals that are closed at the beginning and open at the end +*/ +define fluent function daysInPeriod(Period Interval): + ( "Interval To Day Numbers"(Period)) DayIndex + let startPeriod: start of Period + (24 hours * (DayIndex - 1)), + endPeriod: if (hours between startPeriod and end of Period < 24) then startPeriod + else start of Period + (24 hours * DayIndex) + return Tuple { + dayIndex: DayIndex, + dayPeriod: Interval[startPeriod, endPeriod) + } \ No newline at end of file diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/TestDeviceOrder.cql b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/TestDeviceOrder.cql new file mode 100644 index 000000000..f88b31f5b --- /dev/null +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/DeviceOrder/TestDeviceOrder.cql @@ -0,0 +1,23 @@ +library TestDeviceOrder + +using QICore version '4.1.1' + +include FHIRHelpers version '4.0.1' +include QICoreCommon version '1.5.000' + +valueset "Frailty Device": 'http://example.org/fhir/valueset/frailty-device' + +parameter "Measurement Period" Interval + +context Patient + +define fluent function isDeviceOrder(DeviceRequest List): + DeviceRequest D + where D.status in { 'active', 'completed' } + and D.intent = 'order' + +define "Has Criteria Indicating Frailty": + exists ( (([DeviceRequest: "Frailty Device"]).isDeviceOrder()) FrailtyDeviceOrder + where FrailtyDeviceOrder.doNotPerform() is not true + and FrailtyDeviceOrder.authoredOn.toInterval() during day of "Measurement Period" + ) \ No newline at end of file diff --git a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/EXMLogic/Library-EXMLogic-data-requirements.json b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/EXMLogic/Library-EXMLogic-data-requirements.json index 8bcc39952..861e328f9 100644 --- a/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/EXMLogic/Library-EXMLogic-data-requirements.json +++ b/Src/java/elm-fhir/src/test/resources/org/cqframework/cql/elm/requirements/fhir/EXMLogic/Library-EXMLogic-data-requirements.json @@ -140,32 +140,13 @@ "type": "Coding" } ], "dataRequirement": [ { - "type": "Patient", - "profile": [ "http://hl7.org/fhir/StructureDefinition/Patient" ] - }, { "type": "Patient", "profile": [ "http://hl7.org/fhir/StructureDefinition/Patient" ], - "mustSupport": [ "url", "extension", "value" ], - "codeFilter": [ { - "path": "url", - "code": [ { - "code": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity" - } ] - } ] - }, { - "type": "Patient", - "profile": [ "http://hl7.org/fhir/StructureDefinition/Patient" ], - "mustSupport": [ "url", "extension", "value" ], - "codeFilter": [ { - "path": "url", - "code": [ { - "code": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race" - } ] - } ] + "mustSupport": [ "ethnicity", "url", "value", "race" ] }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/StructureDefinition/Encounter" ], - "mustSupport": [ "type", "status", "period" ], + "mustSupport": [ "type", "status", "period", "use", "rank", "rank.value", "condition", "condition.reference" ], "codeFilter": [ { "path": "type", "valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.292" @@ -178,7 +159,7 @@ }, { "type": "Encounter", "profile": [ "http://hl7.org/fhir/StructureDefinition/Encounter" ], - "mustSupport": [ "period" ], + "mustSupport": [ "period", "use", "rank", "rank.value", "condition", "condition.reference" ], "dateFilter": [ { "path": "period", "valuePeriod": {