@aehrc/smart-forms-renderer 1.0.0-alpha.21 → 1.0.0-alpha.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/lib/components/FormComponents/BooleanItem/BooleanField.d.ts +1 -0
  2. package/lib/components/FormComponents/BooleanItem/BooleanField.js +20 -17
  3. package/lib/components/FormComponents/BooleanItem/BooleanField.js.map +1 -1
  4. package/lib/components/FormComponents/BooleanItem/BooleanItem.js +6 -3
  5. package/lib/components/FormComponents/BooleanItem/BooleanItem.js.map +1 -1
  6. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.d.ts +1 -0
  7. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js +15 -7
  8. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js.map +1 -1
  9. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js +5 -2
  10. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js.map +1 -1
  11. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.d.ts +1 -0
  12. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.js +3 -3
  13. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.js.map +1 -1
  14. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.d.ts +1 -0
  15. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js +14 -7
  16. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js.map +1 -1
  17. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js +6 -3
  18. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js.map +1 -1
  19. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.d.ts +1 -0
  20. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.js +7 -5
  21. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.js.map +1 -1
  22. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js +4 -1
  23. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js.map +1 -1
  24. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.d.ts +1 -0
  25. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.js +3 -3
  26. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.js.map +1 -1
  27. package/lib/components/FormComponents/DecimalItem/DecimalItem.js +1 -1
  28. package/lib/components/FormComponents/DecimalItem/DecimalItem.js.map +1 -1
  29. package/lib/components/FormComponents/IntegerItem/IntegerItem.js +1 -1
  30. package/lib/components/FormComponents/IntegerItem/IntegerItem.js.map +1 -1
  31. package/lib/components/FormComponents/QuantityItem/QuantityItem.js +1 -1
  32. package/lib/components/FormComponents/QuantityItem/QuantityItem.js.map +1 -1
  33. package/lib/components/FormComponents/StringItem/StringItem.js +1 -1
  34. package/lib/components/FormComponents/StringItem/StringItem.js.map +1 -1
  35. package/lib/components/FormComponents/TextItem/TextItem.js +1 -1
  36. package/lib/components/FormComponents/TextItem/TextItem.js.map +1 -1
  37. package/lib/components/FormComponents/UrlItem/UrlItem.js +1 -1
  38. package/lib/components/FormComponents/UrlItem/UrlItem.js.map +1 -1
  39. package/lib/components/Renderer/BaseRenderer.js +6 -4
  40. package/lib/components/Renderer/BaseRenderer.js.map +1 -1
  41. package/lib/components/Renderer/FormBodyTabbed.js +1 -1
  42. package/lib/components/Renderer/FormBodyTabbed.js.map +1 -1
  43. package/lib/hooks/useValidationFeedback.js +33 -2
  44. package/lib/hooks/useValidationFeedback.js.map +1 -1
  45. package/lib/interfaces/questionnaireStore.interface.d.ts +2 -0
  46. package/lib/interfaces/targetConstraint.interface.d.ts +30 -0
  47. package/lib/interfaces/targetConstraint.interface.js +2 -0
  48. package/lib/interfaces/targetConstraint.interface.js.map +1 -0
  49. package/lib/stores/questionnaireResponseStore.d.ts +8 -2
  50. package/lib/stores/questionnaireResponseStore.js +15 -16
  51. package/lib/stores/questionnaireResponseStore.js.map +1 -1
  52. package/lib/stores/questionnaireStore.d.ts +7 -0
  53. package/lib/stores/questionnaireStore.js +22 -8
  54. package/lib/stores/questionnaireStore.js.map +1 -1
  55. package/lib/utils/calculatedExpression.d.ts +3 -2
  56. package/lib/utils/calculatedExpression.js +2 -2
  57. package/lib/utils/calculatedExpression.js.map +1 -1
  58. package/lib/utils/enableWhenExpression.d.ts +4 -3
  59. package/lib/utils/enableWhenExpression.js +4 -4
  60. package/lib/utils/enableWhenExpression.js.map +1 -1
  61. package/lib/utils/fhirpath.d.ts +8 -3
  62. package/lib/utils/fhirpath.js +27 -8
  63. package/lib/utils/fhirpath.js.map +1 -1
  64. package/lib/utils/initialise.d.ts +6 -2
  65. package/lib/utils/initialise.js +16 -4
  66. package/lib/utils/initialise.js.map +1 -1
  67. package/lib/utils/itemControl.d.ts +1 -0
  68. package/lib/utils/itemControl.js +11 -0
  69. package/lib/utils/itemControl.js.map +1 -1
  70. package/lib/utils/manageForm.js +2 -0
  71. package/lib/utils/manageForm.js.map +1 -1
  72. package/lib/utils/questionnaireStoreUtils/createQuestionaireModel.js +4 -0
  73. package/lib/utils/questionnaireStoreUtils/createQuestionaireModel.js.map +1 -1
  74. package/lib/utils/questionnaireStoreUtils/extractTargetConstraint.d.ts +4 -0
  75. package/lib/utils/questionnaireStoreUtils/extractTargetConstraint.js +39 -0
  76. package/lib/utils/questionnaireStoreUtils/extractTargetConstraint.js.map +1 -0
  77. package/lib/utils/targetConstraint.d.ts +23 -0
  78. package/lib/utils/targetConstraint.js +179 -0
  79. package/lib/utils/targetConstraint.js.map +1 -0
  80. package/lib/utils/validate.d.ts +73 -0
  81. package/lib/utils/validate.js +742 -0
  82. package/lib/utils/validate.js.map +1 -0
  83. package/lib/utils/validateQuestionnaire.d.ts +40 -40
  84. package/lib/utils/validateQuestionnaire.js +1 -0
  85. package/lib/utils/validateQuestionnaire.js.map +1 -1
  86. package/lib/utils/validateQuestionnaireResponse.d.ts +71 -0
  87. package/lib/utils/validateQuestionnaireResponse.js +710 -0
  88. package/lib/utils/validateQuestionnaireResponse.js.map +1 -0
  89. package/lib/utils/validateTargetConstraint.d.ts +1 -0
  90. package/lib/utils/validateTargetConstraint.js +34 -0
  91. package/lib/utils/validateTargetConstraint.js.map +1 -0
  92. package/package.json +1 -1
  93. package/src/components/FormComponents/BooleanItem/BooleanField.tsx +82 -73
  94. package/src/components/FormComponents/BooleanItem/BooleanItem.tsx +7 -0
  95. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.tsx +43 -18
  96. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx +6 -0
  97. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.tsx +4 -0
  98. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.tsx +35 -17
  99. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx +8 -2
  100. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.tsx +41 -34
  101. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx +5 -0
  102. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.tsx +4 -0
  103. package/src/components/FormComponents/DecimalItem/DecimalItem.tsx +2 -1
  104. package/src/components/FormComponents/IntegerItem/IntegerItem.tsx +2 -1
  105. package/src/components/FormComponents/QuantityItem/QuantityItem.tsx +2 -1
  106. package/src/components/FormComponents/StringItem/StringItem.tsx +1 -1
  107. package/src/components/FormComponents/TextItem/TextItem.tsx +2 -1
  108. package/src/components/FormComponents/UrlItem/UrlItem.tsx +2 -1
  109. package/src/components/Renderer/BaseRenderer.tsx +6 -4
  110. package/src/components/Renderer/FormBodyTabbed.tsx +1 -1
  111. package/src/hooks/useValidationFeedback.ts +39 -2
  112. package/src/interfaces/questionnaireStore.interface.ts +2 -0
  113. package/src/interfaces/renderProps.interface.ts +1 -0
  114. package/src/interfaces/targetConstraint.interface.ts +36 -0
  115. package/src/stores/questionnaireResponseStore.ts +25 -18
  116. package/src/stores/questionnaireStore.ts +31 -5
  117. package/src/utils/calculatedExpression.ts +4 -4
  118. package/src/utils/enableWhenExpression.ts +8 -7
  119. package/src/utils/fhirpath.ts +57 -9
  120. package/src/utils/initialise.ts +23 -6
  121. package/src/utils/itemControl.ts +15 -0
  122. package/src/utils/manageForm.ts +3 -0
  123. package/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts +6 -0
  124. package/src/utils/questionnaireStoreUtils/extractTargetConstraint.ts +71 -0
  125. package/src/utils/targetConstraint.ts +243 -0
  126. package/src/utils/{validateQuestionnaire.ts → validate.ts} +138 -58
@@ -50,6 +50,8 @@ import { insertCompleteAnswerOptionsIntoQuestionnaire } from '../utils/questionn
50
50
  import type { InitialExpression } from '../interfaces/initialExpression.interface';
51
51
  import type { QItemOverrideComponentProps, SdcUiOverrideComponentProps } from '../interfaces';
52
52
  import type { ComponentType } from 'react';
53
+ import type { TargetConstraint } from '../interfaces/targetConstraint.interface';
54
+ import { readTargetConstraintLocationLinkIds } from '../utils/targetConstraint';
53
55
 
54
56
  /**
55
57
  * QuestionnaireStore properties and methods
@@ -65,6 +67,8 @@ import type { ComponentType } from 'react';
65
67
  * @property currentPageIndex - Index of the current page
66
68
  * @property variables - Questionnaire variables object containing FHIRPath and x-fhir-query variables
67
69
  * @property launchContexts - Key-value pair of launch contexts `Record<launch context name, launch context properties>`
70
+ * @property targetConstraints - Key-value pair of target constraints `Record<target constraint key, target constraint properties>`
71
+ * @property targetConstraintLinkIds - Key-value pair of linkIds against target constraint key(s) `Record<linkId, target constraint keys>`
68
72
  * @property enableWhenItems - EnableWhenItems object containing enableWhen items and their linked questions
69
73
  * @property enableWhenLinkedQuestions - Key-value pair of linked questions to enableWhen items `Record<linkId, linkIds of linked questions>`
70
74
  * @property enableWhenIsActivated - Flag to turn enableWhen checks on/off
@@ -109,6 +113,8 @@ export interface QuestionnaireStoreType {
109
113
  currentPageIndex: number;
110
114
  variables: Variables;
111
115
  launchContexts: Record<string, LaunchContext>;
116
+ targetConstraints: Record<string, TargetConstraint>;
117
+ targetConstraintLinkIds: Record<string, string[]>;
112
118
  enableWhenItems: EnableWhenItems;
113
119
  enableWhenLinkedQuestions: Record<string, string[]>;
114
120
  enableWhenIsActivated: boolean;
@@ -183,6 +189,8 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
183
189
  currentPageIndex: 0,
184
190
  variables: { fhirPathVariables: {}, xFhirQueryVariables: {} },
185
191
  launchContexts: {},
192
+ targetConstraints: {},
193
+ targetConstraintLinkIds: {},
186
194
  calculatedExpressions: {},
187
195
  initialExpressions: {},
188
196
  enableWhenExpressions: { singleExpressions: {}, repeatExpressions: {} },
@@ -229,6 +237,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
229
237
 
230
238
  // Initialise form with questionnaire response and properties in questionnaire model
231
239
  const {
240
+ initialTargetConstraints,
232
241
  initialEnableWhenItems,
233
242
  initialEnableWhenLinkedQuestions,
234
243
  initialEnableWhenExpressions,
@@ -239,10 +248,11 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
239
248
  fhirPathTerminologyCache: updatedFhirPathTerminologyCache
240
249
  } = await initialiseFormFromResponse({
241
250
  questionnaireResponse,
251
+ targetConstraints: questionnaireModel.targetConstraints,
242
252
  enableWhenItems: questionnaireModel.enableWhenItems,
243
253
  enableWhenExpressions: questionnaireModel.enableWhenExpressions,
244
254
  calculatedExpressions: questionnaireModel.calculatedExpressions,
245
- variablesFhirPath: questionnaireModel.variables.fhirPathVariables,
255
+ variables: questionnaireModel.variables,
246
256
  tabs: questionnaireModel.tabs,
247
257
  pages: questionnaireModel.pages,
248
258
  fhirPathContext: fhirPathContext,
@@ -250,6 +260,12 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
250
260
  terminologyServerUrl: terminologyServerUrl
251
261
  });
252
262
 
263
+ // Read target constraint locations
264
+ const targetConstraintLinkIds = readTargetConstraintLocationLinkIds(
265
+ questionnaire,
266
+ initialTargetConstraints
267
+ );
268
+
253
269
  set({
254
270
  sourceQuestionnaire: questionnaire,
255
271
  itemTypes: questionnaireModel.itemTypes,
@@ -260,6 +276,8 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
260
276
  currentPageIndex: firstVisiblePage,
261
277
  variables: questionnaireModel.variables,
262
278
  launchContexts: questionnaireModel.launchContexts,
279
+ targetConstraints: initialTargetConstraints,
280
+ targetConstraintLinkIds: targetConstraintLinkIds,
263
281
  enableWhenItems: initialEnableWhenItems,
264
282
  enableWhenLinkedQuestions: initialEnableWhenLinkedQuestions,
265
283
  enableWhenExpressions: initialEnableWhenExpressions,
@@ -286,6 +304,8 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
286
304
  currentPageIndex: 0,
287
305
  variables: { fhirPathVariables: {}, xFhirQueryVariables: {} },
288
306
  launchContexts: {},
307
+ targetConstraints: {},
308
+ targetConstraintLinkIds: {},
289
309
  enableWhenItems: { singleItems: {}, repeatItems: {} },
290
310
  enableWhenLinkedQuestions: {},
291
311
  enableWhenExpressions: { singleExpressions: {}, repeatExpressions: {} },
@@ -364,7 +384,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
364
384
  await mutateRepeatEnableWhenExpressionInstances({
365
385
  questionnaireResponse: questionnaireResponseStore.getState().updatableResponse,
366
386
  questionnaireResponseItemMap: questionnaireResponseStore.getState().updatableResponseItems,
367
- variablesFhirPath: get().variables.fhirPathVariables,
387
+ variables: get().variables,
368
388
  existingFhirPathContext: get().fhirPathContext,
369
389
  fhirPathTerminologyCache: get().fhirPathTerminologyCache,
370
390
  enableWhenExpressions: enableWhenExpressions,
@@ -387,6 +407,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
387
407
  const updatedResponseItemMap = createQuestionnaireResponseItemMap(updatedResponse);
388
408
  const {
389
409
  isUpdated,
410
+ updatedTargetConstraints,
390
411
  updatedEnableWhenExpressions,
391
412
  updatedCalculatedExpressions,
392
413
  updatedFhirPathContext,
@@ -394,9 +415,10 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
394
415
  } = await evaluateUpdatedExpressions({
395
416
  updatedResponse,
396
417
  updatedResponseItemMap,
418
+ targetConstraints: get().targetConstraints,
397
419
  enableWhenExpressions: get().enableWhenExpressions,
398
420
  calculatedExpressions: get().calculatedExpressions,
399
- variablesFhirPath: get().variables.fhirPathVariables,
421
+ variables: get().variables,
400
422
  existingFhirPathContext: get().fhirPathContext,
401
423
  fhirPathTerminologyCache: get().fhirPathTerminologyCache,
402
424
  terminologyServerUrl: terminologyServerStore.getState().url
@@ -404,6 +426,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
404
426
 
405
427
  if (isUpdated) {
406
428
  set(() => ({
429
+ targetConstraints: updatedTargetConstraints,
407
430
  enableWhenExpressions: updatedEnableWhenExpressions,
408
431
  calculatedExpressions: updatedCalculatedExpressions,
409
432
  fhirPathContext: updatedFhirPathContext,
@@ -436,7 +459,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
436
459
  initialResponse: populatedResponse,
437
460
  initialResponseItemMap: initialResponseItemMap,
438
461
  calculatedExpressions: get().calculatedExpressions,
439
- variablesFhirPath: get().variables.fhirPathVariables,
462
+ variables: get().variables,
440
463
  existingFhirPathContext: get().fhirPathContext,
441
464
  fhirPathTerminologyCache: get().fhirPathTerminologyCache,
442
465
  terminologyServerUrl: terminologyServerStore.getState().url
@@ -453,6 +476,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
453
476
  );
454
477
 
455
478
  const {
479
+ initialTargetConstraints,
456
480
  initialEnableWhenItems,
457
481
  initialEnableWhenLinkedQuestions,
458
482
  initialEnableWhenExpressions,
@@ -460,10 +484,11 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
460
484
  firstVisiblePage
461
485
  } = await initialiseFormFromResponse({
462
486
  questionnaireResponse: updatedResponse,
487
+ targetConstraints: get().targetConstraints,
463
488
  enableWhenItems: get().enableWhenItems,
464
489
  enableWhenExpressions: get().enableWhenExpressions,
465
490
  calculatedExpressions: initialCalculatedExpressions,
466
- variablesFhirPath: get().variables.fhirPathVariables,
491
+ variables: get().variables,
467
492
  tabs: get().tabs,
468
493
  pages: get().pages,
469
494
  fhirPathContext: updatedFhirPathContext,
@@ -474,6 +499,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
474
499
  fhirPathTerminologyCache = evaluateInitialCalculatedExpressionsResult.fhirPathTerminologyCache;
475
500
 
476
501
  set(() => ({
502
+ targetConstraints: initialTargetConstraints,
477
503
  enableWhenItems: initialEnableWhenItems,
478
504
  enableWhenLinkedQuestions: initialEnableWhenLinkedQuestions,
479
505
  enableWhenExpressions: initialEnableWhenExpressions,
@@ -19,7 +19,6 @@ import type { CalculatedExpression } from '../interfaces/calculatedExpression.in
19
19
  import fhirpath from 'fhirpath';
20
20
  import fhirpath_r4_model from 'fhirpath/fhir-context/r4';
21
21
  import type {
22
- Expression,
23
22
  Questionnaire,
24
23
  QuestionnaireItem,
25
24
  QuestionnaireItemAnswerOption,
@@ -34,12 +33,13 @@ import { updateQrItemsInGroup } from './qrItem';
34
33
  import dayjs from 'dayjs';
35
34
  import { updateQuestionnaireResponse } from './genericRecursive';
36
35
  import isEqual from 'lodash.isequal';
36
+ import type { Variables } from '../interfaces';
37
37
 
38
38
  interface EvaluateInitialCalculatedExpressionsParams {
39
39
  initialResponse: QuestionnaireResponse;
40
40
  initialResponseItemMap: Record<string, QuestionnaireResponseItem[]>;
41
41
  calculatedExpressions: Record<string, CalculatedExpression[]>;
42
- variablesFhirPath: Record<string, Expression[]>;
42
+ variables: Variables;
43
43
  existingFhirPathContext: Record<string, any>;
44
44
  fhirPathTerminologyCache: Record<string, any>;
45
45
  terminologyServerUrl: string;
@@ -56,7 +56,7 @@ export async function evaluateInitialCalculatedExpressions(
56
56
  initialResponse,
57
57
  initialResponseItemMap,
58
58
  calculatedExpressions,
59
- variablesFhirPath,
59
+ variables,
60
60
  existingFhirPathContext,
61
61
  terminologyServerUrl
62
62
  } = params;
@@ -81,7 +81,7 @@ export async function evaluateInitialCalculatedExpressions(
81
81
  const fhirPathEvalResult = await createFhirPathContext(
82
82
  initialResponse,
83
83
  initialResponseItemMap,
84
- variablesFhirPath,
84
+ variables,
85
85
  existingFhirPathContext,
86
86
  fhirPathTerminologyCache,
87
87
  terminologyServerUrl
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import type { Expression, QuestionnaireResponse, QuestionnaireResponseItem } from 'fhir/r4';
18
+ import type { QuestionnaireResponse, QuestionnaireResponseItem } from 'fhir/r4';
19
19
  import { createFhirPathContext, handleFhirPathResult } from './fhirpath';
20
20
  import fhirpath from 'fhirpath';
21
21
  import fhirpath_r4_model from 'fhirpath/fhir-context/r4';
@@ -24,12 +24,13 @@ import type {
24
24
  EnableWhenRepeatExpression,
25
25
  EnableWhenSingleExpression
26
26
  } from '../interfaces/enableWhen.interface';
27
+ import type { Variables } from '../interfaces';
27
28
 
28
29
  interface EvaluateInitialEnableWhenExpressionsParams {
29
30
  initialResponse: QuestionnaireResponse;
30
31
  initialResponseItemMap: Record<string, QuestionnaireResponseItem[]>;
31
32
  enableWhenExpressions: EnableWhenExpressions;
32
- variablesFhirPath: Record<string, Expression[]>;
33
+ variables: Variables;
33
34
  existingFhirPathContext: Record<string, any>;
34
35
  fhirPathTerminologyCache: Record<string, any>;
35
36
  terminologyServerUrl: string;
@@ -46,7 +47,7 @@ export async function evaluateInitialEnableWhenExpressions(
46
47
  initialResponse,
47
48
  initialResponseItemMap,
48
49
  enableWhenExpressions,
49
- variablesFhirPath,
50
+ variables,
50
51
  existingFhirPathContext,
51
52
  terminologyServerUrl
52
53
  } = params;
@@ -58,7 +59,7 @@ export async function evaluateInitialEnableWhenExpressions(
58
59
  const fhirPathEvalResult = await createFhirPathContext(
59
60
  initialResponse,
60
61
  initialResponseItemMap,
61
- variablesFhirPath,
62
+ variables,
62
63
  existingFhirPathContext,
63
64
  fhirPathTerminologyCache,
64
65
  terminologyServerUrl
@@ -325,7 +326,7 @@ export async function evaluateEnableWhenExpressions(
325
326
  interface MutateRepeatEnableWhenExpressionInstancesParams {
326
327
  questionnaireResponse: QuestionnaireResponse;
327
328
  questionnaireResponseItemMap: Record<string, QuestionnaireResponseItem[]>;
328
- variablesFhirPath: Record<string, Expression[]>;
329
+ variables: Variables;
329
330
  existingFhirPathContext: Record<string, any>;
330
331
  fhirPathTerminologyCache: Record<string, any>;
331
332
  enableWhenExpressions: EnableWhenExpressions;
@@ -341,7 +342,7 @@ export async function mutateRepeatEnableWhenExpressionInstances(
341
342
  const {
342
343
  questionnaireResponse,
343
344
  questionnaireResponseItemMap,
344
- variablesFhirPath,
345
+ variables,
345
346
  fhirPathTerminologyCache,
346
347
  existingFhirPathContext,
347
348
  enableWhenExpressions,
@@ -356,7 +357,7 @@ export async function mutateRepeatEnableWhenExpressionInstances(
356
357
  const fhirPathEvalResult = await createFhirPathContext(
357
358
  questionnaireResponse,
358
359
  questionnaireResponseItemMap,
359
- variablesFhirPath,
360
+ variables,
360
361
  existingFhirPathContext,
361
362
  fhirPathTerminologyCache,
362
363
  terminologyServerUrl
@@ -22,13 +22,17 @@ import type { CalculatedExpression } from '../interfaces/calculatedExpression.in
22
22
  import type { EnableWhenExpressions } from '../interfaces/enableWhen.interface';
23
23
  import { evaluateEnableWhenExpressions } from './enableWhenExpression';
24
24
  import { evaluateCalculatedExpressions } from './calculatedExpression';
25
+ import { evaluateTargetConstraints } from './targetConstraint';
26
+ import type { TargetConstraint } from '../interfaces/targetConstraint.interface';
27
+ import type { Variables, VariableXFhirQuery } from '../interfaces';
25
28
 
26
29
  interface EvaluateUpdatedExpressionsParams {
27
30
  updatedResponse: QuestionnaireResponse;
28
31
  updatedResponseItemMap: Record<string, QuestionnaireResponseItem[]>;
32
+ targetConstraints: Record<string, TargetConstraint>;
29
33
  calculatedExpressions: Record<string, CalculatedExpression[]>;
30
34
  enableWhenExpressions: EnableWhenExpressions;
31
- variablesFhirPath: Record<string, Expression[]>;
35
+ variables: Variables;
32
36
  existingFhirPathContext: Record<string, any>;
33
37
  fhirPathTerminologyCache: Record<string, any>;
34
38
  terminologyServerUrl: string;
@@ -38,6 +42,7 @@ export async function evaluateUpdatedExpressions(
38
42
  params: EvaluateUpdatedExpressionsParams
39
43
  ): Promise<{
40
44
  isUpdated: boolean;
45
+ updatedTargetConstraints: Record<string, TargetConstraint>;
41
46
  updatedEnableWhenExpressions: EnableWhenExpressions;
42
47
  updatedCalculatedExpressions: Record<string, CalculatedExpression[]>;
43
48
  updatedFhirPathContext: Record<string, any>;
@@ -46,9 +51,10 @@ export async function evaluateUpdatedExpressions(
46
51
  const {
47
52
  updatedResponse,
48
53
  updatedResponseItemMap,
54
+ targetConstraints,
49
55
  enableWhenExpressions,
50
56
  calculatedExpressions,
51
- variablesFhirPath,
57
+ variables,
52
58
  existingFhirPathContext,
53
59
  terminologyServerUrl
54
60
  } = params;
@@ -57,9 +63,10 @@ export async function evaluateUpdatedExpressions(
57
63
  const noExpressionsToBeUpdated =
58
64
  Object.keys(enableWhenExpressions).length === 0 &&
59
65
  Object.keys(calculatedExpressions).length === 0;
60
- if (noExpressionsToBeUpdated || !updatedResponse.item) {
66
+ if (noExpressionsToBeUpdated) {
61
67
  return {
62
68
  isUpdated: false,
69
+ updatedTargetConstraints: targetConstraints,
63
70
  updatedEnableWhenExpressions: enableWhenExpressions,
64
71
  updatedCalculatedExpressions: calculatedExpressions,
65
72
  updatedFhirPathContext: existingFhirPathContext,
@@ -70,7 +77,7 @@ export async function evaluateUpdatedExpressions(
70
77
  const fhirPathEvalResult = await createFhirPathContext(
71
78
  updatedResponse,
72
79
  updatedResponseItemMap,
73
- variablesFhirPath,
80
+ variables,
74
81
  existingFhirPathContext,
75
82
  fhirPathTerminologyCache,
76
83
  terminologyServerUrl
@@ -79,6 +86,14 @@ export async function evaluateUpdatedExpressions(
79
86
  const updatedFhirPathContext = fhirPathEvalResult.fhirPathContext;
80
87
  fhirPathTerminologyCache = fhirPathEvalResult.fhirPathTerminologyCache;
81
88
 
89
+ // Update targetConstraints
90
+ const { targetConstraintsIsUpdated, updatedTargetConstraints } = await evaluateTargetConstraints(
91
+ updatedFhirPathContext,
92
+ fhirPathTerminologyCache,
93
+ targetConstraints,
94
+ terminologyServerUrl
95
+ );
96
+
82
97
  // Update enableWhenExpressions
83
98
  const { enableWhenExpsIsUpdated, updatedEnableWhenExpressions } =
84
99
  await evaluateEnableWhenExpressions(
@@ -97,10 +112,12 @@ export async function evaluateUpdatedExpressions(
97
112
  terminologyServerUrl
98
113
  );
99
114
 
100
- const isUpdated = enableWhenExpsIsUpdated || calculatedExpsIsUpdated;
115
+ const isUpdated =
116
+ enableWhenExpsIsUpdated || calculatedExpsIsUpdated || targetConstraintsIsUpdated;
101
117
 
102
118
  return {
103
119
  isUpdated,
120
+ updatedTargetConstraints,
104
121
  updatedEnableWhenExpressions,
105
122
  updatedCalculatedExpressions,
106
123
  updatedFhirPathContext,
@@ -111,7 +128,7 @@ export async function evaluateUpdatedExpressions(
111
128
  export async function createFhirPathContext(
112
129
  questionnaireResponse: QuestionnaireResponse,
113
130
  questionnaireResponseItemMap: Record<string, QuestionnaireResponseItem[]>,
114
- variablesFhirPath: Record<string, Expression[]>,
131
+ variables: Variables,
115
132
  existingFhirPathContext: Record<string, any>,
116
133
  fhirPathTerminologyCache: Record<string, any>,
117
134
  terminologyServerUrl: string
@@ -119,6 +136,9 @@ export async function createFhirPathContext(
119
136
  fhirPathContext: Record<string, any>;
120
137
  fhirPathTerminologyCache: Record<string, any>;
121
138
  }> {
139
+ const { fhirPathVariables: variablesFhirPath, xFhirQueryVariables: variablesXFhirQuery } =
140
+ variables;
141
+
122
142
  // Add latest resource to fhirPathContext
123
143
  let fhirPathContext: Record<string, any> = {
124
144
  ...existingFhirPathContext,
@@ -126,6 +146,12 @@ export async function createFhirPathContext(
126
146
  rootResource: questionnaireResponse
127
147
  };
128
148
 
149
+ // Add empty x-fhir-query variables to fhirPathContext to prevent false-positive warnings
150
+ fhirPathContext = addEmptyXFhirQueryVariablesToFhirPathContext(
151
+ fhirPathContext,
152
+ variablesXFhirQuery
153
+ );
154
+
129
155
  // Evaluate resource-level variables
130
156
  const fhirPathEvalResult = await evaluateQuestionnaireLevelVariables(
131
157
  questionnaireResponse,
@@ -155,13 +181,32 @@ export async function createFhirPathContext(
155
181
  }
156
182
 
157
183
  // Items don't exist in questionnaireResponseItemMap, but we still have to add them into the fhirPathContext as empty arrays
184
+ const qrItemMapIsEmpty = Object.keys(questionnaireResponseItemMap).length === 0;
158
185
  for (const linkId in variablesFhirPath) {
159
- fhirPathContext = addEmptyLinkIdVariables(linkId, variablesFhirPath, fhirPathContext);
186
+ fhirPathContext = addEmptyLinkIdVariables(
187
+ linkId,
188
+ variablesFhirPath,
189
+ fhirPathContext,
190
+ qrItemMapIsEmpty
191
+ );
160
192
  }
161
193
 
162
194
  return { fhirPathContext, fhirPathTerminologyCache };
163
195
  }
164
196
 
197
+ export function addEmptyXFhirQueryVariablesToFhirPathContext(
198
+ fhirPathContext: Record<string, any>,
199
+ variablesXFhirQuery: Record<string, VariableXFhirQuery>
200
+ ) {
201
+ for (const variableName in variablesXFhirQuery) {
202
+ if (!fhirPathContext[variableName]) {
203
+ fhirPathContext[variableName] = [];
204
+ }
205
+ }
206
+
207
+ return fhirPathContext;
208
+ }
209
+
165
210
  export async function evaluateLinkIdVariables(
166
211
  item: QuestionnaireResponseItem,
167
212
  variablesFhirPath: Record<string, Expression[]>,
@@ -213,7 +258,8 @@ export async function evaluateLinkIdVariables(
213
258
  export function addEmptyLinkIdVariables(
214
259
  linkId: string,
215
260
  variablesFhirPath: Record<string, Expression[]>,
216
- fhirPathContext: Record<string, any>
261
+ fhirPathContext: Record<string, any>,
262
+ qrItemMapIsEmpty: boolean
217
263
  ) {
218
264
  const linkIdVariables = variablesFhirPath[linkId];
219
265
  if (!linkIdVariables || linkIdVariables.length === 0) {
@@ -222,7 +268,9 @@ export function addEmptyLinkIdVariables(
222
268
 
223
269
  for (const variable of linkIdVariables) {
224
270
  if (variable.expression && variable.name) {
225
- if (fhirPathContext[`${variable.name}`] === undefined) {
271
+ // If the variable is not evaluated, add it as an empty array
272
+ // Also, when questionnaireResponseItemMap is empty, no items exist in the questionnaireResponse, therefore no variables are evaluated
273
+ if (fhirPathContext[`${variable.name}`] === undefined || qrItemMapIsEmpty) {
226
274
  fhirPathContext[`${variable.name}`] = [];
227
275
  }
228
276
  }
@@ -19,7 +19,6 @@ import { evaluateInitialEnableWhenExpressions } from './enableWhenExpression';
19
19
  import { getFirstVisibleTab } from './tabs';
20
20
  import { getFirstVisiblePage } from './page';
21
21
  import type {
22
- Expression,
23
22
  Questionnaire,
24
23
  QuestionnaireItem,
25
24
  QuestionnaireItemInitial,
@@ -36,6 +35,9 @@ import { evaluateInitialCalculatedExpressions } from './calculatedExpression';
36
35
  import { createQuestionnaireResponseItemMap } from './questionnaireResponseStoreUtils/updatableResponseItems';
37
36
  import { readQuestionnaireResponse } from './genericRecursive';
38
37
  import { getQrItemsIndex, mapQItemsIndex } from './mapItem';
38
+ import type { TargetConstraint } from '../interfaces/targetConstraint.interface';
39
+ import { evaluateInitialTargetConstraints } from './targetConstraint';
40
+ import type { Variables } from '../interfaces';
39
41
 
40
42
  /**
41
43
  * Initialise a questionnaireResponse from a given questionnaire
@@ -311,10 +313,11 @@ function createNewRepeatGroupQuestionnaireResponseItem(
311
313
 
312
314
  export interface initialFormFromResponseParams {
313
315
  questionnaireResponse: QuestionnaireResponse;
316
+ targetConstraints: Record<string, TargetConstraint>;
314
317
  enableWhenItems: EnableWhenItems;
315
318
  enableWhenExpressions: EnableWhenExpressions;
316
319
  calculatedExpressions: Record<string, CalculatedExpression[]>;
317
- variablesFhirPath: Record<string, Expression[]>;
320
+ variables: Variables;
318
321
  tabs: Tabs;
319
322
  pages: Pages;
320
323
  fhirPathContext: Record<string, any>;
@@ -323,6 +326,7 @@ export interface initialFormFromResponseParams {
323
326
  }
324
327
 
325
328
  export async function initialiseFormFromResponse(params: initialFormFromResponseParams): Promise<{
329
+ initialTargetConstraints: Record<string, TargetConstraint>;
326
330
  initialEnableWhenItems: EnableWhenItems;
327
331
  initialEnableWhenLinkedQuestions: Record<string, string[]>;
328
332
  initialEnableWhenExpressions: EnableWhenExpressions;
@@ -334,10 +338,11 @@ export async function initialiseFormFromResponse(params: initialFormFromResponse
334
338
  }> {
335
339
  const {
336
340
  questionnaireResponse,
341
+ targetConstraints,
337
342
  enableWhenItems,
338
343
  enableWhenExpressions,
339
344
  calculatedExpressions,
340
- variablesFhirPath,
345
+ variables,
341
346
  tabs,
342
347
  pages,
343
348
  fhirPathContext,
@@ -353,24 +358,35 @@ export async function initialiseFormFromResponse(params: initialFormFromResponse
353
358
  questionnaireResponse
354
359
  );
355
360
 
361
+ const evaluateInitialTargetConstraintsResult = await evaluateInitialTargetConstraints({
362
+ initialResponse: questionnaireResponse,
363
+ initialResponseItemMap: initialResponseItemMap,
364
+ targetConstraints: targetConstraints,
365
+ variables: variables,
366
+ existingFhirPathContext: fhirPathContext,
367
+ fhirPathTerminologyCache: fhirPathTerminologyCache,
368
+ terminologyServerUrl
369
+ });
370
+ const { initialTargetConstraints } = evaluateInitialTargetConstraintsResult;
371
+ fhirPathTerminologyCache = evaluateInitialTargetConstraintsResult.fhirPathTerminologyCache;
372
+
356
373
  const evaluateInitialEnableWhenExpressionsResult = await evaluateInitialEnableWhenExpressions({
357
374
  initialResponse: questionnaireResponse,
358
375
  initialResponseItemMap: initialResponseItemMap,
359
376
  enableWhenExpressions: enableWhenExpressions,
360
- variablesFhirPath: variablesFhirPath,
377
+ variables: variables,
361
378
  existingFhirPathContext: fhirPathContext,
362
379
  fhirPathTerminologyCache: fhirPathTerminologyCache,
363
380
  terminologyServerUrl: terminologyServerUrl
364
381
  });
365
382
  const { initialEnableWhenExpressions } = evaluateInitialEnableWhenExpressionsResult;
366
- updatedFhirPathContext = evaluateInitialEnableWhenExpressionsResult.updatedFhirPathContext;
367
383
  fhirPathTerminologyCache = evaluateInitialEnableWhenExpressionsResult.fhirPathTerminologyCache;
368
384
 
369
385
  const evaluateInitialCalculatedExpressionsResult = await evaluateInitialCalculatedExpressions({
370
386
  initialResponse: questionnaireResponse,
371
387
  initialResponseItemMap: initialResponseItemMap,
372
388
  calculatedExpressions: calculatedExpressions,
373
- variablesFhirPath: variablesFhirPath,
389
+ variables: variables,
374
390
  existingFhirPathContext: fhirPathContext,
375
391
  fhirPathTerminologyCache: fhirPathTerminologyCache,
376
392
  terminologyServerUrl
@@ -389,6 +405,7 @@ export async function initialiseFormFromResponse(params: initialFormFromResponse
389
405
  : 0;
390
406
 
391
407
  return {
408
+ initialTargetConstraints,
392
409
  initialEnableWhenItems: initialisedItems,
393
410
  initialEnableWhenLinkedQuestions: linkedQuestions,
394
411
  initialEnableWhenExpressions,
@@ -471,6 +471,21 @@ export function getMaxValueFeedback(qItem: QuestionnaireItem) {
471
471
  return null;
472
472
  }
473
473
 
474
+ export function getRequiredFeedback(qItem: QuestionnaireItem) {
475
+ const itemControl = qItem.extension?.find(
476
+ (extension: Extension) =>
477
+ extension.url === 'https://smartforms.csiro.au/ig/StructureDefinition/required-feedback'
478
+ );
479
+ if (itemControl) {
480
+ const extensionString = itemControl.valueString;
481
+ if (extensionString) {
482
+ return extensionString;
483
+ }
484
+ }
485
+
486
+ return null;
487
+ }
488
+
474
489
  /**
475
490
  * Check if the item has a sdc-questionnaire-minQuantity and minQuantity extension
476
491
  * @author Janardhan Vignarajan
@@ -65,6 +65,9 @@ export async function buildForm(
65
65
  questionnaireResponseStore.getState().buildSourceResponse(initialisedQuestionnaireResponse);
66
66
  await questionnaireStore.getState().updatePopulatedProperties(initialisedQuestionnaireResponse);
67
67
 
68
+ // Adding another call to buildSourceResponse so invalidItems is truly updated - not great, but a cheap fix
69
+ questionnaireResponseStore.getState().buildSourceResponse(initialisedQuestionnaireResponse);
70
+
68
71
  if (readOnly) {
69
72
  questionnaireStore.getState().setFormAsReadOnly(readOnly);
70
73
  }
@@ -31,6 +31,8 @@ import { resolveValueSets } from './resolveValueSets';
31
31
  import { addAdditionalVariables } from './addAdditionalVariables';
32
32
  import { getLinkIdPreferredTerminologyServerTuples, getLinkIdTypeTuples } from '../qItem';
33
33
  import { addDisplayToAnswerOptions, addDisplayToProcessedCodings } from './addDisplayToCodings';
34
+ import type { TargetConstraint } from '../../interfaces/targetConstraint.interface';
35
+ import { extractTargetConstraints } from './extractTargetConstraint';
34
36
 
35
37
  export async function createQuestionnaireModel(
36
38
  questionnaire: Questionnaire,
@@ -49,6 +51,8 @@ export async function createQuestionnaireModel(
49
51
  const pages: Pages = extractPages(questionnaire);
50
52
 
51
53
  const launchContexts: Record<string, LaunchContext> = extractLaunchContexts(questionnaire);
54
+ const targetConstraints: Record<string, TargetConstraint> =
55
+ extractTargetConstraints(questionnaire);
52
56
 
53
57
  let variables: Variables = extractQuestionnaireLevelVariables(questionnaire);
54
58
  variables = addAdditionalVariables(variables, additionalVariables);
@@ -109,6 +113,7 @@ export async function createQuestionnaireModel(
109
113
  pages,
110
114
  variables,
111
115
  launchContexts,
116
+ targetConstraints,
112
117
  enableWhenItems,
113
118
  enableWhenExpressions,
114
119
  calculatedExpressions,
@@ -130,6 +135,7 @@ function createEmptyModel(): QuestionnaireModel {
130
135
  pages: {},
131
136
  variables: { fhirPathVariables: {}, xFhirQueryVariables: {} },
132
137
  launchContexts: {},
138
+ targetConstraints: {},
133
139
  calculatedExpressions: {},
134
140
  initialExpressions: {},
135
141
  enableWhenExpressions: { singleExpressions: {}, repeatExpressions: {} },
@@ -0,0 +1,71 @@
1
+ import type { Extension, Questionnaire } from 'fhir/r4';
2
+ import type {
3
+ TargetConstraint,
4
+ TargetConstraintExpression,
5
+ TargetConstraintHuman,
6
+ TargetConstraintKey,
7
+ TargetConstraintLocation,
8
+ TargetConstraintSeverity
9
+ } from '../../interfaces/targetConstraint.interface';
10
+
11
+ export function constructTargetConstraint(extension: Extension): TargetConstraint | null {
12
+ const targetConstraintUrl = extension.url;
13
+ const targetConstraintKey = extension.extension?.find(
14
+ (ext) => ext.url === 'key' && typeof ext.valueId === 'string'
15
+ ) as TargetConstraintKey | undefined;
16
+
17
+ const targetConstraintSeverity = extension.extension?.find(
18
+ (ext) =>
19
+ ext.url === 'severity' &&
20
+ ext.valueCode &&
21
+ (ext.valueCode === 'error' || ext.valueCode === 'warning')
22
+ ) as TargetConstraintSeverity | undefined;
23
+
24
+ const targetConstraintExpression = extension.extension?.find(
25
+ (ext) => ext.url === 'expression' && ext.valueExpression
26
+ ) as TargetConstraintExpression | undefined;
27
+
28
+ const targetConstraintHuman = extension.extension?.find(
29
+ (ext) => ext.url === 'human' && ext.valueString
30
+ ) as TargetConstraintHuman | undefined;
31
+
32
+ const hasTargetConstraintLocation = extension.extension?.find(
33
+ (ext) => ext.url === 'location' && ext.valueString
34
+ ) as TargetConstraintLocation | undefined;
35
+
36
+ if (
37
+ targetConstraintUrl === 'http://hl7.org/fhir/StructureDefinition/targetConstraint' &&
38
+ targetConstraintKey &&
39
+ targetConstraintSeverity &&
40
+ targetConstraintExpression &&
41
+ targetConstraintHuman
42
+ ) {
43
+ return {
44
+ key: targetConstraintKey.valueId,
45
+ severityCode: targetConstraintSeverity.valueCode,
46
+ valueExpression: targetConstraintExpression.valueExpression,
47
+ human: targetConstraintHuman.valueString,
48
+ location: hasTargetConstraintLocation?.valueString
49
+ };
50
+ }
51
+
52
+ return null;
53
+ }
54
+
55
+ export function extractTargetConstraints(
56
+ questionnaire: Questionnaire
57
+ ): Record<string, TargetConstraint> {
58
+ if (!questionnaire.extension || questionnaire.extension.length === 0) {
59
+ return {};
60
+ }
61
+
62
+ const targetConstraints: Record<string, TargetConstraint> = {};
63
+ for (const ext of questionnaire.extension) {
64
+ const targetConstraint = constructTargetConstraint(ext);
65
+ if (targetConstraint?.key) {
66
+ targetConstraints[targetConstraint?.key] = targetConstraint;
67
+ }
68
+ }
69
+
70
+ return targetConstraints;
71
+ }