@aehrc/smart-forms-renderer 1.0.0-alpha.48.dev1 → 1.0.0-alpha.49
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.
- package/lib/components/Checkbox.styles.js +11 -0
- package/lib/components/Checkbox.styles.js.map +1 -1
- package/lib/components/FormComponents/BooleanItem/BooleanField.js +4 -2
- package/lib/components/FormComponents/BooleanItem/BooleanField.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/CheckboxFormGroup.d.ts +16 -0
- package/lib/components/FormComponents/ChoiceItems/CheckboxFormGroup.js +24 -0
- package/lib/components/FormComponents/ChoiceItems/CheckboxFormGroup.js.map +1 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceAutocompleteField.js +6 -10
- package/lib/components/FormComponents/ChoiceItems/ChoiceAutocompleteField.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxFormGroup.d.ts +16 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxFormGroup.js +24 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxFormGroup.js.map +1 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxGroup.d.ts +0 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxGroup.js +2 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxGroup.js.map +1 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxList.d.ts +0 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxList.js +2 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxList.js.map +1 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js +2 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js +2 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioGroup.d.ts +16 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioGroup.js +29 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioGroup.js.map +1 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioSingle.js +2 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioSingle.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/RadioFormGroup.d.ts +16 -0
- package/lib/components/FormComponents/ChoiceItems/RadioFormGroup.js +29 -0
- package/lib/components/FormComponents/ChoiceItems/RadioFormGroup.js.map +1 -0
- package/lib/components/FormComponents/ItemParts/CheckboxFormGroup.d.ts +16 -0
- package/lib/components/FormComponents/ItemParts/CheckboxFormGroup.js +24 -0
- package/lib/components/FormComponents/ItemParts/CheckboxFormGroup.js.map +1 -0
- package/lib/components/FormComponents/ItemParts/CheckboxSingle.js +1 -1
- package/lib/components/FormComponents/ItemParts/CheckboxSingle.js.map +1 -1
- package/lib/components/FormComponents/ItemParts/CheckboxSingleWithOpenLabel.js +1 -1
- package/lib/components/FormComponents/ItemParts/CheckboxSingleWithOpenLabel.js.map +1 -1
- package/lib/components/FormComponents/ItemParts/RadioFormGroup.d.ts +16 -0
- package/lib/components/FormComponents/ItemParts/RadioFormGroup.js +29 -0
- package/lib/components/FormComponents/ItemParts/RadioFormGroup.js.map +1 -0
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteField.js +5 -8
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteField.js.map +1 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.js +2 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.js.map +1 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerValueSetFields.js +2 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerValueSetFields.js.map +1 -1
- package/lib/components/FormComponents/Tables/GroupTableRow.js +2 -2
- package/lib/components/FormComponents/Tables/GroupTableRow.js.map +1 -1
- package/lib/components/FormComponents/Tables/GroupTableView.js +5 -2
- package/lib/components/FormComponents/Tables/GroupTableView.js.map +1 -1
- package/lib/components/FormComponents/Tables/SelectRowButton.d.ts +1 -1
- package/lib/components/FormComponents/Tables/SelectRowButton.js +2 -2
- package/lib/components/FormComponents/Tables/SelectRowButton.js.map +1 -1
- package/lib/components/Radio.styles.js +11 -0
- package/lib/components/Radio.styles.js.map +1 -1
- package/lib/hooks/useAnswerOptionsToggleExpressions.d.ts +7 -0
- package/lib/hooks/useAnswerOptionsToggleExpressions.js +67 -0
- package/lib/hooks/useAnswerOptionsToggleExpressions.js.map +1 -0
- package/lib/interfaces/answerOptionsToggleExpression.interface.d.ts +9 -0
- package/lib/interfaces/answerOptionsToggleExpression.interface.js +2 -0
- package/lib/interfaces/answerOptionsToggleExpression.interface.js.map +1 -0
- package/lib/stores/extractOperationStore.d.ts +26 -0
- package/lib/stores/extractOperationStore.js +20 -0
- package/lib/stores/extractOperationStore.js.map +1 -0
- package/lib/templates/bloodPressureTemplate.d.ts +23 -0
- package/lib/templates/bloodPressureTemplate.js +80 -0
- package/lib/templates/bloodPressureTemplate.js.map +1 -0
- package/lib/tests/test-data/bloodPressureSample.d.ts +3 -0
- package/lib/tests/test-data/bloodPressureSample.js +250 -0
- package/lib/tests/test-data/bloodPressureSample.js.map +1 -0
- package/lib/tests/test-data/complexTemplateSample.d.ts +3 -0
- package/lib/tests/test-data/complexTemplateSample.js +578 -0
- package/lib/tests/test-data/complexTemplateSample.js.map +1 -0
- package/lib/utils/answerOptionsToggleExpressions.d.ts +24 -0
- package/lib/utils/answerOptionsToggleExpressions.js +144 -0
- package/lib/utils/answerOptionsToggleExpressions.js.map +1 -0
- package/lib/utils/calculatedExpression.js +8 -5
- package/lib/utils/calculatedExpression.js.map +1 -1
- package/lib/utils/checkbox.d.ts +2 -0
- package/lib/utils/checkbox.js +6 -0
- package/lib/utils/checkbox.js.map +1 -0
- package/lib/utils/extractTemplate.d.ts +44 -0
- package/lib/utils/extractTemplate.js +809 -0
- package/lib/utils/extractTemplate.js.map +1 -0
- package/lib/utils/questionnaireStoreUtils/extractAnswerOptionsToggleExpressions.d.ts +3 -0
- package/lib/utils/questionnaireStoreUtils/extractAnswerOptionsToggleExpressions.js +6 -0
- package/lib/utils/questionnaireStoreUtils/extractAnswerOptionsToggleExpressions.js.map +1 -0
- package/lib/utils/templateMatching.d.ts +15 -0
- package/lib/utils/templateMatching.js +62 -0
- package/lib/utils/templateMatching.js.map +1 -0
- package/package.json +2 -2
- package/src/components/Checkbox.styles.ts +14 -0
- package/src/components/FormComponents/BooleanItem/BooleanField.tsx +7 -0
- package/src/components/FormComponents/ChoiceItems/ChoiceAutocompleteField.tsx +6 -10
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.tsx +2 -0
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.tsx +2 -0
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioSingle.tsx +2 -1
- package/src/components/FormComponents/ItemParts/CheckboxSingle.tsx +3 -0
- package/src/components/FormComponents/ItemParts/CheckboxSingleWithOpenLabel.tsx +3 -0
- package/src/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteField.tsx +5 -8
- package/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.tsx +2 -0
- package/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerValueSetFields.tsx +2 -0
- package/src/components/FormComponents/Tables/GroupTableRow.tsx +2 -2
- package/src/components/FormComponents/Tables/GroupTableView.tsx +14 -6
- package/src/components/FormComponents/Tables/SelectRowButton.tsx +6 -3
- package/src/components/Radio.styles.tsx +13 -0
- package/src/utils/calculatedExpression.ts +13 -5
- package/src/utils/checkbox.ts +7 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import fhirpath from 'fhirpath';
|
|
11
|
+
import fhirpath_r4_model from 'fhirpath/fhir-context/r4';
|
|
12
|
+
import { findMatchingItem, FHIR_TEMPLATE_EXTRACT_EXTENSION } from './templateMatching';
|
|
13
|
+
const FHIR_TEMPLATE_REFERENCE_EXTENSION = 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-templateReference';
|
|
14
|
+
const TEMPLATE_EXTRACT_VALUE_EXTENSION = 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-templateExtractValue';
|
|
15
|
+
function evaluateTemplateExpression(questionnaireResponse, expression, context) {
|
|
16
|
+
try {
|
|
17
|
+
// If we have a context, use it as the starting point for evaluation
|
|
18
|
+
const evaluationContext = context || questionnaireResponse;
|
|
19
|
+
// Handle special cases for nested structures
|
|
20
|
+
if (expression.includes('item.where')) {
|
|
21
|
+
// Process nested item expressions
|
|
22
|
+
const result = fhirpath.evaluate(evaluationContext, expression, {}, // Empty context object
|
|
23
|
+
fhirpath_r4_model, {
|
|
24
|
+
async: false
|
|
25
|
+
});
|
|
26
|
+
if (Array.isArray(result)) {
|
|
27
|
+
if (result.length === 0)
|
|
28
|
+
return null;
|
|
29
|
+
if (result.length === 1)
|
|
30
|
+
return result[0];
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
// Handle direct value references
|
|
36
|
+
const result = fhirpath.evaluate(evaluationContext, expression, {}, // Empty context object
|
|
37
|
+
fhirpath_r4_model, {
|
|
38
|
+
async: false
|
|
39
|
+
});
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error('Error evaluating FHIRPath expression:', error);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function findNestedItem(items, linkId) {
|
|
48
|
+
if (!items)
|
|
49
|
+
return null;
|
|
50
|
+
for (const item of items) {
|
|
51
|
+
if (item.linkId === linkId) {
|
|
52
|
+
return item;
|
|
53
|
+
}
|
|
54
|
+
if (item.item) {
|
|
55
|
+
const nested = findNestedItem(item.item, linkId);
|
|
56
|
+
if (nested)
|
|
57
|
+
return nested;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function getNestedAnswerValue(response, linkId, parentLinkId) {
|
|
63
|
+
var _a;
|
|
64
|
+
console.log(`Looking for value at linkId: ${linkId} (parent: ${parentLinkId || 'none'})`);
|
|
65
|
+
// More flexible recursive item finder
|
|
66
|
+
const findItem = (items, currentLevel = 0) => {
|
|
67
|
+
if (!items)
|
|
68
|
+
return null;
|
|
69
|
+
for (const item of items) {
|
|
70
|
+
console.log(`${' '.repeat(currentLevel * 2)}Checking item: ${item.linkId}`);
|
|
71
|
+
// Direct match
|
|
72
|
+
if (item.linkId === linkId) {
|
|
73
|
+
console.log(`${' '.repeat(currentLevel * 2)}✅ Found direct match: ${item.linkId}`);
|
|
74
|
+
return item;
|
|
75
|
+
}
|
|
76
|
+
// Parent match, check children
|
|
77
|
+
if (parentLinkId && item.linkId === parentLinkId && item.item) {
|
|
78
|
+
console.log(`${' '.repeat(currentLevel * 2)}Found parent: ${item.linkId}, checking children...`);
|
|
79
|
+
for (const childItem of item.item) {
|
|
80
|
+
if (childItem.linkId === linkId) {
|
|
81
|
+
console.log(`${' '.repeat(currentLevel * 2)}✅ Found child match: ${childItem.linkId}`);
|
|
82
|
+
return childItem;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Recursive search in children
|
|
87
|
+
if (item.item) {
|
|
88
|
+
const foundItem = findItem(item.item, currentLevel + 1);
|
|
89
|
+
if (foundItem)
|
|
90
|
+
return foundItem;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
95
|
+
const item = findItem(response.item);
|
|
96
|
+
if (!item || !item.answer || item.answer.length === 0) {
|
|
97
|
+
console.log(`❌ No answer found for linkId: ${linkId}`);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const answer = item.answer[0];
|
|
101
|
+
// Extract the value based on its type
|
|
102
|
+
if (answer.valueDecimal !== undefined)
|
|
103
|
+
return answer.valueDecimal;
|
|
104
|
+
if (answer.valueInteger !== undefined)
|
|
105
|
+
return answer.valueInteger;
|
|
106
|
+
if (answer.valueString !== undefined)
|
|
107
|
+
return answer.valueString;
|
|
108
|
+
if (answer.valueBoolean !== undefined)
|
|
109
|
+
return answer.valueBoolean;
|
|
110
|
+
if (((_a = answer.valueQuantity) === null || _a === void 0 ? void 0 : _a.value) !== undefined)
|
|
111
|
+
return answer.valueQuantity.value;
|
|
112
|
+
console.log(`❌ No supported value type found in answer for linkId: ${linkId}`);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function hasTemplateExtractExtension(item) {
|
|
116
|
+
var _a;
|
|
117
|
+
// Enhanced detection with detailed logging
|
|
118
|
+
console.log(`Checking for template extraction extension in item with linkId: ${item.linkId || 'root questionnaire'}`);
|
|
119
|
+
if (!item.extension || item.extension.length === 0) {
|
|
120
|
+
console.log(`No extensions found for item ${item.linkId || 'root questionnaire'}`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
for (const ext of item.extension) {
|
|
124
|
+
console.log(`Examining extension with URL: ${ext.url}`);
|
|
125
|
+
if (ext.url === FHIR_TEMPLATE_EXTRACT_EXTENSION) {
|
|
126
|
+
// Check for direct boolean value
|
|
127
|
+
if (ext.valueBoolean !== undefined) {
|
|
128
|
+
console.log(`Found template extraction extension with valueBoolean: ${ext.valueBoolean}`);
|
|
129
|
+
return ext.valueBoolean;
|
|
130
|
+
}
|
|
131
|
+
// Check for nested template reference
|
|
132
|
+
if (ext.extension) {
|
|
133
|
+
for (const subExt of ext.extension) {
|
|
134
|
+
if (subExt.url === 'template' && ((_a = subExt.valueReference) === null || _a === void 0 ? void 0 : _a.reference)) {
|
|
135
|
+
console.log(`Found template reference: ${subExt.valueReference.reference}`);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Processes datetime extraction from templates, handling both static values and expressions
|
|
146
|
+
* @param template The template containing datetime fields
|
|
147
|
+
* @param response The questionnaire response to extract values from
|
|
148
|
+
* @returns An ISO formatted datetime string
|
|
149
|
+
*/
|
|
150
|
+
function processDateTimeExtraction(template, response, debugInfo) {
|
|
151
|
+
var _a, _b;
|
|
152
|
+
// Check if we have an effectiveDateTime with extraction extension
|
|
153
|
+
const effectiveDateTime = template.effectiveDateTime;
|
|
154
|
+
const effectiveDateTimeExt = (_b = (_a = template._effectiveDateTime) === null || _a === void 0 ? void 0 : _a.extension) === null || _b === void 0 ? void 0 : _b.find((ext) => ext.url === TEMPLATE_EXTRACT_VALUE_EXTENSION);
|
|
155
|
+
// Initialize datetime debug info
|
|
156
|
+
debugInfo.valueProcessing.datetime = {
|
|
157
|
+
source: 'fallback',
|
|
158
|
+
value: ''
|
|
159
|
+
};
|
|
160
|
+
if (effectiveDateTimeExt === null || effectiveDateTimeExt === void 0 ? void 0 : effectiveDateTimeExt.valueString) {
|
|
161
|
+
// Handle specific extraction expressions
|
|
162
|
+
if (effectiveDateTimeExt.valueString === 'now()') {
|
|
163
|
+
// Use current date/time
|
|
164
|
+
const now = new Date().toISOString();
|
|
165
|
+
console.log(`Processing datetime with now() function: ${now}`);
|
|
166
|
+
// Update datetime debug info
|
|
167
|
+
debugInfo.valueProcessing.datetime = {
|
|
168
|
+
source: 'dynamic',
|
|
169
|
+
expression: 'now()',
|
|
170
|
+
value: now,
|
|
171
|
+
originalValue: effectiveDateTime
|
|
172
|
+
};
|
|
173
|
+
return now;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// Try to evaluate as a FHIRPath expression
|
|
177
|
+
const extractedDateTime = evaluateTemplateExpression(response, effectiveDateTimeExt.valueString);
|
|
178
|
+
if (extractedDateTime) {
|
|
179
|
+
console.log(`Extracted datetime using expression: ${extractedDateTime}`);
|
|
180
|
+
// Update datetime debug info
|
|
181
|
+
debugInfo.valueProcessing.datetime = {
|
|
182
|
+
source: 'dynamic',
|
|
183
|
+
expression: effectiveDateTimeExt.valueString,
|
|
184
|
+
value: extractedDateTime,
|
|
185
|
+
originalValue: effectiveDateTime
|
|
186
|
+
};
|
|
187
|
+
return extractedDateTime;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Default to template's static value if available
|
|
192
|
+
if (effectiveDateTime) {
|
|
193
|
+
console.log(`Using template's static datetime: ${effectiveDateTime}`);
|
|
194
|
+
// Update datetime debug info
|
|
195
|
+
debugInfo.valueProcessing.datetime = {
|
|
196
|
+
source: 'static',
|
|
197
|
+
value: effectiveDateTime
|
|
198
|
+
};
|
|
199
|
+
return effectiveDateTime;
|
|
200
|
+
}
|
|
201
|
+
// Final fallback to current date/time
|
|
202
|
+
const fallbackTime = new Date().toISOString();
|
|
203
|
+
console.log(`No datetime information found, using current time: ${fallbackTime}`);
|
|
204
|
+
// Update datetime debug info
|
|
205
|
+
debugInfo.valueProcessing.datetime = {
|
|
206
|
+
source: 'fallback',
|
|
207
|
+
value: fallbackTime
|
|
208
|
+
};
|
|
209
|
+
return fallbackTime;
|
|
210
|
+
}
|
|
211
|
+
export function extractTemplateBased(questionnaire, response) {
|
|
212
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
213
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
214
|
+
const debugInfo = {
|
|
215
|
+
contentAnalysis: {
|
|
216
|
+
detectedTemplates: [],
|
|
217
|
+
confidence: 'Valid',
|
|
218
|
+
patterns: []
|
|
219
|
+
},
|
|
220
|
+
fieldMapping: {
|
|
221
|
+
mappedFields: {},
|
|
222
|
+
assumptions: [],
|
|
223
|
+
alternatives: []
|
|
224
|
+
},
|
|
225
|
+
valueProcessing: {
|
|
226
|
+
values: {},
|
|
227
|
+
transformations: [],
|
|
228
|
+
qualityChecks: []
|
|
229
|
+
},
|
|
230
|
+
resultGeneration: {
|
|
231
|
+
status: 'Pending',
|
|
232
|
+
observations: []
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
console.log('=== Template Extraction Debug ===');
|
|
236
|
+
console.log('Stage 1: Initial Validation');
|
|
237
|
+
// Enhanced validation with more detailed error reporting
|
|
238
|
+
if (!questionnaire) {
|
|
239
|
+
console.log('❌ Failed: Questionnaire is undefined or null');
|
|
240
|
+
debugInfo.contentAnalysis.confidence = 'Invalid';
|
|
241
|
+
return {
|
|
242
|
+
result: null,
|
|
243
|
+
error: 'Missing questionnaire',
|
|
244
|
+
debugInfo
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (!questionnaire.item) {
|
|
248
|
+
console.log('❌ Failed: Questionnaire has no items');
|
|
249
|
+
debugInfo.contentAnalysis.confidence = 'Invalid';
|
|
250
|
+
return {
|
|
251
|
+
result: null,
|
|
252
|
+
error: 'Questionnaire has no items',
|
|
253
|
+
debugInfo
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (!questionnaire.contained || questionnaire.contained.length === 0) {
|
|
257
|
+
console.log('❌ Failed: Questionnaire has no contained resources for templates');
|
|
258
|
+
debugInfo.contentAnalysis.confidence = 'Invalid';
|
|
259
|
+
return {
|
|
260
|
+
result: null,
|
|
261
|
+
error: 'Questionnaire has no contained resources for templates',
|
|
262
|
+
debugInfo
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (!response.item) {
|
|
266
|
+
console.log('❌ Failed: Response has no items');
|
|
267
|
+
debugInfo.contentAnalysis.confidence = 'Invalid';
|
|
268
|
+
return {
|
|
269
|
+
result: null,
|
|
270
|
+
error: 'Response has no items',
|
|
271
|
+
debugInfo
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
console.log('✅ Passed: Required items present');
|
|
275
|
+
// Check for template extraction extension at questionnaire or item level
|
|
276
|
+
console.log('\nStage 2: Template Extraction Extension Check');
|
|
277
|
+
// Enhanced template detection with recursive item checking
|
|
278
|
+
const hasTemplateExtract = hasTemplateExtractExtension(questionnaire);
|
|
279
|
+
let itemsWithTemplates = [];
|
|
280
|
+
const checkItemsForTemplates = (items) => {
|
|
281
|
+
let found = false;
|
|
282
|
+
for (const item of items) {
|
|
283
|
+
if (hasTemplateExtractExtension(item)) {
|
|
284
|
+
found = true;
|
|
285
|
+
itemsWithTemplates.push(item.linkId);
|
|
286
|
+
}
|
|
287
|
+
if (item.item && checkItemsForTemplates(item.item)) {
|
|
288
|
+
found = true;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return found;
|
|
292
|
+
};
|
|
293
|
+
const hasItemTemplates = checkItemsForTemplates(questionnaire.item);
|
|
294
|
+
if (!hasTemplateExtract && !hasItemTemplates) {
|
|
295
|
+
console.log('❌ Failed: No template extraction extension found');
|
|
296
|
+
debugInfo.contentAnalysis.confidence = 'Invalid';
|
|
297
|
+
debugInfo.contentAnalysis.detectedTemplates.push('No template type detected');
|
|
298
|
+
// Enhanced debug information for template detection failure
|
|
299
|
+
if (questionnaire.extension && questionnaire.extension.length > 0) {
|
|
300
|
+
const urls = questionnaire.extension.map(e => e.url).join(', ');
|
|
301
|
+
debugInfo.fieldMapping.assumptions.push(`Found extensions but none match template extraction: ${urls}`);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
debugInfo.fieldMapping.assumptions.push('No extensions found at questionnaire level');
|
|
305
|
+
}
|
|
306
|
+
if (itemsWithTemplates.length > 0) {
|
|
307
|
+
debugInfo.fieldMapping.assumptions.push(`Found items with extensions: ${itemsWithTemplates.join(', ')}`);
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
result: null,
|
|
311
|
+
error: 'Questionnaire is not configured for template extraction',
|
|
312
|
+
debugInfo
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (hasTemplateExtract) {
|
|
316
|
+
console.log('✅ Passed: Template extraction extension found at questionnaire level');
|
|
317
|
+
}
|
|
318
|
+
if (hasItemTemplates) {
|
|
319
|
+
console.log(`✅ Passed: Template extraction extensions found at item level: ${itemsWithTemplates.join(', ')}`);
|
|
320
|
+
}
|
|
321
|
+
const templates = questionnaire.contained;
|
|
322
|
+
console.log('\nStage 3: Template Analysis');
|
|
323
|
+
console.log(`Found ${templates.length} templates:`);
|
|
324
|
+
// Enhanced template analysis
|
|
325
|
+
const observationTemplates = templates.filter(t => t.resourceType === 'Observation');
|
|
326
|
+
const patientTemplates = templates.filter(t => t.resourceType === 'Patient');
|
|
327
|
+
const otherTemplates = templates.filter(t => !['Observation', 'Patient'].includes(t.resourceType));
|
|
328
|
+
console.log(`- ${observationTemplates.length} Observation templates`);
|
|
329
|
+
console.log(`- ${patientTemplates.length} Patient templates`);
|
|
330
|
+
console.log(`- ${otherTemplates.length} Other templates`);
|
|
331
|
+
templates.forEach(template => {
|
|
332
|
+
const templateId = template.id || 'unnamed';
|
|
333
|
+
console.log(`- ${template.resourceType}: ${templateId}`);
|
|
334
|
+
debugInfo.contentAnalysis.detectedTemplates.push(templateId);
|
|
335
|
+
});
|
|
336
|
+
const observations = [];
|
|
337
|
+
// Initialize field mapping with template information
|
|
338
|
+
console.log('\nStage 4: Field Mapping');
|
|
339
|
+
debugInfo.fieldMapping.mappedFields = templates.reduce((acc, template) => {
|
|
340
|
+
var _a, _b, _c, _d;
|
|
341
|
+
if (template.resourceType === 'Observation') {
|
|
342
|
+
const code = (_c = (_b = (_a = template.code) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.code;
|
|
343
|
+
if (code) {
|
|
344
|
+
// Skip the boolean observation for now
|
|
345
|
+
if (template._valueBoolean)
|
|
346
|
+
return acc;
|
|
347
|
+
// Find the corresponding item in the questionnaire using the enhanced matcher
|
|
348
|
+
const item = findMatchingItem(questionnaire.item, template.id || 'unknown', template.resourceType, (_d = template.code) === null || _d === void 0 ? void 0 : _d.coding);
|
|
349
|
+
if (!item) {
|
|
350
|
+
console.log(`❌ Failed to find corresponding item in questionnaire for template: ${template.id}`);
|
|
351
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
352
|
+
check: `Process ${template.id || 'unknown'}`,
|
|
353
|
+
passed: false,
|
|
354
|
+
message: 'Failed to find corresponding item in questionnaire'
|
|
355
|
+
});
|
|
356
|
+
return acc;
|
|
357
|
+
}
|
|
358
|
+
// Enhanced path detection with better support for different questionnaire structures
|
|
359
|
+
let valuePath = "";
|
|
360
|
+
// Check for parent hierarchy to build proper path
|
|
361
|
+
const buildPath = (items, targetLinkId, path = "") => {
|
|
362
|
+
if (!items)
|
|
363
|
+
return "";
|
|
364
|
+
for (const i of items) {
|
|
365
|
+
if (i.linkId === targetLinkId)
|
|
366
|
+
return path ? `${path}.item.where(linkId='${targetLinkId}')` : `item.where(linkId='${targetLinkId}')`;
|
|
367
|
+
if (i.item) {
|
|
368
|
+
const newPath = path ? `${path}.item.where(linkId='${i.linkId}')` : `item.where(linkId='${i.linkId}')`;
|
|
369
|
+
const result = buildPath(i.item, targetLinkId, newPath);
|
|
370
|
+
if (result)
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return "";
|
|
375
|
+
};
|
|
376
|
+
valuePath = buildPath(questionnaire.item, item.linkId);
|
|
377
|
+
if (!valuePath) {
|
|
378
|
+
valuePath = `item.where(linkId='${item.linkId}')`;
|
|
379
|
+
}
|
|
380
|
+
// Add answer type based on item type
|
|
381
|
+
switch (item.type) {
|
|
382
|
+
case 'decimal':
|
|
383
|
+
valuePath += '.answer.valueDecimal';
|
|
384
|
+
break;
|
|
385
|
+
case 'integer':
|
|
386
|
+
valuePath += '.answer.valueInteger';
|
|
387
|
+
break;
|
|
388
|
+
case 'boolean':
|
|
389
|
+
valuePath += '.answer.valueBoolean';
|
|
390
|
+
break;
|
|
391
|
+
case 'string':
|
|
392
|
+
valuePath += '.answer.valueString';
|
|
393
|
+
break;
|
|
394
|
+
case 'quantity':
|
|
395
|
+
valuePath += '.answer.valueQuantity.value';
|
|
396
|
+
break;
|
|
397
|
+
default:
|
|
398
|
+
valuePath += '.answer.value';
|
|
399
|
+
}
|
|
400
|
+
acc[code] = {
|
|
401
|
+
templateId: template.id,
|
|
402
|
+
type: template.resourceType,
|
|
403
|
+
valuePath: valuePath,
|
|
404
|
+
itemType: item.type,
|
|
405
|
+
itemLinkId: item.linkId,
|
|
406
|
+
itemText: item.text || "No text"
|
|
407
|
+
};
|
|
408
|
+
console.log(`Mapped field: ${code} -> ${template.id} (${valuePath})`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return acc;
|
|
412
|
+
}, {});
|
|
413
|
+
// Add relevant assumptions about the mapping based on found patterns
|
|
414
|
+
if (Object.keys(debugInfo.fieldMapping.mappedFields).length === 0) {
|
|
415
|
+
debugInfo.fieldMapping.assumptions.push('No fields could be mapped between templates and questionnaire items');
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
debugInfo.fieldMapping.assumptions.push(`Successfully mapped ${Object.keys(debugInfo.fieldMapping.mappedFields).length} fields`);
|
|
419
|
+
}
|
|
420
|
+
// Check for specific patterns in the template (like height, weight, blood pressure)
|
|
421
|
+
const hasHeight = observationTemplates.some(t => { var _a, _b; return (_b = (_a = t.code) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b.some(c => { var _a; return c.code === '8302-2' || ((_a = c.display) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('height')); }); });
|
|
422
|
+
const hasWeight = observationTemplates.some(t => { var _a, _b; return (_b = (_a = t.code) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b.some(c => { var _a; return c.code === '29463-7' || ((_a = c.display) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('weight')); }); });
|
|
423
|
+
// More precise BP detection to avoid false positives
|
|
424
|
+
const hasBpLoinc = observationTemplates.some(t => { var _a, _b; return (_b = (_a = t.code) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b.some(c => c.code === '85354-9'); } // Blood pressure panel with all children optional
|
|
425
|
+
);
|
|
426
|
+
const hasSystolicComponent = observationTemplates.some(t => {
|
|
427
|
+
var _a;
|
|
428
|
+
return (_a = t.component) === null || _a === void 0 ? void 0 : _a.some(comp => { var _a, _b; return (_b = (_a = comp.code) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b.some(c => { var _a; return c.code === '8480-6' || ((_a = c.display) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('systolic')); }); });
|
|
429
|
+
});
|
|
430
|
+
const hasDiastolicComponent = observationTemplates.some(t => {
|
|
431
|
+
var _a;
|
|
432
|
+
return (_a = t.component) === null || _a === void 0 ? void 0 : _a.some(comp => { var _a, _b; return (_b = (_a = comp.code) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b.some(c => { var _a; return c.code === '8462-4' || ((_a = c.display) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('diastolic')); }); });
|
|
433
|
+
});
|
|
434
|
+
// Separate standalone templates with BP codes
|
|
435
|
+
const hasSystolicTemplate = observationTemplates.some(t => { var _a, _b; return (_b = (_a = t.code) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b.some(c => { var _a; return c.code === '8480-6' || ((_a = c.display) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('systolic')); }); });
|
|
436
|
+
const hasDiastolicTemplate = observationTemplates.some(t => { var _a, _b; return (_b = (_a = t.code) === null || _a === void 0 ? void 0 : _a.coding) === null || _b === void 0 ? void 0 : _b.some(c => { var _a; return c.code === '8462-4' || ((_a = c.display) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('diastolic')); }); });
|
|
437
|
+
// Content-driven pattern detection
|
|
438
|
+
// Instead of fixed assumptions, detect patterns dynamically based on content
|
|
439
|
+
const patterns = [];
|
|
440
|
+
const assumptions = [];
|
|
441
|
+
if (observationTemplates.length > 0) {
|
|
442
|
+
assumptions.push(`Found ${observationTemplates.length} observation templates`);
|
|
443
|
+
}
|
|
444
|
+
if (hasHeight) {
|
|
445
|
+
patterns.push('Height Measurement');
|
|
446
|
+
if (hasWeight) {
|
|
447
|
+
// Both height and weight together form a pattern
|
|
448
|
+
assumptions.push('Detected height and weight template pattern');
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
// Just height alone
|
|
452
|
+
assumptions.push('Detected height measurement template');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else if (hasWeight) {
|
|
456
|
+
// Just weight alone
|
|
457
|
+
patterns.push('Weight Measurement');
|
|
458
|
+
assumptions.push('Detected weight measurement template');
|
|
459
|
+
}
|
|
460
|
+
// Only detect BP if we have a proper complete BP pattern
|
|
461
|
+
const isCompleteBpPattern = hasBpLoinc ||
|
|
462
|
+
(hasSystolicComponent && hasDiastolicComponent) ||
|
|
463
|
+
(hasSystolicTemplate && hasDiastolicTemplate);
|
|
464
|
+
if (isCompleteBpPattern) {
|
|
465
|
+
patterns.push('Blood Pressure Measurement');
|
|
466
|
+
assumptions.push('Detected complete blood pressure template pattern');
|
|
467
|
+
}
|
|
468
|
+
else if (hasSystolicTemplate && !hasDiastolicTemplate) {
|
|
469
|
+
// Just systolic without diastolic - not a complete BP pattern
|
|
470
|
+
patterns.push('Systolic Measurement');
|
|
471
|
+
assumptions.push('Detected systolic pressure template (incomplete BP pattern)');
|
|
472
|
+
}
|
|
473
|
+
else if (!hasSystolicTemplate && hasDiastolicTemplate) {
|
|
474
|
+
// Just diastolic without systolic - not a complete BP pattern
|
|
475
|
+
patterns.push('Diastolic Measurement');
|
|
476
|
+
assumptions.push('Detected diastolic pressure template (incomplete BP pattern)');
|
|
477
|
+
}
|
|
478
|
+
// Add the detected patterns to debug info
|
|
479
|
+
debugInfo.contentAnalysis.patterns.push(...patterns);
|
|
480
|
+
// Add the assumptions to debug info
|
|
481
|
+
debugInfo.fieldMapping.assumptions.push(...assumptions);
|
|
482
|
+
// Update debug info with resource types
|
|
483
|
+
debugInfo.contentAnalysis.patterns.push(...templates.map(template => `${template.resourceType}: ${template.id || 'unnamed'}`));
|
|
484
|
+
console.log('\nStage 5: Template Processing');
|
|
485
|
+
// Process each template
|
|
486
|
+
for (const template of templates) {
|
|
487
|
+
try {
|
|
488
|
+
if (template.resourceType === 'Observation') {
|
|
489
|
+
const templateId = template.id || 'unnamed';
|
|
490
|
+
console.log(`\nProcessing template: ${templateId}`);
|
|
491
|
+
// Skip the boolean observation for now
|
|
492
|
+
if (template._valueBoolean)
|
|
493
|
+
continue;
|
|
494
|
+
const observation = Object.assign(Object.assign({}, template), { id: undefined // Remove template ID
|
|
495
|
+
});
|
|
496
|
+
// Find the corresponding item in the questionnaire using the enhanced matcher
|
|
497
|
+
const item = findMatchingItem(questionnaire.item, template.id || 'unknown', template.resourceType, (_a = template.code) === null || _a === void 0 ? void 0 : _a.coding);
|
|
498
|
+
if (!item) {
|
|
499
|
+
console.log(`❌ Failed to find corresponding item in questionnaire for template: ${template.id}`);
|
|
500
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
501
|
+
check: `Process ${template.id || 'unknown'}`,
|
|
502
|
+
passed: false,
|
|
503
|
+
message: 'Failed to find corresponding item in questionnaire'
|
|
504
|
+
});
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
// Process datetime values for the observation
|
|
508
|
+
const originalDateTime = template.effectiveDateTime;
|
|
509
|
+
observation.effectiveDateTime = processDateTimeExtraction(template, response, debugInfo);
|
|
510
|
+
// Add information to debug info
|
|
511
|
+
debugInfo.valueProcessing.transformations.push(`Set effectiveDateTime to ${observation.effectiveDateTime} for ${templateId}`);
|
|
512
|
+
// Remove the extraction extension since it's been processed
|
|
513
|
+
if (observation._effectiveDateTime) {
|
|
514
|
+
delete observation._effectiveDateTime;
|
|
515
|
+
}
|
|
516
|
+
// Process value extraction with improved nested handling
|
|
517
|
+
if (template.valueQuantity) {
|
|
518
|
+
// First try direct extension on valueQuantity (common in test files)
|
|
519
|
+
let valueExt = (_b = template.valueQuantity.extension) === null || _b === void 0 ? void 0 : _b.find((ext) => ext.url === TEMPLATE_EXTRACT_VALUE_EXTENSION);
|
|
520
|
+
// If not found, try the nested structure from HL7 SDC examples
|
|
521
|
+
if (!valueExt && ((_c = template.valueQuantity._value) === null || _c === void 0 ? void 0 : _c.extension)) {
|
|
522
|
+
valueExt = template.valueQuantity._value.extension.find((ext) => ext.url === TEMPLATE_EXTRACT_VALUE_EXTENSION);
|
|
523
|
+
console.log('Using nested _value extension structure from HL7 SDC example');
|
|
524
|
+
}
|
|
525
|
+
if (valueExt === null || valueExt === void 0 ? void 0 : valueExt.valueString) {
|
|
526
|
+
console.log(`Extracting value with expression: ${valueExt.valueString}`);
|
|
527
|
+
let value;
|
|
528
|
+
// Handle direct answer value references
|
|
529
|
+
if (valueExt.valueString.includes('answer.value')) {
|
|
530
|
+
value = getNestedAnswerValue(response, item.linkId);
|
|
531
|
+
if (value !== null) {
|
|
532
|
+
// Improved unit detection and value conversion based on context
|
|
533
|
+
const isHeightTemplate = (_e = (_d = template.code) === null || _d === void 0 ? void 0 : _d.coding) === null || _e === void 0 ? void 0 : _e.some(c => { var _a; return c.code === '8302-2' || ((_a = c.display) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('height')); });
|
|
534
|
+
// Check if a unit conversion is needed
|
|
535
|
+
if (isHeightTemplate) {
|
|
536
|
+
console.log(`Processing height value: ${value}`);
|
|
537
|
+
// Determine the source unit from the questionnaire item
|
|
538
|
+
const unitExtension = (_f = item.extension) === null || _f === void 0 ? void 0 : _f.find(ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/questionnaire-unit');
|
|
539
|
+
const sourceUnit = ((_g = unitExtension === null || unitExtension === void 0 ? void 0 : unitExtension.valueCoding) === null || _g === void 0 ? void 0 : _g.code) || 'm';
|
|
540
|
+
// Extract target unit from template
|
|
541
|
+
const targetUnit = template.valueQuantity.unit || 'cm';
|
|
542
|
+
// Intelligence for unit conversion
|
|
543
|
+
if (sourceUnit === 'm' && targetUnit === 'cm') {
|
|
544
|
+
// Smart unit conversion - only apply if the value looks like meters
|
|
545
|
+
if (value < 3) {
|
|
546
|
+
const originalValue = value;
|
|
547
|
+
value = value * 100;
|
|
548
|
+
console.log(`Converting height from ${originalValue}m to ${value}cm`);
|
|
549
|
+
debugInfo.valueProcessing.transformations.push(`Converted height from ${originalValue}m to ${value}cm for ${templateId}`);
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
// Value is likely already in cm
|
|
553
|
+
console.log(`Height value ${value} appears to already be in cm, no conversion needed`);
|
|
554
|
+
debugInfo.valueProcessing.transformations.push(`Height value ${value} appears to already be in cm, no conversion needed for ${templateId}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
// Not a height template, just extract the value
|
|
560
|
+
debugInfo.valueProcessing.transformations.push(`Extracted value ${value} for ${templateId}`);
|
|
561
|
+
}
|
|
562
|
+
observation.valueQuantity = {
|
|
563
|
+
value: value,
|
|
564
|
+
unit: template.valueQuantity.unit,
|
|
565
|
+
system: template.valueQuantity.system,
|
|
566
|
+
code: template.valueQuantity.code
|
|
567
|
+
};
|
|
568
|
+
debugInfo.valueProcessing.values[templateId] = value;
|
|
569
|
+
console.log(`✅ Value extracted: ${value}`);
|
|
570
|
+
// Enhanced quality checks based on value type
|
|
571
|
+
if (isHeightTemplate) {
|
|
572
|
+
const inReasonableRange = value > 30 && value < 250;
|
|
573
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
574
|
+
check: `Height value in reasonable range (${value}cm)`,
|
|
575
|
+
passed: inReasonableRange,
|
|
576
|
+
message: inReasonableRange
|
|
577
|
+
? 'Height within expected range'
|
|
578
|
+
: 'Height outside normal range, verify input'
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
else if ((_j = (_h = template.code) === null || _h === void 0 ? void 0 : _h.coding) === null || _j === void 0 ? void 0 : _j.some(c => { var _a; return c.code === '29463-7' || ((_a = c.display) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('weight')); })) {
|
|
582
|
+
const inReasonableRange = value > 0.1 && value < 300;
|
|
583
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
584
|
+
check: `Weight value in reasonable range (${value}kg)`,
|
|
585
|
+
passed: inReasonableRange,
|
|
586
|
+
message: inReasonableRange
|
|
587
|
+
? 'Weight within expected range'
|
|
588
|
+
: 'Weight outside normal range, verify input'
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
592
|
+
check: `Process ${templateId}`,
|
|
593
|
+
passed: true,
|
|
594
|
+
message: 'Successfully processed observation'
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
console.log('❌ Failed to extract value');
|
|
599
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
600
|
+
check: `Process ${templateId}`,
|
|
601
|
+
passed: false,
|
|
602
|
+
message: 'Failed to extract value from questionnaire response'
|
|
603
|
+
});
|
|
604
|
+
continue; // Skip this observation if value extraction failed
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
// Handle complex FHIRPath expressions
|
|
609
|
+
value = evaluateTemplateExpression(response, valueExt.valueString);
|
|
610
|
+
if (value !== null) {
|
|
611
|
+
observation.valueQuantity = {
|
|
612
|
+
value: value,
|
|
613
|
+
unit: template.valueQuantity.unit,
|
|
614
|
+
system: template.valueQuantity.system,
|
|
615
|
+
code: template.valueQuantity.code
|
|
616
|
+
};
|
|
617
|
+
debugInfo.valueProcessing.values[templateId] = value;
|
|
618
|
+
debugInfo.valueProcessing.transformations.push(`Extracted value ${value} for ${templateId} using expression`);
|
|
619
|
+
console.log(`✅ Value extracted with expression: ${value}`);
|
|
620
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
621
|
+
check: `Process ${templateId}`,
|
|
622
|
+
passed: true,
|
|
623
|
+
message: 'Successfully processed observation'
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
console.log('❌ Failed to extract value with expression');
|
|
628
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
629
|
+
check: `Process ${templateId}`,
|
|
630
|
+
passed: false,
|
|
631
|
+
message: 'Failed to extract value using expression'
|
|
632
|
+
});
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
console.log('❌ No template extraction value extension found');
|
|
639
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
640
|
+
check: `Process ${templateId}`,
|
|
641
|
+
passed: false,
|
|
642
|
+
message: 'No template extraction value extension found'
|
|
643
|
+
});
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
else if (template._valueBoolean) {
|
|
648
|
+
// Handle boolean observations
|
|
649
|
+
console.log('Processing boolean observation template');
|
|
650
|
+
// Get the template extraction expression from the boolean extension
|
|
651
|
+
const booleanExt = (_l = (_k = template._valueBoolean) === null || _k === void 0 ? void 0 : _k.extension) === null || _l === void 0 ? void 0 : _l.find((ext) => ext.url === TEMPLATE_EXTRACT_VALUE_EXTENSION);
|
|
652
|
+
if (!(booleanExt === null || booleanExt === void 0 ? void 0 : booleanExt.valueString)) {
|
|
653
|
+
console.log('❌ No template extraction value extension found for boolean value');
|
|
654
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
655
|
+
check: `Process ${templateId}`,
|
|
656
|
+
passed: false,
|
|
657
|
+
message: 'No template extraction value extension found for boolean value'
|
|
658
|
+
});
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
console.log(`Boolean extraction expression: ${booleanExt.valueString}`);
|
|
662
|
+
// Use the extraction expression
|
|
663
|
+
if (booleanExt.valueString.includes('answer.value')) {
|
|
664
|
+
const boolValue = getNestedAnswerValue(response, item.linkId);
|
|
665
|
+
if (boolValue !== null) {
|
|
666
|
+
observation.valueBoolean = boolValue === true || boolValue === 'true';
|
|
667
|
+
debugInfo.valueProcessing.values[templateId] = boolValue;
|
|
668
|
+
debugInfo.valueProcessing.transformations.push(`Extracted boolean value ${boolValue} for ${templateId}`);
|
|
669
|
+
console.log(`✅ Boolean value extracted: ${boolValue}`);
|
|
670
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
671
|
+
check: `Process ${templateId}`,
|
|
672
|
+
passed: true,
|
|
673
|
+
message: 'Successfully processed boolean observation'
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
console.log('❌ Failed to extract boolean value');
|
|
678
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
679
|
+
check: `Process ${templateId}`,
|
|
680
|
+
passed: false,
|
|
681
|
+
message: 'Failed to extract boolean value from questionnaire response'
|
|
682
|
+
});
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
// Handle complex FHIRPath expressions
|
|
688
|
+
const boolValue = evaluateTemplateExpression(response, booleanExt.valueString);
|
|
689
|
+
if (boolValue !== null) {
|
|
690
|
+
observation.valueBoolean = boolValue === true || boolValue === 'true';
|
|
691
|
+
debugInfo.valueProcessing.values[templateId] = boolValue;
|
|
692
|
+
debugInfo.valueProcessing.transformations.push(`Extracted boolean value ${boolValue} for ${templateId} using expression`);
|
|
693
|
+
console.log(`✅ Boolean value extracted with expression: ${boolValue}`);
|
|
694
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
695
|
+
check: `Process ${templateId}`,
|
|
696
|
+
passed: true,
|
|
697
|
+
message: 'Successfully processed boolean observation'
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
console.log('❌ Failed to extract boolean value with expression');
|
|
702
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
703
|
+
check: `Process ${templateId}`,
|
|
704
|
+
passed: false,
|
|
705
|
+
message: 'Failed to extract boolean value using expression'
|
|
706
|
+
});
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
console.log('❌ Template has unsupported value type');
|
|
713
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
714
|
+
check: `Process ${templateId}`,
|
|
715
|
+
passed: false,
|
|
716
|
+
message: 'Template has unsupported value type'
|
|
717
|
+
});
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
observations.push(observation);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
console.error(`Error processing template ${template.id}:`, error);
|
|
725
|
+
debugInfo.valueProcessing.qualityChecks.push({
|
|
726
|
+
check: `Process ${template.id || 'unknown'}`,
|
|
727
|
+
passed: false,
|
|
728
|
+
message: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
// Stage 6: Generate result
|
|
733
|
+
if (observations.length > 0) {
|
|
734
|
+
debugInfo.resultGeneration.status = 'Success';
|
|
735
|
+
debugInfo.resultGeneration.observations = observations;
|
|
736
|
+
console.log(`✅ Successfully extracted ${observations.length} observations`);
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
debugInfo.resultGeneration.status = 'Failed';
|
|
740
|
+
debugInfo.resultGeneration.warnings = ['No observations could be extracted'];
|
|
741
|
+
console.log('❌ No observations could be extracted');
|
|
742
|
+
}
|
|
743
|
+
return {
|
|
744
|
+
result: observations.length > 0 ? observations : null,
|
|
745
|
+
error: observations.length === 0 ? 'No observations could be extracted' : null,
|
|
746
|
+
debugInfo
|
|
747
|
+
};
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
export function mapQItemsTemplate(questionnaire) {
|
|
751
|
+
var _a, _b, _c, _d, _e;
|
|
752
|
+
if (!questionnaire.item || questionnaire.item.length === 0) {
|
|
753
|
+
return {};
|
|
754
|
+
}
|
|
755
|
+
const initialExtension = (_a = questionnaire.extension) === null || _a === void 0 ? void 0 : _a.find((e) => e.url === FHIR_TEMPLATE_EXTRACT_EXTENSION);
|
|
756
|
+
const templateReference = (_c = (_b = questionnaire.extension) === null || _b === void 0 ? void 0 : _b.find((e) => e.url === FHIR_TEMPLATE_REFERENCE_EXTENSION)) === null || _c === void 0 ? void 0 : _c.valueString;
|
|
757
|
+
const initialTemplateMap = {
|
|
758
|
+
[(_d = questionnaire.id) !== null && _d !== void 0 ? _d : 'root']: {
|
|
759
|
+
extractable: (_e = initialExtension === null || initialExtension === void 0 ? void 0 : initialExtension.valueBoolean) !== null && _e !== void 0 ? _e : false,
|
|
760
|
+
templateReference
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
transverseQuestionnaire(questionnaire, mapQItemsTemplateRecursive, initialTemplateMap);
|
|
764
|
+
return initialTemplateMap;
|
|
765
|
+
}
|
|
766
|
+
function mapQItemsTemplateRecursive(qItem, root, parent, qItemTemplateMap) {
|
|
767
|
+
var _a, _b, _c, _d, _e, _f;
|
|
768
|
+
if (!qItemTemplateMap)
|
|
769
|
+
return;
|
|
770
|
+
if (!qItemTemplateMap[qItem.linkId]) {
|
|
771
|
+
qItemTemplateMap[qItem.linkId] = { extractable: false };
|
|
772
|
+
}
|
|
773
|
+
// Check if questionnaire extractable
|
|
774
|
+
const extension = (_a = qItem.extension) === null || _a === void 0 ? void 0 : _a.find((e) => e.url === FHIR_TEMPLATE_EXTRACT_EXTENSION);
|
|
775
|
+
const templateReference = (_c = (_b = qItem.extension) === null || _b === void 0 ? void 0 : _b.find((e) => e.url === FHIR_TEMPLATE_REFERENCE_EXTENSION)) === null || _c === void 0 ? void 0 : _c.valueString;
|
|
776
|
+
if ((extension === null || extension === void 0 ? void 0 : extension.valueBoolean) || (extension === null || extension === void 0 ? void 0 : extension.valueBoolean) === false) {
|
|
777
|
+
qItemTemplateMap[qItem.linkId].extractable = (_d = extension === null || extension === void 0 ? void 0 : extension.valueBoolean) !== null && _d !== void 0 ? _d : false;
|
|
778
|
+
}
|
|
779
|
+
else if (parent && qItemTemplateMap[parent.linkId]) {
|
|
780
|
+
qItemTemplateMap[qItem.linkId].extractable = qItemTemplateMap[parent.linkId].extractable;
|
|
781
|
+
}
|
|
782
|
+
else if (root && qItemTemplateMap[(_e = root.id) !== null && _e !== void 0 ? _e : 'root']) {
|
|
783
|
+
qItemTemplateMap[qItem.linkId].extractable = qItemTemplateMap[(_f = root === null || root === void 0 ? void 0 : root.id) !== null && _f !== void 0 ? _f : 'root'].extractable;
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
qItemTemplateMap[qItem.linkId].extractable = false;
|
|
787
|
+
}
|
|
788
|
+
if (templateReference) {
|
|
789
|
+
qItemTemplateMap[qItem.linkId].templateReference = templateReference;
|
|
790
|
+
}
|
|
791
|
+
if (qItem.item && qItem.item.length !== 0) {
|
|
792
|
+
for (const qChildItem of qItem.item) {
|
|
793
|
+
mapQItemsTemplateRecursive(qChildItem, root, qItem, qItemTemplateMap);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function transverseQuestionnaire(questionnaire, callback, qItemTemplateMap) {
|
|
798
|
+
if (!questionnaire.item)
|
|
799
|
+
return;
|
|
800
|
+
for (const item of questionnaire.item) {
|
|
801
|
+
callback(item, questionnaire, undefined, qItemTemplateMap);
|
|
802
|
+
if (item.item) {
|
|
803
|
+
for (const childItem of item.item) {
|
|
804
|
+
callback(childItem, questionnaire, item, qItemTemplateMap);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
//# sourceMappingURL=extractTemplate.js.map
|