Structured Data Capture
2.8.0 - CI Build

Structured Data Capture, published by HL7 International - FHIR Infrastructure Work Group. This is not an authorized publication; it is the continuous build for version 2.8.0). This version is based on the current content of https://github.com/HL7/sdc/ and changes regularly. See the Directory of published versions

Using Expressions

The Form Behavior, Questionnaire Population and Data Extraction all rely on (or have features that rely on) the use of expressions. This page provides specific guidelines for use and defines some additional conventions that can be leveraged in queries, FHIRPath and CQL by implementations that comply with this implementation guide.

Use of the Expression type

The Expression data type has 5 elements:

  • description provides a human-readable explanation of what the expression is doing. This is development documentation. While not required, it's strongly encouraged unless the expression is so simple there will not be any question as to its intent and function.
  • name allows the result of the expression to be referenced within other expressions. Whether this is necessary depends on the context
  • language indicates the type of expression. SDC only uses three languages: FHIR queries as described below, FHIRPath and CQL. Also see the discussion on FHIRPath vs. CQL.
  • expression contains the expression if it is written in-line (as opposed to by reference)
  • reference points to the expression if it's not inline. For SDC purposes, reference must refer to content from a library imported using the cqf-library extension. An expression can be referenced using the Library.name element as a qualifier. The Library.name element does not have to be used as a qualifier if there is only one library specified AND if the reference is to something defined in that library. This convention applies to CQL, FHIRPath and FHIR queries with the only difference being that CQL will use the CQL expression name while FHIRPath and queries will use the id element on the Library.content element. When referencing CQL, #[expression name] should be used (rather than using #[id]) because there can only be one CQL 'content' repetition in the library but there is a need to differentiate between the multiple expressions that could be defined within a single CQL library instance. The expression name needs to be appropriately escaped.

Expression Extensions

Expressions are introduced into Questionnaires using extensions - none of the 'core' data elements of Questionnaire make use of extensions because they're considered an 'advanced' capability that are not currently supported by a large portion of the systems that make use of the Questionnaire resource. The extensions that make use of expressions are shown in the table below. Columns are interpreted as follows:

  • Extension: The code for the extension (and a link to the extension definition)
  • Source: Indicates where the extension is defined - the FHIR Core specification (core) or this implementation guide (SDC). Items marked as 'core' will have a canonical URL of http://hl7.org/fhir/StructureDefinition/[code]. Those from SDC will have http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-[code]
  • Location: This indicates where the element or extension can appear. Possible values are 'Questionnaire' (for those that appear directly on the Questionnaire element), 'item' (for those that can appear on any type of item), 'question' (for those that can only appear on question items), or 'group' (for those that can appear on group items). In some cases, additional restrictions/guidance will be provided.
  • Content: Indicates whether the expression.language is restricted to be "text/cql" (CQL), "text/fhirpath" (FHIRPath), or "application/x-fhir-query" (query) or is unrestricted (any)
  • Use Cases: Indicates which of the SDC use-cases make use of the extension: Form Behavior (behavior), Questionnaire Population (population), and/or Data Extraction (extraction). Other SDC use-cases do not generally have a need for the expression elements.

Beneath each extension row is an explanation of the purpose of the extension and guidance on its use.

Extension Source Location Content Use Cases Example
variable Core Questionnaire, item any all example

This expression sets a variable that is available for use in expressions within the same item and any descendant items. It has two main uses:

  • It allows a complex calculation to be done once and used in multiple other places. (E.g. Determining the score for one group within the questionnaire response that will then be used in calculations on subsequent groups.)
  • It allows a calculation to be done closer to the root of the questionnaire response or at the root of the questionnaire response where there is access to more of or all the answers from the questionnaire response. The calculated value might then be used as the answer to a descendant question. (Expressions cannot access answers that are not descendants of the current node.)

The content type of a variable can be pretty much anything. It can be a collection or an individual item. It can be a simple element, a complex type, a resource or even a Bundle of resources. The variable can be referenced by its name. Variable expressions SHALL specify a name.

initialExpression SDC question any all example

This expression serves the same purpose as the initial.value on Questionnaire.item, except that rather than specifying a fixed value, the value is calculable. Like initial.value, initialExpression is evaluated only once. The value is set on initial creation of the QuestionnaireResponse or upon the question first becoming enabled. NOTE: This implementation guide does not prohibit lazy initialization (e.g., delayed calculation of an answer, delayed initialization of a field, etc.), but Questionnaire authors should not assume implementors will use lazy initialization.

If used for a readOnly and/or hidden element, initialExpression essentially calculates a 'fixed value' for the element. If used for an editable element, this extension sets the initial value for the element. Since both initial.value and initialExpression generate values before the user has access to the form, it is not allowed to have both present on a Questionnaire.item. In scenarios where the user edits the response directly or edits other dependent elements that cause the re-evaluation of the response (e.g., calculatedExpression),the value MAY subsequently be overridden by the user authoring or editing the response (see considerations in Expression-based Population).

In theory, this expression can rely on the values of the answers to child questions or on variables calculated from the values of other questions, however such questions would have to have initial values (see the discussion below on dependencies). Most typically, it will draw on launchContext extension and itemPopulationContext extension to populate questionnaire elements based on information available from outside the QuestionnaireResponse.

Examples of use for Form Behavior might include defaulting one date or provider in a form to one entered earlier in the form, defaulting a selected location based on patient address, etc. For population, this is the primary mechanism of populating fields. Information passed in launchContext or queried from the health record is used to determine the initial value for the field. The response author then can review and potentially adjust the values. Finally, for extraction, this can be used to fill in 'hidden' elements that are needed for extraction but are not actually seen or edited by the user (e.g., FHIR resource ids when updating existing records).

It's possible for the value of an initialExpression to be referenced by name within other sibling expressions or by expressions on descendant items. However, this is not likely to be common. As such, initialExpressions only need to declare a name if their value will be referenced elsewhere.

candidateExpression SDC question any population example

This extension is used to support data entry. It is only invoked when the user is entering/editing the answer for the question linked to the candidateExpression. It is used when a population process cannot determine the specific value to fill in to a form (like when an answer depends on answers to other questions that are unknown at the population phase), but it can identify a list of candidates to choose from. For example, forms might ask for relevant co-morbidities, other practitioners to inform, etc. The specific answers require human decision-making, but this expression can provide a list of initial candidates based on the patient's recorded health conditions, the patient's care team members or others involved in their care, etc. Another example might be filtering a list of possible dosage instructions based on the patient's age, gender and the previously selected indication but where there are still possibly multiple recommended options.

While this expression could be based on other answers within the questionnaire, most typically it will be based on information found within the health record. The expression must evaluate to a collection of elements with the same type as the question answer. For choice and Reference questions, choiceColumn can be used to manage display.

NOTE: the set of candidate expressions is intended to be a starter set, not exhaustive. The user should be free to enter or look-up additional values. Systems should allow multiple selections in situations where the question is marked as 'repeating' and multiple answers are therefore valid, but should only allow one answer for non-repeating questions.

CandidateExpressions should rarely, if ever, declare a name element.

answerExpression SDC question any population example

Just like answerOption and answerValueSet, this is the third mechanism for capturing the allowed set of answers. (This extension is mutually exclusive with the other two mechanisms.) This expression must evaluate to a collection of elements with the same type as the item.type or, if the type was Reference, to resources allowed as the referenced type. It can be bound to a choiceConstraint extension to indicate whether the answer is restricted to the allowed choices, could be another answer of a matching type, or could be a plain string.

This expression could be based on other answers within the questionnaire. If the allowed set of answers for a question changes after an answer has been specified (whether via population or user entry), the prior answer SHOULD be retained in the user interface until such time as the questionaire is marked as 'complete', even if invalid. This is to minimize impact on users if they accidentally toggle elements within the form that govern the answerExpression. However, in some cases, this will not be possible depending on the data entry controls. Systems MAY wipe the answer on the grounds that a change to the list of possible answers might result in different answers.

If an answer becomes invalid based on evaluation of answerExpression, calculated values falling out of range, etc., the form SHALL flag the answer as invalid and prevent the QuestionnaireResponse from being marked as 'complete'.

Note that if the answerExpression relies on content outside the QuestionnaireResponse (e.g. launch context or query results), the ability to consistently check the validity of the QuestionnaireResponse - particularly for downstream systems - may be difficult. One potential solution is to populate internal (and potentially hidden questions) with information passed in by context or found through query such that validation of answers can be based entirely on information stored within the QuestionnaireResponse, though privacy and/or storage considerations may make this impractical in some cases.

contextExpression SDC question any population and extraction example

This is similar to candidateExpression extension, in that it is also only used when the user is editing the answer for the question associated with the candidateExpression. However, instead of giving candidate answers that can simply be "selected", this option instead presents potentially relevant information. For example, a question might be "Has the patient been admitted for this or a related condition in the past year?". To help guide the answers, the contextExpression could retrieve a list of admission reasons for that patient for encounters in the past year. The user completing the questionnaire could then use that information to guide whether they choose to answer 'yes' or 'no'. ContextExpression is not intended for read-only questions. The user must answer a question - but the user may want to look at context information to help guide the answer.

Also, like candidateExpressions, contextExpressions should rarely, if ever, declare a name element.

calculatedExpression SDC question any behavior example

This expression allows for dynamic calculation of answers to questions as other questions are answered. It behaves similarly to initialExpression extension, but instead of only setting its value when the QuestionnaireResponse is originally created or when a question is enabled, the value updates continuously as the answers to dependent questions change. This expression will be most commonly used for displaying scores, but can be used for any calculated element - patient age (based on current date and birth date), BMI (based on recent weight and height), estimated cost (based on selected items and quantities), etc.

Managing the updating of calculatedExpressions can be tricky as they can have many dependencies. Implementing an event-based listener approach may prove more efficient than simply recalculating all expressions every time any value in the questionnaire changes anywhere. Also, refer to the section below on dependencies that explains how to handle order of evaluation and other concerns.

In most cases, 'calculated' answers should be marked as 'readOnly', however in some cases it may be legitimate to override a calculated element, for example if the calculation cannot be made due to non-available information, but the user still knows the calculated value. This statement applies to any context information of any sort. [For example, it might be possible to enter an age but not a date of birth. If a user has edited a calculated value, it should no longer be changed when other answers in the questionnaire response change. When loading a previously persisted QuestionnaireResponse, the determination of whether a user has edited the calculated field can be determined by whether the calculated value differs from invoking the calculation on the current values in the questionnaire. (As a side-effect, this means that calculated values based on 'context' information such as an age calculated based on 'now' will be treated as user-edited when subsequently opening the QuestionnaireResponse and will not update to reflect the new current date. Client systems MAY provide the user with the ability to reset calculated fields to the calculated value).]

Like initialExpression, it's possible for the value of a calculatedExpression to be referenced by name within other sibling expressions or by expressions on descendant items. However, this is not likely to be common. As such, calculatedExpressions only need to declare a name if their value will be referenced elsewhere.

NOTE: query information can change over time and thus influence the results of calculatedExpression.

enableWhenExpression SDC question FHIRPath or CQL behavior example

This expression does the same thing as the Questionnaire.item.enableWhen structure (and is mutually exclusive with it), but allows for more complex logic. (If enableWhen can meet the need, it should be used instead as it will be more broadly supported.) Specifically, enableWhenExpression allows more complex logic such as support for different types of logic operators (e.g. 'and', 'or', 'not', 'nand', 'nor') among conditions and a mixture of them (e.g. "Enable if Q1=5 and Q2 nand Q3 are true"). It also allows for logic to be based on calculations across questions (possibly leveraging variable) or even based on values passed in by launchContext or queried from outside the questionnaire.

Like calculatedExpression extension, enableWhenExpression needs to be evaluated each time any of the answers it depends on changes. The same event monitoring approach may be appropriate (and even shared). And the dependencies section will be relevant here as well.

The Expression.name element only needs to be populated in the unlikely circumstance the expression is going to be referenced by other expressions on the same item or in descendant items.

itemPopulationContext SDC any any population example

This expression identifies the resource(s) that correspond either to an overall questionnaire, a group within the questionnaire or, occasionally, a single question. The results of executing the specified expression are used to indicate the resources used to populate the item when performing population and also indicate the relevant resource type.

If an expression results in multiple repetitions for a single for the root Questionnaire or for an item where 'repeat' is false, it is an error and no population can occur.

Expression.name SHALL be populated when this extension is being used for expression-based population.

itemExtractionContext SDC any any extraction example

This expression identifies the resource(s) that correspond either to an overall questionnaire, a group within the questionnaire or, occasionally, a single question. The results of executing the specified expression are used to indicate the resources used to identify the data to replace when performing an extraction and also indicate the relevant resource type.

If an expression results in multiple repetitions for a single for the root Questionnaire or for an item where 'repeat' is false, it is an error and no extraction can occur. When performing an extraction and the expression is executed, if a record is found, the data in the QuestionnaireResponse will be used to update that record. If the expression returns no results, the data in the QuestionnaireResponse will be used to create a new record.

Expression.name does not necessarily need to be present for definition-based extraction, as that relies on item.definition rather than other expressions. However, including it in general is a good idea.

Other extensions

There are a couple of additional extensions that are also relevant when working with expressions. They are:

Extension Source Location Use Cases Example
library core Questionnaire all example

Library allows queries, CQL and FHIRPath expressions to be maintained independently from the Questionnaires that use them. This means that the "programmatic" content of the Questionnaire can be updated and adjusted without revising the Questionnaire, which may be appropriate in certain form management environments. It also means that the population and extraction mechanisms can be adjusted as data representations evolve or be adjusted to reflect the needs of additional data repositories. Additionally, it allows re-use of expressions across questionnaires.

This extension identifies libraries that the questionnaire relies on for some or all its expressions. (The mechanism by which expressions point to library content is discussed above.)

launchContext SDC Questionnaire all example

The launch context allows information to be passed into the execution environment based on the context in which the Questionnaire is being evaluated. For example, what patient, what encounter, what user, etc. is "in context" at the time the questionnaire response is being completed. Note that this context could potentially change if a questionnaire is edited after it was initially created, which means that if launchContext elements drive calculatedExpression elements rather than initialExpression elements, they'll be updated to reflect the new user.

Launch context information is passed to the QuestionnaireResponse evaluation process by the launching application. It will pass whatever contexts it is aware of. SDC systems SHOULD support all four of the contexts defined in this specification (e.g. Patient, Encounter, Location, User), but are free to support others. The selected contexts mirror those used by the SMART on FHIR specification.

The resource corresponding to each context will be made available at the indicated launch context name as if the value had been set in a variable.

x-fhir-query enhancements

The Expression type indicates that one of the allowed languages is application/x-fhir-query. This is defined as a FHIR search string. Normally this will not include the base URL and is intended to be invoked against the system that is completing the QuestionnaireResponse (or whatever FHIR repository the Form Filler is configured to use). However, in some cases, a full URL might be specified, for example if the author of the questionnaire wants values to always be retrieved from a specific source. The SDC provides the following rules for the application/x-fhir-query language:

  • If the expression resolves to a complex element of type Coding, Identifier, or Reference, the element content will be substituted using the standard token format.
  • If the expression resolves to a complex element of type CodeableConcept, the processing will behave as though the expression had an additional node specifying ".coding" (i.e. the substitution will execute on the collection of codings resolved to within the CodeableConcept).
  • If the expression resolves to an element of type Quantity, it will be converted to a search type of 'quantity'.
  • If the expression resolves to a collection of more than one value, the substitution will be a list of comma-separated values (i.e. behaving as 'or').

In addition to the base query syntax, SDC allows the injection of FHIRPath into the query expressions. This is to allow the queries to take advantage of context when they are invoked. Systems SHALL evaluate and substitute the results of such queries before executing them. The FHIRPaths are denoted by surrounding them with double curly-braces (i.e. {{ fhirpath goes here }}) in the same way expressions are denoted in the Liquid templating language.

For example:

Observation?code=http://loinc.org|65972-2&date=gt{{today()-7 days}}&subject={{%subject.id}}

would return all Observations with the specified LOINC code made in the last week for the specified patient (the %subject variable would likely have been set using launchContext extension.)

Dependencies between expressions

Expressions will often have dependencies on other expressions. For example, a calculatedExpression might depend on variables that in turn depend on the answers to other questions which might themselves have initialExpressions which might rely on itemPopulationContext queries that in turn have embedded FHIRPaths that reference elements defined in launchContext. If care is not taken, it's possible to construct a questionnaire where the value of an answer depends indirectly on itself, or where filling out an answer disables the question being answered or other inappropriate behavior. Questionnaire rendering systems should check for these situations and gracefully handle them by raising appropriate error messages rather than crashing or locking in an endless loop.

When evaluating expressions, the typical order of calculation will be in the order the expressions appear in the Questionnaire. Those at the root are evaluated first, then items are evaluated in order, evaluating on a depth-first basis. Children of the first item in a group are evaluated before the second item in the group.

However, in some cases, updates to later elements may trigger an update to earlier elements, which then cascade through again. Systems should allow for this and allow for iterative updating of expressions until values reach a stable state - or until an unstable looping condition is detected.

FHIRPath and Questionnaire

The FHIRPath language defines a set of contexts that get passed into expressions and allows the definition of additional contexts and functions. The SDC provides the following guidance for evaluating FHIRPath (and CQL and FHIR Queries) in the context of a Questionnaire:

  • The %context variable will be set to the QuestionnaireResponse if the expression extension is defined on the Questionnaire root. Otherwise, it will be set to the QuestionnaireResponse.item for items whose linkId match the linkId of the Questionnaire.item the expression extension was defined in (i.e. while the extensions and expressions are defined in the Questionnaire, they are evaluated in the context of the corresponding item(s) in the QuestionnaireResponse).
  • A new %questionnaire variable is defined for that corresponds to the Questionnaire resource resolved to by the QuestionnaireResponse.questionnaire element. It is in scope for all expressions defined in the Questionnaire.
  • The %qitem variable is defined as a shortcut to get to the Questionnaire.item that corresponds to the context QuestionnaireResponse.item.
  • All expressions that specify a 'name' can be accessed by other expressions appearing on the same item or any descendant item as though that name was part of context. The same is true for the 'name' element in the launchContext extension. For example, a variable with the name 'score' could be accessed in FHIRPath or CQL using %score. (Note: It is an error if a questionnaire is designed such that there is more than one element in the same scope with a colliding context name.)

FHIRPath vs. CQL

SDC supports both FHIRPath and CQL as languages for defining expressions. In practice, FHIRPath is a proper subset of CQL, so the question of whether to use one or the other comes down to whether any of the additional capabilities of CQL (such as defining internal variables, iterating loops, etc.) is needed. FHIRPath is more widely implemented than CQL, so questionnaires that only make use of FHIRPath will typically be more widely supported.

Missing information

Sometimes information needed for an expression will not be available. Perhaps the system cannot pass encounter information - or maybe the form is being filled in outside the context of an encounter at all. Perhaps a query results in an error because of a server issue or perhaps the user or system filling in the questionnaire does not have permission to access the relevant data. Systems should fail gracefully in such situations. Populated or calculated elements can be left empty. Extracted elements can be omitted. If control logic such as enableWhen is impacted, either display a helpful error message or permit entry of potentially relevant information if it cannot be determined whether the data should be allowed or not.