CQL Execution Framework
The CQL Execution Framework provides a JavaScript library for executing CQL artifacts expressed as JSON ELM.
For more information, see the CQL Execution Framework Overview.
Data Model and Terminology Service Implementations
This library (cql-execution
) focuses on supporting CQL's logical constructs; it does not provide
robust support for any particular data model or terminology service. To fully understand the benefit
of the CQL Execution Framework, it must be used with more robust data model providers (called
PatientSource
implementations) and terminology providers (called CodeService
implementations).
Implementors interested in executing electronic Clinical Quality Measures (eCQMs) using the QDM data
model should consider using the cqm-execution
project (which is based on cql-execution
).
Implementors interested in executing FHIR-based CQL logic should consider using the
cql-exec-fhir PatientSource
with cql-execution
.
Implementors interested in using the National Library of Medicine's Value Set Authority Center
(VSAC) as a terminology service for looking up value sets should consider using the
cql-exec-vsac CodeService
with cql-execution
.
The cql-exec-examples project provides examples
of how cql-execution
, cql-exec-fhir
, and cql-exec-vsac
can be used together.
Current Limitations
This library supports operations defined in CQL 1.4 and 1.5, but is not yet a complete implementation.
Implementors should be aware of the following limitations and gaps in cql-execution
:
- Direct support for specific data models is not provided by this library (see above for details).
-
PatientSource
,CodeService
, andResults
APIs are still evolving and subject to change. - Since this library uses the JavaScript
Number
class for both CQLInteger
and CQLDecimal
, it may display the following limitations related to numbers and math:- Reduced precision compared to that which is specified by the CQL specification
- Issues typically associated with floating point arithmetic
- Decimals without a decimal portion (e.g.,
2.0
) may be treated as CQLInteger
s
- The following STU (non-normative) features introduced in CQL 1.5 are not yet supported:
-
Long
datatype - Aggregate queries
- Fluent functions
- Retrieve search paths
- Retrieve includes
-
- In addition the following features defined prior to CQL 1.5 are also not yet supported:
- Related context retrieves
- Unfiltered context retrieves
- Unfiltered context references to other libraries
- External functions
-
Message
operator
The above is a partial list covering the most significant limitations. For more details, see the CQL_Execution_Features.xlsx spreadsheet.
Project Configuration
To use this project, you should perform the following steps:
To Execute Your CQL
Please note that while the CQL Execution library supports many aspects of CQL, it does not support
everything in the CQL specification. You should check to see what is implemented (by referencing
the unit tests) before expecting it to work! For a working example, see examples
.
There are several steps involved to execute CQL. First, you must create a JSON representation of the ELM. For the easiest integration, we will generate a JSON file using cql-to-elm:
- Install the Java 11 SDK
- Clone the clinical_quality_language repository to a location of your choice
-
cd ${path_to_clinical_quality_language}/Src/java
(replacing${path_to_clinical_quality_language}
with the path to the local clone) ./gradlew :cql-to-elm:installDist
./cql-to-elm/build/install/cql-to-elm/bin/cql-to-elm --format=JSON --input ${path_to_cql} --output ${path_to_cql-execution}/customCQL
The above example puts the example CQL into a subfolder of the cql-execution
project to make the
relative paths to cql-execution
libraries easier, but it doesn't have to go there. If you put
it elsewhere, you'll need to modify the examples below so that the require
statements point to
the correct location of the cql
export.
In the rest of the examples, we'll assume an age.cql
file with the following contents. This
follows the example already in the "examples" folder (but of course you can use your own CQL):
library AgeAtMP version '1'
// NOTE: This example uses a custom data model that is very simplistic and suitable only for
// demonstration and testing purposes. Real-world CQL should use a more appropriate model.
using Simple version '1.0.0'
parameter MeasurementPeriod default Interval[DateTime(2013, 1, 1, 0, 0, 0, 0), DateTime(2014, 1, 1, 0, 0, 0, 0))
context Patient
define InDemographic:
AgeInYearsAt(start of MeasurementPeriod) >= 2 and AgeInYearsAt(start of MeasurementPeriod) < 18
Next, create a JavaScript file to execute the CQL above. This file will need to contain (or
require
) JSON patient representations for testing as well. Our example CQL uses a "Simple"
data model developed only for demonstration and testing purposes. In this model, each patient is
represented using a simple JSON object. For ease of use, let's put the file in the customCQL
directory:
const cql = require('../src/cql');
const measure = require('./age.json');
const lib = new cql.Library(measure);
const executor = new cql.Executor(lib);
const psource = new cql.PatientSource([ {
'id' : '1',
'recordType' : 'Patient',
'name': 'John Smith',
'gender': 'M',
'birthDate' : '1980-02-17T06:15'
}, {
'id' : '2',
'recordType' : 'Patient',
'name': 'Sally Smith',
'gender': 'F',
'birthDate' : '2007-08-02T11:47'
} ]);
const result = executor.exec(psource);
console.log(JSON.stringify(result, undefined, 2));
In the above file, we've assumed the JSON ELM JSON file for the measure is called
age.json
and is in the same directory as the file that requires is. We've
also assumed a couple of very simple patients. Let's call the file we just created
exec-age.js
.
Now we can execute the measure using Node.js:
node ${path_to_cql-execution}/customCQL/exec-age.js
If all is well, it should print the result object to standard out.
To Run the CQL Execution Unit Tests
Execute yarn test
.
To Develop Tests
Many of the tests require JSON ELM data. It is much easier to write CQL rather than JSON ELM, so test authors should create test data by adding new CQL to test/elm/*/data.cql. Some conventions are followed to make testing easier. The following is an example of some test data:
// @Test: And
define AllTrue: true and true
define SomeTrue: true and false
define AllFalse: false and false
The // @Test: And
indicates the name of the test suite it applies to ("And"). The group of
statements that follows the # And
represents the CQL Library that will be supplied as test data
to the "And" test suite.
To convert the CQL to JavaScript containing the JSON ELM representation, execute
yarn build:test-data
. This will use the java cql-to-elm project to generate the
test/elm/*/data.js file containing the following exported variable declaration
(NOTE: It's been slimmed down a bit here to make it easier to read, but nothing substantial
has been removed):
/* And
library TestSnippet version '1'
using Simple version '1.0.0'
context Patient
define AllTrue: true and true
define SomeTrue: true and false
define AllFalse: false and false
*/
module.exports['And'] = {
"library" : {
"identifier" : { "id" : "TestSnippet", "version" : "1" },
"schemaIdentifier" : { "id" : "urn:hl7-org:elm", "version" : "r1" },
"usings" : {
"def" : [
{ "localIdentifier" : "System", "uri" : "urn:hl7-org:elm-types:r1" },
{ "localIdentifier" : "Simple", "uri" : "https://github.com/cqframework/cql-execution/simple", "version" : "1.0.0" }
]
},
"statements" : {
"def" : [ {
"name" : "Patient",
"context" : "Patient",
"expression" : {
"type" : "SingletonFrom",
"operand" : {
"dataType" : "{https://github.com/cqframework/cql-execution/simple}Patient",
"type" : "Retrieve"
}
}
}, {
"name" : "AllTrue",
"context" : "Patient",
"accessLevel" : "Public",
"expression" : {
"type" : "And",
"operand" : [
{ "valueType" : "{urn:hl7-org:elm-types:r1}Boolean", "value" : "true", "type" : "Literal" },
{ "valueType" : "{urn:hl7-org:elm-types:r1}Boolean", "value" : "true", "type" : "Literal" }
]
}
}, {
"name" : "SomeTrue",
"context" : "Patient",
"accessLevel" : "Public",
"expression" : {
"type" : "And",
"operand" : [
{ "valueType" : "{urn:hl7-org:elm-types:r1}Boolean", "value" : "true", "type" : "Literal" },
{ "valueType" : "{urn:hl7-org:elm-types:r1}Boolean", "value" : "false", "type" : "Literal" }
]
}
}, {
"name" : "AllFalse",
"context" : "Patient",
"accessLevel" : "Public",
"expression" : {
"type" : "And",
"operand" : [
{ "valueType" : "{urn:hl7-org:elm-types:r1}Boolean", "value" : "false", "type" : "Literal" },
{ "valueType" : "{urn:hl7-org:elm-types:r1}Boolean", "value" : "false", "type" : "Literal" }
]
}
}
]}
}
}
Notice that since the CQL didn't declare a library name/version, a data model, or a context, default values were inserted into the CQL at generation time. Now this CQL can be used in a test defined in test/elm/*/logical-test.js. For example:
describe('And', () => {
this.beforeEach(() => {
setup(this, data);
});
it('should execute allTrue as true', () => {
this.allTrue.exec(this.ctx).should.be.true();
});
it('should execute someTrue as false', () => {
this.someTrue.exec(this.ctx).should.be.false();
});
it('should execute allFalse as false', () => {
this.allFalse.exec(this.ctx).should.be.false();
});
});
The test suite above uses Mocha and
Should.js. The setup
function sets up the test case by
creating this.lib
(representing the CqlLibrary
instance of the test data), creating @this.ctx
(representing a Context
for execution), and creating local variables for each defined concept
(in this case, this.allTrue
, this.allFalse
, and this.someTrue
). Note that the local variables
use lowercase first letters even though the CQL expression name starts with an uppercase letter.
Watching For Changes
Rather than continually having to run yarn build:test-data
and yarn:test
after every
modification to the test data text file, you can setup a process to watch for changes and
regenerate the data.js
files every time it detects changes in the source text file. Simply
execute yarn watch:test-data
.
Pull Requests
If JavaScript source code is modified, cql4browsers.js
needs to be included in the pull request,
otherwise Travis CI will fail. To generate this file, run:
yarn build:all