Skip to content

Commit

Permalink
Merge branch 'master' into release-v3.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
JPercival authored Aug 8, 2023
2 parents 3a64fee + 8a041ba commit dc67fff
Show file tree
Hide file tree
Showing 14 changed files with 303 additions and 109 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/info.cqframework/cql-to-elm/badge.svg)](https://maven-badges.herokuapp.com/maven-central/info.cqframework/cql-to-elm) [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://chat.fhir.org/#narrow/stream/179220-cql)

# Clinical Quality Language

Clinical Quality Language ([CQL](http://www.hl7.org/implement/standards/product_brief.cfm?product_id=400)) is a Health Level 7 ([HL7](http://www.hl7.org/index.cfm)) standard for the expression of clinical knowledge that can be used within a broad range of clinical domains, including Clinical Decision Support (CDS), and Clinical Quality Measurement (CQM).

This repository contains documentation, examples, and tooling in support of the CQL specification, including a CQL verifier/translator.
This repository contains documentation, examples, and tooling in support of the [CQL specification](https://cql.hl7.org/), including a CQL compiler and ELM runtime.

* [CQL Specification](https://cql.hl7.org/) - Specification for CQL, along with an Authoring Guide and a Developer's guide
* [Getting Started](https://github.com/cqframework/CQL-Formatting-and-Usage-Wiki/wiki/Getting-Started) - A collection of resources to help new users get started
* [Cooking with CQL Examples](https://github.com/cqframework/CQL-Formatting-and-Usage-Wiki/wiki/Cooking-with-CQL-Examples) - Examples from Cooking with CQL sessions

# Background

CQL was developed as part of the Clinical Quality Framework ([CQF](https://oncprojectracking.healthit.gov/wiki/display/TechLabSC/CQF+Home)) initiative, a public-private partnership sponsored by the Centers for Medicare & Medicaid Services (CMS) and the U.S. Office of the National Coordinator for Health Information Technology (ONC) to identify, develop, and harmonize standards for clinical decision support and electronic clinical quality measurement.

The Clinical Quality Language specification is maintained by the HL7 Clinical Decision Support ([CDS](http://www.hl7.org/Special/committees/dss/index.cfm)) Work Group with co-sponsorship from the HL7 Clinical Quality Information ([CQI](http://www.hl7.org/Special/committees/cqi/index.cfm)) Work Group.

# Scope

The primary focus of the tooling in this repository is to support and enable adoption and implementation of the Clinical Quality Language specification. In particular, the CQL-to-ELM translator provides a reference implementation for syntactic and semantic validation of Clinical Quality Language. As such, the features and functionality provided by these tools are ultimately defined by the CQL specification, and that specification is the source-of-truth for those requirements. This relationship to the CQL standard heavily informs and shapes the change management policies for maintenance of these tools.

# Community

The CQL community consists of multiple stakeholders including EHR vendors, clinical knowledge content vendors, knowledge artifact authors, and clinical quality content tool vendors. We encourage participation from these and other relevant stakeholders, and the processes and policies described here are intended to enable participation and support of the CQL tooling. Because this community of stakeholders both depends on and provides feedback to these tools, their participation in these processes is vital.

# Change Management

Changes to the tooling maintained within this repository are managed using as lightweight a process as possible, while still ensuring stable, viable, production quality software. These processes are described in the [Change Management](CHANGE_MANAGEMENT.md) topic.

# Contents
Expand All @@ -29,10 +35,15 @@ Changes to the tooling maintained within this repository are managed using as li
* [Java Quickstart](Src/java-quickstart/README.md)

# License

All code in this repository is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). All documentation is licensed under the [Creative Common Attribution 4.0 International license (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/).

CQL compiler/translator, grammar, and associated tooling:
Copyright 2014 The MITRE Corporation

ELM runtime and associated components (specifically the `java/engine` and `java/engine-fhir` modules):
Copyright 2016 University of Utah

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Expand Down
2 changes: 2 additions & 0 deletions Src/java/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ It contains the following sub-projects:
* **model:** generates and builds Java classes based on the ELM Model Info schema and CQL base type system
* **elm:** generates and builds Java classes based on the ELM XML schema
* **elm-fhir:** contains data requirements processor and fhir-related utilities
* **engine:** contains the ELM runtime (aka "CQL engine")
* **engine-fhir:** contains fhir-related components for the ELM runtime
* **qdm:** contains schema and model info resources for QDM (4.2, 5.0, 5.0.1, 5.0.2, 5.3)
* **quick:** contains schema and model info resources for QUICK and FHIR, DSTU2 (1.0.2), and STU3 (1.4, 1.6, 1.8, and 3.0.1)
* **cql-to-elm:** generates Expression Logical Model (ELM) XML and JSON from CQL source
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Ignore;
import org.testng.annotations.Test;

public class NpmPackageManagerTests implements IWorkerContext.ILoggingService {
Expand All @@ -35,6 +36,7 @@ public void TestSampleIGLocal() {
}

@Test
@Ignore("This test depends on the example.fhir.uv.myig package, which is not currently published")
public void TestSampleContentIGLocal() {
Resource igResource = (Resource) FhirContext.forR4Cached().newXmlParser().parseResource(
NpmPackageManagerTests.class.getResourceAsStream("mycontentig.xml"));
Expand All @@ -55,8 +57,8 @@ public void TestSampleContentIGLocal() {
}
assertTrue(hasFHIR);
assertTrue(hasMyIG);
//assertTrue(hasCommon);
//assertTrue(hasCPG);
assertTrue(hasCommon);
assertTrue(hasCPG);
}

@Test
Expand All @@ -79,6 +81,7 @@ public void TestOpioidMMEIGLocal() {
}

@Test
@Ignore("This test depends on the example.fhir.uv.myig package, which is not currently published")
public void TestLibrarySourceProviderLocal() {
Resource igResource = (Resource) FhirContext.forR4Cached().newXmlParser().parseResource(
NpmPackageManagerTests.class.getResourceAsStream("mycontentig.xml"));
Expand All @@ -88,7 +91,7 @@ public void TestLibrarySourceProviderLocal() {
LibraryLoader reader = new LibraryLoader("4.0.1");
NpmLibrarySourceProvider sp = new NpmLibrarySourceProvider(pm.getNpmList(), reader, this);
InputStream is = sp.getLibrarySource(new VersionedIdentifier().withSystem("http://somewhere.org/fhir/uv/myig").withId("example"));
assertNotNull(is);
//assertNotNull(is);
is = sp.getLibrarySource(new VersionedIdentifier().withSystem("http://somewhere.org/fhir/uv/myig").withId("example").withVersion("0.2.0"));
assertNotNull(is);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1136,42 +1136,42 @@ public Invocation resolveInvocation(String libraryName, String operatorName, Inv

CallContext callContext = new CallContext(libraryName, operatorName, allowPromotionAndDemotion, allowFluent, mustResolve, dataTypes.toArray(new DataType[dataTypes.size()]));
OperatorResolution resolution = resolveCall(callContext);
if (resolution != null || mustResolve) {
checkOperator(callContext, resolution);

List<Expression> convertedOperands = new ArrayList<>();
Iterator<Expression> operandIterator = operands.iterator();
Iterator<DataType> signatureTypes = resolution.getOperator().getSignature().getOperandTypes().iterator();
Iterator<Conversion> conversionIterator = resolution.hasConversions() ? resolution.getConversions().iterator() : null;
while (operandIterator.hasNext()) {
Expression operand = operandIterator.next();
Conversion conversion = conversionIterator != null ? conversionIterator.next() : null;
if (conversion != null) {
operand = convertExpression(operand, conversion);
}

DataType signatureType = signatureTypes.next();
operand = pruneChoices(operand, signatureType);
if (resolution == null && !mustResolve) {
return null;
}

convertedOperands.add(operand);
checkOperator(callContext, resolution);
List<Expression> convertedOperands = new ArrayList<>();
Iterator<Expression> operandIterator = operands.iterator();
Iterator<DataType> signatureTypes = resolution.getOperator().getSignature().getOperandTypes().iterator();
Iterator<Conversion> conversionIterator = resolution.hasConversions() ? resolution.getConversions().iterator() : null;
while (operandIterator.hasNext()) {
Expression operand = operandIterator.next();
Conversion conversion = conversionIterator != null ? conversionIterator.next() : null;
if (conversion != null) {
operand = convertExpression(operand, conversion);
}

invocation.setOperands(convertedOperands);
DataType signatureType = signatureTypes.next();
operand = pruneChoices(operand, signatureType);

if (options.getSignatureLevel() == SignatureLevel.All || (options.getSignatureLevel() == SignatureLevel.Differing
&& !resolution.getOperator().getSignature().equals(callContext.getSignature()))
|| (options.getSignatureLevel() == SignatureLevel.Overloads && resolution.getOperatorHasOverloads())) {
invocation.setSignature(dataTypesToTypeSpecifiers(resolution.getOperator().getSignature().getOperandTypes()));
}
convertedOperands.add(operand);
}

invocation.setResultType(resolution.getOperator().getResultType());
if (resolution.getLibraryIdentifier() != null) {
resolution.setLibraryName(resolveIncludeAlias(resolution.getLibraryIdentifier()));
}
invocation.setResolution(resolution);
return invocation;
invocation.setOperands(convertedOperands);

if (options.getSignatureLevel() == SignatureLevel.All || (options.getSignatureLevel() == SignatureLevel.Differing
&& !resolution.getOperator().getSignature().equals(callContext.getSignature()))
|| (options.getSignatureLevel() == SignatureLevel.Overloads && resolution.getOperatorHasOverloads())) {
invocation.setSignature(dataTypesToTypeSpecifiers(resolution.getOperator().getSignature().getOperandTypes()));
}
return null;

invocation.setResultType(resolution.getOperator().getResultType());
if (resolution.getLibraryIdentifier() != null) {
resolution.setLibraryName(resolveIncludeAlias(resolution.getLibraryIdentifier()));
}
invocation.setResolution(resolution);
return invocation;
}

private Expression pruneChoices(Expression expression, DataType targetType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.cqframework.cql.cql2elm;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThanOrEqualTo;

import org.hl7.elm.r1.VersionedIdentifier;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class LibraryManagerTests {

ModelManager modelManager;
LibraryManager libraryManager;

@BeforeClass
public void setup() {
modelManager = new ModelManager();
libraryManager = new LibraryManager(modelManager);
libraryManager.getLibrarySourceLoader().registerProvider(new TestLibrarySourceProvider("LibraryManagerTests"));
}

@AfterClass
public void tearDown() {
libraryManager.getLibrarySourceLoader().clearProviders();
}


@Test
public void testLibraryStatementsAreSorted() {
// Some optimizations depend on the Library statements being sorted in lexicographic order by name
// This test validates that they are ordered
var lib = libraryManager.resolveLibrary(new VersionedIdentifier().withId("OutOfOrder")).getLibrary();

assertNotNull(lib);
assertNotNull(lib.getStatements().getDef());

var defs = lib.getStatements().getDef();
assertThat(
"The list should be larger than 3 elements to validate it actually sorted",
defs.size(),
greaterThan(3));


for (int i = 0; i < defs.size() - 1; i++) {
var left = defs.get(i);
var right = defs.get(i +1);

// Ensure that the left element is always less than or equal to the right element
// In other words, they are ordered.
assertThat(left.getName().compareTo(right.getName()), lessThanOrEqualTo(0));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
library "OutOfOrder"


define "Z":
1

define "B":
2

define "C":
3

define "A":
4

define "1":
5

define "z":
6

define "100":
7


Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,10 @@ private static boolean evaluateRelationships(Query elm, State state, CqlEngine v
state.push(new Variable().withName(relationship.getAlias()).withValue(relatedElement));
try {
Object satisfiesRelatedCondition = visitor.visitExpression(relationship.getSuchThat(), state);
if (relationship instanceof org.hl7.elm.r1.With
|| relationship instanceof org.hl7.elm.r1.Without) {
if (satisfiesRelatedCondition instanceof Boolean && (Boolean) satisfiesRelatedCondition) {
if ((relationship instanceof org.hl7.elm.r1.With
|| relationship instanceof org.hl7.elm.r1.Without) && Boolean.TRUE.equals(satisfiesRelatedCondition)) {
hasSatisfyingData = true;
break; // Once we have detected satisfying data, no need to continue testing
}
}
} finally {
state.pop();
Expand Down
Loading

0 comments on commit dc67fff

Please sign in to comment.