@case-framework/survey-core 0.6.2 → 0.8.0
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/README.md +148 -23
- package/build/editor/colors.d.mts +2 -12
- package/build/editor/colors.mjs.map +1 -1
- package/build/editor.d.mts +74 -17
- package/build/editor.d.mts.map +1 -1
- package/build/editor.mjs +445 -106
- package/build/editor.mjs.map +1 -1
- package/build/index-SDbZ5WQM.d.mts +2208 -0
- package/build/index-SDbZ5WQM.d.mts.map +1 -0
- package/build/index.d.mts +3 -887
- package/build/index.mjs +615 -1665
- package/build/index.mjs.map +1 -1
- package/build/item-colors-DgoJeSCQ.d.mts +12 -0
- package/build/item-colors-DgoJeSCQ.d.mts.map +1 -0
- package/build/package.json +7 -8
- package/build/projection-Bl6qCinx.mjs +3930 -0
- package/build/projection-Bl6qCinx.mjs.map +1 -0
- package/package.json +7 -8
- package/build/editor/colors.d.mts.map +0 -1
- package/build/index.d.mts.map +0 -1
- package/build/survey-BKblV5rN.mjs +0 -1460
- package/build/survey-BKblV5rN.mjs.map +0 -1
- package/build/survey-DedDemSc.d.mts +0 -997
- package/build/survey-DedDemSc.d.mts.map +0 -1
package/build/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as const_date, $n as responseValuesEqual, $t as CtxPFlagDateEditor, A as createItemTypeDefinitionRegistry, An as deserializeTemplateValue, At as min, B as deserializeSurveyItemPrefill, Bn as FunctionExpression, Bt as str_list_contains, C as SurveyItemTranslations, Cn as findIntroducedBlockingExpressionReferenceDiagnostics, Ct as eq, D as CURRENT_SURVEY_SCHEMA_VERSION, Dn as responseValueReferenceIds, Dt as lt, E as CURRENT_SURVEY_SCHEMA, En as replaceResponseVariableReferences, Et as in_range, F as isBuiltInItemType, Fn as ConstExpression, Ft as response_number, G as generateId, Gn as ValueReferenceMethod, Gt as ConstDateEditor, H as serializeSurveyItemPrefill, Hn as ResponseVariableExpression, Ht as AndExpressionEditor, I as getCompositionLabel, In as ContextVariableExpression, It as response_number_array, J as shuffleIndices, Jn as allocateResponseVariableName, Jt as ConstStringArrayEditor, K as resolveUniqueItemKey, Kn as ResponseSlotRegistry, Kt as ConstNumberArrayEditor, L as SurveyItemCore, Ln as ContextVariableType, Lt as response_string, M as GroupItemCore, Mn as serializeTemplateValue, Mt as response_boolean, N as PageBreakItemCore, Nn as serializeTemplateValues, Nt as response_date, O as createFullRegistry, On as DATE_FUNCTION_SIGNATURES, Ot as lte, P as builtInItemCoreRegistry, Pn as ExpressionEvaluator, Pt as response_date_array, Q as const_boolean, Qn as responseValueElements, Qt as CtxLocaleEditor, R as SurveyItemPrefillApplyMode, Rn as Expression, Rt as response_string_array, S as isContentEmpty, Sn as expressionReferenceDiagnosticKey, St as date_min, T as validateLocale, Tn as isBlockingSurveyExpressionReferenceDiagnostic, Tt as gte, U as createSeededRandom, Un as ReferenceUsageType, Ut as ConstBooleanEditor, V as prefillTargetsEqual, Vn as FunctionExpressionNames, Vt as sum, W as generateCodingKey, Wn as ValueReference, Wt as ConstDateArrayEditor, X as ReservedSurveyItemTypes, Xn as suggestResponseVariableName, Xt as CtxCustomExpressionEditor, Y as structuredCloneMethod, Yn as isValidResponseVariableName, Yt as ConstStringEditor, Z as and, Zn as invalidResponseDomainValues, Zt as CtxCustomValueEditor, _ as getAssetUsagesFromContent, _n as SURVEY_EXPRESSION_REFERENCE_DIAGNOSTIC_POLICY, _r as differenceCalendarDates, _t as date_gt, a as resolvedSlotEncoding, an as GtExpressionEditor, ar as SurveyItemResponse, at as ctx_custom_expression, b as hasRenderableRichTextBlock, bn as collectExpressionResponseReferences, bt as date_lte, c as EXPORTER_PROFILE_SCHEMA_VERSION, cn as LtExpressionEditor, cr as ValueType, ct as ctx_pflag_date, d as SlotTransformMode, dn as MinExpressionEditor, dr as addCalendarDate, dt as ctx_pflag_string, en as CtxPFlagIsDefinedEditor, er as validateResponseSlotValue, et as const_date_array, f as decodeExportColumnId, fn as OrExpressionEditor, fr as calendarDateBounds, ft as date_add_days, g as createRichTextContent, gn as SumExpressionEditor, gr as compareCalendarDates, gt as date_eq, h as ContentType, hn as StrListContainsExpressionEditor, hr as calendarDateToLocalDate, ht as date_diff, i as projectSlot, in as ExpressionEditor, ir as ResponseItem, it as const_string_array, j as toItemTypeDefinitionRegistry, jn as deserializeTemplateValues, jt as or, k as createItemCore, kn as TemplateDefTypes, kt as max, l as ExportColumnSource, ln as LteExpressionEditor, lr as assertResponseValue, lt as ctx_pflag_is_defined, m as Survey, mn as StrEqExpressionEditor, mr as calendarDatePrecision, mt as date_add_years, n as getDefaultProjectionIssues, nn as CtxPFlagStringEditor, nr as SURVEY_RESPONSE_SCHEMA_VERSION, nt as const_number_array, o as DEFAULT_TRANSFORM, on as GteExpressionEditor, or as DurationUnits, ot as ctx_custom_value, p as encodeExportColumnId, pn as ResponseVariableEditor, pr as calendarDateFromLocalDate, pt as date_add_months, q as shuffleArray, qn as RESPONSE_VARIABLE_NAME_MAX_LENGTH, qt as ConstNumberEditor, r as optionMapping, rn as EqExpressionEditor, rr as SurveyResponse, rt as const_string, s as exportSingleResponseSlot, sn as InRangeExpressionEditor, sr as NumberPrecision, st as ctx_locale, t as formatProjectedValue, tn as CtxPFlagNumEditor, tr as initValueForType, tt as const_number, u as META_COLUMN_ORDER, un as MaxExpressionEditor, ur as isResponseValue, ut as ctx_pflag_num, v as getContentPlainText, vn as analyzeSurveyExpressionReferences, vr as formatCalendarDate, vt as date_gte, w as SurveyTranslations, wn as findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories, wt as gt, x as hasRenderableRichTextContent, xn as collectSurveyExpressionLocations, xt as date_max, y as getPlainTextFromRichTextContent, yn as collectConfigValueExpressions, yr as isCalendarDate, yt as date_lt, z as SurveyItemPrefillTargetType, zn as ExpressionType, zt as str_eq } from "./projection-Bl6qCinx.mjs";
|
|
2
2
|
import { SURVEY_EDITOR_ITEM_COLORS } from "./editor/colors.mjs";
|
|
3
|
-
import { format } from "date-fns";
|
|
4
|
-
import { enUS } from "date-fns/locale";
|
|
3
|
+
import { format } from "date-fns/format";
|
|
4
|
+
import { enUS } from "date-fns/locale/en-US";
|
|
5
5
|
//#region src/to_mirgrate/data_types/legacy-types.ts
|
|
6
6
|
const isLegacyItemGroupComponent = (item) => {
|
|
7
7
|
const items = item.items;
|
|
@@ -12,530 +12,14 @@ const isLegacySurveyGroupItem = (item) => {
|
|
|
12
12
|
return items !== void 0 && items.length > 0;
|
|
13
13
|
};
|
|
14
14
|
//#endregion
|
|
15
|
-
//#region src/survey/responses/item-response.ts
|
|
16
|
-
/**
|
|
17
|
-
* SurveyItemResponse to store the response of a survey item.
|
|
18
|
-
*/
|
|
19
|
-
var SurveyItemResponse = class SurveyItemResponse {
|
|
20
|
-
itemId;
|
|
21
|
-
meta;
|
|
22
|
-
response;
|
|
23
|
-
itemType;
|
|
24
|
-
constructor(itemDef, response) {
|
|
25
|
-
this.itemId = itemDef.itemId;
|
|
26
|
-
this.response = response;
|
|
27
|
-
this.itemType = itemDef.itemType;
|
|
28
|
-
}
|
|
29
|
-
serialize() {
|
|
30
|
-
return {
|
|
31
|
-
itemId: this.itemId,
|
|
32
|
-
itemType: this.itemType,
|
|
33
|
-
meta: this.meta,
|
|
34
|
-
response: this.response?.serialize()
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
static deserialize(json) {
|
|
38
|
-
if (!json || typeof json !== "object" || Array.isArray(json)) throw new Error("Invalid survey item response payload");
|
|
39
|
-
if (typeof json.itemId !== "string" || json.itemId.trim().length === 0) throw new Error("Survey item response is missing a valid itemId");
|
|
40
|
-
if (typeof json.itemType !== "string" || json.itemType.trim().length === 0) throw new Error("Survey item response is missing a valid itemType");
|
|
41
|
-
const newResponse = new SurveyItemResponse({
|
|
42
|
-
itemId: json.itemId,
|
|
43
|
-
itemType: json.itemType
|
|
44
|
-
}, json.response ? ResponseItem.deserialize(json.response) : void 0);
|
|
45
|
-
newResponse.meta = json.meta;
|
|
46
|
-
return newResponse;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
var ResponseItem = class ResponseItem {
|
|
50
|
-
_slots;
|
|
51
|
-
constructor(initialValues) {
|
|
52
|
-
this._slots = new Map(initialValues ?? []);
|
|
53
|
-
}
|
|
54
|
-
get(slotId) {
|
|
55
|
-
return this._slots.get(slotId);
|
|
56
|
-
}
|
|
57
|
-
setSlotValue(slotId, slotResponse) {
|
|
58
|
-
if (slotResponse) this._slots.set(slotId, slotResponse);
|
|
59
|
-
else this._slots.delete(slotId);
|
|
60
|
-
}
|
|
61
|
-
serialize() {
|
|
62
|
-
if (this._slots.size === 0) return;
|
|
63
|
-
return { slots: Object.fromEntries(this._slots.entries()) };
|
|
64
|
-
}
|
|
65
|
-
isEmpty() {
|
|
66
|
-
return this._slots.size === 0;
|
|
67
|
-
}
|
|
68
|
-
clone() {
|
|
69
|
-
return new ResponseItem(Array.from(this._slots.entries()));
|
|
70
|
-
}
|
|
71
|
-
static deserialize(json) {
|
|
72
|
-
if (!json || typeof json !== "object" || Array.isArray(json)) throw new Error("Invalid response item payload");
|
|
73
|
-
const { slots } = json;
|
|
74
|
-
if (!slots || typeof slots !== "object" || Array.isArray(slots)) throw new Error("Invalid response slots payload");
|
|
75
|
-
return new ResponseItem(Object.entries(slots).map(([slotId, slotResponse]) => {
|
|
76
|
-
if (slotId.trim().length === 0) throw new Error("Response slot id cannot be empty");
|
|
77
|
-
assertResponseValue(slotResponse, `response.slots.${slotId}`);
|
|
78
|
-
return [slotId, slotResponse];
|
|
79
|
-
}));
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
//#endregion
|
|
83
15
|
//#region src/survey/responses/response-meta.ts
|
|
84
16
|
const SurveyEventTypes = {
|
|
85
17
|
openSurvey: "openSurvey",
|
|
86
18
|
pageChanged: "pageChanged",
|
|
87
|
-
|
|
19
|
+
responseChanged: "responseChanged",
|
|
88
20
|
languageChanged: "languageChanged"
|
|
89
21
|
};
|
|
90
22
|
//#endregion
|
|
91
|
-
//#region src/survey/responses/survey-response.ts
|
|
92
|
-
const SURVEY_RESPONSE_SCHEMA_VERSION = 1;
|
|
93
|
-
/**
|
|
94
|
-
* SurveyResponse to store the responses of a survey.
|
|
95
|
-
*/
|
|
96
|
-
var SurveyResponse = class SurveyResponse {
|
|
97
|
-
key;
|
|
98
|
-
participantId;
|
|
99
|
-
submittedAt;
|
|
100
|
-
versionId;
|
|
101
|
-
responses;
|
|
102
|
-
events;
|
|
103
|
-
context;
|
|
104
|
-
constructor(key, versionId) {
|
|
105
|
-
this.key = key;
|
|
106
|
-
this.participantId = "";
|
|
107
|
-
this.submittedAt = 0;
|
|
108
|
-
this.versionId = versionId;
|
|
109
|
-
this.responses = /* @__PURE__ */ new Map();
|
|
110
|
-
this.events = [];
|
|
111
|
-
}
|
|
112
|
-
serialize() {
|
|
113
|
-
const items = Object.fromEntries(Array.from(this.responses.entries()).flatMap(([itemId, response]) => {
|
|
114
|
-
const serialized = response.serialize();
|
|
115
|
-
if (!serialized.response && !serialized.meta) return [];
|
|
116
|
-
return [[itemId, serialized]];
|
|
117
|
-
}));
|
|
118
|
-
return {
|
|
119
|
-
schemaVersion: 1,
|
|
120
|
-
key: this.key,
|
|
121
|
-
participantId: this.participantId,
|
|
122
|
-
submittedAt: this.submittedAt,
|
|
123
|
-
versionId: this.versionId,
|
|
124
|
-
items,
|
|
125
|
-
events: this.events,
|
|
126
|
-
context: this.context
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
static deserialize(json) {
|
|
130
|
-
if (!json || typeof json !== "object" || Array.isArray(json)) throw new Error("Invalid survey response payload");
|
|
131
|
-
if (json.schemaVersion !== 1) throw new Error(`Unsupported survey response schema version: ${String(json.schemaVersion)}`);
|
|
132
|
-
if (typeof json.key !== "string" || json.key.trim().length === 0) throw new Error("Survey response is missing a valid key");
|
|
133
|
-
if (typeof json.versionId !== "string" || json.versionId.trim().length === 0) throw new Error("Survey response is missing a valid versionId");
|
|
134
|
-
if (!json.items || typeof json.items !== "object" || Array.isArray(json.items)) throw new Error("Survey response is missing a valid items object");
|
|
135
|
-
const response = new SurveyResponse(json.key, json.versionId);
|
|
136
|
-
response.participantId = json.participantId;
|
|
137
|
-
response.submittedAt = json.submittedAt;
|
|
138
|
-
response.responses = new Map(Object.entries(json.items).map(([itemId, itemResponse]) => {
|
|
139
|
-
const deserialized = SurveyItemResponse.deserialize(itemResponse);
|
|
140
|
-
if (deserialized.itemId !== itemId) throw new Error(`Survey response item key mismatch for '${itemId}'`);
|
|
141
|
-
return [itemId, deserialized];
|
|
142
|
-
}));
|
|
143
|
-
response.events = json.events ?? [];
|
|
144
|
-
response.versionId = json.versionId;
|
|
145
|
-
response.key = json.key;
|
|
146
|
-
if (json.context) {
|
|
147
|
-
if (typeof json.context !== "object" || Array.isArray(json.context)) throw new Error("Survey response context must be an object");
|
|
148
|
-
response.context = Object.fromEntries(Object.entries(json.context).map(([contextKey, value]) => {
|
|
149
|
-
assertResponseValue(value, `context.${contextKey}`);
|
|
150
|
-
return [contextKey, value];
|
|
151
|
-
}));
|
|
152
|
-
}
|
|
153
|
-
return response;
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region src/survey/responses/utils.ts
|
|
158
|
-
const initValueForType = (returnType) => {
|
|
159
|
-
switch (returnType) {
|
|
160
|
-
case ValueType.string: return {
|
|
161
|
-
type: ValueType.string,
|
|
162
|
-
value: ""
|
|
163
|
-
};
|
|
164
|
-
case ValueType.number: return {
|
|
165
|
-
type: ValueType.number,
|
|
166
|
-
value: 0
|
|
167
|
-
};
|
|
168
|
-
case ValueType.boolean: return {
|
|
169
|
-
type: ValueType.boolean,
|
|
170
|
-
value: false
|
|
171
|
-
};
|
|
172
|
-
case ValueType.date: return {
|
|
173
|
-
type: ValueType.date,
|
|
174
|
-
value: Math.floor(Date.now() / 1e3)
|
|
175
|
-
};
|
|
176
|
-
case ValueType.stringArray: return {
|
|
177
|
-
type: ValueType.stringArray,
|
|
178
|
-
value: []
|
|
179
|
-
};
|
|
180
|
-
case ValueType.numberArray: return {
|
|
181
|
-
type: ValueType.numberArray,
|
|
182
|
-
value: []
|
|
183
|
-
};
|
|
184
|
-
case ValueType.dateArray: return {
|
|
185
|
-
type: ValueType.dateArray,
|
|
186
|
-
value: []
|
|
187
|
-
};
|
|
188
|
-
case ValueType.duration: return {
|
|
189
|
-
type: ValueType.duration,
|
|
190
|
-
value: 0,
|
|
191
|
-
unit: DurationUnits.seconds
|
|
192
|
-
};
|
|
193
|
-
case ValueType.reference: return {
|
|
194
|
-
type: ValueType.reference,
|
|
195
|
-
value: ""
|
|
196
|
-
};
|
|
197
|
-
case ValueType.durationArray: return {
|
|
198
|
-
type: ValueType.durationArray,
|
|
199
|
-
value: [],
|
|
200
|
-
unit: DurationUnits.seconds
|
|
201
|
-
};
|
|
202
|
-
case ValueType.referenceArray: return {
|
|
203
|
-
type: ValueType.referenceArray,
|
|
204
|
-
value: []
|
|
205
|
-
};
|
|
206
|
-
default: throw new Error("Invalid return type: " + returnType);
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
//#endregion
|
|
210
|
-
//#region src/expressions/expression-evaluator.ts
|
|
211
|
-
var ExpressionEvaluator = class {
|
|
212
|
-
context;
|
|
213
|
-
constructor(context) {
|
|
214
|
-
this.context = context;
|
|
215
|
-
}
|
|
216
|
-
eval(expression) {
|
|
217
|
-
if (expression === void 0) return;
|
|
218
|
-
switch (expression.type) {
|
|
219
|
-
case ExpressionType.Const: return this.evaluateConst(expression);
|
|
220
|
-
case ExpressionType.Function: return this.evaluateFunction(expression);
|
|
221
|
-
case ExpressionType.ResponseVariable: return this.evaluateResponseVariable(expression);
|
|
222
|
-
case ExpressionType.ContextVariable: return this.evaluateContextVariable(expression);
|
|
223
|
-
default: throw new Error(`Unsupported expression type: ${expression.type}`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
setContext(context) {
|
|
227
|
-
this.context = context;
|
|
228
|
-
}
|
|
229
|
-
evaluateConst(expression) {
|
|
230
|
-
return expression.value;
|
|
231
|
-
}
|
|
232
|
-
evaluateResponseVariable(expression) {
|
|
233
|
-
const varRef = expression.responseVariableRef;
|
|
234
|
-
switch (varRef.name) {
|
|
235
|
-
case "get": return this.context?.responses[varRef.itemId]?.response?.get(varRef.slotId);
|
|
236
|
-
case "isDefined": return {
|
|
237
|
-
type: ValueType.boolean,
|
|
238
|
-
value: this.context?.responses[varRef.itemId]?.response?.get(varRef.slotId) !== void 0
|
|
239
|
-
};
|
|
240
|
-
default: throw new Error(`Unsupported value reference method: ${varRef.name}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
evaluateFunction(expression) {
|
|
244
|
-
switch (expression.functionName) {
|
|
245
|
-
case "and": return this.evaluateAnd(expression);
|
|
246
|
-
case "or": return this.evaluateOr(expression);
|
|
247
|
-
case "not": return this.evaluateNot(expression);
|
|
248
|
-
case "str_eq": return this.evaluateStrEq(expression);
|
|
249
|
-
case "eq": return this.evaluateEq(expression);
|
|
250
|
-
case "gt": return this.evaluateGt(expression);
|
|
251
|
-
case "gte": return this.evaluateGte(expression);
|
|
252
|
-
case "lt": return this.evaluateLt(expression);
|
|
253
|
-
case "lte": return this.evaluateLte(expression);
|
|
254
|
-
case "in_range": return this.evaluateInRange(expression);
|
|
255
|
-
case "sum": return this.evaluateSum(expression);
|
|
256
|
-
case "min": return this.evaluateMin(expression);
|
|
257
|
-
case "max": return this.evaluateMax(expression);
|
|
258
|
-
case "list_contains": return this.evaluateListContains(expression);
|
|
259
|
-
default: throw new Error(`Unsupported function: ${expression.functionName}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
evaluateContextVariable(expression) {
|
|
263
|
-
if (!this.context?.surveyContext) return;
|
|
264
|
-
const surveyContext = this.context.surveyContext;
|
|
265
|
-
switch (expression.contextType) {
|
|
266
|
-
case ContextVariableType.Locale: return {
|
|
267
|
-
type: ValueType.string,
|
|
268
|
-
value: surveyContext.locale
|
|
269
|
-
};
|
|
270
|
-
case ContextVariableType.ParticipantFlag: {
|
|
271
|
-
if (!expression.key) return;
|
|
272
|
-
const flagKey = this.eval(expression.key);
|
|
273
|
-
if (flagKey?.type !== ValueType.string || flagKey.value === void 0) return;
|
|
274
|
-
const flagValue = surveyContext.participantFlags?.[flagKey.value];
|
|
275
|
-
switch (expression.asType) {
|
|
276
|
-
case ValueType.boolean: return {
|
|
277
|
-
type: ValueType.boolean,
|
|
278
|
-
value: flagValue !== void 0
|
|
279
|
-
};
|
|
280
|
-
case ValueType.string:
|
|
281
|
-
if (flagValue === void 0) return;
|
|
282
|
-
return {
|
|
283
|
-
type: ValueType.string,
|
|
284
|
-
value: flagValue
|
|
285
|
-
};
|
|
286
|
-
case ValueType.number: {
|
|
287
|
-
if (flagValue === void 0) return;
|
|
288
|
-
const numValue = parseFloat(flagValue);
|
|
289
|
-
return isNaN(numValue) ? void 0 : {
|
|
290
|
-
type: ValueType.number,
|
|
291
|
-
value: numValue
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
case ValueType.date: {
|
|
295
|
-
if (flagValue === void 0) return;
|
|
296
|
-
const timestamp = parseFloat(flagValue);
|
|
297
|
-
return isNaN(timestamp) ? void 0 : {
|
|
298
|
-
type: ValueType.date,
|
|
299
|
-
value: timestamp
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
default:
|
|
303
|
-
if (flagValue === void 0) return;
|
|
304
|
-
return {
|
|
305
|
-
type: ValueType.string,
|
|
306
|
-
value: flagValue
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
case ContextVariableType.CustomValue: {
|
|
311
|
-
if (!expression.key) return;
|
|
312
|
-
const customKey = this.eval(expression.key);
|
|
313
|
-
if (customKey?.type !== ValueType.string || customKey.value === void 0) return;
|
|
314
|
-
const customValue = surveyContext.customValues?.[customKey.value];
|
|
315
|
-
if (customValue === void 0) return;
|
|
316
|
-
if (expression.asType !== void 0) {
|
|
317
|
-
if (customValue?.type !== expression.asType) throw new Error(`Custom value type does not match expected type, got ${customValue?.type} and expected ${expression.asType}`);
|
|
318
|
-
}
|
|
319
|
-
return customValue;
|
|
320
|
-
}
|
|
321
|
-
case ContextVariableType.CustomExpression: {
|
|
322
|
-
if (!expression.key) return;
|
|
323
|
-
const expressionKey = this.eval(expression.key);
|
|
324
|
-
if (expressionKey?.type !== ValueType.string || expressionKey.value === void 0) return;
|
|
325
|
-
const customExpression = surveyContext.customExpressions?.[expressionKey.value];
|
|
326
|
-
if (typeof customExpression !== ExpressionType.Function || customExpression === void 0) return;
|
|
327
|
-
try {
|
|
328
|
-
const args = expression.arguments;
|
|
329
|
-
const result = customExpression(args);
|
|
330
|
-
if (expression.asType !== void 0) {
|
|
331
|
-
if (result?.type !== expression.asType) throw new Error(`Custom expression result type does not match expected type, got ${result?.type} and expected ${expression.asType}`);
|
|
332
|
-
}
|
|
333
|
-
return result;
|
|
334
|
-
} catch (error) {
|
|
335
|
-
throw new Error(`Error evaluating custom expression: ${error}`);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
evaluateAnd(expression) {
|
|
341
|
-
return {
|
|
342
|
-
type: ValueType.boolean,
|
|
343
|
-
value: expression.arguments.every((arg) => this.eval(arg)?.value === true)
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
evaluateOr(expression) {
|
|
347
|
-
return {
|
|
348
|
-
type: ValueType.boolean,
|
|
349
|
-
value: expression.arguments.some((arg) => this.eval(arg)?.value === true)
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
evaluateNot(expression) {
|
|
353
|
-
if (expression.arguments.length !== 1 || expression.arguments[0] === void 0) throw new Error(`Not function expects 1 argument, got ${expression.arguments.length}`);
|
|
354
|
-
const resolvedValue = this.eval(expression.arguments[0]);
|
|
355
|
-
if (resolvedValue?.type !== ValueType.boolean) throw new Error(`Not function expects a boolean argument, got ${resolvedValue?.type}`);
|
|
356
|
-
if (resolvedValue === void 0) return {
|
|
357
|
-
type: ValueType.boolean,
|
|
358
|
-
value: false
|
|
359
|
-
};
|
|
360
|
-
return {
|
|
361
|
-
type: ValueType.boolean,
|
|
362
|
-
value: !resolvedValue.value
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
evaluateListContains(expression) {
|
|
366
|
-
if (expression.arguments.length !== 2) throw new Error(`List contains function expects 2 arguments, got ${expression.arguments.length}`);
|
|
367
|
-
const resolvedList = this.eval(expression.arguments[0]);
|
|
368
|
-
const resolvedItem = this.eval(expression.arguments[1]);
|
|
369
|
-
if (resolvedList === void 0 || resolvedItem === void 0) return {
|
|
370
|
-
type: ValueType.boolean,
|
|
371
|
-
value: false
|
|
372
|
-
};
|
|
373
|
-
const allowedListTypes = [ValueType.stringArray, ValueType.referenceArray];
|
|
374
|
-
const allowedItemTypes = [ValueType.string, ValueType.reference];
|
|
375
|
-
const resolvedListType = resolvedList.type;
|
|
376
|
-
const resolvedItemType = resolvedItem.type;
|
|
377
|
-
if (!allowedListTypes.includes(resolvedListType) || !allowedItemTypes.includes(resolvedItemType)) throw new Error(`List contains function expects a string array list and a string item, got ${resolvedList?.type} and ${resolvedItem?.type}`);
|
|
378
|
-
const list = resolvedList.value;
|
|
379
|
-
const item = resolvedItem.value;
|
|
380
|
-
return {
|
|
381
|
-
type: ValueType.boolean,
|
|
382
|
-
value: list.includes(item)
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
evaluateStrEq(expression) {
|
|
386
|
-
if (expression.arguments.length !== 2) throw new Error(`String equals function expects 2 arguments, got ${expression.arguments.length}`);
|
|
387
|
-
const resolvedA = this.eval(expression.arguments[0]);
|
|
388
|
-
const resolvedB = this.eval(expression.arguments[1]);
|
|
389
|
-
if (resolvedA === void 0 || resolvedB === void 0) return {
|
|
390
|
-
type: ValueType.boolean,
|
|
391
|
-
value: false
|
|
392
|
-
};
|
|
393
|
-
if (resolvedA?.type !== ValueType.string || resolvedB?.type !== ValueType.string) throw new Error(`String equals function expects strings for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
|
|
394
|
-
return {
|
|
395
|
-
type: ValueType.boolean,
|
|
396
|
-
value: resolvedA.value === resolvedB.value
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
evaluateEq(expression) {
|
|
400
|
-
if (expression.arguments.length !== 2) throw new Error(`Equals function expects 2 arguments, got ${expression.arguments.length}`);
|
|
401
|
-
const resolvedA = this.eval(expression.arguments[0]);
|
|
402
|
-
const resolvedB = this.eval(expression.arguments[1]);
|
|
403
|
-
if (resolvedA === void 0 || resolvedB === void 0) return {
|
|
404
|
-
type: ValueType.boolean,
|
|
405
|
-
value: false
|
|
406
|
-
};
|
|
407
|
-
if (resolvedA?.type !== resolvedB?.type) throw new Error(`Equals function expects the same type for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
|
|
408
|
-
return {
|
|
409
|
-
type: ValueType.boolean,
|
|
410
|
-
value: resolvedA.value === resolvedB.value
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
evaluateGt(expression) {
|
|
414
|
-
if (expression.arguments.length !== 2) throw new Error(`Greater than function expects 2 arguments, got ${expression.arguments.length}`);
|
|
415
|
-
const resolvedA = this.eval(expression.arguments[0]);
|
|
416
|
-
const resolvedB = this.eval(expression.arguments[1]);
|
|
417
|
-
if (resolvedA === void 0 || resolvedB === void 0) return {
|
|
418
|
-
type: ValueType.boolean,
|
|
419
|
-
value: false
|
|
420
|
-
};
|
|
421
|
-
if (resolvedA?.type !== resolvedB?.type) throw new Error(`Greater than function expects the same type for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
|
|
422
|
-
return {
|
|
423
|
-
type: ValueType.boolean,
|
|
424
|
-
value: resolvedA.value > resolvedB.value
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
evaluateGte(expression) {
|
|
428
|
-
if (expression.arguments.length !== 2) throw new Error(`Greater than or equal to function expects 2 arguments, got ${expression.arguments.length}`);
|
|
429
|
-
const resolvedA = this.eval(expression.arguments[0]);
|
|
430
|
-
const resolvedB = this.eval(expression.arguments[1]);
|
|
431
|
-
if (resolvedA === void 0 || resolvedB === void 0) return {
|
|
432
|
-
type: ValueType.boolean,
|
|
433
|
-
value: false
|
|
434
|
-
};
|
|
435
|
-
if (resolvedA?.type !== resolvedB?.type) throw new Error(`Greater than or equal to function expects the same type for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
|
|
436
|
-
return {
|
|
437
|
-
type: ValueType.boolean,
|
|
438
|
-
value: resolvedA.value >= resolvedB.value
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
evaluateLt(expression) {
|
|
442
|
-
if (expression.arguments.length !== 2) throw new Error(`Less than function expects 2 arguments, got ${expression.arguments.length}`);
|
|
443
|
-
const resolvedA = this.eval(expression.arguments[0]);
|
|
444
|
-
const resolvedB = this.eval(expression.arguments[1]);
|
|
445
|
-
if (resolvedA === void 0 || resolvedB === void 0) return {
|
|
446
|
-
type: ValueType.boolean,
|
|
447
|
-
value: false
|
|
448
|
-
};
|
|
449
|
-
if (resolvedA?.type !== resolvedB?.type) throw new Error(`Less than function expects the same type for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
|
|
450
|
-
return {
|
|
451
|
-
type: ValueType.boolean,
|
|
452
|
-
value: resolvedA.value < resolvedB.value
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
evaluateLte(expression) {
|
|
456
|
-
if (expression.arguments.length !== 2) throw new Error(`Less than or equal to function expects 2 arguments, got ${expression.arguments.length}`);
|
|
457
|
-
const resolvedA = this.eval(expression.arguments[0]);
|
|
458
|
-
const resolvedB = this.eval(expression.arguments[1]);
|
|
459
|
-
if (resolvedA === void 0 || resolvedB === void 0) return {
|
|
460
|
-
type: ValueType.boolean,
|
|
461
|
-
value: false
|
|
462
|
-
};
|
|
463
|
-
if (resolvedA?.type !== resolvedB?.type) throw new Error(`Less than or equal to function expects the same type for all arguments, got ${resolvedA?.type} and ${resolvedB?.type}`);
|
|
464
|
-
return {
|
|
465
|
-
type: ValueType.boolean,
|
|
466
|
-
value: resolvedA.value <= resolvedB.value
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
evaluateInRange(expression) {
|
|
470
|
-
if (expression.arguments.length !== 4) throw new Error(`In range function expects 4 arguments, got ${expression.arguments.length}`);
|
|
471
|
-
const resolvedValue = this.eval(expression.arguments[0]);
|
|
472
|
-
const resolvedMin = this.eval(expression.arguments[1]);
|
|
473
|
-
const resolvedMax = this.eval(expression.arguments[2]);
|
|
474
|
-
const resolvedInclusive = this.eval(expression.arguments[3])?.value === true;
|
|
475
|
-
if (resolvedValue === void 0 || resolvedMin === void 0 || resolvedMax === void 0) return {
|
|
476
|
-
type: ValueType.boolean,
|
|
477
|
-
value: false
|
|
478
|
-
};
|
|
479
|
-
if (resolvedValue?.type !== ValueType.number || resolvedMin?.type !== ValueType.number || resolvedMax?.type !== ValueType.number) throw new Error(`In range function expects numbers for all arguments, got ${resolvedValue?.type} and ${resolvedMin?.type} and ${resolvedMax?.type}`);
|
|
480
|
-
if (resolvedInclusive) return {
|
|
481
|
-
type: ValueType.boolean,
|
|
482
|
-
value: resolvedValue.value >= resolvedMin.value && resolvedValue.value <= resolvedMax.value
|
|
483
|
-
};
|
|
484
|
-
return {
|
|
485
|
-
type: ValueType.boolean,
|
|
486
|
-
value: resolvedValue.value > resolvedMin.value && resolvedValue.value < resolvedMax.value
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
evaluateSum(expression) {
|
|
490
|
-
if (expression.arguments.length < 1) throw new Error(`Sum function expects at least 1 argument, got ${expression.arguments.length}`);
|
|
491
|
-
return expression.arguments.reduce((sum, arg) => {
|
|
492
|
-
if (arg === void 0) return sum;
|
|
493
|
-
const resolvedValue = this.eval(arg);
|
|
494
|
-
if (resolvedValue === void 0) return sum;
|
|
495
|
-
if (resolvedValue?.type === ValueType.number) return {
|
|
496
|
-
type: ValueType.number,
|
|
497
|
-
value: sum.value + resolvedValue.value
|
|
498
|
-
};
|
|
499
|
-
throw new Error(`Sum function expects all arguments to be numbers, got ${resolvedValue?.type}`);
|
|
500
|
-
}, {
|
|
501
|
-
type: ValueType.number,
|
|
502
|
-
value: 0
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
evaluateMin(expression) {
|
|
506
|
-
if (expression.arguments.length < 1) throw new Error(`Min function expects at least 1 argument, got ${expression.arguments.length}`);
|
|
507
|
-
return expression.arguments.reduce((min, arg) => {
|
|
508
|
-
if (arg === void 0) return min;
|
|
509
|
-
const resolvedValue = this.eval(arg);
|
|
510
|
-
if (resolvedValue === void 0) return min;
|
|
511
|
-
if (resolvedValue?.type === ValueType.number) return {
|
|
512
|
-
type: ValueType.number,
|
|
513
|
-
value: Math.min(min.value, resolvedValue.value)
|
|
514
|
-
};
|
|
515
|
-
throw new Error(`Min function expects all arguments to be numbers, got ${typeof resolvedValue}`);
|
|
516
|
-
}, {
|
|
517
|
-
type: ValueType.number,
|
|
518
|
-
value: Infinity
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
evaluateMax(expression) {
|
|
522
|
-
if (expression.arguments.length < 1) throw new Error(`Max function expects at least 1 argument, got ${expression.arguments.length}`);
|
|
523
|
-
return expression.arguments.reduce((max, arg) => {
|
|
524
|
-
if (arg === void 0) return max;
|
|
525
|
-
const resolvedValue = this.eval(arg);
|
|
526
|
-
if (resolvedValue === void 0) return max;
|
|
527
|
-
if (resolvedValue?.type === ValueType.number) return {
|
|
528
|
-
type: ValueType.number,
|
|
529
|
-
value: Math.max(max.value, resolvedValue.value)
|
|
530
|
-
};
|
|
531
|
-
throw new Error(`Max function expects all arguments to be numbers, got ${typeof resolvedValue}`);
|
|
532
|
-
}, {
|
|
533
|
-
type: ValueType.number,
|
|
534
|
-
value: -Infinity
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
//#endregion
|
|
539
23
|
//#region src/expressions/item-expression-registry.ts
|
|
540
24
|
function getItemExpressionDefinition(definitions, expressionId) {
|
|
541
25
|
return definitions?.find((definition) => definition.id === expressionId);
|
|
@@ -546,824 +30,144 @@ function buildItemExpression(item, definitions, expressionId, params) {
|
|
|
546
30
|
return definition.buildExpression(item, params);
|
|
547
31
|
}
|
|
548
32
|
//#endregion
|
|
549
|
-
//#region src/
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
location: `templateValues.${templateKey}.expression`,
|
|
578
|
-
surface: "template-value"
|
|
579
|
-
}] : [])];
|
|
580
|
-
const candidateRefs = (unknownRef, availableRefs) => {
|
|
581
|
-
let parsed;
|
|
582
|
-
try {
|
|
583
|
-
parsed = new ValueReference(unknownRef);
|
|
584
|
-
} catch {
|
|
585
|
-
return availableRefs.slice(0, 5);
|
|
586
|
-
}
|
|
587
|
-
const sameItemAndMethod = availableRefs.filter((candidate) => {
|
|
588
|
-
try {
|
|
589
|
-
const candidateRef = new ValueReference(candidate);
|
|
590
|
-
return candidateRef.itemId === parsed?.itemId && candidateRef.name === parsed?.name;
|
|
591
|
-
} catch {
|
|
592
|
-
return false;
|
|
33
|
+
//#region src/engine/active-responses.ts
|
|
34
|
+
const itemNode = (id) => JSON.stringify(["item", id]);
|
|
35
|
+
const componentNode = (id, component) => JSON.stringify([
|
|
36
|
+
"component",
|
|
37
|
+
id,
|
|
38
|
+
component
|
|
39
|
+
]);
|
|
40
|
+
const slotNode = (id) => JSON.stringify(["answer", id]);
|
|
41
|
+
function assertDeclarativeVisibility(expression) {
|
|
42
|
+
if (!expression) return;
|
|
43
|
+
if (expression.type === ExpressionType.ContextVariable) {
|
|
44
|
+
if (expression.contextType === ContextVariableType.CustomExpression) throw new Error("Visibility conditions cannot use custom callback expressions; provide a serializable custom context value instead.");
|
|
45
|
+
assertDeclarativeVisibility(expression.key);
|
|
46
|
+
}
|
|
47
|
+
if ("arguments" in expression) for (const argument of expression.arguments ?? []) assertDeclarativeVisibility(argument);
|
|
48
|
+
}
|
|
49
|
+
/** Compile the dependencies once; each evaluation reads an immutable logical session snapshot. */
|
|
50
|
+
var ActiveResponsePlan = class {
|
|
51
|
+
survey;
|
|
52
|
+
parents = /* @__PURE__ */ new Map();
|
|
53
|
+
components = /* @__PURE__ */ new Map();
|
|
54
|
+
slots;
|
|
55
|
+
constructor(survey) {
|
|
56
|
+
this.survey = survey;
|
|
57
|
+
this.slots = survey.getResponseSlotRegistry();
|
|
58
|
+
for (const item of survey.surveyItems.values()) {
|
|
59
|
+
if (item.type === "group") for (const child of item.items ?? []) this.parents.set(child, item.id);
|
|
60
|
+
for (const { slotId, componentId } of item.getConditionalResponseSlots()) this.components.set(slotId, [...this.components.get(slotId) ?? [], componentId]);
|
|
593
61
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
const itemTypeForList = (type) => {
|
|
621
|
-
switch (type) {
|
|
622
|
-
case ValueType.stringArray: return ValueType.string;
|
|
623
|
-
case ValueType.referenceArray: return ValueType.reference;
|
|
624
|
-
case ValueType.numberArray: return ValueType.number;
|
|
625
|
-
case ValueType.dateArray: return ValueType.date;
|
|
626
|
-
case ValueType.durationArray: return ValueType.duration;
|
|
627
|
-
default: return;
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
const inspectFunctionReferenceCompatibility = ({ diagnostics, expression, location, slots }) => {
|
|
631
|
-
if (expression.type !== ExpressionType.Function) return;
|
|
632
|
-
expression.arguments.forEach((argument) => {
|
|
633
|
-
if (argument) inspectFunctionReferenceCompatibility({
|
|
634
|
-
diagnostics,
|
|
635
|
-
expression: argument,
|
|
636
|
-
location,
|
|
637
|
-
slots
|
|
638
|
-
});
|
|
639
|
-
});
|
|
640
|
-
const [left, right] = expression.arguments;
|
|
641
|
-
const leftType = inferExpressionType(left, slots);
|
|
642
|
-
const rightType = inferExpressionType(right, slots);
|
|
643
|
-
if (expression.functionName === "list_contains" ? Boolean(leftType && rightType && itemTypeForList(leftType) !== rightType) : expression.functionName === "eq" ? Boolean(leftType && rightType && leftType !== rightType) : false) diagnostics.push({
|
|
644
|
-
code: "expression-reference-type-mismatch",
|
|
645
|
-
message: `${location.location} compares incompatible values (${leftType} and ${rightType}).`,
|
|
646
|
-
location: location.location,
|
|
647
|
-
ownerItemId: location.ownerItemId,
|
|
648
|
-
ownerItemKey: location.ownerItemKey
|
|
649
|
-
});
|
|
650
|
-
if (expression.functionName !== "eq" && expression.functionName !== "list_contains") return;
|
|
651
|
-
const pairs = [[left, right], [right, left]];
|
|
652
|
-
for (const [variable, constant] of pairs) {
|
|
653
|
-
if (variable?.type !== ExpressionType.ResponseVariable || constant?.type !== ExpressionType.Const || constant.value?.type !== ValueType.reference) continue;
|
|
654
|
-
const slot = slots.get(variable.variableRef);
|
|
655
|
-
if (slot?.allowedReferenceValues && !slot.allowedReferenceValues.includes(constant.value.value)) diagnostics.push({
|
|
656
|
-
code: "unknown-reference-value",
|
|
657
|
-
message: `${location.location} compares "${variable.variableRef}" with missing option "${constant.value.value}".`,
|
|
658
|
-
location: location.location,
|
|
659
|
-
ownerItemId: location.ownerItemId,
|
|
660
|
-
ownerItemKey: location.ownerItemKey,
|
|
661
|
-
reference: variable.variableRef,
|
|
662
|
-
referenceValue: constant.value.value,
|
|
663
|
-
candidates: slot.allowedReferenceValues.slice(0, 5)
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
};
|
|
667
|
-
const analyzeSurveyExpressionReferences = ({ responseSlots, survey }) => {
|
|
668
|
-
const slots = new Map(responseSlots.map((slot) => [slot.ref, slot]));
|
|
669
|
-
const availableRefs = responseSlots.map((slot) => slot.ref);
|
|
670
|
-
const diagnostics = [];
|
|
671
|
-
for (const location of collectSurveyExpressionLocations(survey)) {
|
|
672
|
-
let parsed;
|
|
673
|
-
try {
|
|
674
|
-
parsed = Expression.deserialize(location.expression);
|
|
675
|
-
} catch (error) {
|
|
676
|
-
diagnostics.push({
|
|
677
|
-
code: "invalid-expression",
|
|
678
|
-
message: `${location.location} contains invalid expression JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
679
|
-
location: location.location,
|
|
680
|
-
ownerItemId: location.ownerItemId,
|
|
681
|
-
ownerItemKey: location.ownerItemKey
|
|
682
|
-
});
|
|
683
|
-
continue;
|
|
62
|
+
const graph = /* @__PURE__ */ new Map();
|
|
63
|
+
const labels = /* @__PURE__ */ new Map();
|
|
64
|
+
const refs = (expression) => expression?.responseVariableRefs.map((ref) => slotNode(ref.slotId)) ?? [];
|
|
65
|
+
for (const item of survey.surveyItems.values()) {
|
|
66
|
+
for (const expression of [item.displayConditions?.root, ...Object.values(item.displayConditions?.components ?? {})]) assertDeclarativeVisibility(expression?.serialize());
|
|
67
|
+
const label = item.metadata?.itemLabel?.trim() || item.id;
|
|
68
|
+
const parent = this.parents.get(item.id);
|
|
69
|
+
graph.set(itemNode(item.id), [...parent ? [itemNode(parent)] : [], ...refs(item.displayConditions?.root)]);
|
|
70
|
+
labels.set(itemNode(item.id), `${label} visibility`);
|
|
71
|
+
for (const [component, expression] of Object.entries(item.displayConditions?.components ?? {})) {
|
|
72
|
+
graph.set(componentNode(item.id, component), [itemNode(item.id), ...refs(expression)]);
|
|
73
|
+
labels.set(componentNode(item.id, component), `${label} (${component}) visibility`);
|
|
74
|
+
}
|
|
75
|
+
for (const slot of item.getResponseSlotDefinitions()) {
|
|
76
|
+
const activation = item.getActiveResponseDependencies(slot.slotId);
|
|
77
|
+
graph.set(slotNode(slot.slotId), [
|
|
78
|
+
itemNode(item.id),
|
|
79
|
+
...(this.components.get(slot.slotId) ?? []).map((id) => componentNode(item.id, id)),
|
|
80
|
+
...item.getComputedResponseDependencies(slot.slotId).map(slotNode),
|
|
81
|
+
...activation.slotIds.map(slotNode),
|
|
82
|
+
...activation.componentIds.map((id) => componentNode(item.id, id))
|
|
83
|
+
]);
|
|
84
|
+
labels.set(slotNode(slot.slotId), `${label} (${slot.variableName || slot.slotId})`);
|
|
85
|
+
}
|
|
684
86
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
if (
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
87
|
+
const done = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), path = [];
|
|
88
|
+
const visit = (node) => {
|
|
89
|
+
if (visiting.has(node)) throw new Error(`Cyclic response/visibility dependency: ${[...path.slice(path.indexOf(node)), node].map((id) => labels.get(id) ?? id).join(" → ")}`);
|
|
90
|
+
if (done.has(node)) return;
|
|
91
|
+
visiting.add(node);
|
|
92
|
+
path.push(node);
|
|
93
|
+
for (const dependency of graph.get(node) ?? []) visit(dependency);
|
|
94
|
+
path.pop();
|
|
95
|
+
visiting.delete(node);
|
|
96
|
+
done.add(node);
|
|
97
|
+
};
|
|
98
|
+
for (const node of graph.keys()) visit(node);
|
|
99
|
+
}
|
|
100
|
+
evaluate(responses, context) {
|
|
101
|
+
const diagnostics = [];
|
|
102
|
+
const cache = /* @__PURE__ */ new Map();
|
|
103
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
104
|
+
const resolve = (key, compute) => {
|
|
105
|
+
if (cache.has(key)) return cache.get(key);
|
|
106
|
+
if (visiting.has(key)) throw new Error(`Cyclic response/visibility dependency: ${key}`);
|
|
107
|
+
visiting.add(key);
|
|
108
|
+
try {
|
|
109
|
+
const value = compute();
|
|
110
|
+
cache.set(key, value);
|
|
111
|
+
return value;
|
|
112
|
+
} finally {
|
|
113
|
+
visiting.delete(key);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const condition = (itemId, expression, componentId) => {
|
|
117
|
+
if (!expression) return true;
|
|
118
|
+
const value = evaluator.eval(expression);
|
|
119
|
+
if (value !== void 0 && value.type !== ValueType.boolean) diagnostics.push({
|
|
120
|
+
code: "non-boolean-visibility-result",
|
|
121
|
+
itemId,
|
|
122
|
+
...componentId === void 0 ? {} : { componentId },
|
|
123
|
+
actualType: value.type
|
|
695
124
|
});
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
125
|
+
return value?.type === ValueType.boolean && value.value;
|
|
126
|
+
};
|
|
127
|
+
const isItemVisible = (id) => resolve(itemNode(id), () => {
|
|
128
|
+
const item = this.survey.surveyItems.get(id);
|
|
129
|
+
const parent = this.parents.get(id);
|
|
130
|
+
return !!item && (id === this.survey.rootItem?.id || !!parent && isItemVisible(parent)) && condition(id, item.displayConditions?.root);
|
|
702
131
|
});
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
changed,
|
|
732
|
-
value: rewritten
|
|
733
|
-
};
|
|
734
|
-
};
|
|
735
|
-
const responseValueReferenceIds = (values) => values?.flatMap((value) => value.type === ValueType.reference ? [value.value] : value.type === ValueType.referenceArray ? value.value : []);
|
|
736
|
-
//#endregion
|
|
737
|
-
//#region src/expressions/editors/expression-editor.ts
|
|
738
|
-
var ExpressionEditor = class {
|
|
739
|
-
returnType;
|
|
740
|
-
_editorConfig;
|
|
741
|
-
get editorConfig() {
|
|
742
|
-
return this._editorConfig;
|
|
743
|
-
}
|
|
744
|
-
withEditorConfig(editorConfig) {
|
|
745
|
-
this._editorConfig = editorConfig;
|
|
746
|
-
return this;
|
|
747
|
-
}
|
|
748
|
-
};
|
|
749
|
-
var ConstStringArrayEditor = class extends ExpressionEditor {
|
|
750
|
-
returnType = ValueType.stringArray;
|
|
751
|
-
_values;
|
|
752
|
-
constructor(values, editorConfig) {
|
|
753
|
-
super();
|
|
754
|
-
this._values = values;
|
|
755
|
-
this._editorConfig = editorConfig;
|
|
756
|
-
}
|
|
757
|
-
get values() {
|
|
758
|
-
return this._values;
|
|
759
|
-
}
|
|
760
|
-
set values(values) {
|
|
761
|
-
this._values = values;
|
|
762
|
-
}
|
|
763
|
-
getExpression() {
|
|
764
|
-
return new ConstExpression({
|
|
765
|
-
type: ValueType.stringArray,
|
|
766
|
-
value: this._values
|
|
767
|
-
}, this._editorConfig);
|
|
768
|
-
}
|
|
769
|
-
};
|
|
770
|
-
var ConstStringEditor = class extends ExpressionEditor {
|
|
771
|
-
returnType = ValueType.string;
|
|
772
|
-
_value;
|
|
773
|
-
constructor(value, editorConfig) {
|
|
774
|
-
super();
|
|
775
|
-
this._value = value;
|
|
776
|
-
this._editorConfig = editorConfig;
|
|
777
|
-
}
|
|
778
|
-
get value() {
|
|
779
|
-
return this._value;
|
|
780
|
-
}
|
|
781
|
-
set value(value) {
|
|
782
|
-
this._value = value;
|
|
783
|
-
}
|
|
784
|
-
getExpression() {
|
|
785
|
-
return new ConstExpression({
|
|
786
|
-
type: ValueType.string,
|
|
787
|
-
value: this._value
|
|
788
|
-
}, this._editorConfig);
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
var ConstNumberEditor = class extends ExpressionEditor {
|
|
792
|
-
returnType = ValueType.number;
|
|
793
|
-
_value;
|
|
794
|
-
constructor(value, editorConfig) {
|
|
795
|
-
super();
|
|
796
|
-
this._value = value;
|
|
797
|
-
this._editorConfig = editorConfig;
|
|
798
|
-
}
|
|
799
|
-
get value() {
|
|
800
|
-
return this._value;
|
|
801
|
-
}
|
|
802
|
-
set value(value) {
|
|
803
|
-
this._value = value;
|
|
804
|
-
}
|
|
805
|
-
getExpression() {
|
|
806
|
-
return new ConstExpression({
|
|
807
|
-
type: ValueType.number,
|
|
808
|
-
value: this._value
|
|
809
|
-
}, this._editorConfig);
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
var ConstBooleanEditor = class extends ExpressionEditor {
|
|
813
|
-
returnType = ValueType.boolean;
|
|
814
|
-
_value;
|
|
815
|
-
constructor(value, editorConfig) {
|
|
816
|
-
super();
|
|
817
|
-
this._value = value;
|
|
818
|
-
this._editorConfig = editorConfig;
|
|
819
|
-
}
|
|
820
|
-
get value() {
|
|
821
|
-
return this._value;
|
|
822
|
-
}
|
|
823
|
-
set value(value) {
|
|
824
|
-
this._value = value;
|
|
825
|
-
}
|
|
826
|
-
getExpression() {
|
|
827
|
-
return new ConstExpression({
|
|
828
|
-
type: ValueType.boolean,
|
|
829
|
-
value: this._value
|
|
830
|
-
}, this._editorConfig);
|
|
831
|
-
}
|
|
832
|
-
};
|
|
833
|
-
var ConstDateEditor = class extends ExpressionEditor {
|
|
834
|
-
returnType = ValueType.date;
|
|
835
|
-
_value;
|
|
836
|
-
constructor(value, editorConfig) {
|
|
837
|
-
super();
|
|
838
|
-
this._value = value;
|
|
839
|
-
this._editorConfig = editorConfig;
|
|
840
|
-
}
|
|
841
|
-
get value() {
|
|
842
|
-
return this._value;
|
|
843
|
-
}
|
|
844
|
-
set value(value) {
|
|
845
|
-
this._value = value;
|
|
846
|
-
}
|
|
847
|
-
getExpression() {
|
|
848
|
-
const value = Math.floor(this._value.getTime() / 1e3);
|
|
849
|
-
return new ConstExpression({
|
|
850
|
-
type: ValueType.date,
|
|
851
|
-
value
|
|
852
|
-
}, this._editorConfig);
|
|
853
|
-
}
|
|
854
|
-
};
|
|
855
|
-
var ConstNumberArrayEditor = class extends ExpressionEditor {
|
|
856
|
-
returnType = ValueType.numberArray;
|
|
857
|
-
_values;
|
|
858
|
-
constructor(values, editorConfig) {
|
|
859
|
-
super();
|
|
860
|
-
this._values = values;
|
|
861
|
-
this._editorConfig = editorConfig;
|
|
862
|
-
}
|
|
863
|
-
get values() {
|
|
864
|
-
return this._values;
|
|
865
|
-
}
|
|
866
|
-
set values(values) {
|
|
867
|
-
this._values = values;
|
|
868
|
-
}
|
|
869
|
-
getExpression() {
|
|
870
|
-
return new ConstExpression({
|
|
871
|
-
type: ValueType.numberArray,
|
|
872
|
-
value: this._values
|
|
873
|
-
}, this._editorConfig);
|
|
874
|
-
}
|
|
875
|
-
};
|
|
876
|
-
var ConstDateArrayEditor = class extends ExpressionEditor {
|
|
877
|
-
returnType = ValueType.dateArray;
|
|
878
|
-
_values;
|
|
879
|
-
constructor(values, editorConfig) {
|
|
880
|
-
super();
|
|
881
|
-
this._values = values;
|
|
882
|
-
this._editorConfig = editorConfig;
|
|
883
|
-
}
|
|
884
|
-
get values() {
|
|
885
|
-
return this._values;
|
|
886
|
-
}
|
|
887
|
-
set values(values) {
|
|
888
|
-
this._values = values;
|
|
889
|
-
}
|
|
890
|
-
getExpression() {
|
|
891
|
-
const value = this._values.map((date) => Math.floor(date.getTime() / 1e3));
|
|
892
|
-
return new ConstExpression({
|
|
893
|
-
type: ValueType.dateArray,
|
|
894
|
-
value
|
|
895
|
-
}, this._editorConfig);
|
|
896
|
-
}
|
|
897
|
-
};
|
|
898
|
-
var ResponseVariableEditor = class extends ExpressionEditor {
|
|
899
|
-
_variableName;
|
|
900
|
-
_variableRef;
|
|
901
|
-
constructor(variableName, variableType, editorConfig) {
|
|
902
|
-
super();
|
|
903
|
-
this._variableName = variableName;
|
|
904
|
-
this._variableRef = new ValueReference(variableName);
|
|
905
|
-
this._editorConfig = editorConfig;
|
|
906
|
-
this.returnType = variableType;
|
|
907
|
-
}
|
|
908
|
-
get variableName() {
|
|
909
|
-
return this._variableName;
|
|
910
|
-
}
|
|
911
|
-
get variableRef() {
|
|
912
|
-
return this._variableRef;
|
|
913
|
-
}
|
|
914
|
-
getExpression() {
|
|
915
|
-
return new ResponseVariableExpression(this._variableName, this._editorConfig);
|
|
916
|
-
}
|
|
917
|
-
};
|
|
918
|
-
var ContextVariableEditor = class extends ExpressionEditor {
|
|
919
|
-
_contextType;
|
|
920
|
-
_key;
|
|
921
|
-
_args;
|
|
922
|
-
_asType;
|
|
923
|
-
constructor(contextType, key, args, asType, editorConfig) {
|
|
924
|
-
super();
|
|
925
|
-
this._contextType = contextType;
|
|
926
|
-
this._key = key;
|
|
927
|
-
this._args = args;
|
|
928
|
-
this._asType = asType;
|
|
929
|
-
this._editorConfig = editorConfig;
|
|
930
|
-
}
|
|
931
|
-
getExpression() {
|
|
932
|
-
return new ContextVariableExpression(this._contextType, this._key?.getExpression(), this._args?.map((arg) => arg.getExpression()), this._asType, this._editorConfig);
|
|
933
|
-
}
|
|
934
|
-
};
|
|
935
|
-
var CtxLocaleEditor = class extends ContextVariableEditor {
|
|
936
|
-
returnType = ValueType.string;
|
|
937
|
-
constructor(editorConfig) {
|
|
938
|
-
super(ContextVariableType.Locale, void 0, void 0, ValueType.string, editorConfig);
|
|
939
|
-
}
|
|
940
|
-
};
|
|
941
|
-
var CtxPFlagIsDefinedEditor = class extends ContextVariableEditor {
|
|
942
|
-
returnType = ValueType.boolean;
|
|
943
|
-
constructor(key, editorConfig) {
|
|
944
|
-
super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.boolean, editorConfig);
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
var CtxPFlagStringEditor = class extends ContextVariableEditor {
|
|
948
|
-
returnType = ValueType.string;
|
|
949
|
-
constructor(key, editorConfig) {
|
|
950
|
-
super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.string, editorConfig);
|
|
951
|
-
}
|
|
952
|
-
};
|
|
953
|
-
var CtxPFlagNumEditor = class extends ContextVariableEditor {
|
|
954
|
-
returnType = ValueType.number;
|
|
955
|
-
constructor(key, editorConfig) {
|
|
956
|
-
super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.number, editorConfig);
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
|
-
var CtxPFlagDateEditor = class extends ContextVariableEditor {
|
|
960
|
-
returnType = ValueType.date;
|
|
961
|
-
constructor(key, editorConfig) {
|
|
962
|
-
super(ContextVariableType.ParticipantFlag, key, void 0, ValueType.date, editorConfig);
|
|
963
|
-
}
|
|
964
|
-
};
|
|
965
|
-
var CtxCustomValueEditor = class extends ContextVariableEditor {
|
|
966
|
-
constructor(key, asType, editorConfig) {
|
|
967
|
-
super(ContextVariableType.CustomValue, key, void 0, asType, editorConfig);
|
|
968
|
-
this.returnType = asType;
|
|
969
|
-
}
|
|
970
|
-
};
|
|
971
|
-
var CtxCustomExpressionEditor = class extends ContextVariableEditor {
|
|
972
|
-
constructor(key, args, asType, editorConfig) {
|
|
973
|
-
super(ContextVariableType.CustomExpression, key, args, asType, editorConfig);
|
|
974
|
-
this.returnType = asType;
|
|
975
|
-
}
|
|
976
|
-
};
|
|
977
|
-
var GroupExpressionEditor = class extends ExpressionEditor {
|
|
978
|
-
_args;
|
|
979
|
-
constructor(args, editorConfig) {
|
|
980
|
-
super();
|
|
981
|
-
this._args = args;
|
|
982
|
-
this._editorConfig = editorConfig;
|
|
983
|
-
}
|
|
984
|
-
get args() {
|
|
985
|
-
return this._args;
|
|
986
|
-
}
|
|
987
|
-
addArg(arg, position) {
|
|
988
|
-
if (position === void 0) this._args.push(arg);
|
|
989
|
-
else this._args.splice(position, 0, arg);
|
|
990
|
-
}
|
|
991
|
-
removeArg(position) {
|
|
992
|
-
this._args.splice(position, 1);
|
|
993
|
-
}
|
|
994
|
-
replaceArg(position, arg) {
|
|
995
|
-
this._args[position] = arg;
|
|
996
|
-
}
|
|
997
|
-
swapArgs(activeIndex, overIndex) {
|
|
998
|
-
const newOrder = [...this._args];
|
|
999
|
-
newOrder.splice(activeIndex, 1);
|
|
1000
|
-
newOrder.splice(overIndex, 0, this._args[activeIndex]);
|
|
1001
|
-
this._args = newOrder;
|
|
1002
|
-
}
|
|
1003
|
-
};
|
|
1004
|
-
var AndExpressionEditor = class extends GroupExpressionEditor {
|
|
1005
|
-
returnType = ValueType.boolean;
|
|
1006
|
-
constructor(args, editorConfig) {
|
|
1007
|
-
if (args.some((arg) => arg.returnType !== ValueType.boolean)) throw new Error("And expression editor must have boolean arguments");
|
|
1008
|
-
super(args, editorConfig);
|
|
1009
|
-
}
|
|
1010
|
-
getExpression() {
|
|
1011
|
-
return new FunctionExpression("and", this.args.map((arg) => arg.getExpression()), this._editorConfig);
|
|
1012
|
-
}
|
|
1013
|
-
};
|
|
1014
|
-
var OrExpressionEditor = class extends GroupExpressionEditor {
|
|
1015
|
-
returnType = ValueType.boolean;
|
|
1016
|
-
constructor(args, editorConfig) {
|
|
1017
|
-
if (args.some((arg) => arg.returnType !== ValueType.boolean)) throw new Error("Or expression editor must have boolean arguments");
|
|
1018
|
-
super(args, editorConfig);
|
|
1019
|
-
}
|
|
1020
|
-
getExpression() {
|
|
1021
|
-
return new FunctionExpression("or", this.args.map((arg) => arg.getExpression()), this._editorConfig);
|
|
1022
|
-
}
|
|
1023
|
-
};
|
|
1024
|
-
var StrListContainsExpressionEditor = class extends ExpressionEditor {
|
|
1025
|
-
returnType = ValueType.boolean;
|
|
1026
|
-
_list;
|
|
1027
|
-
_item;
|
|
1028
|
-
constructor(list, item, editorConfig) {
|
|
1029
|
-
super();
|
|
1030
|
-
if (list.returnType !== ValueType.stringArray) throw new Error("List contains expression editor must have a string array list");
|
|
1031
|
-
if (item.returnType !== ValueType.string) throw new Error("List contains expression editor must have a string item");
|
|
1032
|
-
this._list = list;
|
|
1033
|
-
this._item = item;
|
|
1034
|
-
this._editorConfig = editorConfig;
|
|
1035
|
-
}
|
|
1036
|
-
get list() {
|
|
1037
|
-
return this._list;
|
|
1038
|
-
}
|
|
1039
|
-
get item() {
|
|
1040
|
-
return this._item;
|
|
1041
|
-
}
|
|
1042
|
-
set list(list) {
|
|
1043
|
-
this._list = list;
|
|
1044
|
-
}
|
|
1045
|
-
set item(item) {
|
|
1046
|
-
this._item = item;
|
|
1047
|
-
}
|
|
1048
|
-
getExpression() {
|
|
1049
|
-
return new FunctionExpression("list_contains", [this._list?.getExpression(), this._item?.getExpression()], this._editorConfig);
|
|
1050
|
-
}
|
|
1051
|
-
};
|
|
1052
|
-
var StrEqExpressionEditor = class extends ExpressionEditor {
|
|
1053
|
-
returnType = ValueType.boolean;
|
|
1054
|
-
_a;
|
|
1055
|
-
_b;
|
|
1056
|
-
constructor(a, b, editorConfig) {
|
|
1057
|
-
super();
|
|
1058
|
-
this._a = a;
|
|
1059
|
-
this._b = b;
|
|
1060
|
-
this._editorConfig = editorConfig;
|
|
1061
|
-
}
|
|
1062
|
-
get a() {
|
|
1063
|
-
return this._a;
|
|
1064
|
-
}
|
|
1065
|
-
get b() {
|
|
1066
|
-
return this._b;
|
|
1067
|
-
}
|
|
1068
|
-
set a(a) {
|
|
1069
|
-
this._a = a;
|
|
1070
|
-
}
|
|
1071
|
-
set b(b) {
|
|
1072
|
-
this._b = b;
|
|
1073
|
-
}
|
|
1074
|
-
getExpression() {
|
|
1075
|
-
return new FunctionExpression("str_eq", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
|
|
1076
|
-
}
|
|
1077
|
-
};
|
|
1078
|
-
var EqExpressionEditor = class extends ExpressionEditor {
|
|
1079
|
-
returnType = ValueType.boolean;
|
|
1080
|
-
_a;
|
|
1081
|
-
_b;
|
|
1082
|
-
constructor(a, b, editorConfig) {
|
|
1083
|
-
super();
|
|
1084
|
-
this._a = a;
|
|
1085
|
-
this._b = b;
|
|
1086
|
-
this._editorConfig = editorConfig;
|
|
1087
|
-
}
|
|
1088
|
-
get a() {
|
|
1089
|
-
return this._a;
|
|
1090
|
-
}
|
|
1091
|
-
get b() {
|
|
1092
|
-
return this._b;
|
|
1093
|
-
}
|
|
1094
|
-
set a(a) {
|
|
1095
|
-
this._a = a;
|
|
1096
|
-
}
|
|
1097
|
-
set b(b) {
|
|
1098
|
-
this._b = b;
|
|
1099
|
-
}
|
|
1100
|
-
getExpression() {
|
|
1101
|
-
return new FunctionExpression("eq", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
|
|
1102
|
-
}
|
|
1103
|
-
};
|
|
1104
|
-
var GtExpressionEditor = class extends ExpressionEditor {
|
|
1105
|
-
returnType = ValueType.boolean;
|
|
1106
|
-
_a;
|
|
1107
|
-
_b;
|
|
1108
|
-
constructor(a, b, editorConfig) {
|
|
1109
|
-
super();
|
|
1110
|
-
this._a = a;
|
|
1111
|
-
this._b = b;
|
|
1112
|
-
this._editorConfig = editorConfig;
|
|
1113
|
-
}
|
|
1114
|
-
get a() {
|
|
1115
|
-
return this._a;
|
|
1116
|
-
}
|
|
1117
|
-
get b() {
|
|
1118
|
-
return this._b;
|
|
1119
|
-
}
|
|
1120
|
-
set a(a) {
|
|
1121
|
-
this._a = a;
|
|
1122
|
-
}
|
|
1123
|
-
set b(b) {
|
|
1124
|
-
this._b = b;
|
|
1125
|
-
}
|
|
1126
|
-
getExpression() {
|
|
1127
|
-
return new FunctionExpression("gt", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
|
|
1128
|
-
}
|
|
1129
|
-
};
|
|
1130
|
-
var GteExpressionEditor = class extends ExpressionEditor {
|
|
1131
|
-
returnType = ValueType.boolean;
|
|
1132
|
-
_a;
|
|
1133
|
-
_b;
|
|
1134
|
-
constructor(a, b, editorConfig) {
|
|
1135
|
-
super();
|
|
1136
|
-
this._a = a;
|
|
1137
|
-
this._b = b;
|
|
1138
|
-
this._editorConfig = editorConfig;
|
|
1139
|
-
}
|
|
1140
|
-
get a() {
|
|
1141
|
-
return this._a;
|
|
1142
|
-
}
|
|
1143
|
-
get b() {
|
|
1144
|
-
return this._b;
|
|
1145
|
-
}
|
|
1146
|
-
set a(a) {
|
|
1147
|
-
this._a = a;
|
|
1148
|
-
}
|
|
1149
|
-
set b(b) {
|
|
1150
|
-
this._b = b;
|
|
1151
|
-
}
|
|
1152
|
-
getExpression() {
|
|
1153
|
-
return new FunctionExpression("gte", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
|
|
1154
|
-
}
|
|
1155
|
-
};
|
|
1156
|
-
var LtExpressionEditor = class extends ExpressionEditor {
|
|
1157
|
-
returnType = ValueType.boolean;
|
|
1158
|
-
_a;
|
|
1159
|
-
_b;
|
|
1160
|
-
constructor(a, b, editorConfig) {
|
|
1161
|
-
super();
|
|
1162
|
-
this._a = a;
|
|
1163
|
-
this._b = b;
|
|
1164
|
-
this._editorConfig = editorConfig;
|
|
1165
|
-
}
|
|
1166
|
-
get a() {
|
|
1167
|
-
return this._a;
|
|
1168
|
-
}
|
|
1169
|
-
get b() {
|
|
1170
|
-
return this._b;
|
|
1171
|
-
}
|
|
1172
|
-
set a(a) {
|
|
1173
|
-
this._a = a;
|
|
1174
|
-
}
|
|
1175
|
-
set b(b) {
|
|
1176
|
-
this._b = b;
|
|
1177
|
-
}
|
|
1178
|
-
getExpression() {
|
|
1179
|
-
return new FunctionExpression("lt", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
|
|
1180
|
-
}
|
|
1181
|
-
};
|
|
1182
|
-
var LteExpressionEditor = class extends ExpressionEditor {
|
|
1183
|
-
returnType = ValueType.boolean;
|
|
1184
|
-
_a;
|
|
1185
|
-
_b;
|
|
1186
|
-
constructor(a, b, editorConfig) {
|
|
1187
|
-
super();
|
|
1188
|
-
this._a = a;
|
|
1189
|
-
this._b = b;
|
|
1190
|
-
this._editorConfig = editorConfig;
|
|
1191
|
-
}
|
|
1192
|
-
get a() {
|
|
1193
|
-
return this._a;
|
|
1194
|
-
}
|
|
1195
|
-
get b() {
|
|
1196
|
-
return this._b;
|
|
1197
|
-
}
|
|
1198
|
-
set b(b) {
|
|
1199
|
-
this._b = b;
|
|
1200
|
-
}
|
|
1201
|
-
set a(a) {
|
|
1202
|
-
this._a = a;
|
|
1203
|
-
}
|
|
1204
|
-
getExpression() {
|
|
1205
|
-
return new FunctionExpression("lte", [this._a?.getExpression(), this._b?.getExpression()], this._editorConfig);
|
|
1206
|
-
}
|
|
1207
|
-
};
|
|
1208
|
-
var InRangeExpressionEditor = class extends ExpressionEditor {
|
|
1209
|
-
returnType = ValueType.boolean;
|
|
1210
|
-
value;
|
|
1211
|
-
min;
|
|
1212
|
-
max;
|
|
1213
|
-
inclusive;
|
|
1214
|
-
constructor(value, min, max, inclusive, editorConfig) {
|
|
1215
|
-
super();
|
|
1216
|
-
this.value = value;
|
|
1217
|
-
this.min = min;
|
|
1218
|
-
this.max = max;
|
|
1219
|
-
this.inclusive = inclusive;
|
|
1220
|
-
this._editorConfig = editorConfig;
|
|
1221
|
-
}
|
|
1222
|
-
getExpression() {
|
|
1223
|
-
return new FunctionExpression("in_range", [
|
|
1224
|
-
this.value?.getExpression(),
|
|
1225
|
-
this.min?.getExpression(),
|
|
1226
|
-
this.max?.getExpression(),
|
|
1227
|
-
this.inclusive?.getExpression()
|
|
1228
|
-
], this._editorConfig);
|
|
1229
|
-
}
|
|
1230
|
-
};
|
|
1231
|
-
var SumExpressionEditor = class extends GroupExpressionEditor {
|
|
1232
|
-
returnType = ValueType.number;
|
|
1233
|
-
constructor(args, editorConfig) {
|
|
1234
|
-
super(args, editorConfig);
|
|
1235
|
-
}
|
|
1236
|
-
getExpression() {
|
|
1237
|
-
return new FunctionExpression("sum", this.args.map((arg) => arg.getExpression()), this._editorConfig);
|
|
1238
|
-
}
|
|
1239
|
-
};
|
|
1240
|
-
var MinExpressionEditor = class extends GroupExpressionEditor {
|
|
1241
|
-
returnType = ValueType.number;
|
|
1242
|
-
constructor(args, editorConfig) {
|
|
1243
|
-
super(args, editorConfig);
|
|
1244
|
-
}
|
|
1245
|
-
getExpression() {
|
|
1246
|
-
return new FunctionExpression("min", this.args.map((arg) => arg.getExpression()), this._editorConfig);
|
|
1247
|
-
}
|
|
1248
|
-
};
|
|
1249
|
-
var MaxExpressionEditor = class extends GroupExpressionEditor {
|
|
1250
|
-
returnType = ValueType.number;
|
|
1251
|
-
constructor(args, editorConfig) {
|
|
1252
|
-
super(args, editorConfig);
|
|
1253
|
-
}
|
|
1254
|
-
getExpression() {
|
|
1255
|
-
return new FunctionExpression("max", this.args.map((arg) => arg.getExpression()), this._editorConfig);
|
|
132
|
+
const isComponentVisible = (id, component) => resolve(componentNode(id, component), () => isItemVisible(id) && condition(id, this.survey.surveyItems.get(id)?.displayConditions?.components?.[component], component));
|
|
133
|
+
const getAnswer = (slotId) => resolve(slotNode(slotId), () => {
|
|
134
|
+
const owner = this.slots.get(slotId);
|
|
135
|
+
if (!owner || !isItemVisible(owner.itemId) || !(this.components.get(slotId) ?? []).every((id) => isComponentVisible(owner.itemId, id))) return void 0;
|
|
136
|
+
const item = this.survey.surveyItems.get(owner.itemId);
|
|
137
|
+
if (!owner.computed) return item.getActiveResponseValue(slotId, responses[owner.itemId]?.response?.get(slotId), (id) => isComponentVisible(owner.itemId, id), getAnswer);
|
|
138
|
+
const sources = new ResponseItem();
|
|
139
|
+
for (const id of item.getComputedResponseDependencies(slotId)) sources.setSlotValue(id, getAnswer(id));
|
|
140
|
+
return item.getComputedResponseValue(slotId, sources, (id) => isComponentVisible(owner.itemId, id));
|
|
141
|
+
});
|
|
142
|
+
const evaluator = new ExpressionEvaluator({
|
|
143
|
+
responses,
|
|
144
|
+
surveyContext: context,
|
|
145
|
+
resolveResponseValue: getAnswer
|
|
146
|
+
});
|
|
147
|
+
const getItemResponse = (id) => {
|
|
148
|
+
const response = new ResponseItem();
|
|
149
|
+
for (const slot of this.survey.surveyItems.get(id)?.getResponseSlotDefinitions() ?? []) if (!slot.computed) response.setSlotValue(slot.slotId, getAnswer(slot.slotId));
|
|
150
|
+
return response.isEmpty() ? void 0 : response;
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
evaluator,
|
|
154
|
+
getAnswer,
|
|
155
|
+
getItemResponse,
|
|
156
|
+
isItemVisible,
|
|
157
|
+
isComponentVisible,
|
|
158
|
+
getDiagnostics: () => diagnostics.map((diagnostic) => ({ ...diagnostic }))
|
|
159
|
+
};
|
|
1256
160
|
}
|
|
1257
161
|
};
|
|
1258
162
|
//#endregion
|
|
1259
|
-
//#region src/expressions/editors/expression-editor-generators.ts
|
|
1260
|
-
const const_string_array = (...values) => {
|
|
1261
|
-
return new ConstStringArrayEditor(values);
|
|
1262
|
-
};
|
|
1263
|
-
const const_string = (value) => {
|
|
1264
|
-
return new ConstStringEditor(value);
|
|
1265
|
-
};
|
|
1266
|
-
const const_number_array = (...values) => {
|
|
1267
|
-
return new ConstNumberArrayEditor(values);
|
|
1268
|
-
};
|
|
1269
|
-
const const_number = (value) => {
|
|
1270
|
-
return new ConstNumberEditor(value);
|
|
1271
|
-
};
|
|
1272
|
-
const const_boolean = (value) => {
|
|
1273
|
-
return new ConstBooleanEditor(value);
|
|
1274
|
-
};
|
|
1275
|
-
const const_date = (value) => {
|
|
1276
|
-
return new ConstDateEditor(value);
|
|
1277
|
-
};
|
|
1278
|
-
const const_date_array = (...values) => {
|
|
1279
|
-
return new ConstDateArrayEditor(values);
|
|
1280
|
-
};
|
|
1281
|
-
const response_string = (valueRef) => {
|
|
1282
|
-
return new ResponseVariableEditor(valueRef, ValueType.string);
|
|
1283
|
-
};
|
|
1284
|
-
const response_string_array = (valueRef) => {
|
|
1285
|
-
return new ResponseVariableEditor(valueRef, ValueType.stringArray);
|
|
1286
|
-
};
|
|
1287
|
-
const response_number = (valueRef) => {
|
|
1288
|
-
return new ResponseVariableEditor(valueRef, ValueType.number);
|
|
1289
|
-
};
|
|
1290
|
-
const response_boolean = (valueRef) => {
|
|
1291
|
-
return new ResponseVariableEditor(valueRef, ValueType.boolean);
|
|
1292
|
-
};
|
|
1293
|
-
const response_date = (valueRef) => {
|
|
1294
|
-
return new ResponseVariableEditor(valueRef, ValueType.date);
|
|
1295
|
-
};
|
|
1296
|
-
const response_number_array = (valueRef) => {
|
|
1297
|
-
return new ResponseVariableEditor(valueRef, ValueType.numberArray);
|
|
1298
|
-
};
|
|
1299
|
-
const response_date_array = (valueRef) => {
|
|
1300
|
-
return new ResponseVariableEditor(valueRef, ValueType.dateArray);
|
|
1301
|
-
};
|
|
1302
|
-
const ctx_locale = () => {
|
|
1303
|
-
return new CtxLocaleEditor();
|
|
1304
|
-
};
|
|
1305
|
-
const ctx_pflag_is_defined = (key) => {
|
|
1306
|
-
return new CtxPFlagIsDefinedEditor(key);
|
|
1307
|
-
};
|
|
1308
|
-
const ctx_pflag_string = (key) => {
|
|
1309
|
-
return new CtxPFlagStringEditor(key);
|
|
1310
|
-
};
|
|
1311
|
-
const ctx_pflag_num = (key) => {
|
|
1312
|
-
return new CtxPFlagNumEditor(key);
|
|
1313
|
-
};
|
|
1314
|
-
const ctx_pflag_date = (key) => {
|
|
1315
|
-
return new CtxPFlagDateEditor(key);
|
|
1316
|
-
};
|
|
1317
|
-
const ctx_custom_value = (key, dType) => {
|
|
1318
|
-
return new CtxCustomValueEditor(key, dType);
|
|
1319
|
-
};
|
|
1320
|
-
const ctx_custom_expression = (key, args, returnType) => {
|
|
1321
|
-
return new CtxCustomExpressionEditor(key, args, returnType);
|
|
1322
|
-
};
|
|
1323
|
-
const and = (...args) => {
|
|
1324
|
-
return new AndExpressionEditor(args);
|
|
1325
|
-
};
|
|
1326
|
-
const or = (...args) => {
|
|
1327
|
-
return new OrExpressionEditor(args);
|
|
1328
|
-
};
|
|
1329
|
-
const str_list_contains = (list, item) => {
|
|
1330
|
-
return new StrListContainsExpressionEditor(list, item);
|
|
1331
|
-
};
|
|
1332
|
-
const str_eq = (a, b) => {
|
|
1333
|
-
return new StrEqExpressionEditor(a, b);
|
|
1334
|
-
};
|
|
1335
|
-
const eq = (a, b) => {
|
|
1336
|
-
return new EqExpressionEditor(a, b);
|
|
1337
|
-
};
|
|
1338
|
-
const gt = (a, b) => {
|
|
1339
|
-
return new GtExpressionEditor(a, b);
|
|
1340
|
-
};
|
|
1341
|
-
const gte = (a, b) => {
|
|
1342
|
-
return new GteExpressionEditor(a, b);
|
|
1343
|
-
};
|
|
1344
|
-
const lt = (a, b) => {
|
|
1345
|
-
return new LtExpressionEditor(a, b);
|
|
1346
|
-
};
|
|
1347
|
-
const lte = (a, b) => {
|
|
1348
|
-
return new LteExpressionEditor(a, b);
|
|
1349
|
-
};
|
|
1350
|
-
const in_range = (value, min, max, inclusive) => {
|
|
1351
|
-
return new InRangeExpressionEditor(value, min, max, inclusive);
|
|
1352
|
-
};
|
|
1353
|
-
const sum = (...args) => {
|
|
1354
|
-
return new SumExpressionEditor(args);
|
|
1355
|
-
};
|
|
1356
|
-
const min = (...args) => {
|
|
1357
|
-
return new MinExpressionEditor(args);
|
|
1358
|
-
};
|
|
1359
|
-
const max = (...args) => {
|
|
1360
|
-
return new MaxExpressionEditor(args);
|
|
1361
|
-
};
|
|
1362
|
-
//#endregion
|
|
1363
163
|
//#region src/engine/engine.ts
|
|
1364
|
-
var SurveyEngineCore = class {
|
|
164
|
+
var SurveyEngineCore = class SurveyEngineCore {
|
|
165
|
+
activeResponsePlan;
|
|
166
|
+
activeState;
|
|
167
|
+
responseDiagnostics = /* @__PURE__ */ new Map();
|
|
1365
168
|
surveyDef;
|
|
1366
169
|
renderedSurveyTree;
|
|
170
|
+
renderedItemsCache;
|
|
1367
171
|
context;
|
|
1368
172
|
locale;
|
|
1369
173
|
sessionSeedValue;
|
|
@@ -1378,12 +182,24 @@ var SurveyEngineCore = class {
|
|
|
1378
182
|
this._openedAt = this._getTimestamp();
|
|
1379
183
|
this.sessionSeedValue = generateId();
|
|
1380
184
|
this.surveyDef = survey;
|
|
185
|
+
const { issues: configurationIssues, plan } = SurveyEngineCore.inspectConfiguration(survey);
|
|
186
|
+
if (configurationIssues.length) throw new Error(configurationIssues.join("\n"));
|
|
187
|
+
this.activeResponsePlan = plan;
|
|
1381
188
|
this.context = context ? context : { locale: "en" };
|
|
1382
189
|
this.locale = this.context.locale;
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
190
|
+
if (prefills) {
|
|
191
|
+
const registry = survey.getResponseSlotRegistry();
|
|
192
|
+
this.prefills = Object.create(null);
|
|
193
|
+
for (const [slotId, value] of Object.entries(prefills)) {
|
|
194
|
+
const owner = registry.get(slotId);
|
|
195
|
+
if (!owner || owner.computed) throw new Error(`Cannot restore unknown or computed response slot "${slotId}"`);
|
|
196
|
+
const item = survey.surveyItems.get(owner.itemId);
|
|
197
|
+
(this.prefills[owner.itemId] ??= new SurveyItemResponse({
|
|
198
|
+
itemId: item.id,
|
|
199
|
+
itemType: item.type
|
|
200
|
+
}, new ResponseItem())).response.setSlotValue(slotId, value);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
1387
203
|
this.prefillEnvironment = options?.prefillEnvironment;
|
|
1388
204
|
this.dateLocales = dateLocales || [{
|
|
1389
205
|
code: "en",
|
|
@@ -1391,7 +207,7 @@ var SurveyEngineCore = class {
|
|
|
1391
207
|
}];
|
|
1392
208
|
this.responses = this.initResponseObject(this.surveyDef.surveyItems);
|
|
1393
209
|
this.initCache();
|
|
1394
|
-
this.applyConfiguredPrefills();
|
|
210
|
+
if (options?.applyConfiguredPrefills !== false) this.applyConfiguredPrefills();
|
|
1395
211
|
this.evalExpressions();
|
|
1396
212
|
if (!survey.rootItem) throw new Error("constructor: root item not found");
|
|
1397
213
|
this.renderedSurveyTree = this.renderGroup(survey.rootItem);
|
|
@@ -1402,7 +218,7 @@ var SurveyEngineCore = class {
|
|
|
1402
218
|
}];
|
|
1403
219
|
}
|
|
1404
220
|
setContext(context) {
|
|
1405
|
-
this.context
|
|
221
|
+
this.updateContext(context);
|
|
1406
222
|
}
|
|
1407
223
|
getDateLocales() {
|
|
1408
224
|
return this.dateLocales.slice();
|
|
@@ -1417,9 +233,10 @@ var SurveyEngineCore = class {
|
|
|
1417
233
|
return found?.locale;
|
|
1418
234
|
}
|
|
1419
235
|
updateContext(context) {
|
|
236
|
+
const localeChanged = this.locale !== context.locale;
|
|
1420
237
|
this.context = context;
|
|
1421
238
|
this.locale = context.locale;
|
|
1422
|
-
this._registerSurveyEvent({
|
|
239
|
+
if (localeChanged) this._registerSurveyEvent({
|
|
1423
240
|
type: SurveyEventTypes.languageChanged,
|
|
1424
241
|
timestamp: this._getTimestamp(),
|
|
1425
242
|
value: context.locale
|
|
@@ -1427,21 +244,49 @@ var SurveyEngineCore = class {
|
|
|
1427
244
|
this.evalExpressions();
|
|
1428
245
|
this.reRenderSurveyTree();
|
|
1429
246
|
}
|
|
247
|
+
/** Change the presentation language without losing participant/custom context. */
|
|
248
|
+
updateLocale(locale) {
|
|
249
|
+
if (this.context.locale === locale) return false;
|
|
250
|
+
this.updateContext({
|
|
251
|
+
...this.context,
|
|
252
|
+
locale
|
|
253
|
+
});
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
1430
256
|
setResponse(targetKey, response) {
|
|
1431
257
|
const target = this.getResponseItem(targetKey);
|
|
1432
258
|
if (!target) throw new Error("setResponse: target not found for key: " + targetKey);
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
}
|
|
259
|
+
const item = this.surveyDef.surveyItems.get(targetKey);
|
|
260
|
+
const previousSlots = structuredClone(target.response?.serialize()?.slots ?? {});
|
|
261
|
+
const normalized = item.normalizeResponse(response);
|
|
262
|
+
target.response = normalized.response;
|
|
263
|
+
this.responseDiagnostics.set(targetKey, normalized.diagnostics ?? []);
|
|
264
|
+
const nextSlots = target.response?.serialize()?.slots ?? {};
|
|
265
|
+
const timestamp = this._getTimestamp();
|
|
266
|
+
const comparable = (value) => value === void 0 ? void 0 : JSON.stringify(value, Object.keys(value).sort());
|
|
267
|
+
for (const slotId of /* @__PURE__ */ new Set([...Object.keys(previousSlots), ...Object.keys(nextSlots)])) {
|
|
268
|
+
if (comparable(previousSlots[slotId]) === comparable(nextSlots[slotId])) continue;
|
|
269
|
+
this._registerSurveyEvent({
|
|
270
|
+
type: SurveyEventTypes.responseChanged,
|
|
271
|
+
timestamp,
|
|
272
|
+
slotId,
|
|
273
|
+
operation: nextSlots[slotId] === void 0 ? "clear" : "set"
|
|
274
|
+
});
|
|
275
|
+
}
|
|
1439
276
|
this.evalExpressions();
|
|
1440
277
|
this.reRenderSurveyTree();
|
|
1441
278
|
}
|
|
1442
279
|
get openedAt() {
|
|
1443
280
|
return this._openedAt;
|
|
1444
281
|
}
|
|
282
|
+
/** Invalid visibility results in the current evaluation; unanswered conditions are omitted. */
|
|
283
|
+
getEvaluationDiagnostics() {
|
|
284
|
+
return this.activeEvaluation().getDiagnostics();
|
|
285
|
+
}
|
|
286
|
+
/** Diagnostics from the latest initialization or response update for an item. */
|
|
287
|
+
getResponseDiagnostics(itemId) {
|
|
288
|
+
return (this.responseDiagnostics.get(itemId) ?? []).map((diagnostic) => ({ ...diagnostic }));
|
|
289
|
+
}
|
|
1445
290
|
get sessionSeed() {
|
|
1446
291
|
return this.sessionSeedValue;
|
|
1447
292
|
}
|
|
@@ -1468,9 +313,7 @@ var SurveyEngineCore = class {
|
|
|
1468
313
|
case "large":
|
|
1469
314
|
max = this.surveyDef.maxItemsPerPage.large;
|
|
1470
315
|
break;
|
|
1471
|
-
case "small":
|
|
1472
|
-
max = this.surveyDef.maxItemsPerPage.small;
|
|
1473
|
-
break;
|
|
316
|
+
case "small": max = this.surveyDef.maxItemsPerPage.small;
|
|
1474
317
|
}
|
|
1475
318
|
if (currentPage.length >= max) {
|
|
1476
319
|
pages.push([...currentPage]);
|
|
@@ -1480,8 +323,17 @@ var SurveyEngineCore = class {
|
|
|
1480
323
|
if (currentPage.length > 0) pages.push([...currentPage]);
|
|
1481
324
|
return pages;
|
|
1482
325
|
}
|
|
326
|
+
/** Read-only projection, with stable identity until the engine renders a new tree. */
|
|
327
|
+
getRenderedSurveyItems() {
|
|
328
|
+
if (this.renderedItemsCache?.tree !== this.renderedSurveyTree) this.renderedItemsCache = {
|
|
329
|
+
tree: this.renderedSurveyTree,
|
|
330
|
+
items: Object.freeze(flattenTree(this.renderedSurveyTree).map((item) => Object.freeze(item)))
|
|
331
|
+
};
|
|
332
|
+
return this.renderedItemsCache.items;
|
|
333
|
+
}
|
|
1483
334
|
getRenderedSurveyItem(itemId) {
|
|
1484
|
-
|
|
335
|
+
const item = this.getRenderedSurveyItems().find((item) => item.id === itemId);
|
|
336
|
+
return item ? { ...item } : void 0;
|
|
1485
337
|
}
|
|
1486
338
|
onPageChanged(pageIndex) {
|
|
1487
339
|
this._registerSurveyEvent({
|
|
@@ -1493,11 +345,44 @@ var SurveyEngineCore = class {
|
|
|
1493
345
|
getEvents() {
|
|
1494
346
|
return this._events.slice();
|
|
1495
347
|
}
|
|
348
|
+
/** Current session answers, including retained answers to hidden items. */
|
|
1496
349
|
getResponses() {
|
|
350
|
+
return this.collectResponses(true);
|
|
351
|
+
}
|
|
352
|
+
/** Answers to currently visible items and response slots on every page; does not erase hidden session answers. */
|
|
353
|
+
getSubmissionResponses() {
|
|
354
|
+
return this.collectResponses(false);
|
|
355
|
+
}
|
|
356
|
+
/** Flat submission with events and item metadata kept separate from answer identity. */
|
|
357
|
+
getSurveyResponse(key, includeHidden = false) {
|
|
358
|
+
const response = new SurveyResponse(key);
|
|
359
|
+
for (const item of this.collectResponses(includeHidden)) response.addItemResponse(item);
|
|
360
|
+
response.evaluationContext = structuredClone({
|
|
361
|
+
locale: this.context.locale,
|
|
362
|
+
participantFlags: this.context.participantFlags
|
|
363
|
+
});
|
|
364
|
+
response.events = structuredClone(this._events);
|
|
365
|
+
response.context = this.context.customValues && structuredClone(this.context.customValues);
|
|
366
|
+
return response;
|
|
367
|
+
}
|
|
368
|
+
getAnswer(slotId) {
|
|
369
|
+
const owner = this.surveyDef.getResponseSlotRegistry().get(slotId);
|
|
370
|
+
if (!owner) throw new Error(`Unknown response slot "${slotId}"`);
|
|
371
|
+
return owner.computed ? this.getComputedResponseValue(slotId) : this.responses[owner.itemId]?.response?.get(slotId);
|
|
372
|
+
}
|
|
373
|
+
setAnswer(slotId, value) {
|
|
374
|
+
const owner = this.surveyDef.getResponseSlotRegistry().get(slotId);
|
|
375
|
+
if (!owner || owner.computed) throw new Error(`Cannot write unknown or computed response slot "${slotId}"`);
|
|
376
|
+
const response = this.responses[owner.itemId]?.response?.clone() ?? new ResponseItem();
|
|
377
|
+
response.setSlotValue(slotId, value);
|
|
378
|
+
this.setResponse(owner.itemId, response);
|
|
379
|
+
}
|
|
380
|
+
collectResponses(includeHidden) {
|
|
1497
381
|
const renderedPositions = /* @__PURE__ */ new Map();
|
|
1498
382
|
flattenTree(this.renderedSurveyTree).filter((item) => item.type !== "page-break").forEach((item, index) => {
|
|
1499
383
|
if (this.responses[item.id]) renderedPositions.set(item.id, index);
|
|
1500
384
|
});
|
|
385
|
+
const active = includeHidden ? void 0 : this.activeEvaluation();
|
|
1501
386
|
const responses = [];
|
|
1502
387
|
this.surveyDef.surveyItems.forEach((item, itemId) => {
|
|
1503
388
|
if (!item.isInteractive()) return;
|
|
@@ -1505,10 +390,31 @@ var SurveyEngineCore = class {
|
|
|
1505
390
|
if (!response) return;
|
|
1506
391
|
const position = renderedPositions.get(itemId);
|
|
1507
392
|
response.meta = position === void 0 ? void 0 : { position };
|
|
1508
|
-
|
|
393
|
+
if (!includeHidden && position === void 0) return;
|
|
394
|
+
if (includeHidden) {
|
|
395
|
+
responses.push(response);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const submission = new SurveyItemResponse(response, active.getItemResponse(itemId));
|
|
399
|
+
submission.meta = response.meta && { ...response.meta };
|
|
400
|
+
responses.push(submission);
|
|
1509
401
|
});
|
|
1510
402
|
return responses;
|
|
1511
403
|
}
|
|
404
|
+
/** Author and intrinsic validation failures, restricted to currently rendered items. */
|
|
405
|
+
getInvalidResponseItems(itemIds) {
|
|
406
|
+
const rendered = new Set(this.getRenderedSurveyItems().map((item) => item.id));
|
|
407
|
+
return [...new Set(itemIds ?? rendered)].filter((id) => rendered.has(id) && Object.values(this.getValidationValues(id) ?? {}).some((valid) => !valid));
|
|
408
|
+
}
|
|
409
|
+
getIntrinsicValidationValues(itemKey) {
|
|
410
|
+
const active = this.activeEvaluation();
|
|
411
|
+
if (!active.isItemVisible(itemKey)) return {};
|
|
412
|
+
return { ...this.surveyDef.surveyItems.get(itemKey)?.getResponseValidationValues(active.getItemResponse(itemKey), (id) => active.isComponentVisible(itemKey, id), (expression) => active.evaluator.eval(Expression.deserialize(expression))) };
|
|
413
|
+
}
|
|
414
|
+
getAuthorValidationValues(itemKey) {
|
|
415
|
+
if (!this.activeEvaluation().isItemVisible(itemKey)) return {};
|
|
416
|
+
return Object.fromEntries(Object.entries(this.cache.validations[itemKey] ?? {}).map(([key, validation]) => [key, validation.result]));
|
|
417
|
+
}
|
|
1512
418
|
getDisplayConditionValue(itemKey, componentKey) {
|
|
1513
419
|
if (componentKey) return this.cache.displayConditions[itemKey]?.components?.[componentKey]?.result;
|
|
1514
420
|
return this.cache.displayConditions[itemKey]?.root?.result;
|
|
@@ -1520,12 +426,9 @@ var SurveyEngineCore = class {
|
|
|
1520
426
|
return this.cache.templateValues[templateValueKey]?.value;
|
|
1521
427
|
}
|
|
1522
428
|
getValidationValues(itemKey) {
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1525
|
-
return Object.keys(
|
|
1526
|
-
acc[validationKey] = validations[validationKey].result;
|
|
1527
|
-
return acc;
|
|
1528
|
-
}, {});
|
|
429
|
+
const result = this.getIntrinsicValidationValues(itemKey);
|
|
430
|
+
for (const [key, valid] of Object.entries(this.getAuthorValidationValues(itemKey))) result[key] = valid && (result[key] ?? true);
|
|
431
|
+
return Object.keys(result).length ? result : void 0;
|
|
1529
432
|
}
|
|
1530
433
|
initCache() {
|
|
1531
434
|
this.cache = {
|
|
@@ -1595,40 +498,39 @@ var SurveyEngineCore = class {
|
|
|
1595
498
|
initResponseObject(items) {
|
|
1596
499
|
const respGroup = {};
|
|
1597
500
|
items.forEach((item, itemId) => {
|
|
1598
|
-
if (!item.isInteractive) return;
|
|
501
|
+
if (!item.isInteractive()) return;
|
|
1599
502
|
else {
|
|
1600
503
|
const prefill = this.prefills?.[itemId];
|
|
1601
504
|
const applyPrefill = prefill && prefill.itemType === item.type;
|
|
505
|
+
const normalized = item.normalizeResponse(applyPrefill ? prefill.response : void 0);
|
|
506
|
+
this.responseDiagnostics.set(itemId, normalized.diagnostics ?? []);
|
|
1602
507
|
respGroup[itemId] = new SurveyItemResponse({
|
|
1603
508
|
itemId,
|
|
1604
509
|
itemType: item.type
|
|
1605
|
-
},
|
|
510
|
+
}, normalized.response);
|
|
1606
511
|
}
|
|
1607
512
|
});
|
|
1608
513
|
return respGroup;
|
|
1609
514
|
}
|
|
1610
515
|
applyConfiguredPrefills() {
|
|
1611
|
-
const evalEngine = new ExpressionEvaluator({
|
|
1612
|
-
responses: this.responses,
|
|
1613
|
-
surveyContext: this.context
|
|
1614
|
-
});
|
|
1615
516
|
const maxPasses = Math.max(this.surveyDef.surveyItems.size, 1);
|
|
1616
517
|
for (let pass = 0; pass < maxPasses; pass++) {
|
|
1617
|
-
this.evalTemplateValues(
|
|
518
|
+
this.evalTemplateValues(this.createEvaluator());
|
|
1618
519
|
let changedInPass = false;
|
|
1619
520
|
this.surveyDef.surveyItems.forEach((item) => {
|
|
1620
521
|
if (!item.isInteractive()) return;
|
|
1621
|
-
changedInPass = this.applyConfiguredPrefillsForItem(item
|
|
522
|
+
changedInPass = this.applyConfiguredPrefillsForItem(item) || changedInPass;
|
|
1622
523
|
});
|
|
1623
524
|
if (!changedInPass) break;
|
|
1624
525
|
}
|
|
1625
|
-
this.evalTemplateValues(
|
|
526
|
+
this.evalTemplateValues(this.createEvaluator());
|
|
1626
527
|
}
|
|
1627
|
-
applyConfiguredPrefillsForItem(item
|
|
528
|
+
applyConfiguredPrefillsForItem(item) {
|
|
1628
529
|
const responseItem = this.responses[item.id];
|
|
1629
530
|
if (!responseItem) return false;
|
|
1630
531
|
let changed = false;
|
|
1631
532
|
for (const prefill of item.prefills ?? []) {
|
|
533
|
+
const evalEngine = this.createEvaluator();
|
|
1632
534
|
const targetSlot = item.resolvePrefillTarget(prefill.target);
|
|
1633
535
|
if (!targetSlot) continue;
|
|
1634
536
|
if ((prefill.apply ?? SurveyItemPrefillApplyMode.ifEmpty) === SurveyItemPrefillApplyMode.ifEmpty && responseItem.response?.get(targetSlot.slotId) !== void 0) continue;
|
|
@@ -1639,8 +541,11 @@ var SurveyEngineCore = class {
|
|
|
1639
541
|
if (!normalizedValue) continue;
|
|
1640
542
|
const nextResponse = responseItem.response?.clone() ?? new ResponseItem();
|
|
1641
543
|
nextResponse.setSlotValue(targetSlot.slotId, normalizedValue);
|
|
1642
|
-
|
|
1643
|
-
|
|
544
|
+
const normalized = item.normalizeResponse(nextResponse);
|
|
545
|
+
this.responseDiagnostics.set(item.id, normalized.diagnostics ?? []);
|
|
546
|
+
const next = normalized.response?.isEmpty() ? void 0 : normalized.response;
|
|
547
|
+
if (JSON.stringify(responseItem.response?.serialize()) !== JSON.stringify(next?.serialize())) changed = true;
|
|
548
|
+
responseItem.response = next;
|
|
1644
549
|
}
|
|
1645
550
|
return changed;
|
|
1646
551
|
}
|
|
@@ -1656,11 +561,9 @@ var SurveyEngineCore = class {
|
|
|
1656
561
|
case "templateValue": return this.getTemplateValue(prefill.source.key);
|
|
1657
562
|
case "previousResponse": {
|
|
1658
563
|
if (!this.prefillEnvironment?.getPreviousResponse) return;
|
|
1659
|
-
const refTarget = prefill.source.ref.target ?? prefill.target;
|
|
1660
564
|
const resolved = this.prefillEnvironment.getPreviousResponse({
|
|
1661
565
|
surveyKey: prefill.source.ref.surveyKey,
|
|
1662
|
-
|
|
1663
|
-
target: refTarget
|
|
566
|
+
slotId: prefill.source.ref.slotId
|
|
1664
567
|
});
|
|
1665
568
|
if (!resolved) return;
|
|
1666
569
|
return resolved;
|
|
@@ -1726,6 +629,11 @@ var SurveyEngineCore = class {
|
|
|
1726
629
|
};
|
|
1727
630
|
}
|
|
1728
631
|
renderGroup(groupDef, parent) {
|
|
632
|
+
if (!this.activeEvaluation().isItemVisible(groupDef.id)) return {
|
|
633
|
+
id: groupDef.id,
|
|
634
|
+
type: groupDef.type,
|
|
635
|
+
items: []
|
|
636
|
+
};
|
|
1729
637
|
if (!parent) parent = {
|
|
1730
638
|
id: groupDef.id,
|
|
1731
639
|
type: "group",
|
|
@@ -1747,13 +655,48 @@ var SurveyEngineCore = class {
|
|
|
1747
655
|
getResponseItem(itemId) {
|
|
1748
656
|
return this.responses[itemId];
|
|
1749
657
|
}
|
|
658
|
+
/** Resolve computed references against current answers and conditions, with cycle detection. */
|
|
659
|
+
getComputedResponseValue(slotId) {
|
|
660
|
+
return this.activeEvaluation().getAnswer(slotId);
|
|
661
|
+
}
|
|
662
|
+
/** Evaluate against the same current responses/context used by conditions and prefills. */
|
|
663
|
+
evaluateExpression(expression) {
|
|
664
|
+
return this.activeEvaluation().evaluator.eval(Expression.deserialize(expression));
|
|
665
|
+
}
|
|
666
|
+
activeEvaluation() {
|
|
667
|
+
return this.activeState ??= this.activeResponsePlan.evaluate(this.responses, this.context);
|
|
668
|
+
}
|
|
669
|
+
createEvaluator() {
|
|
670
|
+
return this.activeResponsePlan.evaluate(this.responses, this.context).evaluator;
|
|
671
|
+
}
|
|
672
|
+
/** Active answer used by conditions and scoring; hidden session values remain in getAnswer(). */
|
|
673
|
+
getActiveAnswer(slotId) {
|
|
674
|
+
return this.activeEvaluation().getAnswer(slotId);
|
|
675
|
+
}
|
|
676
|
+
/** Validate static runtime contracts without constructing a response session. */
|
|
677
|
+
static getConfigurationIssues(survey) {
|
|
678
|
+
return SurveyEngineCore.inspectConfiguration(survey).issues;
|
|
679
|
+
}
|
|
680
|
+
static inspectConfiguration(survey) {
|
|
681
|
+
const issues = [...survey.surveyItems.values()].flatMap((item) => item.getExecutionConfigurationIssues().map((issue) => `${(item.metadata?.itemLabel ?? "")?.trim() || item.id}: ${issue}`));
|
|
682
|
+
let plan;
|
|
683
|
+
try {
|
|
684
|
+
plan = new ActiveResponsePlan(survey);
|
|
685
|
+
} catch (error) {
|
|
686
|
+
issues.push(error instanceof Error ? error.message : String(error));
|
|
687
|
+
}
|
|
688
|
+
issues.push(...survey.getResponseSlotRegistry().diagnostics.map((diagnostic) => diagnostic.message));
|
|
689
|
+
issues.push(...survey.getBlockingExpressionIssues());
|
|
690
|
+
return {
|
|
691
|
+
issues,
|
|
692
|
+
plan
|
|
693
|
+
};
|
|
694
|
+
}
|
|
1750
695
|
evalExpressions() {
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
surveyContext: this.context
|
|
1754
|
-
});
|
|
696
|
+
this.activeState = this.activeResponsePlan.evaluate(this.responses, this.context);
|
|
697
|
+
const evalEngine = this.activeState.evaluator;
|
|
1755
698
|
this.evalTemplateValues(evalEngine);
|
|
1756
|
-
this.evalDisplayConditions(
|
|
699
|
+
this.evalDisplayConditions();
|
|
1757
700
|
this.evalDisableConditions(evalEngine);
|
|
1758
701
|
this.evalValidations(evalEngine);
|
|
1759
702
|
}
|
|
@@ -1778,34 +721,18 @@ var SurveyEngineCore = class {
|
|
|
1778
721
|
}
|
|
1779
722
|
evaluatedValue = {
|
|
1780
723
|
type: ValueType.string,
|
|
1781
|
-
value:
|
|
724
|
+
value: formatCalendarDate(evaluatedValue.value, templateValue.templateDef.dateFormat)
|
|
1782
725
|
};
|
|
1783
|
-
break;
|
|
1784
|
-
default: break;
|
|
1785
726
|
}
|
|
1786
727
|
this.cache.templateValues[templateValueKey].value = evaluatedValue;
|
|
1787
728
|
});
|
|
1788
729
|
}
|
|
1789
|
-
evalDisplayConditions(
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
if (
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
console.warn("evalDisplayConditions: display condition expression returned undefined: " + itemKey);
|
|
1796
|
-
return;
|
|
1797
|
-
}
|
|
1798
|
-
this.cache.displayConditions[itemKey].root.result = resolvedValue.value;
|
|
1799
|
-
}
|
|
1800
|
-
if (displayCondition.components) Object.keys(displayCondition.components).forEach((componentKey) => {
|
|
1801
|
-
const resolvedValue = evalEngine.eval(displayCondition.components[componentKey].expression);
|
|
1802
|
-
if (resolvedValue === void 0 || resolvedValue.type !== ValueType.boolean) {
|
|
1803
|
-
console.warn("evalDisplayConditions: display condition component expression returned undefined: " + itemKey + "." + componentKey);
|
|
1804
|
-
return;
|
|
1805
|
-
}
|
|
1806
|
-
this.cache.displayConditions[itemKey].components[componentKey].result = resolvedValue.value;
|
|
1807
|
-
});
|
|
1808
|
-
});
|
|
730
|
+
evalDisplayConditions() {
|
|
731
|
+
const active = this.activeEvaluation();
|
|
732
|
+
for (const [itemId, conditions] of Object.entries(this.cache.displayConditions)) {
|
|
733
|
+
if (conditions.root) conditions.root.result = active.isItemVisible(itemId);
|
|
734
|
+
for (const [id, condition] of Object.entries(conditions.components ?? {})) condition.result = active.isComponentVisible(itemId, id);
|
|
735
|
+
}
|
|
1809
736
|
}
|
|
1810
737
|
evalDisableConditions(evalEngine) {
|
|
1811
738
|
Object.keys(this.cache.disabledConditions).forEach((itemKey) => {
|
|
@@ -1827,6 +754,7 @@ var SurveyEngineCore = class {
|
|
|
1827
754
|
const resolvedValue = evalEngine.eval(validation[validationKey].expression);
|
|
1828
755
|
if (resolvedValue === void 0 || resolvedValue.type !== ValueType.boolean) {
|
|
1829
756
|
console.warn("evalValidations: validation expression returned undefined: " + itemId + "." + validationKey);
|
|
757
|
+
this.cache.validations[itemId][validationKey].result = false;
|
|
1830
758
|
return;
|
|
1831
759
|
}
|
|
1832
760
|
this.cache.validations[itemId][validationKey].result = resolvedValue.value;
|
|
@@ -1845,6 +773,9 @@ const flattenTree = (itemTree) => {
|
|
|
1845
773
|
//#endregion
|
|
1846
774
|
//#region src/json-patch.ts
|
|
1847
775
|
var JsonPatchError = class extends Error {
|
|
776
|
+
code;
|
|
777
|
+
path;
|
|
778
|
+
operationIndex;
|
|
1848
779
|
constructor(message, code, path, operationIndex) {
|
|
1849
780
|
super(message);
|
|
1850
781
|
this.code = code;
|
|
@@ -1902,7 +833,8 @@ const getValueAtSegments = (document, segments, path) => {
|
|
|
1902
833
|
return current;
|
|
1903
834
|
};
|
|
1904
835
|
const getValueAtJsonPointer = (document, pointer) => {
|
|
1905
|
-
|
|
836
|
+
const segments = parseJsonPointer(pointer);
|
|
837
|
+
return getValueAtSegments(document, segments, pointer);
|
|
1906
838
|
};
|
|
1907
839
|
const deepEqualJson = (left, right) => {
|
|
1908
840
|
if (left === right) return true;
|
|
@@ -1937,7 +869,12 @@ const applyAdd = (document, path, value) => {
|
|
|
1937
869
|
return document;
|
|
1938
870
|
}
|
|
1939
871
|
if (!isRecord(parent)) throw new JsonPatchError(`JSON pointer "${path}" parent is not an object or array.`, "invalid-target-parent", path);
|
|
1940
|
-
parent
|
|
872
|
+
Object.defineProperty(parent, key, {
|
|
873
|
+
configurable: true,
|
|
874
|
+
enumerable: true,
|
|
875
|
+
value: structuredCloneMethod(value),
|
|
876
|
+
writable: true
|
|
877
|
+
});
|
|
1941
878
|
return document;
|
|
1942
879
|
};
|
|
1943
880
|
const applyRemove = (document, path) => {
|
|
@@ -2005,9 +942,11 @@ const applyJsonPatch = (document, operations) => {
|
|
|
2005
942
|
patchedDocument = applyAdd(patchedDocument, operation.path, value);
|
|
2006
943
|
break;
|
|
2007
944
|
}
|
|
2008
|
-
case "test":
|
|
2009
|
-
|
|
945
|
+
case "test": {
|
|
946
|
+
const value = getValueAtJsonPointer(patchedDocument, operation.path);
|
|
947
|
+
if (!deepEqualJson(value, operation.value)) throw new JsonPatchError(`JSON Patch test failed at "${operation.path}".`, "test-failed", operation.path);
|
|
2010
948
|
break;
|
|
949
|
+
}
|
|
2011
950
|
}
|
|
2012
951
|
} catch (error) {
|
|
2013
952
|
if (error instanceof JsonPatchError) throw new JsonPatchError(error.message, error.code, error.path, operationIndex);
|
|
@@ -2017,93 +956,13 @@ const applyJsonPatch = (document, operations) => {
|
|
|
2017
956
|
return patchedDocument;
|
|
2018
957
|
};
|
|
2019
958
|
//#endregion
|
|
2020
|
-
//#region src/response-exporter/
|
|
2021
|
-
/**
|
|
2022
|
-
|
|
2023
|
-
"
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
"
|
|
2027
|
-
];
|
|
2028
|
-
/** Built-in transform modes for slot export. */
|
|
2029
|
-
const SlotTransformMode = {
|
|
2030
|
-
default: "default",
|
|
2031
|
-
mask: "mask",
|
|
2032
|
-
dateFormat: "dateFormat"
|
|
2033
|
-
};
|
|
2034
|
-
/** Separator between item key path and slot coding path in default export headers. */
|
|
2035
|
-
const EXPORT_COLUMN_SLOT_SEPARATOR = "__";
|
|
2036
|
-
/** Source of an export column. */
|
|
2037
|
-
const ExportColumnSource = {
|
|
2038
|
-
meta: "meta",
|
|
2039
|
-
context: "context",
|
|
2040
|
-
response: "response"
|
|
2041
|
-
};
|
|
2042
|
-
//#endregion
|
|
2043
|
-
//#region src/response-exporter/slot-formatter.ts
|
|
2044
|
-
/** Default transform config when no profile override. */
|
|
2045
|
-
const DEFAULT_TRANSFORM = { mode: SlotTransformMode.default };
|
|
2046
|
-
/**
|
|
2047
|
-
* Export a single response slot to a string. Pure function for unit testing.
|
|
2048
|
-
* Uses default formatting by ValueType unless overridden by transformConfig.
|
|
2049
|
-
*/
|
|
2050
|
-
function exportSingleResponseSlot(params) {
|
|
2051
|
-
const { value, valueType, transformConfig } = params;
|
|
2052
|
-
if (value === void 0 || value === null) return "";
|
|
2053
|
-
const mode = transformConfig.mode;
|
|
2054
|
-
if (mode === SlotTransformMode.mask) return maskValue(value, transformConfig.maskChar ?? "***");
|
|
2055
|
-
if (mode === SlotTransformMode.dateFormat && (valueType === ValueType.date || valueType === ValueType.dateArray)) return formatDateValue(value, transformConfig.dateFormat?.pattern ?? "yyyy-MM-dd");
|
|
2056
|
-
return formatDefault(value);
|
|
2057
|
-
}
|
|
2058
|
-
const BooleanExportValues = {
|
|
2059
|
-
TRUE: "TRUE",
|
|
2060
|
-
FALSE: "FALSE"
|
|
2061
|
-
};
|
|
2062
|
-
function formatDefault(value) {
|
|
2063
|
-
switch (value.type) {
|
|
2064
|
-
case ValueType.string: return String(value.value);
|
|
2065
|
-
case ValueType.reference: return String(value.value);
|
|
2066
|
-
case ValueType.boolean: return value.value ? BooleanExportValues.TRUE : BooleanExportValues.FALSE;
|
|
2067
|
-
case ValueType.number: return value.precision === NumberPrecision.int ? String(Math.floor(value.value)) : String(value.value);
|
|
2068
|
-
case ValueType.date: return (/* @__PURE__ */ new Date(value.value * 1e3)).toISOString();
|
|
2069
|
-
case ValueType.duration: {
|
|
2070
|
-
const d = value;
|
|
2071
|
-
return `${d.precision === NumberPrecision.int ? Math.floor(d.value) : d.value} ${d.unit}`;
|
|
2072
|
-
}
|
|
2073
|
-
case ValueType.stringArray: return value.value.join("; ");
|
|
2074
|
-
case ValueType.referenceArray: return value.value.join("; ");
|
|
2075
|
-
case ValueType.numberArray: {
|
|
2076
|
-
const arr = value.value;
|
|
2077
|
-
const prec = value.precision;
|
|
2078
|
-
const fmt = (n) => prec === NumberPrecision.int ? Math.floor(n) : n;
|
|
2079
|
-
return arr.map(fmt).join("; ");
|
|
2080
|
-
}
|
|
2081
|
-
case ValueType.dateArray: return value.value.map((ts) => (/* @__PURE__ */ new Date(ts * 1e3)).toISOString()).join("; ");
|
|
2082
|
-
case ValueType.durationArray: {
|
|
2083
|
-
const d = value;
|
|
2084
|
-
const prec = d.precision === NumberPrecision.int;
|
|
2085
|
-
const fmt = (n) => prec ? Math.floor(n) : n;
|
|
2086
|
-
return d.value.map((v) => `${fmt(v)} ${d.unit}`).join("; ");
|
|
2087
|
-
}
|
|
2088
|
-
default: return String(value.value ?? "");
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
function formatDateValue(value, pattern) {
|
|
2092
|
-
if (value.type === ValueType.date) return format(/* @__PURE__ */ new Date(value.value * 1e3), pattern);
|
|
2093
|
-
if (value.type === ValueType.dateArray) return value.value.map((ts) => format(/* @__PURE__ */ new Date(ts * 1e3), pattern)).join("; ");
|
|
2094
|
-
return formatDefault(value);
|
|
2095
|
-
}
|
|
2096
|
-
function maskValue(value, maskChar) {
|
|
2097
|
-
return valueToString(value) ? maskChar : "";
|
|
2098
|
-
}
|
|
2099
|
-
function valueToString(value) {
|
|
2100
|
-
switch (value.type) {
|
|
2101
|
-
case ValueType.string:
|
|
2102
|
-
case ValueType.reference: return String(value.value);
|
|
2103
|
-
case ValueType.stringArray:
|
|
2104
|
-
case ValueType.referenceArray: return value.value.join(" ");
|
|
2105
|
-
default: return String(value.value ?? "");
|
|
2106
|
-
}
|
|
959
|
+
//#region src/response-exporter/validate-configuration.ts
|
|
960
|
+
/** Allow incomplete authoring drafts, but never export invalid item coding paths. */
|
|
961
|
+
function validateExportConfiguration(survey, registry = survey.getResponseSlotRegistry()) {
|
|
962
|
+
const issues = [...survey.surveyItems.values()].flatMap((item) => item.getExecutionConfigurationIssues().map((issue) => `${(item.metadata?.itemLabel ?? "")?.trim() || item.id}: ${issue}`));
|
|
963
|
+
issues.push(...registry.diagnostics.map((diagnostic) => diagnostic.message));
|
|
964
|
+
issues.push(...survey.getBlockingExpressionIssues());
|
|
965
|
+
if (issues.length) throw new Error(issues.join("\n"));
|
|
2107
966
|
}
|
|
2108
967
|
//#endregion
|
|
2109
968
|
//#region src/response-exporter/csv-serializer.ts
|
|
@@ -2141,181 +1000,226 @@ function serializeToCsv(columns, rows, options) {
|
|
|
2141
1000
|
}
|
|
2142
1001
|
//#endregion
|
|
2143
1002
|
//#region src/response-exporter/exporter.ts
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
1003
|
+
const canonical = (value) => JSON.stringify(value, (_key, entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a.localeCompare(b))) : entry);
|
|
1004
|
+
/** Export only against schemas selected by the host, never against response version tags. */
|
|
1005
|
+
var SurveyResponseExporter = class SurveyResponseExporter {
|
|
1006
|
+
batches;
|
|
1007
|
+
options;
|
|
1008
|
+
constructor(batches, options = {}) {
|
|
1009
|
+
this.batches = batches;
|
|
1010
|
+
this.options = options;
|
|
1011
|
+
if (!batches.length) throw new Error("Supply at least one schema-bound export batch");
|
|
1012
|
+
if (batches.some((batch) => !batch.schema || !Array.isArray(batch.responses))) throw new Error("Each export batch requires a schema and responses array");
|
|
1013
|
+
if (batches.length > 1 && !options.codingPolicy) throw new Error("Combined export requires an explicit original or harmonized coding policy");
|
|
1014
|
+
}
|
|
1015
|
+
batch(index) {
|
|
1016
|
+
const batch = this.batches[index];
|
|
1017
|
+
if (!batch) throw new Error(`Unknown export batch: ${index}`);
|
|
1018
|
+
return batch;
|
|
1019
|
+
}
|
|
1020
|
+
profile(batch) {
|
|
1021
|
+
if (!this.options.profile) return batch.profile;
|
|
1022
|
+
return {
|
|
1023
|
+
...batch.profile,
|
|
1024
|
+
...this.options.profile,
|
|
1025
|
+
slotOverrides: {
|
|
1026
|
+
...batch.profile?.slotOverrides,
|
|
1027
|
+
...this.options.profile.slotOverrides
|
|
1028
|
+
},
|
|
1029
|
+
columnHeaders: {
|
|
1030
|
+
...batch.profile?.columnHeaders,
|
|
1031
|
+
...this.options.profile.columnHeaders
|
|
1032
|
+
},
|
|
1033
|
+
contextOverrides: {
|
|
1034
|
+
...batch.profile?.contextOverrides,
|
|
1035
|
+
...this.options.profile.contextOverrides
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
2165
1038
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
const profile = this.resolveProfileForVersion(versionId);
|
|
2186
|
-
const columns = [];
|
|
2187
|
-
for (const { itemId, slots } of survey.getResponseSlotDefinitions()) {
|
|
2188
|
-
const keyInfo = keyByItemId.get(itemId);
|
|
2189
|
-
const itemBaseKey = keyInfo ? [...keyInfo.keyPath, keyInfo.key].join(".") : itemId;
|
|
2190
|
-
for (const slot of slots) {
|
|
2191
|
-
const columnId = responseColumnId(itemId, slot.slotId);
|
|
2192
|
-
const codingPath = [...slot.keyPath, slot.key].filter((part) => part.trim().length > 0);
|
|
2193
|
-
const defaultHeader = codingPath.length > 0 ? `${itemBaseKey}__${codingPath.join(".")}` : `${itemBaseKey}__${slot.slotId}`;
|
|
2194
|
-
const header = this.resolveColumnHeader(profile, columnId, defaultHeader);
|
|
2195
|
-
columns.push({
|
|
2196
|
-
id: columnId,
|
|
2197
|
-
header,
|
|
2198
|
-
source: ExportColumnSource.response,
|
|
2199
|
-
itemId,
|
|
2200
|
-
slotId: slot.slotId,
|
|
2201
|
-
valueType: slot.valueType
|
|
2202
|
-
});
|
|
1039
|
+
compileContexts() {
|
|
1040
|
+
return new Map(this.batches.map((batch) => {
|
|
1041
|
+
const registry = batch.schema.getResponseSlotRegistry();
|
|
1042
|
+
validateExportConfiguration(batch.schema, registry);
|
|
1043
|
+
const slots = new Map(registry.definitions.map((slot) => [slot.slotId, slot]));
|
|
1044
|
+
const profile = this.profile(batch);
|
|
1045
|
+
const transforms = new Map([...slots.values()].map((slot) => [slot.slotId, {
|
|
1046
|
+
...DEFAULT_TRANSFORM,
|
|
1047
|
+
...slot.exportSettings,
|
|
1048
|
+
...profile?.slotOverrides?.[slot.slotId]
|
|
1049
|
+
}]));
|
|
1050
|
+
const columns = [...slots.values()].flatMap((slot) => projectSlot(slot, transforms.get(slot.slotId)).map((column) => ({
|
|
1051
|
+
...column,
|
|
1052
|
+
header: profile?.columnHeaders?.[column.id]?.trim() ?? column.header
|
|
1053
|
+
})));
|
|
1054
|
+
const encodings = /* @__PURE__ */ new Map();
|
|
1055
|
+
for (const slotId of new Set(columns.map((column) => column.slotId))) {
|
|
1056
|
+
const { include: _include, ...encoding } = resolvedSlotEncoding(slots.get(slotId), transforms.get(slotId));
|
|
1057
|
+
encodings.set(slotId, canonical(encoding));
|
|
2203
1058
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2215
|
-
for (const vid of vids) for (const c of this.computeResponseColumns(vid)) if (!seen.has(c.id)) {
|
|
2216
|
-
seen.add(c.id);
|
|
2217
|
-
responseCols.push(c);
|
|
2218
|
-
}
|
|
2219
|
-
const profile = this.resolveProfileForVersion(versionId);
|
|
2220
|
-
const metaColumns = META_COLUMN_ORDER.map((metaField) => ({
|
|
2221
|
-
id: metaColumnId(metaField),
|
|
2222
|
-
header: this.resolveColumnHeader(profile, metaColumnId(metaField), metaField),
|
|
2223
|
-
source: ExportColumnSource.meta,
|
|
2224
|
-
metaField
|
|
2225
|
-
}));
|
|
2226
|
-
const contextKeys = /* @__PURE__ */ new Set();
|
|
2227
|
-
for (const r of responses ?? []) if (r.context) for (const k of Object.keys(r.context)) contextKeys.add(k);
|
|
2228
|
-
const contextOrder = profile?.contextColumnOrder;
|
|
2229
|
-
const contextCols = Array.from(contextKeys).sort(contextOrder ? (a, b) => {
|
|
2230
|
-
const ia = contextOrder.indexOf(a);
|
|
2231
|
-
const ib = contextOrder.indexOf(b);
|
|
2232
|
-
if (ia >= 0 && ib >= 0) return ia - ib;
|
|
2233
|
-
if (ia >= 0) return -1;
|
|
2234
|
-
if (ib >= 0) return 1;
|
|
2235
|
-
return a.localeCompare(b);
|
|
2236
|
-
} : void 0).map((contextKey) => ({
|
|
2237
|
-
id: contextColumnId(contextKey),
|
|
2238
|
-
header: this.resolveColumnHeader(profile, contextColumnId(contextKey), `context.${contextKey}`),
|
|
2239
|
-
source: ExportColumnSource.context,
|
|
2240
|
-
contextKey
|
|
1059
|
+
return [batch, {
|
|
1060
|
+
registry,
|
|
1061
|
+
slots,
|
|
1062
|
+
profile,
|
|
1063
|
+
transforms,
|
|
1064
|
+
encodings,
|
|
1065
|
+
columns,
|
|
1066
|
+
included: new Set(columns.map((column) => column.id)),
|
|
1067
|
+
activePlan: columns.some((column) => slots.get(column.slotId)?.computed) ? new ActiveResponsePlan(batch.schema) : void 0
|
|
1068
|
+
}];
|
|
2241
1069
|
}));
|
|
2242
|
-
const respOrder = profile?.responseColumnOrder ?? [];
|
|
2243
|
-
const byOrder = respOrder.filter((id) => responseCols.some((c) => c.id === id)).map((id) => responseCols.find((c) => c.id === id));
|
|
2244
|
-
const remaining = responseCols.filter((c) => !respOrder.includes(c.id)).sort((a, b) => a.header.localeCompare(b.header));
|
|
2245
|
-
const respCols = [...byOrder, ...remaining];
|
|
2246
|
-
const columns = [
|
|
2247
|
-
...metaColumns,
|
|
2248
|
-
...contextCols,
|
|
2249
|
-
...respCols
|
|
2250
|
-
];
|
|
2251
|
-
this.assertUniqueHeaders(columns);
|
|
2252
|
-
return columns;
|
|
2253
|
-
}
|
|
2254
|
-
resolveProfileForVersion(versionId) {
|
|
2255
|
-
return this.profiles.find((p) => !p.versionId || p.versionId === versionId);
|
|
2256
|
-
}
|
|
2257
|
-
resolveColumnHeader(profile, columnId, defaultHeader) {
|
|
2258
|
-
const override = profile?.columnHeaders?.[columnId]?.trim();
|
|
2259
|
-
return override && override.length > 0 ? override : defaultHeader;
|
|
2260
|
-
}
|
|
2261
|
-
assertUniqueHeaders(columns) {
|
|
2262
|
-
const seen = /* @__PURE__ */ new Map();
|
|
2263
|
-
for (const column of columns) {
|
|
2264
|
-
const existingId = seen.get(column.header);
|
|
2265
|
-
if (existingId && existingId !== column.id) throw new Error(`Duplicate export column header '${column.header}' for columns '${existingId}' and '${column.id}'`);
|
|
2266
|
-
seen.set(column.header, column.id);
|
|
2267
|
-
}
|
|
2268
1070
|
}
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
for (const
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
1071
|
+
validateProfile(profile, batches, contexts, extraResponses = []) {
|
|
1072
|
+
if (!profile) return;
|
|
1073
|
+
if (profile.schemaVersion !== 1) throw new Error(`Unsupported exporter profile schema version: ${String(profile.schemaVersion)}`);
|
|
1074
|
+
const slots = new Map(batches.flatMap((batch) => [...contexts.get(batch).slots]));
|
|
1075
|
+
const contextKeys = new Set([...batches.flatMap((batch) => batch.responses), ...extraResponses].flatMap((response) => Object.keys(response.context ?? {})));
|
|
1076
|
+
const columns = /* @__PURE__ */ new Set([
|
|
1077
|
+
...[...slots.values()].flatMap((slot) => projectSlot(slot, { mode: "default" }, true).map((column) => column.id)),
|
|
1078
|
+
...META_COLUMN_ORDER.map((field) => encodeExportColumnId(["meta", field])),
|
|
1079
|
+
...[...contextKeys].map((key) => encodeExportColumnId(["context", key]))
|
|
1080
|
+
]);
|
|
1081
|
+
for (const id of Object.keys(profile.slotOverrides ?? {})) if (!slots.has(id)) throw new Error(`Profile references unknown response slot "${id}"`);
|
|
1082
|
+
for (const id of [...Object.keys(profile.columnHeaders ?? {}), ...profile.responseColumnOrder ?? []]) if (!columns.has(id)) throw new Error(`Profile references unknown export column "${id}"`);
|
|
1083
|
+
for (const id of profile.responseColumnOrder ?? []) if (![...slots.values()].some((slot) => projectSlot(slot, { mode: "default" }, true).some((column) => column.id === id))) throw new Error(`Response column order contains a non-response column: ${id}`);
|
|
1084
|
+
for (const key of [...Object.keys(profile.contextOverrides ?? {}), ...profile.contextColumnOrder ?? []]) if (!contextKeys.has(key)) throw new Error(`Profile references unknown context key "${key}"`);
|
|
1085
|
+
for (const slot of slots.values()) if ((slot.naming?.kind === "matrix" || slot.rowId || slot.columnId) && projectSlot(slot, { mode: "default" }, true).some((column) => Object.hasOwn(profile.columnHeaders ?? {}, column.id))) throw new Error(`Matrix variable "${slot.slotId}" cannot have an individual header override; edit its matrix coding components`);
|
|
1086
|
+
for (const [id, header] of Object.entries(profile.columnHeaders ?? {})) if (typeof header !== "string" || !header.trim()) throw new Error(`Export header for "${id}" cannot be empty`);
|
|
1087
|
+
if (new Set(profile.responseColumnOrder).size !== (profile.responseColumnOrder?.length ?? 0)) throw new Error("Response column order contains duplicate identities");
|
|
1088
|
+
}
|
|
1089
|
+
computeResponseColumns(batchIndex = 0, extraResponses = []) {
|
|
1090
|
+
const batch = this.batch(batchIndex);
|
|
1091
|
+
const contexts = this.compileContexts();
|
|
1092
|
+
this.validateProfile(batch.profile, [batch], contexts, extraResponses);
|
|
1093
|
+
this.validateProfile(this.options.profile, this.batches, contexts, extraResponses);
|
|
1094
|
+
return contexts.get(batch).columns;
|
|
1095
|
+
}
|
|
1096
|
+
computeColumns(extraResponses = []) {
|
|
1097
|
+
return this.columns(this.compileContexts(), extraResponses);
|
|
1098
|
+
}
|
|
1099
|
+
columns(contexts, extraResponses = []) {
|
|
1100
|
+
this.validateProfile(this.options.profile, this.batches, contexts, extraResponses);
|
|
1101
|
+
const columns = /* @__PURE__ */ new Map();
|
|
1102
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
1103
|
+
const encodings = /* @__PURE__ */ new Map();
|
|
1104
|
+
this.batches.forEach((batch) => {
|
|
1105
|
+
const context = contexts.get(batch);
|
|
1106
|
+
this.validateProfile(batch.profile, [batch], contexts, extraResponses);
|
|
1107
|
+
for (const slot of context.slots.values()) {
|
|
1108
|
+
const signature = canonical({
|
|
1109
|
+
type: slot.valueType,
|
|
1110
|
+
allowedValues: slot.allowedValues?.map((value) => canonical(value)).sort(),
|
|
1111
|
+
codes: this.options.codingPolicy === "harmonized" ? context.profile?.slotOverrides?.[slot.slotId]?.optionCodes ?? slot.referenceValueKeys : slot.referenceValueKeys,
|
|
1112
|
+
computed: slot.computed,
|
|
1113
|
+
scores: slot.referenceValueScores
|
|
2292
1114
|
});
|
|
2293
|
-
|
|
1115
|
+
const previous = signatures.get(slot.slotId);
|
|
1116
|
+
if (previous && previous !== signature) throw new Error(`Incompatible schema definitions for slot "${slot.slotId}"; supply a compatible explicit harmonization mapping`);
|
|
1117
|
+
signatures.set(slot.slotId, signature);
|
|
2294
1118
|
}
|
|
2295
|
-
|
|
2296
|
-
const
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
});
|
|
1119
|
+
for (const column of context.columns) {
|
|
1120
|
+
const signature = context.encodings.get(column.slotId);
|
|
1121
|
+
if (encodings.has(column.id) && encodings.get(column.id) !== signature) throw new Error(`Incompatible export encodings for column "${column.id}"; supply one harmonized profile`);
|
|
1122
|
+
encodings.set(column.id, signature);
|
|
1123
|
+
const existing = columns.get(column.id);
|
|
1124
|
+
if (existing && existing.header !== column.header) throw new Error(`Conflicting names for column "${column.id}"; select an explicit harmonized output header`);
|
|
1125
|
+
columns.set(column.id, column);
|
|
2303
1126
|
}
|
|
1127
|
+
});
|
|
1128
|
+
const profile = this.options.profile ?? (this.batches.length === 1 ? this.batches[0].profile : void 0);
|
|
1129
|
+
const allResponses = [...this.batches.flatMap((batch) => batch.responses), ...extraResponses];
|
|
1130
|
+
const contextKeys = [...new Set(allResponses.flatMap((response) => Object.keys(response.context ?? {})))];
|
|
1131
|
+
const ordered = (values, order, id, name) => values.sort((a, b) => {
|
|
1132
|
+
const left = order.indexOf(id(a)), right = order.indexOf(id(b));
|
|
1133
|
+
if (left >= 0 || right >= 0) return (left < 0 ? Infinity : left) - (right < 0 ? Infinity : right);
|
|
1134
|
+
return name(a).localeCompare(name(b));
|
|
1135
|
+
});
|
|
1136
|
+
const result = [
|
|
1137
|
+
...META_COLUMN_ORDER.map((field) => ({
|
|
1138
|
+
id: encodeExportColumnId(["meta", field]),
|
|
1139
|
+
header: profile?.columnHeaders?.[encodeExportColumnId(["meta", field])]?.trim() ?? field,
|
|
1140
|
+
source: ExportColumnSource.meta,
|
|
1141
|
+
metaField: field
|
|
1142
|
+
})),
|
|
1143
|
+
...ordered(contextKeys, profile?.contextColumnOrder ?? [], (value) => value, (value) => value).map((key) => ({
|
|
1144
|
+
id: encodeExportColumnId(["context", key]),
|
|
1145
|
+
header: profile?.columnHeaders?.[encodeExportColumnId(["context", key])]?.trim() ?? `context.${key}`,
|
|
1146
|
+
source: ExportColumnSource.context,
|
|
1147
|
+
contextKey: key
|
|
1148
|
+
})),
|
|
1149
|
+
...ordered([...columns.values()], profile?.responseColumnOrder ?? [], (value) => value.id, (value) => value.header)
|
|
1150
|
+
];
|
|
1151
|
+
const headers = /* @__PURE__ */ new Set();
|
|
1152
|
+
for (const column of result) {
|
|
1153
|
+
if (headers.has(column.header)) throw new Error(`Duplicate export column header '${column.header}'`);
|
|
1154
|
+
headers.add(column.header);
|
|
1155
|
+
}
|
|
1156
|
+
return result;
|
|
1157
|
+
}
|
|
1158
|
+
row(response, batch, columns, context) {
|
|
1159
|
+
const { registry, slots, profile, included, transforms } = context;
|
|
1160
|
+
const diagnostics = registry.validateAnswers(response.answers);
|
|
1161
|
+
if (diagnostics.length) throw new Error(diagnostics.map((issue) => issue.message).join("\n"));
|
|
1162
|
+
const row = Object.create(null);
|
|
1163
|
+
const recorded = Object.create(null);
|
|
1164
|
+
if (context.activePlan) for (const [slotId, value] of Object.entries(response.answers)) {
|
|
1165
|
+
const slot = slots.get(slotId);
|
|
1166
|
+
const item = batch.schema.surveyItems.get(slot.itemId);
|
|
1167
|
+
(recorded[slot.itemId] ??= new SurveyItemResponse({
|
|
1168
|
+
itemId: slot.itemId,
|
|
1169
|
+
itemType: item.type
|
|
1170
|
+
}, new ResponseItem())).response.setSlotValue(slotId, structuredClone(value));
|
|
1171
|
+
}
|
|
1172
|
+
const active = context.activePlan?.evaluate(recorded, {
|
|
1173
|
+
locale: response.evaluationContext?.locale ?? batch.schema.locales[0] ?? "en",
|
|
1174
|
+
participantFlags: response.evaluationContext?.participantFlags,
|
|
1175
|
+
customValues: response.context
|
|
1176
|
+
});
|
|
1177
|
+
for (const column of columns) if (column.source === ExportColumnSource.meta) switch (column.metaField) {
|
|
1178
|
+
case "responseId":
|
|
1179
|
+
row[column.header] = response.key;
|
|
1180
|
+
break;
|
|
1181
|
+
case "participantId":
|
|
1182
|
+
row[column.header] = response.participantId ?? "";
|
|
1183
|
+
break;
|
|
1184
|
+
case "sourceId":
|
|
1185
|
+
row[column.header] = batch.sourceId ?? "";
|
|
1186
|
+
break;
|
|
1187
|
+
case "submittedAt": row[column.header] = response.submittedAt ? format(/* @__PURE__ */ new Date(response.submittedAt * 1e3), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx") : "";
|
|
1188
|
+
}
|
|
1189
|
+
else if (column.source === ExportColumnSource.context) {
|
|
1190
|
+
const value = response.context?.[column.contextKey];
|
|
1191
|
+
row[column.header] = exportSingleResponseSlot({
|
|
1192
|
+
value,
|
|
1193
|
+
valueType: value?.type ?? ValueType.string,
|
|
1194
|
+
transformConfig: profile?.contextOverrides?.[column.contextKey] ?? {
|
|
1195
|
+
...DEFAULT_TRANSFORM,
|
|
1196
|
+
categorical: "ids"
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
} else {
|
|
1200
|
+
const slot = slots.get(column.slotId);
|
|
1201
|
+
row[column.header] = slot && included.has(column.id) ? formatProjectedValue(slot, column, slot.computed ? active?.getAnswer(slot.slotId) : response.answers[slot.slotId], transforms.get(slot.slotId)) : "";
|
|
2304
1202
|
}
|
|
2305
1203
|
return row;
|
|
2306
1204
|
}
|
|
2307
|
-
/**
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
1205
|
+
/** Explicitly associate one response with a supplied batch schema. */
|
|
1206
|
+
exportResponse(response, batchIndex = 0) {
|
|
1207
|
+
return new SurveyResponseExporter([{
|
|
1208
|
+
...this.batch(batchIndex),
|
|
1209
|
+
responses: [response]
|
|
1210
|
+
}], { profile: this.options.profile }).exportRows()[0];
|
|
1211
|
+
}
|
|
1212
|
+
exportRows() {
|
|
1213
|
+
const contexts = this.compileContexts();
|
|
1214
|
+
return this.rows(this.columns(contexts), contexts);
|
|
2315
1215
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
1216
|
+
rows(columns, contexts) {
|
|
1217
|
+
return this.batches.flatMap((batch) => batch.responses.map((response) => this.row(response, batch, columns, contexts.get(batch))));
|
|
1218
|
+
}
|
|
1219
|
+
exportResponsesToCsv(options) {
|
|
1220
|
+
const contexts = this.compileContexts();
|
|
1221
|
+
const columns = this.columns(contexts);
|
|
1222
|
+
return serializeToCsv(columns, this.rows(columns, contexts), options);
|
|
2319
1223
|
}
|
|
2320
1224
|
};
|
|
2321
1225
|
//#endregion
|
|
@@ -2324,20 +1228,35 @@ var SurveyResponseExporter = class {
|
|
|
2324
1228
|
* Generate a structured codebook from a survey definition.
|
|
2325
1229
|
*/
|
|
2326
1230
|
function generateCodebook(survey, options) {
|
|
1231
|
+
const registry = survey.getResponseSlotRegistry();
|
|
1232
|
+
validateExportConfiguration(survey, registry);
|
|
1233
|
+
const slots = registry.definitions;
|
|
2327
1234
|
const locales = options?.locales ?? survey.locales;
|
|
2328
|
-
const
|
|
2329
|
-
const
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
1235
|
+
const slotDefsByItemId = /* @__PURE__ */ new Map();
|
|
1236
|
+
for (const slot of slots) {
|
|
1237
|
+
const entries = slotDefsByItemId.get(slot.itemId) ?? [];
|
|
1238
|
+
entries.push({
|
|
1239
|
+
slotId: slot.slotId,
|
|
1240
|
+
primitiveId: slot.primitiveId,
|
|
1241
|
+
...slot.rowId ? { rowId: slot.rowId } : {},
|
|
1242
|
+
...slot.columnId ? { columnId: slot.columnId } : {},
|
|
1243
|
+
variableName: slot.variableName,
|
|
1244
|
+
valueType: slot.valueType,
|
|
1245
|
+
encoding: resolvedSlotEncoding(slot, {
|
|
1246
|
+
mode: "default",
|
|
1247
|
+
...slot.exportSettings,
|
|
1248
|
+
...options?.profile?.slotOverrides?.[slot.slotId]
|
|
1249
|
+
}),
|
|
1250
|
+
optionLabels: slot.referenceValueLabels,
|
|
1251
|
+
allowedValues: slot.allowedValues,
|
|
1252
|
+
...slot.computed ? { computed: structuredClone(slot.computed) } : {},
|
|
1253
|
+
...slot.referenceValueKeys ? { referenceValueKeys: { ...slot.referenceValueKeys } } : {},
|
|
1254
|
+
...slot.referenceValueScores ? { referenceValueScores: { ...slot.referenceValueScores } } : {}
|
|
1255
|
+
});
|
|
1256
|
+
slotDefsByItemId.set(slot.itemId, entries);
|
|
1257
|
+
}
|
|
2336
1258
|
const items = [];
|
|
2337
1259
|
for (const item of survey.surveyItems.values()) {
|
|
2338
|
-
const keyInfo = keyByItemId.get(item.id);
|
|
2339
|
-
const key = keyInfo?.key ?? item.id;
|
|
2340
|
-
const keyPath = keyInfo?.keyPath ?? [];
|
|
2341
1260
|
const slotList = slotDefsByItemId.get(item.id) ?? [];
|
|
2342
1261
|
slotList.sort((a, b) => a.slotId.localeCompare(b.slotId));
|
|
2343
1262
|
let contentByLocale;
|
|
@@ -2355,8 +1274,8 @@ function generateCodebook(survey, options) {
|
|
|
2355
1274
|
}
|
|
2356
1275
|
items.push({
|
|
2357
1276
|
id: item.id,
|
|
2358
|
-
|
|
2359
|
-
|
|
1277
|
+
editorName: survey.resolveItemEditorName(item.id, locales[0]),
|
|
1278
|
+
breadcrumb: survey.getItemDisplayPath(item.id, locales[0]).breadcrumb,
|
|
2360
1279
|
type: item.type,
|
|
2361
1280
|
interactive: item.isInteractive(),
|
|
2362
1281
|
slots: slotList,
|
|
@@ -2365,10 +1284,41 @@ function generateCodebook(survey, options) {
|
|
|
2365
1284
|
}
|
|
2366
1285
|
return {
|
|
2367
1286
|
surveyKey: survey.surveyKey,
|
|
2368
|
-
items
|
|
1287
|
+
items,
|
|
1288
|
+
projection: new SurveyResponseExporter([{
|
|
1289
|
+
schema: survey,
|
|
1290
|
+
responses: [],
|
|
1291
|
+
profile: options?.profile
|
|
1292
|
+
}]).computeColumns(),
|
|
1293
|
+
profile: options?.profile && structuredClone(options.profile),
|
|
1294
|
+
missingValue: ""
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
/** Describe precisely the combined projection and each host-selected source definition. */
|
|
1298
|
+
function generateExportCodebook(batches, options = {}) {
|
|
1299
|
+
const exporter = new SurveyResponseExporter(batches, options);
|
|
1300
|
+
return {
|
|
1301
|
+
schemaVersion: 1,
|
|
1302
|
+
codingPolicy: options.codingPolicy ?? "original",
|
|
1303
|
+
columns: exporter.computeColumns(),
|
|
1304
|
+
missingValue: "",
|
|
1305
|
+
profile: options.profile && structuredClone(options.profile),
|
|
1306
|
+
sources: batches.map((batch) => ({
|
|
1307
|
+
sourceId: batch.sourceId,
|
|
1308
|
+
surveySchemaVersion: batch.schema.serialize().schemaVersion,
|
|
1309
|
+
profile: batch.profile && structuredClone(batch.profile),
|
|
1310
|
+
variables: batch.schema.getResponseSlotRegistry().definitions.map((slot) => ({
|
|
1311
|
+
...slot,
|
|
1312
|
+
encoding: resolvedSlotEncoding(slot, {
|
|
1313
|
+
mode: "default",
|
|
1314
|
+
...slot.exportSettings,
|
|
1315
|
+
...options.profile?.slotOverrides?.[slot.slotId] ?? batch.profile?.slotOverrides?.[slot.slotId]
|
|
1316
|
+
})
|
|
1317
|
+
}))
|
|
1318
|
+
}))
|
|
2369
1319
|
};
|
|
2370
1320
|
}
|
|
2371
1321
|
//#endregion
|
|
2372
|
-
export { AndExpressionEditor, CURRENT_SURVEY_SCHEMA, ConstBooleanEditor, ConstDateArrayEditor, ConstDateEditor, ConstExpression, ConstNumberArrayEditor, ConstNumberEditor, ConstStringArrayEditor, ConstStringEditor, ContentType, ContextVariableExpression, ContextVariableType, CtxCustomExpressionEditor, CtxCustomValueEditor, CtxLocaleEditor, CtxPFlagDateEditor, CtxPFlagIsDefinedEditor, CtxPFlagNumEditor, CtxPFlagStringEditor, DEFAULT_TRANSFORM, DurationUnits,
|
|
1322
|
+
export { ActiveResponsePlan, AndExpressionEditor, CURRENT_SURVEY_SCHEMA, CURRENT_SURVEY_SCHEMA_VERSION, ConstBooleanEditor, ConstDateArrayEditor, ConstDateEditor, ConstExpression, ConstNumberArrayEditor, ConstNumberEditor, ConstStringArrayEditor, ConstStringEditor, ContentType, ContextVariableExpression, ContextVariableType, CtxCustomExpressionEditor, CtxCustomValueEditor, CtxLocaleEditor, CtxPFlagDateEditor, CtxPFlagIsDefinedEditor, CtxPFlagNumEditor, CtxPFlagStringEditor, DATE_FUNCTION_SIGNATURES, DEFAULT_TRANSFORM, DurationUnits, EXPORTER_PROFILE_SCHEMA_VERSION, EqExpressionEditor, Expression, ExpressionEditor, ExpressionEvaluator, ExpressionType, FunctionExpression, FunctionExpressionNames, GroupItemCore, GtExpressionEditor, GteExpressionEditor, InRangeExpressionEditor, JsonPatchError, LtExpressionEditor, LteExpressionEditor, META_COLUMN_ORDER, MaxExpressionEditor, MinExpressionEditor, NumberPrecision, OrExpressionEditor, PageBreakItemCore, RESPONSE_VARIABLE_NAME_MAX_LENGTH, ReferenceUsageType, ReservedSurveyItemTypes, ResponseItem, ResponseSlotRegistry, ResponseVariableEditor, ResponseVariableExpression, SURVEY_EDITOR_ITEM_COLORS, SURVEY_EXPRESSION_REFERENCE_DIAGNOSTIC_POLICY, SURVEY_RESPONSE_SCHEMA_VERSION, SlotTransformMode, StrEqExpressionEditor, StrListContainsExpressionEditor, SumExpressionEditor, Survey, SurveyEngineCore, SurveyEventTypes, SurveyItemCore, SurveyItemPrefillApplyMode, SurveyItemPrefillTargetType, SurveyItemResponse, SurveyItemTranslations, SurveyResponse, SurveyResponseExporter, SurveyTranslations, TemplateDefTypes, ValueReference, ValueReferenceMethod, ValueType, addCalendarDate, allocateResponseVariableName, analyzeSurveyExpressionReferences, and, applyJsonPatch, assertResponseValue, buildItemExpression, builtInItemCoreRegistry, calendarDateBounds, calendarDateFromLocalDate, calendarDatePrecision, calendarDateToLocalDate, collectConfigValueExpressions, collectExpressionResponseReferences, collectSurveyExpressionLocations, compareCalendarDates, const_boolean, const_date, const_date_array, const_number, const_number_array, const_string, const_string_array, createFullRegistry, createItemCore, createItemTypeDefinitionRegistry, createRichTextContent, createSeededRandom, ctx_custom_expression, ctx_custom_value, ctx_locale, ctx_pflag_date, ctx_pflag_is_defined, ctx_pflag_num, ctx_pflag_string, date_add_days, date_add_months, date_add_years, date_diff, date_eq, date_gt, date_gte, date_lt, date_lte, date_max, date_min, decodeExportColumnId, decodeJsonPointerSegment, deserializeSurveyItemPrefill, deserializeTemplateValue, deserializeTemplateValues, differenceCalendarDates, encodeExportColumnId, encodeJsonPointerSegment, eq, escapeCsvCell, exportSingleResponseSlot, expressionReferenceDiagnosticKey, findIntroducedBlockingExpressionReferenceDiagnostics, findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories, flattenTree, formatCalendarDate, formatProjectedValue, generateCodebook, generateCodingKey, generateExportCodebook, generateId, getAssetUsagesFromContent, getCompositionLabel, getContentPlainText, getDefaultProjectionIssues, getItemExpressionDefinition, getPlainTextFromRichTextContent, getValueAtJsonPointer, gt, gte, hasRenderableRichTextBlock, hasRenderableRichTextContent, in_range, initValueForType, invalidResponseDomainValues, isBlockingSurveyExpressionReferenceDiagnostic, isBuiltInItemType, isCalendarDate, isContentEmpty, isLegacyItemGroupComponent, isLegacySurveyGroupItem, isResponseValue, isValidResponseVariableName, lt, lte, max, min, optionMapping, or, parseJsonPointer, prefillTargetsEqual, projectSlot, replaceResponseVariableReferences, resolveUniqueItemKey, responseValueElements, responseValueReferenceIds, responseValuesEqual, response_boolean, response_date, response_date_array, response_number, response_number_array, response_string, response_string_array, serializeSurveyItemPrefill, serializeTemplateValue, serializeTemplateValues, serializeToCsv, shuffleArray, shuffleIndices, str_eq, str_list_contains, structuredCloneMethod, suggestResponseVariableName, sum, toItemTypeDefinitionRegistry, validateLocale, validateResponseSlotValue };
|
|
2373
1323
|
|
|
2374
1324
|
//# sourceMappingURL=index.mjs.map
|