SDC Examples

The section dives into a variety of examples and common use cases for using the $extract and $populate flows with the Commure FHIR API. It explains some of the additional extensions we've introduced that make the processes more ergonomic and flexible.

As a reminder, Commure only supports definition-based extraction and expression-based population. These are experimental features that are a work in progress in both the FHIR specification and Commure's implementation.

This page is a list of JSON-based examples of how to format Questionnaire resources, as a kind of API reference. For a happy-path, realistic clinical scenario, please review the SDC Scenario. This scenario includes example requests to the FHIR server.

Definition-Based Extraction

From the FHIR documentation:

To use this method:

  1. Include the questionnaire-itemContext extension either on the Questionnaire root or on 'group' items within the Questionnaire to identify the resource that will serve as the context for any extraction. The itemContext is used to set the context for the item.definition paths. If the itemContext is empty, then the Questionnaire is being used to create a resource. If the itemContext has a resource (or set of resources), then the Questionnaire is being used to update the resource(s).

  2. On descendant items of that element, fill in the Questionnaire.item.definition to point to the resource or profile element that Questionnaire item corresponds to. (Profiles may be relevant for data that is sliced or has fixed values for some properties.). The definition SHALL have the full canonical URL of the resource (or profile) followed by '#' followed by the snapshot.path of the element the Questionnaire item corresponds to.

  3. If necessary, define questionnaire-hidden items that have 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.

We will cover step 1 and 2 first, and then cover step 3 after we have covered expression-based population.

* Please note that the FHIR spec used to call this the questionnaire-itemContext extension, but now refers to it more specifically as the questionnaire-itemExtractionContext extension.

Setting Up a Questionnaire for Extraction

The first step in Resource extraction is to add an itemContext* extension wrapping around the items of the Questionnaire that you want to be extracted into the resource.

  1. Include the questionnaire-itemContext extension either on the Questionnaire root or on 'group' items within the Questionnaire to identify the resource that will serve as the context for any extraction. The itemContext is used to set the context for the item.definition paths. If the itemContext is empty, then the Questionnaire is being used to create a resource. If the itemContext has a resource (or set of resources), then the Questionnaire is being used to update the resource(s).

We recommend that you do this all in the root of the Questionnaire, rather than trying to limit the scope of the itemContext to just the nested items to which it corresponds. This serves as a useful form of documentation: a user can read the top of the Questionnaire and understand what Resources it is intended to extract.

For example, if you are building a Questionnaire that extracts both a Patient and an Account, the top part of your form would look like this:

1{
2 "resourceType": "Questionnaire",
3 "title": "Patient and Account Form",
4 "status": "draft",
5 "url": "http://commure.com/fhir/Questionnaire/patient-account-form",
6 "extension": [
7 {
8 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext",
9 "valueExpression": {
10 "language": "application/x-fhir-query",
11 "expression": "Account0"
12 }
13 },
14 {
15 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext",
16 "valueExpression": {
17 "language": "application/x-fhir-query",
18 "expression": "Patient",
19 "name": "Patient0"
20 }
21 }
22 ],
23 "item": [ ... ]
24}

While the name field isn't strictly necessary, it is helpful for both self-documenting, and also disambiguating between resources if your Questionnaire extracts more than one resource of the same type.

Extracting a specific field with definition

After having set up the root-level itemContexts, it's time to map the items in your Questionnaire to specific fields in your extracted FHIR resources.

  1. On descendant items of that element, fill in the Questionnaire.item.definition to point to the resource or profile element that Questionnaire item corresponds to. (Profiles may be relevant for data that is sliced or has fixed values for some properties.). The definition SHALL have the full canonical URL of the resource (or profile) followed by '#' followed by the snapshot.path of the element the Questionnaire item corresponds to.

Continuing with our Patient and Account example:

1{
2 ...
3 "extension": [
4 ...
5 {
6 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext",
7 "valueExpression": {
8 "language": "application/x-fhir-query",
9 "expression": "Patient",
10 "name": "patient"
11 }
12 }
13 ],
14 "item": [
15 {
16 "linkId": "patient-0",
17 "type": "group",
18 "item": [
19 {
20 "linkId": "patient-0-birth-date",
21 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.birthDate",
22 "type": "date"
23 },
24 {
25 "linkId": "patient-0-active",
26 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.active",
27 "type": "boolean"
28 }
29 ],
30 "extension": [
31 {
32 "url": "https://commure.com/fhir/sdc/StructureDefinition/sdc-named-context",
33 "valueId": "Patient0"
34 }
35 ]
36 }
37 ]
38}

Expression-Based Population

From the FHIR documentation:

To use this method:

  1. Include the questionnaire-launchContext extension to identify any contextual information that needs to be passed into the population process. Typical contexts would be the Patient or Encounter resources in whose context the questionnaire is being completed, but other elements are also possible (e.g. an AdverseEvent if performing an adverse event report). These 'context' elements will be available as expression variables for use in subsequent steps.
  2. If appropriate, use the questionnaire-itemContext on group items to establish the context for a group. When populating the questionnaire, this will do two things: it will create a group repetition for each row returned from the query; and it will set the specified variable name to that resource repetition for use in processing items within the group.
  3. For Full Population, use the questionnaire-initialExpression to cause the initial answer for the question to be set to the specified expression. This should always be a FHIRPath or CQL expression that resolves to an item of the appropriate type unless the element has a type of Reference, in which case the expression can ALSO be a FHIR query - and the item will be populated with a reference to the resource. Note, this extension SHALL NOT appear on groups.

We will cover steps 1 and 3 first, and then cover step 2 in the edge case considerations at the end.

* Please note that the FHIR spec used to call this the questionnaire-itemContext extension, but now refers to it more specifically as the questionnaire-itemPopulationContext extension.

Setting Up a Questionnaire for Population

Similar to how we set up itemContexts at the root of the Questionnaire to kick off extraction, we'll also set up launchContexts alongside them to kick off population.

  1. Include the questionnaire-launchContext extension to identify any contextual information that needs to be passed into the population process. Typical contexts would be the Patient or Encounter resources in whose context the questionnaire is being completed, but other elements are also possible (e.g. an AdverseEvent if performing an adverse event report). These 'context' elements will be available as expression variables for use in subsequent steps.

These resources are the values that we will expect to be passed in as a subject or content Parameter in calls to $populate.

Example of a Patient launchContext:

1{
2 "resourceType": "Questionnaire",
3 "status": "draft",
4 "extension": [
5 {
6 "extension": [
7 {
8 "url": "name",
9 "valueId": "patient"
10 },
11 {
12 "url": "type",
13 "valueCode": "patient"
14 },
15 {
16 "url": "description",
17 "valueString": "Patient that is the subject of this questionnaire"
18 }
19 ],
20 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext"
21 }
22 ]
23}

The name section can be any variable name you like; it will be used like this - %patient - in the ensuing expressions inside the items. The type should be just an all-lowercase string of the FHIR resource type. description is human-readable documentation.

Using a FHIR path to set the initial value of a Questionnaire item

After setting up the launchContext, the way to leverage those resources is fairly straightforward if you are familiar with FHIR path:

  1. For Full Population, use the questionnaire-initialExpression to cause the initial answer for the question to be set to the specified expression. This should always be a FHIRPath or CQL expression that resolves to an item of the appropriate type unless the element has a type of Reference, in which case the expression can ALSO be a FHIR query - and the item will be populated with a reference to the resource. Note, this extension SHALL NOT appear on groups.

The QuestionnaireResponse will get pre-populated with the value traversed into by the associated FHIR Path expression:

1{
2 "resourceType": "Questionnaire",
3 "status": "draft",
4 "extension": [
5 {
6 "extension": [
7 {
8 "url": "name",
9 "valueId": "patient"
10 },
11 {
12 "url": "type",
13 "valueCode": "patient"
14 },
15 {
16 "url": "description",
17 "valueString": "Patient that is the subject of this questionnaire"
18 }
19 ],
20 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext"
21 }
22 ],
23 "item": [
24 {
25 "extension": [
26 {
27 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
28 "valueExpression": {
29 "language": "text/fhirpath",
30 "expression": "%patient.birthDate"
31 }
32 }
33 ],
34 "linkId": "patient-birthdate",
35 "text": "Date of birth",
36 "type": "date"
37 }
38 ]
39}

Commure does not currently support CQL expressions in initialExpressions.

Edge cases and quirks

When used in concert with each other, expression-based population and definition-based extraction introduce a few more wrinkles and edge cases. We enumerate some of them here.

Retaining IDs through the populate-and-extract lifecycle

Step 3 in the FHIR documentation for definition-based extraction noted:

  1. If necessary, define questionnaire-hidden items that have 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.

So if we are populating a QuestionnaireResponse with a Patient with Patient.id 123, we want the extracted Patient resource to also have Patient.id 123. But we don't want to burden the user with seeing this Patient.id field. This is how we accomplish this:

1{
2 ...
3 "extension": [
4 ...
5 {
6 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext",
7 "valueExpression": {
8 "language": "application/x-fhir-query",
9 "expression": "Patient",
10 "name": "patient"
11 }
12 }
13 ],
14 "item": [
15 {
16 "linkId": "patient-0",
17 "type": "group",
18 "item": [
19 {
20 "linkId": "patient-0-id",
21 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.id",
22 "type": "date",
23 "extension": [
24 {
25 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
26 "valueExpression": {
27 "expression": "%patient.id",
28 "language": "text/fhirpath"
29 }
30 },
31 {
32 "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden",
33 "valueBoolean": true
34 }
35 ]
36 },
37 {
38 "linkId": "patient-0-birth-date",
39 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.birthDate",
40 "type": "date"
41 },
42 {
43 "linkId": "patient-0-active",
44 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.active",
45 "type": "boolean"
46 }
47 ],
48 "extension": [
49 {
50 "url": "https://commure.com/fhir/sdc/StructureDefinition/sdc-named-context",
51 "valueId": "Patient0"
52 }
53 ]
54 }
55 ]
56}

Retaining indexes when extracting a pre-populated 0..* or 1..* field

When performing a populate-and-extract flow, it is important for the indexes of 0..* to be preserved. Absent a marker for indicating, for example, that a certain Patient.name.given value was at index 0, the extraction process would assume that we are dealing with an addition to the list of values at Patient.name.given.

Step 2 in the FHIR documentation for expression-based population noted:

  1. If appropriate, use the questionnaire-itemContext on group items to establish the context for a group. When populating the questionnaire, this will do two things: it will create a group repetition for each row returned from the query; and it will set the specified variable name to that resource repetition for use in processing items within the group.

We piggyback on this approach to population "context" to solve this issue. Commure appends a custom extension, sdc-merge-context, to index-sensitive values during the $populate call. In order to declare a value as "index-sensitive," you must (1) provide a definition for the group's item that has the 0..* or 1..* cardinality, and (2) provide an itemContext that, similarly to expression-based population's initialExpression, sets the context as described in the FHIR documentation.

Here is an example with Patient.name:

