Structured Data Capture, published by HL7 International / FHIR Infrastructure. This guide is not an authorized publication; it is the continuous build for version 4.0.0-ballot 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: Trial-use |
Contents:
Questionnaires are excellent tools for data capture. They allow tight control over what data is gathered and ensure information is gathered consistently across multiple users. However, data gathered using different questionnaires - or even different versions of the same questionnaire - is often not comparable. It is also not very searchable or easily integrated with discrete data sources. Because of this, the general recommendation in FHIR is to use questionnaires for raw data capture but then to convert the resulting QuestionnaireResponse instances into other FHIR resources - Observations, MedicationStatements, FamilyMemberHistories, etc. This allows the data gathered to then be easily combined with other data into FHIR documents and messages and exposed over FHIR REST interfaces.
Such conversion can be done with custom code written on a Questionnaire by Questionnaire basis. However, it makes the process much easier if it's possible to write generic software that can convert any arbitrary QuestionnaireResponse into appropriate FHIR resources leveraging metadata embedded in the Questionnaire. This portion of the SDC guide defines mechanisms for doing so.
Note to balloters: The definition based $extract functionality has been refined significantly along with a new mechanism for extraction Template based extraction that uses contained resource(s) as template(s) for extracting data.
Feedback is encouraged for this new functionality.
entity
reference of type 'source' that points back to the original
QuestionnaireResponse. If Observations are generated, they can also have an explicit derivedFrom
link pointing back to the
QuestionnaireResponse.id
of the resource must be retained through the round-trip process to allow for the update.
This can be supported by storing the id as a hidden question not shown to the
user. This question item would have a type of 'string' and, if using the definition-based extraction approach, a definition that corresponded to the
'id' element of the relevant resource type (e.g. http://hl7.org/fhir/StructureDefinition/AllergyIntolerance#AllergyIntolerance.id
).
Note that the inclusion of the id as a hidden question is only relevant for the definition-based and StructureMap-based approaches. It is not needed
for the Observation-based approach.Questionnaire.derivedFrom
relationship to the same canonical URL
the QuestionnaireResponse refers to. The derived Questionnaire would contain the same content as the base Questionnaire, but would have additional
extensions inserted to support data extraction.Provenance.entity.what
where the Provenance.entity.role
would be 'source'. Software information would be captured in
Provenance.agent
. If the produced records are Observations, the resulting Observation(s) can also directly point to the
QuestionnaireResponse using Observation.derivedFrom
.Like Questionnaire population, extracting data from a QuestionnaireResponse is a complex process involving querying existing FHIR data and using more advanced technologies such as FHIRPath and StructureMap. It's therefore a function that systems may also wish to offload to a separate system. The QuestionnaireResponse extract has been created for this purpose. It takes in a completed QuestionnaireResponse and returns either an individual FHIR resource or a Bundle of resources, depending on the type of Questionnaire. The operation does not post the created resources to a server. It's up to the client system to determine what action(s) to take with the created content.
NOTE: It's the responsibility of the client system to ensure that any generated resources are valid against necessary profiles, etc. before using content produced by this operation.
This specification defines four different mechanisms to embed information in Questionnaires to support subsequent resource extraction:
Systems are free to experiment with other extraction mechanisms but cannot expect support for those from other SDC-conformant systems.
Each mechanism has its own profile that includes the additional resource elements or extensions relevant for supporting a particular mechanism: SDC Questionnaire Extract - Observation,
SDC Questionnaire Extract - Definition, SDC Questionnaire Extract - Template,
and SDC Questionnaire Extract - Structure Map profiles.
Each profile identifies
specific 'must support' elements and extensions that systems that claim to support a specific SDC extraction mechanism SHALL be capable of extracting data, as befits the CapabilityStatement(s)
they claim conformance to. Each system should choose which approach(es) it wishes to use and support based on the elements specified in that profile.
Some of these mechanisms make use of FHIR-based queries, FHIRPath and/or CQL as well as extensions that include expressions in one of these languages. Implementers should read the Using Expressions page for background and guidance on these technologies and extensions.
This is the simplest of the extraction mechanisms. It leverages the same data elements as are used for the
Observation-based population mechanism. It takes advantage of the fact that most questions in the healthcare space typically
correspond to the value
element of an Observation. It also takes advantage of the Questionnaire.item.code
element that
identifies what a concept each question or group corresponds to.
The SDC Questionnaire Extract - Observation profile
has been created to support this mechanism. An example for this profile can be found here.
To use this method:
item.code
element on each question to be extracted. Typically, this will be a LOINC code, but in some
jurisdictions/environments, SNOMED CT or other codes may be relevant.item.code
present - this might represent the code of the panel or the Observation.code of an Observation with no
value but with multiple Observation.component
elements. Child question items can then assert the item.code
of the "member-of"
Observations or the Observation.component.code
values.item.code
elements rather than the item as a whole, then only the specified codes should be propagated rather than all. For example, an item might list codes for "Body weight", "Body weight (clothed)" and "Body weight (unclothed)". In that case, only the "Body weight" code would be appropriate to include in the extracted content, even though all three might be appropriate for population. If any item.codes are tagged with 'true', then only the tagged Codings will propagate. I.e. any sibling item.codes with no extension are considered to have an extension of 'false'. If no extension appears on any of the item.codes but an extension appears on the item, an ancestor item or the Questionnaire as a whole, then all codes are considered to be roughly equivalent translations and to be appropriate to list as sibling Codings within Observation.code.
If an item has the extension flag set to 'true', some descendant items may have the extension with a value of 'false'. If this occurs, then the item tagged with 'false' and its descendants will be excluded from the extraction process.
For example:
<item>
<extension url="http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationExtract">
<valueBoolean value="true" />
</extension>
<linkId value="code-pop-demo"/>
<code>
<system value="http://loinc.org"/>
<code value="29463-7"/>
<display value="Body weight"/>
</code>
<code>
<system value="http://loinc.org"/>
<code value="3141-9"/>
<display value="Body weight Measured"/>
</code>
<code>
<system value="http://loinc.org"/>
<code value="8341-0"/>
<display value="Dry body weight Measured"/>
</code>
<text value="What is your current weight?"/>
<type value="quantity"/>
<answerOption>
<valueCoding>
<system value="http://unitsofmeasure.org"/>
<code value="kg"/>
</valueCoding>
</answerOption>
<answerOption>
<valueCoding>
<system value="http://unitsofmeasure.org"/>
<code value="[lb_av]"/>
</valueCoding>
</answerOption>
</item>
When performing the extraction process, the system will create a batch that will contain creates or updates of Observation instances. It will go through the QuestionnaireResponse and identify all answers marked for extraction (if the corresponding Questionnaire item or the root Questionnaire has a questionnaire-observationExtract extension). For each of those it will then determine whether to create a new observation, update an existing observation or do nothing. Guidelines for making this decision are as follows:
Take no action if: |
|
---|---|
Update if: |
|
Create a new Observation if: | the conditions in the preceding two rows are not met |
If updating, the original Observation SHALL be adjusted to have the new value or component.value and the status changed to "amended", then PUT to the source system. If creating, data elements SHOULD be populated as follows:
Observation.basedOn
and Observation.partOf
- copy from QuestionnaireResponse elements of the same nameObservation.status
- set to 'final'Observation.category
- if this can be inferred from any of the Questionnaire.item.code
values or from known context of the
Questionnaire itself, then fill it in. It can also be asserted using the
observation-extract-category extension.Observation.code
- add all the Questionnaire.item.code
values as Observation.code.coding
instancesObservation.subject
- set to QuestionnaireResponse.subject
Observation.encounter
- set to QuestionnaireResponse.encounter
(if an Encounter)Observation.effectiveDateTime
- set to QuestionnaireResponse.authored
.Observation.issued
- set to QuestionnaireResponse.authored
Observation.performer
- set to QuestionnaireResponse.author
Observation.value[x]
- set to QuestionnaireResponse.item.answer.value[x]
Observation.derivedFrom
- set to a reference to the QuestionnaireResponseObservation.interpretation
and Observation.referenceRange
- if these can be inferred from the
QuestionnaireResponse.item.code
(and for interpretation the answer value too), they can be populated, otherwise omit
If the Questionnaire.item that is linked to an Observation contains child items that are also linked to Observations, then things get more complex as a
determination will need to be made on whether to link the parent to child as Observation.component
or as Observation.hasMember
.
In the ideal situation, the system will recognize the Observation.item.code and know which approach is correct for that type of Observation. If not, then
the system could query for other records of the child type and see if they appear as components anywhere. If unsure, systems should use "hasMember".
Considerations and rules when using this approach:
Observation.focus
is relevant or for capturing Observation.dataAbsentReason.
Patient.birthDate
), systems MAY take advantage of this knowledge to update the value of resources other than Observations, however such use
is discouraged - using one of the other extraction techniques is likely better and safer.
This approach to extraction is more generic than the observation-based extraction. It supports extracting data into any type of FHIR resource rather than being limited to only Observation.
It also supports extracting additional Observation properties not defined for the observation-based extract, e.g. explicit effective time ranges, interpretations, comments, etc.
The technique is called "definition-based" because it uses StructureDefinitions to identify resource types/profiles, and their properties which are then associated with items in the Questionnaire
using the Questionnaire.item.definition
element.
The SDC Questionnaire Extract - Definition profile has been created to support this mechanism.
The basic approach to the definition-based extraction mechanism is to walk the QuestionnaireResponse and for each item that has the
sdc-questionnaire-definitionExtract extension, create a new stub resource of that type/profile,
then scan the item and all its children and populate values in the resource based on items that have a matching definition
property set, or
have sdc-questionnaire-definitionExtractValue extensions on them
that have the same definition canonical URL value as in the definitionExtract
extension. Once all the children have been processed,
the system will populate the bundle.entry.resource
property with the extracted resource, and the bundle.entry.request.method
and
other properties with values based on the definitionExtract
extension. If the definition was to a profile, any slicing information or
fixed/pattern values should also be extracted from the profile and applied to the resource where appropriate.
For example an identifier might have a fixed system property defined in a profile slice.
The item's definition
property is composed of:
http://hl7.org/fhir/StructureDefinition/Patient
or http://example.org/fhir/StructureDefinition/LocalizedPatientProfile
#
symbol.snapshot.element.id
of the element in the StructureDefinition that corresponds to the Questionnaire item.Patient.name.given
Putting this all together, a definition property looks like this: http://hl7.org/fhir/StructureDefinition/Patient#Patient.name.given
.
Note: The complete element id does not actually need to appear in a profile snapshot, it is also possible to 'extend' an id to walk further into the data types of the specified element.
For example, the example abovehttp://hl7.org/fhir/StructureDefinition/Patient#Patient.name.given
is valid, even though the referenced profile's snapshot only containsPatient.name
, notPatient.name.given
.
However, for more complex references (e.g. referring to a particular extension or a particular repetition), it will be necessary to define a distinct profile where slicing ensures that a single element id corresponds to the desired element. I.e. you would need to usehttp://example.org/fhir/StructureDefinition/LocalizedPatientProfile#Patient.name.given:foo
where 'foo' was a slice that referred to the first middle name in order to tie to a definition that specific.
To use this method:
entry.fulUrl
value (as a new uuid value for each resource instance).
The sdc-questionnaire-extractAllocateId extension can be used to allocate
a named new uuid for this purpose. It should be defined on the item (or root) such that its children cover the resource that is to be created, and also
the resources that are to reference it.
This variable name can then be used in the definitionExtract extensions's fullUrl
fhirpath expression, and also in any reference properties
- typically using the definitionExtractValue extension (further described below).
%newPatientUuid
or %newEncounterUuid
definition
(resource/profile canonical URL),
and optionally fullUrl, ifNoneMatch, ifModifiedSince, ifMatch and ifNoneExist (fhirpath expression expressions used to populate the transaction bundle entry containing the extracted resource).
bundle.entry.resource
property with the extracted resource, then populate the other
bundle.entry
properties:
fullUrl
- the result of the fhirpath expression for the fullUrl
property, if none is provided, a new uuid will be allocated for the resource.
(this will often be an expression that uses the named variable from the allocateId
extension e.g. %newPatientUuid
)
request.method
- if the resource has no id
property set the value to POST
(requesting a create),
otherwise set the value to PUT
(requesting an update).resource.id
could be set using a hidden item (pre-populated during data-entry), or using the definitionExtractValue extension.
request.url
- if the resource is being updated the url will be the resource type and the id
property of the resource. e.g. Patient/123
,
otherwise it will be the resource type. e.g. Patient
.
request.ifNoneMatch
- the result of the fhirpath expression for the ifNoneMatch
property
request.ifModifiedSince
- the result of the fhirpath expression for the ifModifiedSince
property (evaluates to a date time value - or nothing)
request.ifMatch
- the result of the fhirpath expression for the ifMatch
property
request.ifNoneExist
- the result of the fhirpath expression for the ifNoneExist
property
definition
property in the extension
SHALL be unique in the scope that they are introduced in the questionnaire.
i.e. You can't define two http://hl7.org/fhir/StructureDefinition/Condition
extractions on the same item (or any of its children), but you can have
multiple on sibling items do this.definition
property being the "scoper"
to identify the resource/property to extract the item answer into.
Note: In previous versions of the SDC specification the questionnaire-itemExtractionContext extension was used to indicate the resource type, however this has been deprecated and we encourage moving to the newer sdc-questionnaire-definitionExtract extension.
Questionnaire.item.definition
to associate an answer to the item with a property in the extracted resource.http://hl7.org/fhir/StructureDefinition/Patient#Patient.name
on a group item to collect the child items to the same Patient.name element.
During extraction, the system will create a new patient name element for each repetition of the group item, and then populate each name with the child items.http://hl7.org/fhir/StructureDefinition/Patient#Patient.name.given
and http://hl7.org/fhir/StructureDefinition/Patient#Patient.name.family
.
Patient.contact.name.text
and the system will create the necessary Patient.contact and Patient.contact.name elements
to then set the text values in. In this case if the item was repeating, the last node in the iteration is the one that will be repeated (name in this case).definitionExtractValue
is used on an item that also has its item.definition property set, that would be assumed to be extracted resource context.
Hence you could set the system and type values in an
identifier when associated with that item.
answer.join(', ')
.Example Parent | Parent Type | Example Child Definition | Possible Child Type | Notes |
---|---|---|---|---|
Patient | definitionExtract | Patient.name | group item | Create a new HumanName object and add it to the name collection in patient
|
Patient.name | group item | Patient.name.text | value item | Set the text property in the name object |
Patient | definitionExtract | Patient.name.text | value item/extractValue | Create a new HumanName object, add it to the name collection in patient, and set the text property
|
Patient | definitionExtract | Patient.identifier.value | value item/extractValue | Create a new Identifier object, add it to the identifier collection in patient, and set the value property
|
Patient | definitionExtract | Patient.extension | group item | Create a new Extension object and add it to the extension collection in patient
|
Patient.extension | group item | Patient.extension.value | value item | Set the value property in the extension object |
Patient.extension | group item | Patient.extension.url | value item/extractValue | Set the URL property in the extension object |
Patient.extension.value | value item | Patient.extension.url | value item/extractValue | Set the URL property for the same extension instance that has the value in it (walks up the tree to the common ancestor) |
Patient.identifier.value | value item | Patient.identifier.type.text | value item/extractValue | Create a new CodeableConcept object, add it to the type collection of the identifier object that has the value in it, and set the text property in that type object
|
Note: Anywhere in a resource that you need to set multiple item's values into properties of a backbone element, you will need to use a group item to collect the values together, and set the definition
property on this group item
to the backbone element. The system will then create the backbone element and populate it with the child items.
value[x]
http://hl7.org/fhir/StructureDefinition/Observation#Observation.valueQuantity
Patient.extension.value.code
could be a Quantity or a Coding, thus needs to be disambiguated.
Patient.extension.valueCoding.code
or Patient.extension.valueQuantity.value
would be the appropriate definitions to use.
However if I was using the definition Patient.extension.value
and the item was of type boolean or Coding, there is no need to type slice,
as the value is of the correct type to just assign (it doesn't need to create the intermediate valueQuantity or valueCoding before trying to set the actual value from the item).
Questionnaire.item.initial.value[x]
or that use the
questionnaire-initialExpression extension to define their content to use to populate
resource elements that the user will not be filling in. (The initialExpressions might in turn depend on
variable and
questionnaire-launchContext extensions, used as described in the
Expression-based population
section, however that evaluation is all outside the scope of the $extract operation, and those variables are not available to $extract processing.Note: The extraction processes as defined does not support "merging" the answers into an existing instance of a resource as an update. Instead the "update" mode of this extraction process is a "replace" style process. So if you need to update content that was not created by the extraction routine, you will need to inject ALL data from the resource into the questionnaire (and hide properties as needed). This way all the data will be available to fill out into the resource so that it can be included as an update in the outgoing transaction bundle. This design was used as the target server for the bundle isn't necessarily going to be the same server that was pre-populated from.
Examples of questionnaires using this approach can be found here and in Questionnaire/extract-complex-defn3.
Other considerations and rules when using this approach:
definition
property
matches up with the StructureDefinition that is being referenced. It can also warn if there is no definitionExtract that would initiate the extraction process for
the resource/profile.
The template based approach provides an alternative to the definition based approach and where the full power of StructureMaps isn't required.
It supports the same level of capability, however is unable to leverage any of the information inside a profile where the definition based approach can.
The SDC Questionnaire Extract - Template profile has been created to support this mechanism.
This technique is called "template-based" because it uses a template resource(s) to provide all the "boiler-plate" content for the resource that is to be extracted. These templated resources are contained within the Questionnaire resource and referred to by either the sdc-questionnaire-templateExtract or sdc-questionnaire-templateExtractBundle extensions.
The template is annotated with expressions to indicate which parts of the template should be populated with data from the QuestionnaireResponse, and which parts should be removed if no data is present. The expressions are defined using the FHIRPath language, and the QuestionnaireResponse context of the expressions is based on the location of the template reference in the questionnaire, and/or any inline context expressions defined in the template.
The difference between the templateExtract (resource) and templateExtractBundle extensions is that the template resource extension will extract each resource individually as a separate bundle entry and includes expressions to populate the other bundle entry properties, whereas the template bundle extension extract the transaction bundle as a single resource. The processing of the template is otherwise identical between the two extensions.
To use this method:
resourceId
property in the templateExtract
extension (this is not possible using the templateExtractBundle based extraction).
Expression
datatype, and it has a name property, then that variable will be made available to any child property extraction expressions.
_value
and value
represent the same FHIR property and must be considered the same property.
So ensure that the extract extension is removed from _value
, the value
is set (and any other extensions can remain).value
and not just an extension so they will have a value that is expected to be either removed (when no data) or replaced (when data is present).
If fhirpath expressions produce errors/exceptions or invalid/incompatible value types, the
extraction engine SHALL log the error and continue processing the next property.
If there were any fatal or error level issues, the extraction engine SHALL return an OperationOutcome with the issues.
If there are only warning/information issues, they can be returned along with the transaction bundle.
Template based extraction using the templateExtractBundle extension is incompatible with modular forms.
If using the templateExtract extension with modular forms, the assembly will merge all the contained resources
from the module into the output Questionnaire, and may need to rename the contained reference ids if collisions
are to occur.
Note: Many templating engines have constructs like conditionals and loops. Although not immediately obvious, these can be implemented using this template approach. Conditional properties are implemented with the fhirpath expression in the templateExtractContext or templateExtractValue extension returning no results, and thus excluding the property, backbone element or entry in a collection from the extracted resource.
Loops are similar in nature where the looping is provided based on the fhirpath expression returning multiple results, and thus creating multiple entries in an array, or even multiple resources in the transaction bundle driven by either multiple items in the questionnaire, or contexts in the bundle template.
There are several examples of both of these in the template example, such as how it creates multiple names in the patient resource from multiple items in the questionnaire name item group, or where there is no IHI identifier added if the relevant item hasn't been answered in the questionnaire.
The StructureMap approach is the most sophisticated approach of the three - and the most powerful.
It allows significant transformation of data, including code translations when generating output resources. It also allows the conversion process
between data and Questionnaire to be maintained independently and to draw on shared sources across Questionnaires. This can be an advantage in
certain environments where the content of the questionnaire may need tight control, but the data environment can be more dynamic. This comes at the cost
of requiring expertise in the FHIR mapping language, which is not (yet?) a common skill.
The SDC Questionnaire Extract - StructureMap profile has been created to support this mechanism. An example for this profile can be found here.
To use this method:
To extract data from the completed QuestionnaireResponse, simply invoke the StructureMap on it. A sample Questionnaire and associated StructureMap that shows this approach can be found here and here.
Considerations when using this approach:
Following are the output expectations of $extract: