Each of these operators returns true if the intervals X and Y are in the given relationship to each other. If either or both arguments are null, the result is null. Otherwise, the result is false.
In addition, CQL allows meets and overlaps to be invoked without the before or after suffix, indicating that either relationship should return true. In other words, X meets Y is equivalent to X meets before Y or X meets after Y, and similarly for the overlaps operator.
Note that to use these operators, the intervals must be of the same point type. For example, it is invalid to compare an interval of date/times with an interval of numbers.
5.5.3. Timing Relationships
In addition to the interval comparison operators described above, CQL allows various timing relationships to be expressed by directly accessing the start and end boundaries of the intervals involved. For example:
This expression returns true if the start of X is before the start of Y.
In addition, timing phrases allow the use of time durations to offset the relationship. For example:
X starts 3 days before start Y
This returns true if the start of X is equal to three days before the start of Y. Timing phrases can also include less than, more than, or less and or more to determine how the time duration is interpreted. For example:
X starts 3 days or less before start Y
X starts less than 3 days before start Y
X starts 3 days or more before start Y
X starts more than 3 days before start Y
The first expression returns true if the start of X is within the interval beginning three days before the start of Y and ending just before the start of Y. The second expression returns true if the start of Y is within the interval beginning just after three days before the start of Y and ending just before the start of Y. The third expression returns true if the start of X is three days or more before the start of Y. And the fourth expression returns true if the start of X is more than three days before the start of Y.
Timing phrases can also support inclusive comparisons using on or and or on syntax. For example:
X starts 3 days or less before or on start Y
X starts less than 3 days on or after end Y
The first expression returns true if the start of X is within the interval beginning three days before the start of Y and ending exactly on the start of Y. The second expression returns true if the start of X is within the interval beginning exactly on the end of Y and ending less than 3 days after the end of Y.
Note that on or and or on can be used with both before and after. This flexibility is to allow for natural phrasing.
Timing phrases also allow the use of within to establish a range for comparison:
X starts within 3 days of start Y
This expression returns true if the start of X is in the interval beginning three days before the start of Y and ending 3 days after the start of Y.
In addition, if either comparand is a date/time, rather than an interval, it can be used in any of the timing phrases without the boundary access modifiers:
dateTimeX within 3 days of dateTimeY
In other words, the timing phrases in general compare two quantities, either of which may be an date/time interval or date/time point value, and the boundary access modifiers can be added to a given timing phrase to access the boundary of an interval.
The following table describes the operators that can be used to construct timing phrases:
Operator |
Beginning Boundary (starts/ends) |
Ending Boundary (start/end) |
Duration Offset |
Or Less/
Or More
| Or Before/ Or After |
Less Than/ More Than |
Or On/ On Or |
same as |
yes |
yes |
no |
no |
yes |
no |
no |
before |
yes |
yes |
yes |
yes |
no |
yes |
yes |
after |
yes |
yes |
yes |
yes |
no |
yes |
yes |
within…of |
yes |
yes |
required |
no |
no |
no |
no |
during |
yes |
no |
no |
no |
no |
no |
no |
includes |
no |
yes |
no |
no |
no |
no |
no |
A yes in the Beginning Boundary column indicates that the operator can be preceded by starts or ends if the left comparand is an interval.
A yes in the Ending Boundary column indicates that the timing phrase can be succeeded by a start or end if the right comparand is an interval.
A yes in the duration offset column indicates that the timing phrase may include a duration offset.
A yes in the Or Less/OrMore column indicates that the timing phrase may include an or less/or more modifier.
A yes in the Or Before/Or After column indicates that the timing phrase may include an or before/or after modifier.
A yes in the Less Than/More Than column indicates that the timing phrase may include a less than/more than modifier.
And finally, a yes in the Or On/On Or column indicates that the timing phrase may include a on or/or on modifier.
In addition, to support more natural-language phrasing of timing operations, the keyword occurs may appear anywhere that starts or ends can appear in the timing phrase. For example:
X occurs within 3 days of start Y
The occurs keyword is both optional and ignored by CQL. It is only provided to enable more natural phrasing.
5.5.4. Computing Intervals
CQL provides several operators that can be used to combine existing intervals into new intervals. For example:
Interval[1, 3] union Interval[3, 6]
This expression returns the interval [1, 6]. Note that interval union is only defined if the arguments overlap or meet.
Interval intersect results in the overlapping portion of two intervals:
Interval[1, 4] intersect Interval[3, 6]
This expression results in the interval [3, 4].
Interval except computes the difference between two intervals. In other words, the result is points in the left operand that are not in the right operand. For example:
Interval[1, 4] except Interval[3, 6]
This expression results in the interval [1, 2]. Note that except is only defined for cases that result in a well-formed interval. For example, if either argument properly includes the other and does not start or end it, the result of subtracting one interval from the other would be two intervals, and the result is thus not defined and results in null.
The following diagrams depict the union, intersect, and except operators for intervals:
5.5.5. Date/Time Intervals
Because CQL supports date/time values with varying levels of precision, intervals of date/times can potentially involve imprecise date/time values. To ensure well-defined intervals and consistent semantics, date/time intervals are always considered to contain the full set of values contained by the boundaries of the interval. For example, the following interval expression contains all the instants of time, to the millisecond precision, beginning at midnight on January 1st, 2014, and ending at midnight on January 1st, 2015:
interval[DateTime(2014, 1, 1, 0, 0, 0, 0), DateTime(2015, 1, 1, 0, 0, 0, 0)]
However, if the boundaries of the interval are specified to a lower precision, the interval is interpreted as beginning at some time within the most specified precision, and ending at some time within the most specified precision. For example, the following interval expression contains all the instants of time, to the millisecond precision, beginning sometime in the year 2014, and ending sometime in the year 2015:
interval[DateTime(2014), DateTime(2015)]
When calculating the duration of the interval, this imprecision will in general result in an uncertainty, just as it does when calculating the duration between two imprecise date/time values.
In addition, the boundaries may even be specified to different levels of precision. For example, the following interval expression contains all the instants of time, to the millisecond precision, beginning sometime in the year 2014, and ending sometime on January 1st, 2015:
interval[DateTime(2014), DateTime(2015, 1, 1)]
5.6. List Operators
Clinical information is almost always stored, collected, and presented in terms of lists of information. As a result, the expression of clinical knowledge almost always involves dealing with lists of information in some way. The query construct already discussed provides a powerful mechanism for dealing with lists, but CQL also provides a comprehensive set of operations for dealing with lists in other ways. These operations can be broadly categorized into three groups:
-
General List Operations – Operations for dealing with lists in general, such as constructing lists, accessing elements, and determining the number of elements
-
Comparisons – Operations for comparing one list to another
-
Computation – Operations for constructing new lists based on existing ones
5.6.1. Operating on Lists
Although the most common source of lists in CQL is the retrieve expression, lists can also be constructed directly using the list selector discussed in List Values.
The elements of a list can be accessed using the indexer ([]) operator. For example:
This expression accesses the first element of the list X.
If a list contains a single element, the singleton from operator can be used to extract it:
singleton from \{ 1 }
singleton from \{ 1, 2, 3 }
Using singleton from on a list with multiple elements will result in a run-time error.
The index of an element e in a list X can be obtained using the IndexOf operator. For example:
IndexOf({'a', 'b', 'c' }, 'b') // returns 1
If the element is not found in the list, IndexOf returns -1.
In addition, the number of elements in a list can be determined using the Count operator. For example:
This expression returns the value 5.
Membership in lists can be determined using the in operator and its inverse, contains:
{ 1, 2, 3, 4, 5 } contains 4
4 in { 1, 2, 3, 4, 5 }
The exists operator can be used to test whether a list contains any elements:
exists ( { 1, 2, 3, 4, 5 } )
exists ( { } )
The first expression returns true, while the second expression returns false. This is most often used in queries to determine whether a query returns any results.
The First and Last operators can be used to retrieve the first and last elements of a list. For example:
First({ 1, 2, 3, 4, 5 })
Last({ 1, 2, 3, 4, 5 })
First({})
Last({})
In the above examples, the first expression returns 1, and the second expression returns 5. The last two expressions both return null since there is no first or last element of an empty list. Note that the First and Last operators refer to the position of an element in the list, not the temporal relationship between elements. In order to extract the earliest or latest elements of a list, the list would first need to be sorted appropriately.
In addition, to provide consistent and intuitive semantics when dealing with lists, whenever an operation needs to determine whether or not a given list contains an element (including list operations discussed later such as intersect, except, and distinct), CQL uses the notion of equivalent, rather than pure equality.
5.6.2. Comparing Lists
In addition to list equality, already discussed in Comparison Operators, lists can be compared using the following operators:
Operator |
Description |
X includes Y |
Returns true if every element in list Y is also in list X, using equivalence semantics |
X properly includes Y |
Returns true if every element in list Y is also in list X and list X has more elements than list Y |
X included in Y |
Returns true if every element in list X is also in list Y, using equivalence semantics |
X properly included in Y |
Returns true if every element in list X is also in list Y, and list Y has more elements than list X |
{ 1, 2, 3, 4, 5 } includes { 5, 2, 3 }
{ 5, 2, 3 } included in { 1, 2, 3, 4, 5 }
{ 1, 2, 3, 4, 5 } includes { 4, 5, 6 }
{ 4, 5, 6 } included in { 1, 2, 3, 4, 5 }
In the above examples, the first two expressions are true, but the last two expressions are false.
The properly modifier ensures that the lists are not the same list. For example:
{ 1, 2, 3 } includes { 1, 2, 3 }
{ 1, 2, 3 } included in { 1, 2, 3 }
{ 1, 2, 3 } properly includes { 1, 2, 3 }
{ 1, 2, 3 } properly included in { 1, 2, 3 }
{ 1, 2, 3, 4, 5 } properly includes { 2, 3, 4 }
{ 2, 3, 4 } properly included in { 1, 2, 3, 4, 5 }
In the above examples, the first two expressions are true, but the next two expressions are false, because although each element is in the other list, the properly requires that one list be strictly larger than the other, as in the last two expressions.
Note that during is a synonym for included in and can be used anywhere included in is allowed. The syntax allows for both keywords to enable more natural phrasing of time-based relationships depending on context.
5.6.3. Computing Lists
CQL provides several operators for computing new lists from existing ones.
To eliminate duplicates from a list, use the distinct operator:
distinct { 1, 1, 2, 2, 3, 4, 5 }
Note that the distinct operator uses the notion of equivalence (~) to detect duplicates. Because equivalence is defined for all types, this means that distinct can be used on lists with elements of any type. In particular, duplicates can be eliminated from lists of tuples, and the operation will use tuple equivalence (i.e. tuples are equal if they have the same type and the same values (or no value) for each element of the same name).
To combine all the elements from multiple lists, use the union operator:
{ 1, 2, 3 } union { 3, 4, 5 }
Note that duplicates are eliminated in the result of a union.
To compute only the common elements from multiple lists, use the intersect operator:
{ 1, 2, 3 } intersect { 3, 4, 5 }
To remove the elements in one list from another list, use the except operator:
{ 1, 2, 3 } except { 3, 4, 5 }
The following diagrams depict the union, intersect, and except operators:
As with the distinct operator, the intersect, and except operators use the equivalent operator to determine when two elements are the same.
Because lists may contain lists, CQL provides a flatten operation that can flatten lists of lists:
flatten { { 1, 2, 3 }, { 3, 4, 5 } }
Note that unlike the union operator, duplicate elements are retained in the result.
Note also that flatten only flattens one level, it is not recursive.
Although the examples in this section primarily use lists of integers, these operators work on lists with elements of any type.
5.6.4. Lists of Intervals
Most list operators in CQL operate on lists of any type, but for lists of intervals, CQL supports a collapse operator that determines the list of unique intervals from a given list of intervals. Consider the following intervals:
If we want to determine the total duration covered by these intervals, we cannot simply use the distinct operator, because each of these intervals is different. Yet two of them overlap, so they cover part of the same range. We also can’t simply perform an aggregate union of the intervals because some of them don’t overlap, so there isn’t a single interval that covers the entire range.
The solution is the collapse operator which returns the set of intervals that completely cover the ranges covered by the inputs:
Now, when we take the Sum of the durations of the intervals, we are guaranteed not to overcount any particular point in the ranges that may have been included in multiple intervals in the original set.
5.7. Aggregate Operators
Summaries and statistical calculations are a critical aspect of being able to represent clinical knowledge, especially in the quality measurement domain. Thus, CQL includes a comprehensive set of aggregate operators.
Aggregate operators are defined to work on lists of values. For example, the Count operator works on any list:
This expression returns the number of Encounter events.
The Sum operator, however, works only on lists of numbers:
This example results in the sum 15. To sum the results of a list of Observation values, for example, a query is used to extract the values to be summed:
Sum([Observation] R return R.result)
In general, nulls encountered during aggregation are ignored, and with the exception of Count, AllTrue, and AnyTrue, the result of the invocation of an aggregate on an empty list is null. Count is defined to return 0 for an empty list. AllTrue is defined to return true for an empty list, and AnyTrue is defined to return false for an empty list.
The following table lists the aggregate operators available in CQL:
Operator |
Description |
Count |
Returns the number of elements in its argument |
Sum |
Returns the numeric sum of the elements in the list |
Min |
Returns the minimum value of any element in the list |
Max |
Returns the maximum value of any element in the list |
Avg |
Returns the numeric average (mean) of all elements in the list |
Median |
Returns the statistical median of all elements in the list |
Mode |
Returns the most frequently occurring value in the list |
StdDev |
Returns the sample standard deviation (square root of the sample variance) of the elements in the list |
PopStdDev |
Returns the population standard deviation (square root of the population variance) of the elements in the list |
Variance |
Returns the sample variance (average distance of the data elements from the sample mean, corrected for bias by using N-1 as the denominator in the mean calculation, rather than N) of the elements in the list |
PopVariance |
Returns the population variance (average distance of the data elements from the population mean) of the elements in the list |
AllTrue |
Returns true if all the elements in the list are true, false otherwise |
AnyTrue |
Returns true if any of the elements in the list are true, false otherwise |
5.8. Clinical Operators
CQL supports several operators for use with the various clinical types in the language.
5.8.1. Quantity Operators
All quantities in CQL have unit and value components, which can be accessed in the same way as properties. For example:
define IsTall: height.units = 'm' and height.value > 2
However, because CQL supports operations on quantities directly, this expression could be simplified to:
define IsTall: height > 2 'm'
This formulation also has the advantage of allowing for the case that the actual value of height is expressed in inches.
CQL supports the standard comparison operators (= != < <= > >=) and the standard arithmetic operators (+ - * /) for quantities. In addition, aggregate operators that utilize these basic comparisons and computations are also supported, such as Min, Max, Sum, etc.
Note that complete support for unit conversion for all valid UCUM units would be ideal, but practical CQL implementations will likely provide support for a subset of units for commonly used clinical dimensions. At a minimum, however, a CQL implementation must respect units and throw an error if it is not capable of normalizing the quantities involved in a given expression to a common unit.
5.8.2. Terminology Operators
In addition to providing first-class valueset and codesystem constructs, CQL provides operators for retrieving and testing membership in valuesets and codesystems:
valueset "Acute Pharyngitis": '2.16.840.1.113883.3.464.1003.102.12.1011'
define InPharyngitis: SomeCodeValue in "Acute Pharyngitis"
These statements define the InPharyngitis expression as true if the Code-valued expression SomeCodeValue is in the "Acute Pharyngitis" valueset. Note that valueset membership is based strictly on the definition of equivalence (i.e. two codes are the same if they have the same values for the code, system, and version elements). CQL explicitly forbids the notion of terminological equivalence among codes being used in this context.
Note that this operator can be invoked with a code argument of type String, Code, and Concept. When invoked with a Concept, the result is true if any Code in the Concept is a member of the given valueset.
A common terminological operation involves determining whether a given concept is implied, or subsumed by another. This operation is generally referred to as subsumption and although useful, is deliberately omitted from this specification. The reason for this omission is that subsumption is generally a very complex operation, with different terminology systems providing different mechanisms for defining and interpreting such relationships. As a result, specifying how that occurs is beyond the scope of CQL at this time. This is not to say that a specific library of subsumption operators could not be provided and broadly adopted and used, only that the CQL specification does not attempt to dictate the semantics of that operation.
5.8.3. Patient Operators
To support determination of patient age consistently throughout quality logic, CQL defines several age-related operators:
Operator |
Description |
AgeInYearsAt(X) |
Determines the age of the patient in years as of the date X |
AgeInYears() |
Determines the age of the patient in years as of today. Equivalent to AgeInYearsAt(Today()) |
AgeInMonthsAt(X) |
Determines the age of the patient in months as of the date X |
AgeInMonths() |
Determines the age of the patient in months as of today. Equivalent to AgeInMonthsAt(Today()) |
AgeInDaysAt(X) |
Determines the age of the patient in days as of the date X |
AgeInDays() |
Determines the age of the patient in days as of today. Equivalent to AgeInDaysAt(Today()) |
AgeInHoursAt(X) |
Determines the age of the patient in hours as of the date/time X |
AgeInHours() |
Determines the age of the patient in hours as of now.
Equivalent to AgeInHoursAt(Now())
|
CalculateAgeInYearsAt(D, X) |
Determines the age of a person with birthdate D in years as of the date X |
CalculateAgeInYears(D) |
Determines the age of a person with birthdate D in years as of today. Equivalent to CalculateAgeInYearsAt(D, Today()) |
CalculateAgeInMonthsAt(D, X) |
Determines the age of a person with birthdate D in months as of the date X |
CalculateAgeInMonths(D) |
Determines the age of a person with birthdate D in months as of today. Equivalent to CalculateAgeInMonthsAt(D, Today()) |
CalculateAgeInDaysAt(D, X) |
Determines the age of a person with birthdate D in days as of the date X |
CalculateAgeInDays(D) |
Determines the age of a person with birthdate D in days as of today. Equivalent to CalculateAgeInDaysAt(D, Today()) |
CalculateAgeInHoursAt(D, X) |
Determines the age of a person with birthdate D in hours as of the datetime X |
CalculateAgeInHours(D) |
Determines the age of a person with birthdate D in hours as of now. Equivalent to CalculateAgeInHoursAt(D, Now()) |
These operators calculate age using calendar duration.
Note that when Age operators are invoked in a Population context, the result is a list of patient ages, not a single age for the current patient.
6. Authoring Artifact Logic
This section provides a walkthrough of the process of developing shareable artifact logic using CQL. The walkthrough is based on the development of the logic for a simplified Chlamydia Screening quality measure and its associated decision support rule.
Although the examples in this guide focus on populations of patients, CQL can also be used to express non-patient-based artifacts such as episode-of-care measures, or organizational measures such as number of staff in a facility. For examples of these types of measures, see the Examples included with this specification.
6.1. Running Example
The running example for this walkthrough is a simplification of CMS153, version 2, Chlamydia Screening for Women. The original QDM for this measure was simplified by including only references to the following QDM data elements:
This results in the following QDM:
Note that these simplifications result in a measure that is not clinically relevant, and the result of this walkthrough is in no way intended to be used in a production scenario. The walkthrough is intended only to demonstrate how CQL can be used to construct shareable clinical logic.
As an aside, one of the simplifications made to the QDM presented above is the removal of the notion of occurrencing. Readers familiar with that concept as defined in QDM should be aware that CQL by design does not include this notion. CQL queries are expressive enough that the correlation accomplished by occurrencing in QDM is not required in CQL.
The following table lists the QDM data elements involved and their mappings to the QUICK data structures:
QDM Data Element |
QUICK Equivalent |
Patient Characteristic Birthdate |
Patient.birthDate |
Patient Characteristic Sex |
Patient.gender |
Diagnosis |
Condition |
Laboratory Test, Order |
DiagnosticOrder |
Laboratory Test, Result |
DiagnosticReport |
Note that the specific mapping to the QUICK data structures is beyond the scope of this walkthrough; it is only provided here to demonstrate the link back to the original QDM.
Note also that the use of the QDM as a starting point was deliberately chosen to provide familiarity and is not a general requirement for building CQL. Artifact development could also begin directly from clinical guidelines expressed in other formats or directly from relevant clinical domain expertise. Using the QDM provides a familiar way to establish the starting requirements.
6.2. Clinical Quality Measure Logic
For clinical quality measures, the CQL library simply provides a repository for definitions of the populations involved. CQL is intended to support both CQM and CDS applications, so it does not contain quality measure specific constructs. Rather, the containing artifact definition, such as an HQMF document, would reference the appropriate criteria expression by name within the CQL document.
With that in mind, a CQL library intended to represent the logic for a CQM must expose at least the population definitions needed for the measure. In this case, we have criteria definitions for:
Looking at the Initial Patient Population, we have the demographic criteria:
For the age criteria, CQL defines an AgeInYearsAt operator that returns the age of the patient as of a given date/time. Using this operator, and assuming a measurement period of the year 2013, we can express the patient age criteria as:
AgeInYearsAt(@2013-01-01) >= 16 and AgeInYearsAt(@2013-01-01) < 24
In order to use the AgeInYearsAt operator, we must be in the Patient context:
In addition, to allow this criteria to be referenced both within the CQL library by other expressions, as well as potentially externally, we need to assign an identifier:
define InInitialPopulation:
AgeInYearsAt(@2013-01-01) >= 16 and AgeInYearsAt(@2013-01-01) < 24
Because the quality measure is defined over a measurement period, and many, if not all, of the criteria we build will have some relationship to this measurement period, it is useful to define the measurement period directly:
define MeasurementPeriod: Interval[
@2013-01-01T00:00:00.0,
@2014-01-01T00:00:00.0
)
This establishes MeasurementPeriod as the interval beginning precisely at midnight on January 1st, 2013, and ending immediately before midnight on January 1st, 2014. We can now use this in the age criteria:
define InInitialPopulation:
AgeInYearsAt(start of MeasurementPeriod) >= 16
and AgeInYearsAt(start of MeasurementPeriod) < 24
Even more useful would be to define MeasurementPeriod as a parameter that can be provided when the quality measure is evaluated. This allows us to use the same logic to evaluate the quality measure for different years. So instead of using a define statement, we have:
parameter MeasurementPeriod default Interval[
@2013-01-01T00:00:00.0,
@2014-01-01T00:00:00.0
)
The InInitialPopulation expression remains the same, but it now accesses the value of the parameter instead of the define statement.
Since we are in the Patient context and have access to the attributes of the Patient (as defined by the data model in use), the gender criteria can be expressed as follows:
Patient.gender in "Female Administrative Sex"
This criteria requires that the gender attribute of a Patient be a code that is in the valueset identified by "Female Administrative Sex". Of course, this requires the valueset definition:
valueset "Female Administrative Sex": '2.16.840.1.113883.3.560.100.2'
Putting it all together, we now have:
library CMS153_CQM version '2'
using QUICK
parameter MeasurementPeriod default Interval[
@2013-01-01T00:00:00.0,
@2014-01-01T00:00:00.0
)
valueset "Female Administrative Sex": '2.16.840.1.113883.3.560.100.2'
context Patient
define InInitialPopulation:
AgeInYearsAt(start of MeasurementPeriod) >= 16
and AgeInYearsAt(start of MeasurementPeriod) < 24
and Patient.gender in "Female Administrative Sex"
The next step is to capture the rest of the initial population criteria, beginning with this QDM statement:
"Diagnosis: Other Female Reproductive Conditions" overlaps with "Measurement Period"
This criteria has three main components:
Using the mapping to QUICK, the equivalent retrieve in CQL is:
[Condition: "Other Female Reproductive Conditions"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod
This query retrieves all Condition events for the patient with a code in the "Other Female Reproductive Conditions" valueset that overlap the measurement period. Note that in order to use the overlaps operator, we had to construct an interval from the onsetDateTime and abatementDate elements. If the model had an interval-valued “effective time” element, we could have used that directly, rather than having to construct an interval.
The result of the query is a list of conditions. However, this isn’t quite what the QDM statement is actually saying. In QDM, the statement can be read loosely as “include patients in the initial patient population that have at least one active diagnosis from the Other Female Reproductive Conditions valueset.” To express this in CQL, what we really need to ask is whether the equivalent retrieve above returns any results, which is accomplished with the exists operator:
exists ([Condition: "Other Female Reproductive Conditions"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
Incorporating the next QDM statement:
OR: "Diagnosis: Genital Herpes" overlaps with "Measurement Period"
exists ([Condition: "Other Female Reproductive Conditions"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod
)
or exists ([Condition: "Genital Herpes"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod
)
Which we can repeat for each Diagnosis, Active statement. Note here that even though we are using the same alias, C, for each query, they do not clash because they are only declared within their respective queries (or scopes).
Next, we get to the Laboratory Test statements:
-
OR: "Laboratory Test, Order: Pregnancy Test"
-
OR: "Laboratory Test, Order: Pap Test"
-
OR: "Laboratory Test, Order: Lab Tests During Pregnancy"
-
OR: "Laboratory Test, Order: Lab Tests for Sexually Transmitted Infections"
-
during "Measurement Period"
We use the same approach. The equivalent retrieve for the first criteria is:
exists ([DiagnosticOrder: "Pregnancy Test"] O
where Last(O.event E where E.status = 'completed' sort by E.date).date
during MeasurementPeriod)
This query is retrieving pregnancy tests that were completed within the measurement period. Because diagnostic orders do not have a top-level completion date, the date must be retrieved with a nested query on the events associated with the diagnostic orders. The nested query returns the set of completed events ordered by their completion date, the Last invocation returns the most recent of those events, and the .date accessor retrieves the value of the date element of that event.
And finally, translating the rest of the statements allows us to express the entire initial population as:
define InInitialPopulation:
AgeInYearsAt(start of MeasurementPeriod) >= 16
and AgeInYearsAt(start of MeasurementPeriod) < 24
and Patient.gender in "Female Administrative Sex"
and
(
exists ([Condition: "Other Female Reproductive Conditions"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
or exists ([Condition: "Genital Herpes"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
or exists ([Condition: "Genococcal Infections and Venereal Diseases"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
...
or exists ([DiagnosticOrder: "Pregnancy Test"] O
where Last(O.event E where E.status = 'completed' sort by E.date).date
during MeasurementPeriod)
...
)
6.3. Using Define Statements
Because CQL allows any number of define statements with any identifiers, we can structure the logic of the measure to communicate more meaning to readers of the logic. For example, if we look at the description of the quality measure:
Percentage of women 16-24 years of age who were identified as sexually active and who had at least one test for chlamydia during the measurement period.
it becomes clear that the intent of the Diagnosis, Active and Laboratory Test, Order QDM criteria is to attempt to determine whether or not the patient is sexually active. Of course, we’re dealing with a simplified measure and so much of the nuance of the original measure is lost; the intent here is not to determine whether this is in fact a good way in practice to determine whether or not a patient is sexually active, but rather to show how CQL can be used to help communicate aspects of the meaning of quality logic that would otherwise be lost or obscured.
With this in mind, rather than expressing the entire initial patient population as a single define, we can break it up into several more understandable and more meaningful expressions:
define InDemographic:
AgeInYearsAt(start of MeasurementPeriod) >= 16
and AgeInYearsAt(start of MeasurementPeriod) < 24
and Patient.gender in "Female Administrative Sex"
define SexuallyActive:
exists ([Condition: "Other Female Reproductive Conditions"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
or exists ([Condition: "Genital Herpes"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
or exists ([Condition: "Genococcal Infections and Venereal Diseases"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
...
or exists ([DiagnosticOrder: "Pregnancy Test"] O
where Last(O.event E where E.status = 'completed' sort by E.date).date
during MeasurementPeriod)
...
define InInitialPopulation:
InDemographic and SexuallyActive
Restructuring the logic in this way not only simplifies the expressions involved and makes them more understandable, but it clearly communicates the intent of each group of criteria.
Note that the InInitialPopulation expression is returning a boolean value indicating whether or not the patient should be included in the initial population.
The next population to define is the denominator, which in our simplified expression of the measure is the same as the initial population. Because the intent of the CQL library for a quality measure is only to define the logic involved in defining the populations, it is assumed that the larger context (such as an HQMF artifact definition) is providing the overall structure, including the meaning of the various populations involved. As such, each population definition with the CQL library should include only those aspects that are unique to that population.
For example, the actual criteria for the denominator is that the patient is in the initial patient population. But because that notion is already implied by the definition of a population measure (that the denominator is a subset of the initial population), the CQL for the denominator should simply be:
define InDenominator: true
This approach to defining the criteria is more flexible from the perspective of actually evaluating a quality measure, but it may be somewhat confusing when looking at the CQL in isolation.
Note that the approach to defining population criteria will actually be established by the CQF-Based HQMF Implementation Guide. We follow this approach here just for simplicity.
Following this approach then, we express the numerator as:
define InNumerator:
exists ([DiagnosticReport: "Chlamydia Screening"] R
where R.issued during MeasurementPeriod and R.result is not null)
Note that the R.result is not null condition is required because the original QDM statement involves a test for the presence of an attribute:
"Laboratory Test, Result: Chlamydia Screening (result)" during "Measurement Period"
The (result) syntax indicates that the item should only be included if there is some value present for the result attribute. The equivalent expression in CQL is the null test.
Finally, putting it all together, we have a complete, albeit simplified, definition of the logic involved in defining the population criteria for a measure:
library CMS153_CQM version '2'
using QUICK
valueset "Female Administrative Sex": '2.16.840.1.113883.3.560.100.2'
...
parameter MeasurementPeriod default Interval[
@2013-01-01T00:00:00.0,
@2014-01-01T00:00:00.0
)
context Patient
define InDemographic:
AgeInYearsAt(start of MeasurementPeriod) >= 16
and AgeInYearsAt(start of MeasurementPeriod) < 24
and Patient.gender in "Female Administrative Sex"
define SexuallyActive:
exists ([Condition: "Other Female Reproductive Conditions"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
or exists ([Condition: "Genital Herpes"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
or exists ([Condition: "Genococcal Infections and Venereal Diseases"] C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
...
or exists ([DiagnosticOrder: "Pregnancy Test"] O
where Last(O.event E where E.status = 'completed').date
during MeasurementPeriod)
...
define InInitialPopulation:
InDemographic and SexuallyActive
define InDenominator: true
define InNumerator:
exists ([DiagnosticReport: "Chlamydia Screening"] R
where R.issued during MeasurementPeriod and R.result is not null)
6.4. Clinical Decision Support Logic
Using the same simplified measure expression as a basis, we will now build a complementary clinical decision support rule that can provide guidance at the point-of-care. In general, when choosing what decision support artifacts will be most complementary to a given quality measure, several factors must be considered including EHR and practitioner workflows, data availability, the potential impacts of the guidance, and many others.
Though these are all important considerations and should not be ignored, they are beyond the scope of this document, and for the purposes of this walkthrough, we will assume that a point-of-care decision support intervention has been selected as the most appropriate artifact.
When building a point-of-care intervention based on a quality measure, several specific factors must be considered.
First, quality measures typically contain logic designed to identify a specific setting in which a particular aspect of health quality is to be measured. This usually involves identifying various types of encounters. By contrast, a point-of-care decision support artifact is typically written to be evaluated in a specific context, so the criteria around establishing the setting can typically be ignored. For the simplified measure we are dealing with, the encounter setting criteria were removed as part of the simplification.
Second, quality measures are designed to measure quality within a specific timeframe, whereas point-of-care measures don’t necessarily have those same restrictions. For example, the diagnoses in the current example are restricted to the measurement period. There may be some clinically relevant limit on the amount of time that should be used to search for diagnoses, but it does not necessarily align with the measurement period. For the purposes of this walkthrough, we will make the simplifying assumption that any past history of the relevant diagnoses is a potential indicator of sexual activity.
Third, quality measures are written retrospectively, that is, they are always dealing with events that occurred in the past. By contrast, decision support artifacts usually involve prospective, as well as retrospective data. As such, different types of clinical events may be involved, such as planned or proposed events.
Fourth, quality measures, especially proportion measures, typically express the numerator criteria as a positive result, whereas the complementary logic for a decision support rule would be looking for the absence of the criteria. For example, the criteria for membership in the numerator of the measure we are using is that the patient has had a Chlamydia screening within the measurement period. For the point-of-care intervention, that logic becomes a test for patients that have not had a Chlamydia screening.
And finally, although present in some quality measures, many do not include criteria to determine whether or not there is some practitioner- or patient-provided reason for not taking some course of action. This is often due to the lack of a standardized mechanism for describing this criteria and is usually handled on a measure-by-measure basis as part of actually evaluating measures. Regardless of the reason, because a point-of-care intervention has the potential to interrupt a practitioner workflow, the ability to determine whether or not a course of action being proposed has already been considered and rejected is critical.
With these factors in mind, and using the CQL for the measure we have already built, deriving a point-of-care intervention is fairly straightforward.
To begin with, we are using the same data model, QUICK, the same valueset declarations, and the same context:
library CMS153_CDS version '2'
using QUICK
codesystem "SNOMED": 'http://snomed.info/sct'
valueset "Female Administrative Sex": '2.16.840.1.113883.3.560.100.2'
...
context Patient
Note that we are not using the MeasurementPeriod parameter. There are other potential uses for parameters within the point-of-care intervention (for example, to specify a threshold for how far back to look for a Chlamydia screening), but we are ignoring those aspects for the purposes of this walkthrough.
For the InDemographic criteria, we are then simply concerned with female patients between the ages of 16 and 24, so we change the criteria to use the AgeInYears, rather than the AgeInYearsAt operator, to determine the patient’s age as of today:
define InDemographic:
AgeInYears() >= 16 and AgeInYears() < 24
and Patient.gender in "Female Administrative Sex"
Similarly for the SexuallyActive criteria, we remove the relationship to the measurement period:
define SexuallyActive:
exists ([Condition: "Other Female Reproductive Conditions"])
or exists ([Condition: "Genital Herpes"])
or exists ([Condition: "Genococcal Infections and Venereal Diseases"])
...
or exists ([DiagnosticOrder: "Pregnancy Test"])
...
For the numerator, we need to invert the logic, so that we are looking for patients that have not had a Chlamydia screening, and rather than the measurement period, we are looking for the test within the last year:
not exists ([DiagnosticReport: "Chlamydia Screening"] R
where R.issued during Interval[Today() - 1 years, Today()]
and R.result is not null)
In addition, we need a test to ensure that the patient does not have a planned Chlamydia screening:
not exists ([ProcedureRequest: "Chlamydia Screening"] R
where R.orderedOn same day or after Today())
And to ensure that there is not a reason for not performing a Chlamydia screening:
not exists ([Observation: "Reason for not performing Chlamydia Screening"])
We combine those into a NoScreening criteria:
define NoScreening:
not exists ([DiagnosticReport: "Chlamydia Screening"] R
where R.issued during Interval[Today() - 1 years, Today()]
and R.result is not null)
and not exists ([ProcedureRequest: "Chlamydia Screening"] R
where R.orderedOn same day or after Today())
and not exists ([Observation: "Reason for not performing Chlamydia Screening"])
And finally, we provide an overall condition that indicates whether or not this intervention should be triggered:
define NeedScreening: InDemographic and SexuallyActive and NoScreening
Now, this library can be referenced by a CDS knowledge artifact, and the condition can reference the NeedScreening expression, which loosely reads: the patient needs screening if they are in the appropriate demographic, have indicators of sexual activity, and do not have screening.
In addition, this library should include the proposal aspect of the intervention. In general, the overall artifact definition (such as a CDS KAS artifact) would define what actions should be performed when the condition is satisfied. Portions of that action definition may reference other expressions within a CQL library, just as the HQMF for a quality measure may reference multiple expressions within CQL to identify the various populations in the measure. In this case, the intervention may construct a proposal for a Chlamydia Screening:
define ChlamydiaScreeningRequest: ProcedureRequest {
type: Code '442487003' from "SNOMED-CT" +
display ' Screening for Chlamydia trachomatis (procedure)',
status: 'proposed'
// values for other elements of the request...
}
The containing artifact would then use this expression as the target of an action, evaluating the expression if the condition of the decision support rule is met, and returning the result as the proposal to the calling environment.
6.5. Using Libraries to Share Logic
The previous examples of building a quality measure and a decision support artifact have so far demonstrated the ability to describe the logic involved using the same underlying data model, as well as the same expression language. Now we can take that one step further and look at the use of CQL libraries to actually express the artifacts using the same logic, rather than just the same data model and language.
We start by identifying the aspects that are identical between the two:
-
SexuallyActive criteria, without the timing relationship
-
ChlamydiaScreening test, without the timing relationship
With these in mind, we can create a new library, CMS153_Common, that will contain the common elements:
library CMS153_Common version '2'
using QUICK
valueset "Female Administrative Sex": '2.16.840.1.113883.3.560.100.2'
...
context Patient
define ConditionsIndicatingSexualActivity:
[Condition: "Other Female Reproductive Conditions"]
union [Condition: "Genital Herpes"]
union ...
define LaboratoryTestsIndicatingSexualActivity:
[DiagnosticOrder: "Pregnancy Test"]
union [DiagnosticOrder: "Pap"]
union ...
define ResultsPresentForChlamydiaScreening:
[DiagnosticReport: "Chlamydia Screening"] R where R.result is not null
Using this library, we can then rewrite the CQM to reference the common elements from the library:
library CMS153_CQM version '2'
using QUICK
include CMS153_Common version '2' called Common
parameter MeasurementPeriod default Interval[
@2013-01-01T00:00:00.0,
@2014-01-01T00:00:00.0
)
context Patient
define InDemographic:
AgeInYearsAt(start of MeasurementPeriod) >= 16
and AgeInYearsAt(start of MeasurementPeriod) < 24
and Patient.gender in Common."Female Administrative Sex"
define SexuallyActive:
exists (Common.ConditionsIndicatingSexualActivity C
where Interval[C.onsetDateTime, C.abatementDate] overlaps MeasurementPeriod)
or exists (Common.LaboratoryTestsIndicatingSexualActivity R
where R.issued during MeasurementPeriod)
define InInitialPopulation:
InDemographic and SexuallyActive
define InDenominator:
true
define InNumerator:
exists (Common.ResultsPresentForChlamydiaScreening S
where S.issued during MeasurementPeriod)
And similarly for the CDS artifact:
library CMS153_CDS version '2'
using QUICK
include CMS153_Common version '2' called Common
valueset "Reason for not performing Chlamydia Screening": 'TBD'
context Patient
define InDemographic:
AgeInYears() >= 16 and AgeInYears() < 24
and Patient.gender in Common."Female Administrative Sex"
define SexuallyActive:
exists (Common.ConditionsIndicatingSexualActivity)
or exists (Common.LaboratoryTestsIndicatingSexualActivity)
define NoScreening:
not exists (Common.ResultsPresentForChlamydiaScreening S
where S.issued during Interval[Today() - 1 years, Today()])
and not exists ([ProcedureRequest: Common."Chlamydia Screening"] R
where R.orderedOn same day or after Today()
define NeedScreening: InDemographic and SexuallyActive and NoScreening
In addition to sharing between quality measures and clinical decision support artifacts, the use of common libraries will allow the same logic to be shared by multiple quality measures or decision support artifacts. For example, a set of artifacts for measurement and improvement of the treatment of diabetes could all use a common library that provides base definitions for determining when a patient is part of the population of interest.