@case-framework/survey-core 0.1.0 → 0.2.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/build/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as ValueReference, D as structuredCloneMethod, E as shuffleIndices, S as ReferenceUsageType, T as generateId, _ as Expression, a as validateLocale, b as FunctionExpressionNames, c as SurveyItemCore, d as deserializeTemplateValues, f as serializeTemplateValue, g as ContextVariableType, h as ContextVariableExpression, i as SurveyTranslations, l as TemplateDefTypes, m as ConstExpression, o as SurveyItemKey, p as serializeTemplateValues, r as SurveyItemTranslations, s as ReservedSurveyItemTypes, t as Survey, u as deserializeTemplateValue, v as ExpressionType, w as ValueReferenceMethod, x as ResponseVariableExpression, y as FunctionExpression } from "./survey-C3ZHI-5z.mjs";
1
+ import { A as ValueReference, C as ContextVariableType, D as FunctionExpressionNames, E as FunctionExpression, F as structuredCloneMethod, M as generateCodingKey, N as generateId, O as ResponseVariableExpression, P as shuffleIndices, S as ContextVariableExpression, T as ExpressionType, _ as deserializeTemplateValue, a as createFullRegistry, b as serializeTemplateValues, c as toItemTypeDefinitionRegistry, d as builtInItemCoreRegistry, f as isBuiltInItemType, g as TemplateDefTypes, h as SurveyItemCore, i as validateLocale, j as ValueReferenceMethod, k as ReferenceUsageType, l as GroupItemCore, m as ReservedSurveyItemTypes, n as SurveyItemTranslations, o as createItemCore, p as SurveyItemKey, r as SurveyTranslations, s as createItemTypeDefinitionRegistry, t as Survey, u as PageBreakItemCore, v as deserializeTemplateValues, w as Expression, x as ConstExpression, y as serializeTemplateValue } from "./survey-DQmpzihl.mjs";
2
2
  import { format } from "date-fns";
3
3
  import { enUS } from "date-fns/locale";
4
4
 
@@ -113,6 +113,9 @@ var SurveyResponse = class SurveyResponse {
113
113
  response.participantId = json.participantId;
114
114
  response.submittedAt = json.submittedAt;
115
115
  response.responses = new Map(json.responses.map((r) => [r.itemId, SurveyItemResponse.deserialize(r)]));
116
+ response.events = json.events ?? [];
117
+ response.versionId = json.versionId;
118
+ response.key = json.key;
116
119
  response.context = json.context;
117
120
  return response;
118
121
  }
@@ -1582,5 +1585,361 @@ const flattenTree = (itemTree) => {
1582
1585
  };
1583
1586
 
1584
1587
  //#endregion
