Common CQL Artifacts for FHIR (US-Based)
2.0.0 - Informative 2
Common CQL Artifacts for FHIR (US-Based), published by HL7 International / Clinical Decision Support. This guide is not an authorized publication; it is the continuous build for version 2.0.0 built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/HL7/us-cql-ig/ and changes regularly. See the Directory of published versions
| Page standards status: Informative |
This topic provides query management guidance for implementers to support the use of CQL within FHIR artifacts such as questionnaires, typically from a DTR application. The functions and guidance provided here facilitate:
NOTE: The guidance in this section discusses several options for accessing data more broadly when more specific capabilities are not available. As much as possible, these fallback retrieval patterns should be bounded by clinical context, measurement or prior authorization period, resource type, and supported search parameters, and should avoid broad payer-driven data sweeps where targeted queries are available.
All data access within CQL is expressed abstractly, using the Retrieve expression, and surfaced from a packaged Library as dataRequirement elements. These data requirements describe what data the logic needs, independent of how any particular server exposes it. A DTR-style client that evaluates CQL must bridge that gap: translating abstract data requirements into concrete FHIR searches, executing them against a server whose capabilities vary, and shaping the results so the CQL engine can evaluate the logic correctly.
Query management is the collection of techniques and helper functions used to bridge that gap. This topic complements the Initial Data Retrieval discussion in the Architectural Guidance topic, which describes how the effective data requirements for a specific set of expressions are determined; the guidance here focuses on turning those requirements into robust, performant queries against real-world servers.
A recurring theme throughout is the separation between correctness and optimization: query management techniques such as _elements filtering, _include, and less-selective search parameters change how data is retrieved, but the CQL logic must remain correct regardless of which technique is used. When a technique cannot be supported by a given server, the data-access layer should fall back to a more broadly supported query and, where necessary, filter locally, rather than allowing the result of the logic to change.
The search parameter used to satisfy a given retrieve may vary depending on the capabilities of the server being accessed. Many US Core search parameters are optional (for example, the code parameter for Condition), so a query that is ideal against one server may not be supported by another.
This situation, and the use of a Module Configuration Library to provide alternative FHIR queries, is described in the Performant Data Access discussion in the Architectural Guidance topic. In summary, when a preferred search parameter is unavailable, a less-selective filter can be used (for example category=problem-list-item in place of code) followed by local filtering in the CQL, at the cost of transferring more data.
Related to this, terminology-based filters (the :in modifier used to filter a retrieve by value set) are also optional server capabilities. The Terminology Considerations discussion describes the approaches available when a server cannot perform value set membership filtering server-side, including choosing a less-selective filter or in-lining an expansion of the value set codes.
Because modifier elements and modifier extensions can change the overall meaning of a resource, any resource retrieved for evaluation should be considered with respect to these elements before the data is used.
This is discussed in detail in the Element Considerations section of the Overall Patterns topic:
Condition.clinicalStatus), authors must consider whether each possible value would impact the intent of the expression. The FHIRCommon library provides fluent functions such as .active() and .confirmed() for filtering based on these status elements.checkModifiers() function can be used within CQL to ensure that either no modifier extensions are present, or that only expected modifier extensions are present.implicitRules element is a modifier element that, if present, must be understood. A checkImplicitRules() function can be used to guard against unexpected implicit rules.From a query management perspective, these checks can be performed either in the CQL logic itself, or as part of the surrounding application environment (for example, by validating retrieved resources before handing them to the CQL engine). Best practice is to be explicit about where the responsibility lies so that unexpected modifiers cause a clear error rather than silently altering the result of the logic.
CQL logic frequently traverses references between resources. For example, a MedicationRequest may reference a Medication resource through medicationReference, or an Observation may reference the Specimen from which it was collected. When data requirements analysis reveals such a traversal, the data-access layer has two broad choices for retrieving the referenced resources:
MedicationRequest resources, collect the referenced Medication ids, then query for those Medication resources)._include search parameter to ask the server to return the referenced resources alongside the primary results in a single round-trip.For example, to retrieve medication requests together with their referenced medications:
[base]/MedicationRequest?subject=Patient/123&_include=MedicationRequest:medication
The _revinclude parameter handles the reverse direction — retrieving resources that reference the primary results. This is useful when the logic needs, for example, the Provenance resources that reference the retrieved data, or the Observation resources that reference a retrieved Specimen:
[base]/Specimen?subject=Patient/123&_revinclude=Observation:specimen
Guidance for managing _include and _revInclude:
_include/_revInclude when a reference traversal is present in the data requirements, as it reduces the number of round-trips to the server and avoids a second query that depends on the results of the first._include/_revInclude target. The set of supported includes is advertised in the server's CapabilityStatement (rest.resource.searchInclude and searchRevInclude). When a target is not supported, fall back to issuing separate queries..references() function in the shared libraries handles this distinction)._revinclude can pull back a large number of resources; where only a specific reference is of interest, prefer a targeted include over _revinclude=*.As with other query management techniques, the choice between separate queries and includes is an optimization: the CQL logic must produce the same result regardless of which approach is used.
Because data requirements analysis can determine precisely which elements of a resource the CQL logic accesses, a client can use the _elements search parameter to request only those elements from the server, reducing the size of the payload that must be transferred and parsed:
[base]/Observation?subject=Patient/123&category=laboratory&_elements=status,code,value,effective
This can be a meaningful optimization for resources with large or numerous elements that the logic does not use. However, several considerations apply:
_elements is a hint, not a guarantee. A server may return more elements than requested. In particular, servers are expected to always return mandatory elements and any modifier elements, regardless of the _elements value, so that the returned resources remain safe to process.implicitRules (see Modifier Elements and Extensions) so that the guard checks can be performed. Omitting these to save payload would undermine correctness.SUBSETTED. When a server honors _elements, it flags the returned resources with the SUBSETTED tag to indicate that they are not complete representations. These resources should not be persisted back as if they were complete._elements. As with the other techniques described here, _elements is purely an optimization. If a server ignores the parameter and returns full resources, the logic must still evaluate correctly.CQL-based artifacts are commonly evaluated against a range of servers that differ along several dimensions. Query management must account for each:
USCoreCommon exposes both the deprecated .sex() function and the forward-compatible .individualSex() function so that logic can be written once and evaluated against either representation.A general pattern for managing these differences is to keep version-specific behavior out of the artifact logic and instead resolve it in the shared libraries (through compatibility functions) and the surrounding configuration (through module configuration and version manifests). This keeps the authored expressions stable while allowing the same artifact to be deployed against a heterogeneous set of servers.
Many retrieves share the same routine post-retrieval filtering — for example, restricting observations to results that are final, amended, or corrected, or restricting resources to a particular status or category. Rather than repeating these filters in every expression, the shared libraries provide fluent functions that encapsulate the common cases.
For observations, these are described in the Common Elements and Functions section of the Observation Patterns topic, including:
resulted() (returns observations with a status of final, amended, or corrected), along with final(), amended(), and corrected(), and the corresponding singleton predicates isResulted(), isFinal(), isAmended(), and isCorrected()..hasCategory(Code), .isLaboratory(), .isVitalSign(), .isSurvey(), and related helpers.For example, retrieving only resulted laboratory observations is as simple as:
define "Resulted Laboratory Results":
"All Laboratory Results".resulted()
In general, the USCoreElements library follows the convention of providing an All Statuses variant of each observation expression that returns observations regardless of status, alongside a default expression that applies .resulted(). This lets authors choose between the filtered and unfiltered forms without re-writing the underlying retrieve. Where a use case needs a different common filter, the same pattern — a small fluent function that expresses the filter once — should be followed and contributed back to the shared libraries.
Many data elements that logic needs to access are represented as extensions rather than as first-class elements — for example, the US Core race, ethnicity, birth sex, and tribal affiliation extensions on Patient, or extensions defined by Da Vinci CRD/DTR. Accessing extensions directly through their canonical URLs is verbose and error-prone, so the shared libraries expose expected extensions as named fluent functions.
The USCoreCommon library defines these functions on top of the generic ext() and exts() fluent functions (which return a single extension or all extensions with a given URL, respectively). For example:
define fluent function birthSex(patient FHIR.Patient):
patient.ext('http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex').value as FHIR.code
This allows logic to access the extension by name, without repeating the canonical URL or the value type cast:
define "Patient Birth Sex":
Patient.birthSex()
USCoreCommon provides functions of this kind for the US Core patient extensions (race(), ethnicity(), birthSex(), individualSex(), tribalAffiliation(), sexParameterForClinicalUse()), as well as for other profile-specific extensions such as the PDMP prescription transmission method. Refer to the US Core Common library for the complete set.
Guidance for extension retrieval:
exts() for repeating extensions. When an extension may appear more than once (for example, the detailed component of the race extension), use exts() to return all occurrences and process them as a list, as illustrated by the race() and ethnicity() functions.Some data elements needed by an artifact are not stored directly as a single value but must be derived from other data, sometimes through non-trivial computation. To keep artifact logic focused and consistent, this guide provides reusable libraries and expressions for these complex derivations.
The total duration of a medication — accounting for dosage, timing, and dispense information — is a common but intricate calculation. The Cumulative Medication Duration library provides logic for computing cumulative medication duration from FHIR MedicationRequest, MedicationDispense, and MedicationAdministration resources, following the guidance established in the 5.6 version of the Quality Data Model.
Note that this logic assumes single-instruction dosing; split-dosing, tapering, and other more complex dosing instructions are not handled. As with all such patterns, care should be taken to ensure the data on which the logic runs conforms to this assumption.
Body Surface Area (BSA) is often needed for dosing and eligibility logic. Best practice is to prefer a recorded value when one is available, and only compute a value as a fallback. The USCoreElements library provides:
"Most Recent BSA": the most recent resulted Body surface area observation value, converted to m2.CalculateBSA(algorithm, height, weight): computes BSA from height and weight using either the Mosteller or DuBois and DuBois formula, for use when no recorded observation is available.An artifact can then combine these to prefer the recorded value and fall back to a calculation, for example:
define "BSA":
Coalesce("Most Recent BSA", "Calculated BSA - Mosteller")
This pattern — retrieve the recorded value where present, compute only as a documented fallback, and make the computation method explicit — generalizes to other derived clinical values.