Skip to content

sdc-populate: item FHIRPath variables are never evaluated when the Questionnaire has no root extensions #2162

Open

Description

@pavlushkin

Summary

In @aehrc/sdc-populate 4.7.1, item-level variable extensions with language: text/fhirpath are evaluated only when Questionnaire.extension is present and non-empty. With no root extensions (or extension: []), population skips every item FHIRPath variable, so an initialExpression that reads one prefills nothing. Population still reports success, and the only issue is Attempting to access an undefined environment variable: <name>, which points at the initialExpression rather than at the skipped variable.

Item application/x-fhir-query variables are not affected: they are collected separately and their results are bound as usual. Only the text/fhirpath variables on items are skipped.

The impact is narrow. Any root extension, even an unrelated one, avoids it, and most populated forms have a root launchContext. populateQuestionnaire also runs population only when the form has a root launchContext, a root sourceQueries or an x-fhir-query variable somewhere (inputParameters.ts:65-76). So in practice the trigger is a form with no root extensions whose data comes from an x-fhir-query variable on an item, read through an item FHIRPath variable. Without a launchContext such a query cannot embed {{%patient.id}}: it is sent literally.

Steps to reproduce

  1. A Questionnaire with no root extension, and a group item with two variables and a child item:
    • weightObs (application/x-fhir-query): Observation?code=http://loinc.org|29463-7
    • weight (text/fhirpath): %weightObs.entry.resource.value.value
    • child item weight (decimal) with initialExpression: %weight
  2. Call populateQuestionnaire with a fetchResourceCallback that returns a searchset Bundle holding one Observation with valueQuantity.value 72.

Expected: the weight item is prefilled with 72, and populatedContext.weight is [72].
Actual: no answer. The query runs once and populatedContext.weightObs holds the Bundle, but populatedContext has no weight. populateSuccess is true, with one issue: Error: Attempting to access an undefined environment variable: weight.

The same form with different root extensions:

Root extension Answer populatedContext.weight Issues
absent none missing undefined environment variable: weight
[] none missing undefined environment variable: weight
one unrelated extension (http://example.org/dummy) valueDecimal: 72 [72] none

Reproduced with @aehrc/sdc-populate@4.7.1, fhirpath 4.10.1 and Node 22, calling populateQuestionnaire directly with a fetchResourceCallback that resolves to the Bundle itself. Calling populate directly gives the same result: a form with no root extensions, one item variable %patient.birthDate and a patient context parameter prefills nothing, and prefills the birth date once an unrelated root extension is added. (SDC requires a context name to match a root declaration, so that call is not conformant; it only isolates the gate.)

Cause

extractAndEvaluateFhirPathVariables in packages/sdc-populate/src/SDCPopulateQuestionnaireOperation/utils/createFhirPathContext.ts (:119-154, at f846e2d8, tag sdc-populate@4.7.1; unchanged on main at 9ec3e213, 4.7.2) evaluates root variables, then item variables. It starts with a guard meant for the root part (:126-128):

if (!questionnaire.extension || questionnaire.extension.length === 0) {
  return;
}

It returns before the item variables are read (:140-153). It is called once, from createFhirPathContext (:101-108), which both populate and syncXFhirQueries use.

History: the function, guard included, was added in 66169ce (December 2024, sdc-populate 3.0.0), when population started evaluating FHIRPath variables. Every release since then is affected.

The unit test skips evaluation if no extensions exist (createFhirPathContext.test.ts:228-243) uses item: [], so no test has item variables without root extensions.

The renderer has no such gate. extractQuestionnaireLevelVariables (questionnaireStoreUtils/extractVariables.ts:26-28) returns early on no root extensions, but only root variables are read there. Item variables are collected per item in extractOtherExtensions.ts:318-329 (from the source; not reproduced).

Suggested fix

Guard only the root part. evaluateFhirPathVariables already returns on an empty list, so the early return can go:

// Extract and evaluate FHIRPath variables from questionnaire-level
const questionnaireLevelVariables = getFhirPathVariables(questionnaire.extension ?? []);
await evaluateFhirPathVariables(
  questionnaireLevelVariables,
  fhirPathContext,
  issues,
  fetchTerminologyRequestConfig,
  fetchResourceRequestConfig
);

// Extract and evaluate FHIRPath variables from item-level (unchanged)

Add a test next to skips evaluation if no extensions exist: a Questionnaire with no extension and an item variable q1Var = 1 + 1, expecting fhirPathContext.q1Var to be [2] and no issues.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions