Using CQL With FHIR
3.0.0-ballot - STU 3 Ballot International flag

Using CQL With FHIR - Downloaded Version null See the Directory of published versions

Patterns

Page standards status: Informative

This topic provides general guidance and best-practices for authors building FHIR-based Knowledge Artifacts that make use of Clinical Quality Language (CQL). The topics provide informative guidance to facilitate authoring CQL directly with the FHIR data model, including support for primitives, choices, slices, and extensions, as well as guidance for dealing with missing information, negation, the use of terminologies in FHIR, and some discussion of profile-informed authoring.

FHIR Patterns

Primitives

As an exchange specification, FHIR has a rich syntax for expressing the values of elements defined in FHIR resources. In particular, FHIR data types for representing basic values such as integers, strings, and dates and times allow for extensions. This means that a FHIR string is not just a string value, but has elements (specifically, id, extension, and value, where the value element contains the actual string value). This means that to access the actual value of a FHIR string element in CQL, authors would need to reference the value element:

define "Patient is Female":
  Patient.gender.value = 'female'

To avoid this, a FHIRHelpers library defines implicit conversions for all the FHIR types, allowing authors to treat FHIR elements as integers, strings, etc. directly:

define "Patient is Female":
  Patient.gender = 'female'

Note that these conversions are performed automatically by the CQL-to-ELM translator when they are used by CQL, resulting in a conversion error if the FHIRHelpers library is not included using an include declaration:

include hl7.fhir.uv.cql.FHIRHelpers version '4.0.2-ballot'

NOTE: This STU 3 edition of the implementation guide introduces a new version of FHIRHelpers (4.0.2-ballot) that adds getValue() functions for all the primitive types in FHIR. This is a backwards-compatible change, but is being added to improve type inference for FHIRPath expressions that use the getValue() approach defined in the FHIRPath topic of the FHIR specification to access the value of FHIR primitives.

FHIRPath Representation of Primitive Values

As an aside, the FHIRPath specification establishes a mental model wherein the value of primitive elements is not a node in the graph. By contrast, most representations of FHIR resources in object-oriented languages, CQL included, represent FHIR "primitives" as classes with a value element. This difference in mental models has implications for some fundamental FHIRPath operations, including:

  1. Because FHIRPath models do not have an actual value element, the getValue() must be used to access the actual primitive value (i.e. Patient.gender.value is not, strictly speaking, valid FHIRPath, though some FHIRPath implementations do allow this).
  2. For the same reason, FHIR primitive values cannot be constructed with a general-purpose selector (i.e. FHIR.string { value: 'Abel' } is valid CQL, but in a pure FHIRPath environment, a FHIR string constructor would need to be provided, and some FHIRPath implementations handle this implicitly).
  3. The result of graph operations such as .children() and .descendants() should not include value elements, even if the underlying object representations use value elements directly. CQL implementations should take care to ensure they provide the correct handling for these operations.

Choices

FHIR includes the notion of choice types, or elements that can be represented as any of a number of types. For example, the Patient.deceased element can be specified as a boolean or as a dateTime. CQL also supports choice types, so these elements are represented directly as Choice Types within the ModelInfo.

When authoring CQL using FHIR, logic must take into account the possible choice types of the elements involved. For example, the Observation.effective element may be represented as a dateTime or a Period (among other types):

define "Blood Pressure Observations Within 30 Days":
  [Observation: "Blood Pressure"] O
    where O.status = 'final'
      and (
        (O.effective as dateTime).value 30 days or less before Today()
          or (O.effective as Period) starts 30 days or less before Today()
      )

Rather than requiring different representations to be considered in the logic each time they are encountered, a fluent function can be defined that accepts a choice type argument:

define fluent function toInterval(choice Choice<FHIR.dateTime, FHIR.Period>):
  case
    when choice is FHIR.dateTime then
      Interval[FHIRHelpers.ToDateTime(choice as FHIR.dateTime), FHIRHelpers.ToDateTime(choice as FHIR.dateTime)]
    when choice is FHIR.Period then
      FHIRHelpers.ToInterval(choice as FHIR.Period)
    else null as Interval<DateTime>
  end