1585
- export { AndExpressionEditor, AttributionType, ConstBooleanEditor, ConstDateArrayEditor, ConstDateEditor, ConstExpression, ConstNumberArrayEditor, ConstNumberEditor, ConstStringArrayEditor, ConstStringEditor, ContentType, ContextVariableExpression, ContextVariableType, CtxCustomExpressionEditor, CtxCustomValueEditor, CtxLocaleEditor, CtxPFlagDateEditor, CtxPFlagIsDefinedEditor, CtxPFlagNumEditor, CtxPFlagStringEditor, DurationUnits, EqExpressionEditor, Expression, ExpressionEditor, ExpressionEvaluator, ExpressionType, FunctionExpression, FunctionExpressionNames, GtExpressionEditor, GteExpressionEditor, InRangeExpressionEditor, LtExpressionEditor, LteExpressionEditor, MaxExpressionEditor, MinExpressionEditor, NumberPrecision, OrExpressionEditor, ReferenceUsageType, ReservedSurveyItemTypes, ResponseItem, ResponseVariableEditor, ResponseVariableExpression, StrEqExpressionEditor, StrListContainsExpressionEditor, SumExpressionEditor, Survey, SurveyEngineCore, SurveyEventTypes, SurveyItemCore, SurveyItemKey, SurveyItemResponse, SurveyItemTranslations, SurveyResponse, SurveyTranslations, TemplateDefTypes, ValueReference, ValueReferenceMethod, ValueType, and, const_boolean, const_date, const_date_array, const_number, const_number_array, const_string, const_string_array, ctx_custom_expression, ctx_custom_value, ctx_locale, ctx_pflag_date, ctx_pflag_is_defined, ctx_pflag_num, ctx_pflag_string, deserializeTemplateValue, deserializeTemplateValues, eq, flattenTree, generateId, gt, gte, in_range, initValueForType, isLegacyItemGroupComponent, isLegacySurveyGroupItem, lt, lte, max, min, or, response_boolean, response_date, response_date_array, response_number, response_number_array, response_string, response_string_array, serializeTemplateValue, serializeTemplateValues, shuffleIndices, str_eq, str_list_contains, structuredCloneMethod, sum, validateLocale };
1588
+ //#region src/response-exporter/types.ts
1589
+ /** Meta column keys in fixed order (common attributes first). */
1590
+ const META_COLUMN_ORDER = [
1591
+ "submittedAt",
1592
+ "participantId",
1593
+ "responseId",
1594
+ "versionId"
1595
+ ];
1596
+ /** Built-in transform modes for slot export. */
1597
+ const SlotTransformMode = {
1598
+ default: "default",
1599
+ mask: "mask",
1600
+ dateFormat: "dateFormat"
1601
+ };
1602
+ /** Source of an export column. */
1603
+ const ExportColumnSource = {
1604
+ meta: "meta",
1605
+ context: "context",
1606
+ response: "response"
1607
+ };
1608
+
1609
+ //#endregion
1610
+ //#region src/response-exporter/slot-formatter.ts
1611
+ /** Default transform config when no profile override. */
1612
+ const DEFAULT_TRANSFORM = { mode: SlotTransformMode.default };
1613
+ /**
1614
+ * Export a single response slot to a string. Pure function for unit testing.
1615
+ * Uses default formatting by ValueType unless overridden by transformConfig.
1616
+ */
1617
+ function exportSingleResponseSlot(params) {
1618
+ const { value, valueType, transformConfig } = params;
1619
+ if (value === void 0 || value === null) return "";
1620
+ const mode = transformConfig.mode;
1621
+ if (mode === SlotTransformMode.mask) return maskValue(value, transformConfig.maskChar ?? "***");
1622
+ if (mode === SlotTransformMode.dateFormat && (valueType === ValueType.date || valueType === ValueType.dateArray)) return formatDateValue(value, transformConfig.dateFormat?.pattern ?? "yyyy-MM-dd");
1623
+ return formatDefault(value);
1624
+ }
1625
+ const BooleanExportValues = {
1626
+ TRUE: "TRUE",
1627
+ FALSE: "FALSE"
1628
+ };
1629
+ function formatDefault(value) {
1630
+ switch (value.type) {
1631
+ case ValueType.string: return String(value.value);
1632
+ case ValueType.reference: return String(value.value);
1633
+ case ValueType.boolean: return value.value ? BooleanExportValues.TRUE : BooleanExportValues.FALSE;
1634
+ case ValueType.number: return value.precision === NumberPrecision.int ? String(Math.floor(value.value)) : String(value.value);
1635
+ case ValueType.date: return (/* @__PURE__ */ new Date(value.value * 1e3)).toISOString();
1636
+ case ValueType.duration: {
1637
+ const d = value;
1638
+ return `${d.precision === NumberPrecision.int ? Math.floor(d.value) : d.value} ${d.unit}`;
1639
+ }
1640
+ case ValueType.stringArray: return value.value.join("; ");
1641
+ case ValueType.referenceArray: return value.value.join("; ");
1642
+ case ValueType.numberArray: {
1643
+ const arr = value.value;
1644
+ const prec = value.precision;
1645
+ const fmt = (n) => prec === NumberPrecision.int ? Math.floor(n) : n;
1646
+ return arr.map(fmt).join("; ");
1647
+ }
1648
+ case ValueType.dateArray: return value.value.map((ts) => (/* @__PURE__ */ new Date(ts * 1e3)).toISOString()).join("; ");
1649
+ case ValueType.durationArray: {
1650
+ const d = value;
1651
+ const prec = d.precision === NumberPrecision.int;
1652
+ const fmt = (n) => prec ? Math.floor(n) : n;
1653
+ return d.value.map((v) => `${fmt(v)} ${d.unit}`).join("; ");
1654
+ }
1655
+ default: return String(value.value ?? "");
1656
+ }
1657
+ }
1658
+ function formatDateValue(value, pattern) {
1659
+ if (value.type === ValueType.date) return format(/* @__PURE__ */ new Date(value.value * 1e3), pattern);
1660
+ if (value.type === ValueType.dateArray) return value.value.map((ts) => format(/* @__PURE__ */ new Date(ts * 1e3), pattern)).join("; ");
1661
+ return formatDefault(value);
1662
+ }
1663
+ function maskValue(value, maskChar) {
1664
+ return valueToString(value) ? maskChar : "";
1665
+ }
1666
+ function valueToString(value) {
1667
+ switch (value.type) {
1668
+ case ValueType.string:
1669
+ case ValueType.reference: return String(value.value);
1670
+ case ValueType.stringArray:
1671
+ case ValueType.referenceArray: return value.value.join(" ");
1672
+ default: return String(value.value ?? "");
1673
+ }
1674
+ }
1675
+
1676
+ //#endregion
1677
+ //#region src/response-exporter/csv-serializer.ts
1678
+ const DEFAULT_OPTIONS = {
1679
+ separator: ",",
1680
+ lineEnding: "\n",
1681
+ includeHeader: true
1682
+ };
1683
+ /**
1684
+ * Escape a cell value for CSV. Quotes the cell when it contains separator, quote, CR, or LF.
1685
+ * Doubles embedded quotes. Preserves multiline content inside quoted cells.
1686
+ */
1687
+ function escapeCsvCell(value, separator = ",") {
1688
+ if (value === "") return value;
1689
+ if (!(value.includes(separator) || value.includes("\"") || value.includes("\r") || value.includes("\n"))) return value;
1690
+ return `"${value.replace(/"/g, "\"\"")}"`;
1691
+ }
1692
+ /**
1693
+ * Serialize export rows to CSV string.
1694
+ */
1695
+ function serializeToCsv(columns, rows, options) {
1696
+ const opts = {
1697
+ ...DEFAULT_OPTIONS,
1698
+ ...options
1699
+ };
1700
+ const sep = opts.separator;
1701
+ const lines = [];
1702
+ const keys = columns.map((c) => c.key);
1703
+ if (opts.includeHeader) lines.push(keys.map((k) => escapeCsvCell(k, sep)).join(sep));
1704
+ for (const row of rows) {
1705
+ const cells = keys.map((k) => escapeCsvCell(row[k] ?? "", sep));
1706
+ lines.push(cells.join(sep));
1707
+ }
1708
+ return lines.join(opts.lineEnding);
1709
+ }
1710
+
1711
+ //#endregion
1712
+ //#region src/response-exporter/exporter.ts
1713
+ const VALUE_REF_SEP$1 = "...";
1714
+ function parseValueRef(ref) {
1715
+ const parts = ref.split(VALUE_REF_SEP$1);
1716
+ if (parts.length !== 3 || parts[1] !== ValueReferenceMethod.get) return {
1717
+ itemId: "",
1718
+ slotId: ""
1719
+ };
1720
+ return {
1721
+ itemId: parts[0],
1722
+ slotId: parts[2]
1723
+ };
1724
+ }
1725
+ function slotOverrideKey(itemId, slotId) {
1726
+ return `${itemId}::${slotId}`;
1727
+ }
1728
+ function formatMetaValue(response, metaField) {
1729
+ switch (metaField) {
1730
+ case "submittedAt":
1731
+ if (response.submittedAt == null || response.submittedAt === 0) return "";
1732
+ return format(/* @__PURE__ */ new Date(response.submittedAt * 1e3), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx");
1733
+ case "participantId": return response.participantId ?? "";
1734
+ case "responseId": return response.key;
1735
+ case "versionId": return response.versionId;
1736
+ default: return "";
1737
+ }
1738
+ }
1739
+ var SurveyResponseExporter = class {
1740
+ versions = /* @__PURE__ */ new Map();
1741
+ constructor(versionBindings, profiles = []) {
1742
+ this.profiles = profiles;
1743
+ for (const vb of versionBindings) this.versions.set(vb.versionId, vb);
1744
+ }
1745
+ getSurveyForVersion(versionId) {
1746
+ return this.versions.get(versionId);
1747
+ }
1748
+ /**
1749
+ * Compute response-only columns from survey definition.
1750
+ */
1751
+ computeResponseColumns(versionId) {
1752
+ const binding = this.versions.get(versionId);
1753
+ if (!binding) return [];
1754
+ const survey = binding.survey;
1755
+ const slots = survey.getAvailableResponseValueSlots();
1756
+ const keyIndex = survey.keyIndex;
1757
+ const keyByItemId = new Map(keyIndex.map((r) => [r.itemId, r]));
1758
+ const profile = this.resolveProfileForVersion(versionId);
1759
+ const columns = [];
1760
+ for (const [valueRef, valueType] of Object.entries(slots)) {
1761
+ const { itemId, slotId } = parseValueRef(valueRef);
1762
+ if (!itemId || !slotId) continue;
1763
+ if (valueRef.includes(`${VALUE_REF_SEP$1}${ValueReferenceMethod.isDefined}`)) continue;
1764
+ const keyInfo = keyByItemId.get(itemId);
1765
+ const defaultExportKey = `${keyInfo ? [...keyInfo.keyPath, keyInfo.key].join(".") : itemId}__${slotId}`;
1766
+ const exportKey = (profile?.slotOverrides?.[slotOverrideKey(itemId, slotId)])?.exportKey ?? defaultExportKey;
1767
+ columns.push({
1768
+ key: exportKey,
1769
+ source: ExportColumnSource.response,
1770
+ itemId,
1771
+ slotId,
1772
+ valueType
1773
+ });
1774
+ }
1775
+ return columns;
1776
+ }
1777
+ /**
1778
+ * Compute full export columns: meta first, then context (from responses), then response columns.
1779
+ * Column order: meta (fixed) -> context (profile order or alphabetical) -> response (profile order + alphabetical).
1780
+ */
1781
+ computeColumns(versionId, responses, versionIds) {
1782
+ const vids = versionIds ?? [versionId];
1783
+ const responseCols = [];
1784
+ const seen = /* @__PURE__ */ new Set();
1785
+ for (const vid of vids) for (const c of this.computeResponseColumns(vid)) if (!seen.has(c.key)) {
1786
+ seen.add(c.key);
1787
+ responseCols.push(c);
1788
+ }
1789
+ const profile = this.resolveProfileForVersion(versionId);
1790
+ const metaColumns = META_COLUMN_ORDER.map((metaField) => ({
1791
+ key: metaField,
1792
+ source: ExportColumnSource.meta,
1793
+ metaField
1794
+ }));
1795
+ const contextKeys = /* @__PURE__ */ new Set();
1796
+ for (const r of responses ?? []) if (r.context) for (const k of Object.keys(r.context)) contextKeys.add(k);
1797
+ const contextOrder = profile?.contextColumnOrder;
1798
+ const contextCols = Array.from(contextKeys).sort(contextOrder ? (a, b) => {
1799
+ const ia = contextOrder.indexOf(a);
1800
+ const ib = contextOrder.indexOf(b);
1801
+ if (ia >= 0 && ib >= 0) return ia - ib;
1802
+ if (ia >= 0) return -1;
1803
+ if (ib >= 0) return 1;
1804
+ return a.localeCompare(b);
1805
+ } : void 0).map((contextKey) => ({
1806
+ key: `context.${contextKey}`,
1807
+ source: ExportColumnSource.context,
1808
+ contextKey
1809
+ }));
1810
+ const respOrder = profile?.responseColumnOrder ?? [];
1811
+ const byOrder = respOrder.filter((k) => responseCols.some((c) => c.key === k)).map((k) => responseCols.find((c) => c.key === k));
1812
+ const remaining = responseCols.filter((c) => !respOrder.includes(c.key)).sort((a, b) => a.key.localeCompare(b.key));
1813
+ const respCols = [...byOrder, ...remaining];
1814
+ return [
1815
+ ...metaColumns,
1816
+ ...contextCols,
1817
+ ...respCols
1818
+ ];
1819
+ }
1820
+ resolveProfileForVersion(versionId) {
1821
+ return this.profiles.find((p) => !p.versionId || p.versionId === versionId);
1822
+ }
1823
+ getTransformConfig(versionId, itemId, slotId) {
1824
+ return this.resolveProfileForVersion(versionId)?.slotOverrides?.[slotOverrideKey(itemId, slotId)] ?? DEFAULT_TRANSFORM;
1825
+ }
1826
+ getContextTransformConfig(versionId, contextKey) {
1827
+ return this.resolveProfileForVersion(versionId)?.contextOverrides?.[contextKey] ?? DEFAULT_TRANSFORM;
1828
+ }
1829
+ exportResponse(response) {
1830
+ if (!this.versions.get(response.versionId)) return null;
1831
+ const columns = this.computeColumns(response.versionId, [response]);
1832
+ const row = {};
1833
+ for (const col of columns) {
1834
+ if (col.source === ExportColumnSource.meta && col.metaField) {
1835
+ row[col.key] = formatMetaValue(response, col.metaField);
1836
+ continue;
1837
+ }
1838
+ if (col.source === ExportColumnSource.context && col.contextKey) {
1839
+ const value = response.context?.[col.contextKey];
1840
+ const transformConfig = this.getContextTransformConfig(response.versionId, col.contextKey);
1841
+ const valueType = value?.type ?? ValueType.string;
1842
+ row[col.key] = exportSingleResponseSlot({
1843
+ value,
1844
+ valueType,
1845
+ transformConfig
1846
+ });
1847
+ continue;
1848
+ }
1849
+ if (col.source === ExportColumnSource.response && col.itemId != null && col.slotId != null && col.valueType != null) {
1850
+ const value = response.responses.get(col.itemId)?.response?.get(col.slotId);
1851
+ const transformConfig = this.getTransformConfig(response.versionId, col.itemId, col.slotId);
1852
+ row[col.key] = exportSingleResponseSlot({
1853
+ value,
1854
+ valueType: col.valueType,
1855
+ transformConfig
1856
+ });
1857
+ }
1858
+ }
1859
+ return row;
1860
+ }
1861
+ /**
1862
+ * Compute columns for a batch of responses (union of response columns across versions).
1863
+ */
1864
+ computeColumnsForBatch(responses) {
1865
+ if (responses.length === 0) return [];
1866
+ const versionIds = [...new Set(responses.map((r) => r.versionId))];
1867
+ const primaryVersionId = versionIds[0];
1868
+ return this.computeColumns(primaryVersionId, responses, versionIds);
1869
+ }
1870
+ exportResponsesToCsv(responses, options) {
1871
+ if (responses.length === 0) return "";
1872
+ return serializeToCsv(this.computeColumnsForBatch(responses), responses.map((r) => this.exportResponse(r)).filter((r) => r !== null), options);
1873
+ }
1874
+ };
1875
+
1876
+ //#endregion
1877
+ //#region src/response-exporter/codebook.ts
1878
+ const VALUE_REF_SEP = "...";
1879
+ /**
1880
+ * Extract plain text from Content for codebook display.
1881
+ */
1882
+ function contentToPlainText(c) {
1883
+ if (!c) return "";
1884
+ return c.content ?? "";
1885
+ }
1886
+ /**
1887
+ * Generate a structured codebook from a survey definition.
1888
+ */
1889
+ function generateCodebook(survey, options) {
1890
+ const locales = options?.locales ?? survey.locales;
1891
+ const keyIndex = survey.keyIndex;
1892
+ const keyByItemId = new Map(keyIndex.map((r) => [r.itemId, r]));
1893
+ const slots = survey.getAvailableResponseValueSlots();
1894
+ const valueRefToSlots = /* @__PURE__ */ new Map();
1895
+ for (const [valueRef, valueType] of Object.entries(slots)) {
1896
+ const parts = valueRef.split(VALUE_REF_SEP);
1897
+ if (parts.length !== 3 || parts[1] !== ValueReferenceMethod.get) continue;
1898
+ const itemId = parts[0];
1899
+ const slotId = parts[2];
1900
+ const list = valueRefToSlots.get(itemId) ?? [];
1901
+ if (!list.some((s) => s.slotId === slotId)) list.push({
1902
+ slotId,
1903
+ valueType
1904
+ });
1905
+ valueRefToSlots.set(itemId, list);
1906
+ }
1907
+ const items = [];
1908
+ for (const item of survey.surveyItems.values()) {
1909
+ const keyInfo = keyByItemId.get(item.id);
1910
+ const key = keyInfo?.key ?? item.id;
1911
+ const keyPath = keyInfo?.keyPath ?? [];
1912
+ const slotList = valueRefToSlots.get(item.id) ?? [];
1913
+ slotList.sort((a, b) => a.slotId.localeCompare(b.slotId));
1914
+ let contentByLocale;
1915
+ if (locales.length > 0) {
1916
+ contentByLocale = {};
1917
+ const itemTranslations = survey.getItemTranslations(item.id);
1918
+ if (itemTranslations) for (const locale of locales) {
1919
+ const all = itemTranslations.getAllForLocale(locale);
1920
+ if (all) {
1921
+ const flat = {};
1922
+ for (const [contentKey, content] of Object.entries(all)) flat[contentKey] = contentToPlainText(content);
1923
+ contentByLocale[locale] = flat;
1924
+ }
1925
+ }
1926
+ }
1927
+ items.push({
1928
+ id: item.id,
1929
+ key,
1930
+ keyPath,
1931
+ type: item.type,
1932
+ interactive: item.isInteractive(),
1933
+ slots: slotList,
1934
+ contentByLocale
1935
+ });
1936
+ }
1937
+ return {
1938
+ surveyKey: survey.surveyKey,
1939
+ items: items.sort((a, b) => a.key.localeCompare(b.key))
1940
+ };
1941
+ }
1942
+
1943
+ //#endregion
1944
+ export { AndExpressionEditor, AttributionType, ConstBooleanEditor, ConstDateArrayEditor, ConstDateEditor, ConstExpression, ConstNumberArrayEditor, ConstNumberEditor, ConstStringArrayEditor, ConstStringEditor, ContentType, ContextVariableExpression, ContextVariableType, CtxCustomExpressionEditor, CtxCustomValueEditor, CtxLocaleEditor, CtxPFlagDateEditor, CtxPFlagIsDefinedEditor, CtxPFlagNumEditor, CtxPFlagStringEditor, DEFAULT_TRANSFORM, DurationUnits, EqExpressionEditor, Expression, ExpressionEditor, ExpressionEvaluator, ExpressionType, FunctionExpression, FunctionExpressionNames, GroupItemCore, GtExpressionEditor, GteExpressionEditor, InRangeExpressionEditor, LtExpressionEditor, LteExpressionEditor, META_COLUMN_ORDER, MaxExpressionEditor, MinExpressionEditor, NumberPrecision, OrExpressionEditor, PageBreakItemCore, ReferenceUsageType, ReservedSurveyItemTypes, ResponseItem, ResponseVariableEditor, ResponseVariableExpression, SlotTransformMode, StrEqExpressionEditor, StrListContainsExpressionEditor, SumExpressionEditor, Survey, SurveyEngineCore, SurveyEventTypes, SurveyItemCore, SurveyItemKey, SurveyItemResponse, SurveyItemTranslations, SurveyResponse, SurveyResponseExporter, SurveyTranslations, TemplateDefTypes, ValueReference, ValueReferenceMethod, ValueType, and, builtInItemCoreRegistry, const_boolean, const_date, const_date_array, const_number, const_number_array, const_string, const_string_array, createFullRegistry, createItemCore, createItemTypeDefinitionRegistry, ctx_custom_expression, ctx_custom_value, ctx_locale, ctx_pflag_date, ctx_pflag_is_defined, ctx_pflag_num, ctx_pflag_string, deserializeTemplateValue, deserializeTemplateValues, eq, escapeCsvCell, exportSingleResponseSlot, flattenTree, generateCodebook, generateCodingKey, generateId, gt, gte, in_range, initValueForType, isBuiltInItemType, isLegacyItemGroupComponent, isLegacySurveyGroupItem, lt, lte, max, min, or, response_boolean, response_date, response_date_array, response_number, response_number_array, response_string, response_string_array, serializeTemplateValue, serializeTemplateValues, serializeToCsv, shuffleIndices, str_eq, str_list_contains, structuredCloneMethod, sum, toItemTypeDefinitionRegistry, validateLocale };
1586
1945
  //# sourceMappingURL=index.mjs.map