Structured Data Capture, published by HL7 International / FHIR Infrastructure. This guide is not an authorized publication; it is the continuous build for version 3.0.0 built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/HL7/sdc/ and changes regularly. See the Directory of published versions
Page standards status: Informative |
Contents:
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.
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.
(e.g. itemExtractionContext doesn't typically need a name when used for extraction of the element it is on.
However, if the value is leveraged in downstream itemExtractionContexts, then it will need a name. When an extension with a type of Expression has a name, the result of evaluating that expression becomes available in subsequent expressions (either
following extensions within the same element, or extensions appearing on descendant items) as a 'variable'. These variables are referenced by
prepending '%' to the 'name' of the expression. I.e. the result of an expression named 'patient' would be available for use in subsequent expressions
as '%patient'.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.
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 is 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:
http://hl7.org/fhir/StructureDefinition/[code]
. Those from SDC will have
http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-[code]
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.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:
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. It is not allowed to define variable names that are already reserved by the base specification or by other variables in the questionnaire. |
|||||
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. This expression could be based on other answers within the questionnaire. If the allowed set of answers for a question change 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 questionnaire is marked as 'complete', even if invalid. This is to minimize the 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. |
|||||
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 is 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. The overall set of values from which answers are allowed to be selected (typically independent of what happens to be in the patient record) is defined by answerExpression. 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. As an example, a question might ask "Please identify any concurrent medications that might be related to the reaction". A candidateExpression could provide a list of all of a patient's known current medications (via medicationrequest, medicationstatement, etc. On the other hand, an answerExpression might evaluate to the value set of medications appropriate for the jurisdiction in which the questionnaire is being completed, allowing the user to select medications that were not listed in the patient's record in addition to the candidates found. CandidateExpressions may use name in situations where a user will select a candidate for one question which will in turn lead to population of other questions. For example, if in the above example, a question about concomitant medications might have child questions that capture information about the dose, start date or other information. The query that returned the candidates might be referenced by name in initialExpressions for child questions that use the parent answer together with the candidates to grab the additional details about the selected medication to populate the child answers. |
|||||
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 and display that with a label of "Reasons for admissions in the last year". The user completing the questionnaire could then use that information to guide whether they choose to answer 'yes' or 'no'. The extension is complex including both an expression that defines the results to make available as well as a label so the user completing the Questionnaire understands what the data being returned means. 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 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 is 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 like nested bracketed expressions with AND/OR, use of NOT, comparison of values across questions, etc. (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. |
|||||
answerOptionToggleExpression | SDC | choice, open-choice | FHIRPath or CQL | behavior | example |
This is a complex extension that performs a similar function to enableWhen, except that rather than acting on items, it acts on individual answer options within
an item. The extension does NOT define what options exist. Instead, it toggles a subset of the possible options (specified in the
The base set of options available comes from answerOption, answerValueSet
or answerExpression. If any of the options listed in the More than one repetition of this extension is possible. If an option appears in more than one repetition, then it will be enabled if it appears in ANY of the extensions whose expressions are evaluated as true. I.e. the expressions that pertain to a given option are treated as 'or'. AnswerOptionToggleExpresssions should generally never need a 'name' declared. |
|||||
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. This expression does not actually populate an element (that's handled by initialExpression). Instead, it establishes a variable that is used to aid in population, often by providing a variable that can be used within the initialExpression that determines what the element should be populated with. 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 is generally a good idea. |
|||||
constraint | Core | Questionnaire, item | any | all | example |
An invariant that must or should be satisfied before responses to the questionnaire can be considered "complete". It contains an expression as part of the complex extension, but should never have a 'name'. Note: The relative path should follow the same syntax as OperationOutcome.expression. |
Please note that the expressions on calculated, initial and answerExpression are expected to resolve to the same type as the question type, allowing for the [implicit conversions](http://hl7.org/fhirpath/#conversion) defined in FHIRPath. The only exception is that an expression that resolves to a Resource can satisfy a question of type 'Reference', in which case the result will be treated as a Reference to the returned resource.
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. It is not allowed to define names that are already reserved by the base specification or by other variables in the questionnaire. |
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.
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={{%patient.id}}
would return all Observations with the specified LOINC code made in the last week for the specified patient (the %patient variable would likely have been set using launchContext extension.)
NOTE: Some of the FHIRPath content may need to be escaped (precent-encoded) in order to constitute a valid URL.
Aside from specifying a FHIR search string, SDC also allows the use of search-type operations (i.e. operations that do not affect state and whose arguments can be expressed entirely on the URL) to be included in the query expression.
For example:
Observation/$stats?subject=Patient/123&code=2339-0&system=http://loinc.org&duration=24&statistic=average
would return an Observation resource with the average for Glucose [Mass/volume] in Blood (2339-0) in the last 24 hours for the specified patient.
The SDC IG provides the following rules for the application/x-fhir-query
language:
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.
The FHIRPath language defines a set of contexts that get passed into expressions and also allows the definition of additional contexts and functions. SDC provides the following supplemental guidance for evaluating FHIRPath, and CQL and FHIR Queries in the context of a Questionnaire:
%resource
variable when it appears in expressions on elements in Questionnaire will be evaluated as the root of the QuestionnaireResponse.%context
variable will be set to the QuestionnaireResponse if the expression extension is defined on the Questionnaire root.
Otherwise, it will be interpreted as the QuestionnaireResponse.item(s) whose linkId matches 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.%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.)In addition, a number of extensions have been proposed to the FHIRPath language - some to the base language and some to the FHIR-specific set of extensions to the language. These extensions have been adopted as 'local' FHIRPath extensions for implementers of this implementation guide. Systems that accept FHIRPath, CQL or FHIRQuery expressions SHOULD support all of these extensions in their FHIRPath implementations:
%questionnaire
variable is defined that corresponds to the Questionnaire resource resolved to by the QuestionnaireResponse.questionnaire
element. It is in scope for all expressions defined in the Questionnaire.%qitem
variable is defined as a shortcut to get to the Questionnaire.item that corresponds to the context QuestionnaireResponse.item. It
is only valid for FHIRPath expressions defined within a Questionnaire item.The following FHIRPath extension functions are also defined:
sum()
, min()
, max()
, count()
, and avg()
. These are short-cuts for the equivalent
.aggregate()
. The sum, min and average expressions are already listed
here. The equivalents of the remainder are:
value.aggregate(iif($total.empty(), $this, iif($this > $total, $this, $total)))
value.aggregate(1 + $total, 0)
answers()
returns a list of all answers for items beneath the current node, but excluding the current node, including descendant questions. This
is typically used with an aggregation function. For example answers().sum() would provide the total of all answers beneath the current node. The function
is equivalent to descendants().where($this is QuestionnaireResponse.item.answer.value)
ordinal()
returns the ordinal value for code or Coding. If an ordinalValue extension
is defined on the element, it will be returned. If not, will be looked up on the bound value set expansion (or for Questionnaires, on the corresponding
Questionnaire.answerOption and. If not found there, will be looked up on the underlying CodeSystem. If the type is other than code or Coding or no ordinal value
is defined for the context element, the result will be an empty set. If the FHIRPath engine is unable to resolve the corresponding value set, code system or questionnaire
options, it SHOULD cause the expression to fail.The change requests proposing the addition of these concepts to the base FHIRPath specification and to the FHIR-specific supplement can be found here and here.
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.
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.
For unanswered items, expressions or %context that refer to QuestionnaireResponse items that don't actually 'exist' (because they don't have an answer) should still resolve as items with no answer rather than 'null'.
In some cases, a Questionnaire may contain expressions that result in an error. This could be the result of an invalid expression, unexpected data, or an expression handling engine that does not handle some aspect of the expression provided (e.g. FHIRPath library doesn't support certain Questionnaire-specific variables. In other cases, the system might support some expression languages, but not others. E.g. The system can handle FHIRPath but not CQL. In either case, the system needs to determine whether it can safely expose the Questionnaire and what information, if any, about the failure/limitation it should expose.
Obviously, if the issue with population, extraction or validation comes from invoking an external operation rather than handling the process inline, the result will be an OperationOutcome indicating failure and why. The Form Filler will then need to relay that information to the user in whatever it deems to be the most appropriate manner.
The other side of this issue is how a Form Filler can search for a Form Manager that will be able to populate or extract for a particular Questionnaire. At present, there is no computable way for systems to expose what expression languages they support, what 'extensions' they support within those languages, etc. However, systems SHALL document their capabilities around languages and language extensions in the textual documentation of their CapabilityStatements so that it is clear to human readers. A proposed R5 CapabilityStatement refactoring might provide such support in the future. We will evaluate using those new features in a future release.