This can then be written as:

define "Blood Pressure Observations Within 30 Days (refined)":
  [Observation: "Blood Pressure"] O
    where O.status = 'final'
      and O.effective.toInterval() starts 30 days or less before Today()

Slices

Another common pattern in FHIR is the use of slices to constrain list-valued elements into sub-lists and elements. Consider the Blood Pressure that defines "Systolic" and "Diastolic" elements:

define "Blood Pressure With Slices":
  [Observation: "Blood Pressure"] BP
    where (singleton from (BP.component C where C.code ~ "Systolic blood pressure")).value < 140 'mm[Hg]'
      and (singleton from (BP.component C where C.code ~ "Diastolic blood pressure")).value < 90 'mm[Hg]'

To reuse slices, CQL fluent functions can be defined for each slice:

define fluent function systolic(observation Observation):
  singleton from (observation.component C where C.code ~ "Systolic blood pressure")

define fluent function diastolic(observation Observation):
  singleton from (observation.component C where C.code ~ "Diastolic blood pressure")

These fluent functions can then be used to access the slices:

define "Blood Pressure With Slices (refined)":
  [Observation: "Blood Pressure"] BP
    where BP.systolic().value < 140 'mm[Hg]'
      and BP.diastolic().value < 90 'mm[Hg]'

Extensions

FHIR also supports defining extensions to allow additional information beyond what is available in the base FHIR resources to be specified. Profiles then make use of these extensions to establish how this additional information is exchanged in specific use cases. As a simple example, consider the interpreterRequired extension for Patients:

define "Patient Interpreter Required":
  Patient P
    let interpreterRequired: singleton from (
        P.extension E where E.url.value = 'http://hl7.org/fhir/StructureDefinition/patient-interpreterRequired'
    ).value as FHIR.boolean
    where interpreterRequired is true

In this example, a let clause is used to build an interpreterRequired element in the query that finds the interpreterRequired extension value. As with slicing, fluent functions can be used to provide access to extensions:

define fluent function interpreterRequired(patient Patient):
  (singleton from (
    patient.extension E where E.url = 'http://hl7.org/fhir/StructureDefinition/patient-interpreterRequired'
  )).value as FHIR.boolean

This function can then be used to easily access the interpreterRequired extension:

define "Patient Interpreter Required (refined)":
  Patient P
    where P.interpreterRequired() is true

This approach can be used for complex extensions as well (i.e. extensions that have multiple elements, rather than just a single value). For example, consider the citizenship extension which defines code and period:

define "Patient With Citizenship In Switzerland":
  Patient P
    let 
      citizenship: singleton from (
        P.extension E where E.url.value = 'http://hl7.org/fhir/StructureDefinition/patient-citizenship'
      ),
      "code": singleton from (citizenship.extension E where E.url = 'code' return E.value as CodeableConcept),
      period: singleton from (citizenship.extension E where E.url = 'period' return E.value as Period)
    where "code" ~ "Switzerland (CH)"
      and period includes Today()

Again, these can be accessed directly using a let clause as above, or by defining a fluent function:

define fluent function citizenship(patient Patient):
  (patient.extension E where E.url = 'http://hl7.org/fhir/StructureDefinition/patient-citizenship') C
    return {
      "code": singleton from (C.extension E where E.url = 'code' return E.value as CodeableConcept),
      period: singleton from (C.extension E where E.url = 'period' return E.value as Period)
    }

define "Patient With Citizenship In Switzerland (refined)":
  Patient P
    return P.citizenship().single()."code" ~ "Switzerland (CH)"
      and P.citizenship().single().period includes Today()

To make this even easier, the FHIRCommon library discssed in the next section, introduces fluent functions for dealing with extensions generally, so that these examples can be further simplified to:

define fluent function interpreterRequired(patient Patient):
  patient.ext('http://hl7.org/fhir/StructureDefinition/patient-interpreterRequired').value as FHIR.boolean

and

define fluent function citizenship(patient Patient):
  (patient.exts('http://hl7.org/fhir/StructureDefinition/patient-citizenship')) C
    return {
      "code": C.ext('code').value as CodeableConcept,
      period: C.ext('period').value as Period
    }

FHIRCommon

For common use cases, this implementation guide provides a FHIRCommon library that defines many of these types of functions and declarations that are commonly used with CQL and FHIR. By including a reference to this implementation guide, content IGs can build CQL that refers to these common functions by including the FHIRCommon library:

include hl7.fhir.uv.cql.FHIRCommon

Element Considerations

Whether logic in general should make use of the various elements of a resource (or profile of a resource) depends on measure or rule intent. However, there are some general guidelines that should be followed to ensure correct expression and evaluation of CQL.

Element Cardinality

To begin with, all elements in FHIR resources and profiles have a cardinality that determines whether and how many values may appear in that element. Cardinality is expressed as a range, typically from 0 or 1 to 1 or *. A cardinality of 0..1 means the element is optional. A cardinality of 1..1 means the element is required. A cardinality of 0..* means the element may appear any number of times, and a cardinality of 1..* means the element must appear at least once, but may appear multiple times. Although other cardinalities are possible, those described above are the most common.

NOTE: Cardinality determines whether and how many values may appear for a given element, but the fact that an element is specified as required (e.g., 1..1) does not mean that expressions using that profile must use that element.

Must Support Elements

In addition, elements in FHIR profiles may be marked must support, meaning that implementations are required to provide values for the element if they are present in the system. To ensure expression logic can be evaluated correctly, expressions should only make use of elements that are marked must support (or otherwise have a reasonable expectation of being present). The specific meaning and implications of the must support element are provided by implementation guides; authors should refer to the must support documentation to ensure that they are using resources consistent with the expectations of the implementation guides.

Modifier Elements

And finally, elements in FHIR resources and profiles may be marked as modifier elements, meaning that the value of the element may change the overall meaning of the resource. For example, the clinicalStatus element of a Condition is a modifier element because the value determines whether the Condition overall represents the presence or absence of a condition. As a result, for each modifier element, authors must carefully consider whether each possible value would impact the intent of the expression.

Modifier Extensions

In addition to modifier elements, extensions in FHIR may be modifier extensions, and any FHIR resource that has modifier extensions that are not understood cannot be processed. Applications may consider performing this check as part of the overall environment, or the CQL logic may be used to ensure that either no modifier extensions are specified, or that only expected modifier extensions are present using the checkModifiers() function.

ImplicitRules

A key modifier element that must be considered for any FHIR resource is implicitRules. If this element is specified, it must be respected and understood. Applications may consider performing this check as part of the overall environment, or the CQL logic may be used to ensure that either no implicit rules are specified, or that the implicitRules are expected:

define fluent function checkImplicitRules(resource Resource, knownImplicitRules String):
  Message(
    resource, 
    resource.imiplicitRules !~ knownImplicitRules, 
    'implicit-rules-check', 
    'Error', 
    'Implicit rules check failed for resource ' + resource.resourceType + '\' + resource.id
  )

Accessing Data

To summarize, cardinality determines whether data will be present at all, must support determines whether the element can reasonably be expected to be present, and modifier elements must always be considered to determine the impact of possible values of the element on the result of the expression.

With these element considerations in mind, there are some general patterns for accessing data within CQL using the Retrieve expression:

define "All Allergies and Intolerances":
  [USCore."AllergyIntolerance"]

This retrieve expression consists only of the name of the profiled resource being requested, and will result in all AllergyIntolerance resources that conform to the profile being retrieved.

Because the clinicalStatus and verificationStatus elements of the AllergyIntolerance resource are modifier elements, they should be considered whenever accessing and using these types of resources.

The FHIRCommon library provides fluent functions for checking these status elements:

define "Active Confirmed Allergies and Intolerances":
  "All Allergies and Intolerances".allergyActive().allergyConfirmed()

Derived ModelInfo

Note that in addition to using FHIR directly, CQL also supports models derived from implementation guides specifically. For example:

using USCore version '7.0.0'

With this approach, the profiles defined in the USCore implementation guide are represented directly as types in the model, with types derived from base definition of the profile. For example, the US Core Encounter Profile is derived from the FHIR Encounter resource. The Computable Name of the profile is used to define the identifier of the type in CQL, with the exception that if the computable name is prefixed with the name of the model, the prefix is dropped. For example:

define "Encounters":
  ["EncounterProfile"]

Conceptually, this results in any Encounter resource that conforms to the US Core Encounter Profile.

For detailed information on how Derived ModelInfo is produced for an implementation guide, see the Derived ModelInfo section.

Profile-informed Authoring

As an alternative to Derived ModelInfo, Profile-informed Authoring can be used. This approach was used specifically for QICore and USCore versions 6 and below as a way to simplify the model presented to CQL authors. Similar to derived model info, this approach creates types based on the profiles defined in the implementation guide, however, it automates the patterns described above, and "flattens" the hierarchy so that authors are presented with as simple a view of the profile as possible. For example:

define "Blood Pressure With Slices":
  ["BloodPressureProfile"] BP
    where BP.systolic.value < 140 'mm[Hg]'
      and BP.diastolic.value < 90 'mm[Hg]'

define "Patient With Birthsex":
  Patient P
    where P.birthsex = 'M'

define "Patient With Race":
  Patient P
    where P.race.ombCategory contains "American Indian or Alaska Native"
      and P.race.detailed contains "Alaska Native"

For detailed information on how ModelInfo is produced for an implementation guide, see the Profile-informed ModelInfo section.

Use of Terminologies

FHIR supports various types of terminology values, including:

These types map to the following CQL primitive types, respectively:

These types are used extensively throughout FHIR to define terminology-valued elements. In addition to the type of element, FHIR provides the ability to fix the value of these elements to specific codes, in the form of a direct-reference code (fixed constraint to a specific code in a CodeSystem), or to bind these elements to a ValueSet (i.e. establish the set of possible values for the element). These bindings can be different binding strengths

Within CQL, references to terminology code systems, value sets, codes, and concepts are directly supported, and all such usages are declared within CQL libraries, as described in the Terminology section of the CQL Author's Guide.

When referencing terminology-valued elements within CQL, the following comparison operations are supported:

As a general rule, the equivalent (~) operator should be used whenever the terminology being compared is a direct-reference code, and the in operator should be used whenever the terminology being compared is a value set. The equal (=) operator should only be used with code-valued elements that have a required binding.

Note that although the contains operator is also concerned with the concept of membership, the current version of the CQL specification does not include a terminological overload of the contains operator. A terminological contains operation is being considered for inclusion in the next version of CQL, but until the specification supports it, the contains operator should not be used with terminology-valued elements in FHIR. As a workaround, the FHIRCommon library defines an includesCode function that can be used to provide a terminological contains operation.

code

In FHIR, code-valued elements are most often used with required bindings, meaning that the only values that can appear are established by the specification. Because of this, basic string comparison can be used, for example:

  where Encounter.status = 'finished'

NOTE: The comparison here is to the code value, not the display

NOTE: Note also that there are edge-cases where the string-valued elements may contain terminology values. For more detail on this case, refer to the Using CQL IG

CodeableConcept

Most terminology-valued elements in FHIR are CodeableConcepts. If the terminology being compared is a value set (e.g. valueset "Inpatient Encounter"), use the in operator:

  where Encounter.type in "Inpatient Encounter"

Note that the in operator works whether the element is single cardinality or multi-cardinality.

If the terminology being compared is a direct-reference code (e.g. code "Blood Pressure"), use the ~ operator:

  where Observation.code ~ "Blood Pressure"

Note that this comparison only works if the element is single-cardinality. For multi-cardinality elements with direct-reference code comparison (e.g. code "Right Breast"), each CodeableConcept must be tested using the ~ operator, so an exists is used:

  where exists (Condition.bodySite S where S ~ "Right Breast")

Coding

Some terminology-valued elements in FHIR use the Coding type specifically. The same comparison patterns are used for elements of this type. For value sets (e.g. valueset "Inpatient Class"), use in:

  where Encounter.class in "Inpatient Class"

And for direct-reference codes (e.g. code "Inpatient"), use ~:

  where Encounter.class ~ "Inpatient"

Date, Time, and DateTime Values

Comparing Date and DateTime values results in implicit conversion of the Date to a DateTime with null components, and this can lead to unexpected partial comparison case. Authors should be explicit about precision when comparing Date and DateTime values, as comparing with mismatched precision can yield null results (which are often intepreted as false). In most cases, using day of precision is appropriate when comparing a Date and a DateTime.

In addition, authors should consider using day of when comparing between events within a specific period (such as within the measurement period) and date values are clinically appropriate, and should consider using minute of or second of precision when comparing between events where time values are clinically appropriate.

For more information on Date, DateTime, and Time comparison, see Comparing Dates and Times in the CQL Author's Guide.

Note that a new feature of CQL R2, default comparison precision may be useful in supporting this best practice.

Timezone and Timezone Offset Handling

In CQL generally, logic is always evaluated with an evaluation request timestamp that is used to ensure consistent and deterministic behavior of expressions that involve time. In particular, the result of the Now, Today, and TimeOfDay functions are based on the evaluation request timestamp, so that they always return the same value within a given evaluation request, and that value is relative to the request, not to the server evaluating the logic.

In addition, all operations on DateTime values are defined to take timezone offset information into account. When DateTime values are constructed without a timezone offset, the timezone offset of the evaluation request timestamp is used. Whenever two DateTime values are compared to at least hour precision, they are normalized to (i.e. converted to the same timezone offset as) the timezone offset of the evaluation request timestamp.

This behavior is critical for clinical logic for two reasons:

  1. Like any application involving datetime data that may be collected in different locations (and therefore in different timezone offsets), accurate comparison requires that datetimes be converted to a consistent timezone offset
  2. Clinical logic often cares when midnight happens, so that calculations such as "on hospital day 2" can be performed correctly. Logic that is looking for when a "day" boundary has elapsed often has to be performed based on the originating location of the data, because "midnight" in a hospital in Kansas, is not the same "midnight" as the data center in Virginia that is hosting the application.

For these reasons it is critical that the client timezone offset be communicated correctly between the client and the server, and this implementation guide provides the following facilities to support this:

  1. The CQL evaluation operations define the requestTimestamp parameter to allow the client to set the timestamp explicitly.
  2. It is strongly recommended that all communication between clients and servers making use of CQL logic use headers to communicate timezone information as recommended in the Client Timezone topic in the FHIR specification.

See also: Constructing Date and Time Values in the CQL specification

Time-Valued Quantities

For time-valued quantities, in addition to the definite duration UCUM units, CQL defines calendar duration keywords to support calendar-based durations and arithmetic. For example, UCUM defines an annum ('a') as 365.25 days, whereas the year ('year') duration in CQL is specifically a calendar year. This difference is important, especially when performing calendar arithmetic.

For example, if we take a DateTime and subtract a calendar year

@2019-01-01T05:00:00 - 1 year

This results in 2018-01-01T05:00:00

However, if we take the same DateTime and subtract a UCUM annum

@2019-01-01T05:00:00 - 1 'a'

This results in run-time error because a UCUM annum is defined as 365.25 days and this value should never be used for calendar arithmetic. If this is truly the intended calculation, authors must convert the annum to seconds:

@2019-01-01T05:00:00 - convert 1 'a' to 's'

This results in 2017-12-31T23:00:00.

Note carefully that when implicitly converting FHIR Duration values to CQL, a UCUM annum is converted to a calendar year, and a UCUM mo is converted to a calendar month. This is because when years and months appear in FHIR durations, it is almost universally the intent that they represent calendar durations, rather than definite-time durations:

Patient.birthDate + (Condition.onset as Age)

If the duration in value in FHIR truly represents a definite-time duration, conversion to seconds is required in order to perform the date/time calculation.

See the definition of the Quantity type in the CQL Author's Guide, as well as the Date/Time Arithmetic discussion for more information. This behavior is inherited from FHIRPath and described in the Use of FHIR Quantity topic in the FHIRPath topic in the base FHIR specification.

Missing Information

Because clinical information is often incomplete, CQL provides constructs and support for representing and dealing with unknown or missing information. In FHIR, when the value of an element is not present, accessing that element will result in a null:

MedicationRequest.doNotPerform

Given an instance of a MedicationRequest resource that does not have a doNotPerform element specified, the above expression will return null. In general, null results will propagate through operations. For example:

MedicationRequest.doNotPerform = false

If the MedicationRequest instance does not have a doNotPerform element, this expression will return null. When a null result is encountered in the evaluation of a criteria (such as a where clause), it will be interpreted as false. For this reason, best-practice when comparing boolean-valued elements such as doNotPerform is to use the is true | false predicate test:

MedicationRequest MR
  where MR.doNotPerform is not true

This pattern ensures that whether the instance does not have a doNotPerform element, or the doNotPerform element is false, the result of the expression is true, correctly accounting for the potential missing information.

Another common case encountered in FHIR is the use of an unknown code in terminology-valued elements:

MedicationRequest.status = 'unknown'

This is a special-case of characterizing missing information within FHIR resources. To treat this status value as a null, the following pattern can be used:

if MedicationRequest.status is null or MedicationRequest.status ~ 'unknown'

For more information about dealing with Missing Information in CQL in general, see the Missing Information topic in the CQL Author's Guide.

Activity Extent

FHIR offers several possibilities for describing what activity (i.e. request or event) is being performed (e.g. the code element of a Procedure, or the medication element of a MedicationRequest):

  1. Specify a particular code
  2. Specify a higher-level code that includes all the concepts by subsumption
  3. Specify the items with a value set (via the codeOptions extension)
  4. Specify a protocol
  5. Use a RequestOrchestration to group items
  6. Use the basedOn element rather than coding the activity

These approaches allow for the extent of an activity to be defined:

Specific Code

The first two approaches make use of terminology to define the extent of an activity, and is the most common approach. The code in a terminology may identify a single, precise concept, or it may identify a class of concepts, such as a type of procedure, or a class of medications.

For example, the following MedicationAdministration indicates a specific drug:

{
  "resourceType" : "MedicationAdministration",
  ...,
  "medicationCodeableConcept" : {
      "coding" : [{
          "system" : "http://www.nlm.nih.gov/research/umls/rxnorm",
          "code" : "1116635",
          "display" : "ticagrelor 90 MG Oral Tablet"
      }]
  },
  ...
}

As opposed to specifying only a concept code:

{
  "resourceType" : "MedicationAdministration",
  ...,
  "medicationCodeableConcept" : {
      "coding" : [{
          "system" : "http://www.nlm.nih.gov/research/umls/rxnorm",
          "code" : "11289",
          "display" : "warfarin"
      }]
  },
  ...
}

Retrieving resources with codes specified using these approaches can be accomplished with a simple Retrieve:

define "Antithrombotic Therapy Administered":
  [MedicationAdministration: "Antithrombotic Therapy"] AntithromboticTherapy
    where AntithromboticTherapy.status = 'completed'
      and AntithromboticTherapy.category ~ "Inpatient Setting"

This example retrieves MedicationAdministration resources that have a code in the Antithrombotic Therapy value set, a status of completed, and a category of Inpatient Setting.

Code Options

The third approach (specifying the items with a value set) is enabled through the use of the codeOptions extension. Rather than specifying a code, this extension is used to indicate that the activity may be any one of the codes in the value set:

{
  "resourceType" : "MedicationAdministration",
  ...,
  "medicationCodeableConcept" : {
      "extension" : [{
          "url" : "http://hl7.org/fhir/StructureDefinition/codeOptions",
          "valueCanonical" : "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1110.62"
      }],
      "text" : "Value Set: Antithrombotic Therapy for Ischemic Stroke"
  },
  ...
}

When this pattern is used in FHIR resources, the CQL needs to take this into account by looking for the codeOptions extension:

define "Antithrombotic Therapy Class Administered":
  [MedicationAdministration] Administered
    where Administered.medication.codeOptions() = "Antithrombotic Therapy".id
      and Administered.status = 'completed'
      and Administered.category ~ "Inpatient Setting"

This example retrieves MedicationAdministration resources that use the codeOptions extension to specify a candidate medication in the Antithrombotic Therapy value set, a status of completed, and a category of Inpatient Setting.

NOTE: See the FHIRCommon library for the definition of the codeOptions() fluent function.

To ensure both approaches are accounted for, these two expressions would then be used together:

define "Antithrombotics Administered":
  "Antithrombotic Therapy Administered"
    union "Antithrombotic Therapy Class Administered"

NOTE: Profile-informed authoring exposes elements that have a codeOptions extension using a Choice of CodeableConcept and ValueSet, which is then translated as a union, accounting for both cases as part of profile-informed authoring.

Structural Options

The other three approaches make use of structures such as PlanDefinition, RequestOrchestration, and the relationships between events and requests to establish the extent of an activity. See the Clinical Guidelines implementation guide for more information on using these approaches to characterize and manage the extent of activities.

Negation in FHIR

The HL7 Cross-Paradigm Specification: Representing Negatives provides guidance and best practices for the representation of pertinent negatives and other negative semantics in clinical information. The following sections describe how these best practices may be represented in FHIR resources and profiles, as well as guidance for accessing negated information in CQL.

For an example of a set of profiles following these best practices to support the representation of negation in FHIR, see the Negation profiles in QI-Core.

In summary, negation statements typically cover three different use cases:

  1. Documentation that an event did not occur
  2. Documentation that an activity should not be performed (i.e. is prohibited)
  3. Documentation that a requested activity was not performed

Given the representation of negative information in FHIR, two commonly used patterns for negation in clinical logic are:

  • Absence of evidence for a particular event
  • Documentation of an event not occurring (represented as one of the above 3 use cases), together with a reason

For the purposes of clinical reasoning, when looking for documentation that a particular event did not occur, it must be documented with a reason in order to meet the intent. If a reason is not part of the intent, then the absence of evidence pattern should be used, rather than documentation of an event not occurring.

To address the reason an action did not occur (negation rationale), clinical logic must define the event it expects to occur using appropriate terminology to identify the kind of event (using a value set or direct-reference code), and then use additional criteria to indicate that the event did not occur, as well as identifying a reason.

The following examples illustrate methods to indicate (a) presence of evidence of an action, (b) absence of evidence of an action, and (c) negation rationale for not performing an action. In each case, the "action" is an administration of medication included within a value set for "Antithrombotic Therapy".

Presence

Evidence that "Antithrombotic Therapy" (defined by a medication-specific value set) was administered:

define "Antithrombotic Administered":
  [MedicationAdministration: "Antithrombotic Therapy"] AntithromboticTherapy
    where AntithromboticTherapy.status = 'completed'
      and AntithromboticTherapy.category ~ "Inpatient Setting"

Absence

No evidence that "Antithrombotic Therapy" medication was administered:

define "No Antithrombotic Therapy":
  not exists (
    [MedicationAdministration: "Antithrombotic Therapy"] AntithromboticTherapy
      where AntithromboticTherapy.status = 'completed'
        and AntithromboticTherapy.category ~ "Inpatient Setting"
  )

Negation Rationale