1{
2 ...
3 "extension": [
4 ...
5 {
6 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext",
7 "valueExpression": {
8 "language": "application/x-fhir-query",
9 "expression": "Patient",
10 "name": "patient"
11 }
12 },
13 {
14 "extension": [
15 {
16 "url": "name",
17 "valueId": "patient"
18 },
19 {
20 "url": "type",
21 "valueCode": "patient"
22 },
23 {
24 "url": "description",
25 "valueString": "Patient that is the subject of this questionnaire"
26 }
27 ],
28 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext"
29 }
30 ],
31 "item": [
32 {
33 "linkId": "patient-0",
34 "type": "group",
35 "item": [
36 {
37 "linkId": "patient-0-name",
38 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.name",
39 "type": "group",
40 "extension": [
41 {
42 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext",
43 "valueExpression": {
44 "name": "name",
45 "language": "text/fhirpath",
46 "expression": "%patient.name[0]"
47 }
48 }
49 ],
50 "item": [
51 {
52 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.name.given",
53 "linkId": "patient-name-first",
54 "text": "First name",
55 "type": "string",
56 "extension": [
57 {
58 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
59 "valueExpression": {
60 "language": "text/fhirpath",
61 "expression": "%name.given[0]"
62 }
63 }
64 ]
65 },
66 {
67 "linkId": "patient-name-middle",
68 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.name.given",
69 "text": "Middle name",
70 "type": "string",
71 "extension": [
72 {
73 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
74 "valueExpression": {
75 "language": "text/fhirpath",
76 "expression": "%name.given[1]"
77 }
78 }
79 ]
80 }
81 ]
82 }
83 ],
84 "extension": [
85 {
86 "url": "https://commure.com/fhir/sdc/StructureDefinition/sdc-named-context",
87 "valueId": "Patient0"
88 }
89 ]
90 }
91 ]
92}
93

Providing those two values will result in output from $populate that looks something like:

1...
2{
3 "extension": [
4 {
5 "extension": [
6 {
7 "url": "index",
8 "valueUnsignedInt": 0
9 }
10 ],
11 "url": "https://commure.com/fhir/sdc/StructureDefinition/sdc-merge-context"
12 }
13 ],
14 "linkId": "patient-name-given",
15 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.name.given",
16 "answer": [
17 {
18 "valueString": "Jane"
19 }
20 ]
21}
22...

And when you pass a QuestionnaireResponse with that value into $extract, Commure's extraction process will correctly preserve the index of that value in the list.

Advanced: populating and extracting an extension

Performing SDC on extensions can be tricky because of all the hidden fields and layers involved. However, the concepts to effectively populate and extract them have already been introduced above - with the exception of needing to introduce an enableWhen to avoid extraction of subtly incomplete fields. Here is a full example of populating and extracting the qicore-military-service extension on Patient:

1{
2 "resourceType": "Questionnaire",
3 "status": "draft",
4 "extension": [
5 {
6 "extension": [
7 {
8 "url": "name",
9 "valueId": "patient"
10 },
11 {
12 "url": "type",
13 "valueCode": "patient"
14 },
15 {
16 "url": "description",
17 "valueString": "Patient that is the subject of this questionnaire"
18 }
19 ],
20 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext"
21 },
22 {
23 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext",
24 "valueExpression": {
25 "language": "application/x-fhir-query",
26 "expression": "Patient",
27 "name": "Patient"
28 }
29 }
30 ],
31 "item": [
32 {
33 "linkId": "military-service",
34 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension",
35 "type": "group",
36 "extension": [
37 {
38 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext",
39 "valueExpression": {
40 "name": "militaryExtension",
41 "language": "text/fhirpath",
42 "expression": "%patient.extension.where(url='http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-military-service')"
43 }
44 }
45 ],
46 "item": [
47 {
48 "linkId": "military-service-extension-url",
49 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension.url",
50 "type": "string",
51 "enableWhen": [
52 {
53 "question": "military-service-extension-value",
54 "operator": "exists",
55 "answerBoolean": true
56 }
57 ],
58 "extension": [
59 {
60 "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden",
61 "valueBoolean": true
62 },
63 {
64 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
65 "valueExpression": {
66 "language": "text/fhirpath",
67 "expression": "%militaryExtension.url"
68 }
69 }
70 ],
71 "initial": [
72 {
73 "valueString": "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-military-service"
74 }
75 ]
76 },
77 {
78 "linkId": "military-service-extension-value",
79 "definition": "http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension.valueCodeableConcept.coding",
80 "answerValueSet": "https://commure.com/fhir/ValueSet/military-service/uhsinc.com",
81 "text": "Military Service",
82 "type": "choice",
83 "extension": [
84 {
85 "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression",
86 "valueExpression": {
87 "language": "text/fhirpath",
88 "expression": "%militaryExtension.value.coding"
89 }
90 }
91 ]
92 }
93 ]
94 }
95 ]
96}
97