Evidence that "Antithrombotic Therapy" medication administration did not occur for an acceptable medical reason as defined by a value set referenced by the clinical logic (i.e., negation rationale):

define "Antithrombotic Not Administered":
  [MedicationAdministration: "Antithrombotic Therapy"] NotAdministered
    where NotAdministered.status = 'not-done'
      and NotAdministered.statusReason in "Medical Reason"

In this example for negation rationale, the logic looks for a member of the value set "Medical Reason" as the rationale for not administering any of the anticoagulant and antiplatelet medications specified in the "Antithrombotic Therapy" value set.

As discussed in the Activity Extent section, to represent "Antithrombotic Therapy Not Administered", implementing systems can use the codeOptions extension on the MedicationRequest.medication element to reference the "Antithrombotic Therapy" value set, and set the status to not-done. This MedicationRequest then represents the statement that a provider did not administer any of the medications in the "Antithrombotic Therapy" value set.

When this pattern is used in FHIR resources, the CQL needs to take this into account by looking for the codeOptions extension:

define "Antithrombotic Class Not Administered":
  [MedicationAdministration] NotAdministered
    where NotAdministered.medication.codeOptions() = "Antithrombotic Therapy".id
      and NotAdministered.status = 'not-done'
      and NotAdministered.statusReason in "Medical Reason"

To ensure both cases are accounted for, these two expressions would then be used together:

define "Antithrombotics Not Administered":
  "Antithrombotic Not Administered"
    union "Antithrombotic Class Not Administered"

This approach ensures that the logic will retrieve negated activities whether they are recorded as singular activities (i.e. with a code from the value set) or as indications that none of the activities were performed (i.e. with a reference to a value set).

NOTE: Profile-informed authoring exposes elements that have a codeOptions extension using a Choice of CodeableConcept and ValueSet, which is then translated as a union, accounting for both cases as part of profile-informed authoring.

Prohibited Activities

Evidence that "Antithrombotic Therapy" medication was prohibited for an acceptable medical reason makes use of the appropriate Request resource:

define "Antithrombotic Therapy Prohibited":
  [MedicationRequest: "Antithrombotic Therapy"] Prohibited
    where Prohibited.status = 'active'
      and Prohibited.doNotPerform is true
      and Prohibited.statusReason in "Medical Reason"

This example retrieves MedicationRequest resources with a code in the Antithrombotic Therapy value set that have a status of active, a doNotPerform of true, and a statusReason in the Medical Reason value set.

As with negation of events, the extent of the activity can be accounted for by searching for instances that make use of the codeOptions extension.

Rejected Requests

Evidence that a proposal to administer "Antithrombotic Therapy" was rejected for an acceptable medical reason makes use of the Task resource:

define "Antithrombotic Therapy Requested":
  [MedicationRequest: "Antithrombotic Therapy"] MR
    where MR.status = 'active'
      and MR.doNotPerform is not true

define "Antithrombotic Therapy Rejected":
  "Antithrombotic Therapy Requested" MR
    with [Task: Fulfill] T
      such that T.focus.references(MR)
        and T.status = 'rejected'
        and T.statusReason in "Medical Reason"

This example retrieves "Antithrombotic Therapy Requested" resources that have a fulfillment Task focused on the request, a status of rejected, and a statusReason in the Medical Reason value set.

As with negation of events, the extent of the activity can be accounted for by searching for request instances that make use of the codeOptions extension.

Note that because a rejected task may appear on any request, whenever logic is searching for a positive request, it must ensure that there is not an associated rejection:

define "Antithrombotic Therapy Requested":
  [MedicationRequest: "Antithrombotic Therapy"] MR
    without [Task: Fulfill] T such that T.focus.references(MR) and T.status = 'rejected'
    where MR.status = 'active'
      and MR.doNotPerform is not true

NOTE: We seek comment on whether this pattern should be adopted generally. Specifically, ARE implementers using Tasks to reject orders in this way, and is the overhead of having to check that every request is not rejected worth it? Is there an alternative approach?