@esheet/adapters 0.0.3 → 0.0.4-1

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 CHANGED
@@ -1 +1 @@
1
- # Adapter Pakcage
1
+ # Adapter Package
@@ -0,0 +1,21 @@
1
+ import type { FormDefinition } from '@esheet/core';
2
+ import type { FhirQuestionnaire, FhirQuestionnaireResponse, FhirImportOptions, FhirExportOptions, ResponseImportOptions, ResponseExportOptions } from './types.js';
3
+ import { isFhirQuestionnaire, isFhirQuestionnaireResponse } from './utils.js';
4
+ export { isFhirQuestionnaire, isFhirQuestionnaireResponse };
5
+ /**
6
+ * Convert a FHIR R4 Questionnaire to an eSheet FormDefinition.
7
+ */
8
+ export declare function importFromFhir(questionnaire: FhirQuestionnaire, options?: FhirImportOptions): FormDefinition;
9
+ /**
10
+ * Convert an eSheet FormDefinition to a FHIR R4 Questionnaire.
11
+ */
12
+ export declare function exportToFhir(form: FormDefinition, options?: FhirExportOptions): FhirQuestionnaire;
13
+ /**
14
+ * Convert a FHIR QuestionnaireResponse to an eSheet answer map.
15
+ */
16
+ export declare function importResponseFromFhir(response: FhirQuestionnaireResponse, options?: ResponseImportOptions): Record<string, unknown>;
17
+ /**
18
+ * Convert eSheet answers to a FHIR QuestionnaireResponse.
19
+ */
20
+ export declare function exportResponseToFhir(form: FormDefinition, answers: Record<string, unknown>, options: ResponseExportOptions): FhirQuestionnaireResponse;
21
+ //# sourceMappingURL=fhir-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fhir-adapter.d.ts","sourceRoot":"","sources":["../../src/fhir/fhir-adapter.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,cAAc,EAOf,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EACV,iBAAiB,EAEjB,yBAAyB,EAMzB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EAKtB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,mBAAmB,EACnB,2BAA2B,EAc5B,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,CAAC;AAM5D;;GAEG;AACH,wBAAgB,cAAc,CAC5B,aAAa,EAAE,iBAAiB,EAChC,OAAO,CAAC,EAAE,iBAAiB,GAC1B,cAAc,CA0BhB;AAiSD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,iBAAiB,CAmCnB;AAsID;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,yBAAyB,EACnC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB;AAuDD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,EAAE,qBAAqB,GAC7B,yBAAyB,CAa3B"}
@@ -0,0 +1,564 @@
1
+ // ---------------------------------------------------------------------------
2
+ // FHIR R4 Adapter - Bidirectional conversion
3
+ // ---------------------------------------------------------------------------
4
+ import { isFhirQuestionnaire, isFhirQuestionnaireResponse, mapFhirTypeToEsheet, mapEsheetTypeToFhir, convertAnswerOptionToFieldOption, convertOptionToFhirAnswerOption, mapFhirOperatorToEsheet, mapEsheetOperatorToFhir, mapEnableBehaviorToLogic, extractEnableWhenAnswer, getExtensionValue, createItemControlExtension, createSignatureRequiredExtension, generateUUID, FHIR_EXT, } from './utils.js';
5
+ // Re-export type guards
6
+ export { isFhirQuestionnaire, isFhirQuestionnaireResponse };
7
+ // ---------------------------------------------------------------------------
8
+ // Import: FHIR Questionnaire → FormDefinition
9
+ // ---------------------------------------------------------------------------
10
+ /**
11
+ * Convert a FHIR R4 Questionnaire to an eSheet FormDefinition.
12
+ */
13
+ export function importFromFhir(questionnaire, options) {
14
+ const warnings = [];
15
+ const formId = options?.formId ?? questionnaire.id ?? questionnaire.name ?? generateUUID();
16
+ const form = {
17
+ id: formId,
18
+ title: questionnaire.title,
19
+ description: questionnaire.description,
20
+ fields: [],
21
+ _sourceData: extractFormMetadata(questionnaire, options),
22
+ };
23
+ if (questionnaire.item) {
24
+ form.fields = questionnaire.item.map((item, index) => convertItemToField(item, `item[${index}]`, warnings, options));
25
+ }
26
+ if (warnings.length > 0) {
27
+ const meta = form._sourceData;
28
+ meta._conversionWarnings =
29
+ warnings;
30
+ }
31
+ return form;
32
+ }
33
+ function extractFormMetadata(q, options) {
34
+ const meta = {};
35
+ const preserve = options?.preserveExtensions !== false;
36
+ if (q.url)
37
+ meta.url = q.url;
38
+ if (q.version)
39
+ meta.version = q.version;
40
+ if (q.name)
41
+ meta.name = q.name;
42
+ if (q.status)
43
+ meta.status = q.status;
44
+ if (q.publisher)
45
+ meta.publisher = q.publisher;
46
+ if (q.date)
47
+ meta.date = q.date;
48
+ if (q.subjectType)
49
+ meta.subjectType = q.subjectType;
50
+ if (q.derivedFrom)
51
+ meta.derivedFrom = q.derivedFrom;
52
+ if (q.code)
53
+ meta.code = q.code;
54
+ if (preserve && q.extension) {
55
+ meta.fhirExtensions =
56
+ q.extension;
57
+ }
58
+ return meta;
59
+ }
60
+ function convertItemToField(item, path, warnings, options) {
61
+ const typeResult = mapFhirTypeToEsheet(item.type, item);
62
+ const existingIds = new Set();
63
+ // Build base field
64
+ const base = {
65
+ id: item.linkId,
66
+ question: item.text,
67
+ required: item.required,
68
+ };
69
+ // Build _sourceData for round-trip
70
+ const fieldMeta = buildFieldMeta(item, typeResult, options);
71
+ const hasFieldMeta = Object.keys(fieldMeta).length > 0;
72
+ // Convert enableWhen to rules
73
+ const rules = convertEnableWhenToRules(item.enableWhen, item.enableBehavior);
74
+ const rulesSpread = rules.length > 0 ? { rules } : {};
75
+ // Handle warnings for lossy conversions
76
+ addConversionWarnings(item, path, typeResult, warnings);
77
+ // Build field based on type
78
+ switch (typeResult.fieldType) {
79
+ case 'section':
80
+ return buildSectionField(base, item, path, warnings, options, hasFieldMeta ? fieldMeta : undefined);
81
+ case 'radio':
82
+ case 'check':
83
+ case 'dropdown':
84
+ case 'multiselectdropdown':
85
+ return {
86
+ ...base,
87
+ ...(hasFieldMeta ? { _sourceData: fieldMeta } : {}),
88
+ ...rulesSpread,
89
+ fieldType: typeResult.fieldType,
90
+ options: convertAnswerOptions(item.answerOption, existingIds),
91
+ };
92
+ case 'rating':
93
+ case 'slider':
94
+ return {
95
+ ...base,
96
+ ...(hasFieldMeta ? { _sourceData: fieldMeta } : {}),
97
+ ...rulesSpread,
98
+ fieldType: typeResult.fieldType,
99
+ options: convertAnswerOptions(item.answerOption, existingIds),
100
+ };
101
+ case 'text':
102
+ case 'longtext':
103
+ return {
104
+ ...base,
105
+ ...(hasFieldMeta ? { _sourceData: fieldMeta } : {}),
106
+ ...rulesSpread,
107
+ fieldType: typeResult.fieldType,
108
+ ...(typeResult.inputType ? { inputType: typeResult.inputType } : {}),
109
+ };
110
+ case 'display':
111
+ // Display fields have no question — FHIR item.text maps to content (the displayed text)
112
+ return {
113
+ id: base.id,
114
+ ...(hasFieldMeta ? { _sourceData: fieldMeta } : {}),
115
+ ...rulesSpread,
116
+ fieldType: 'display',
117
+ content: item.text,
118
+ };
119
+ case 'boolean':
120
+ case 'signature':
121
+ case 'diagram':
122
+ return {
123
+ ...base,
124
+ ...(hasFieldMeta ? { _sourceData: fieldMeta } : {}),
125
+ ...rulesSpread,
126
+ fieldType: typeResult.fieldType,
127
+ };
128
+ default:
129
+ return {
130
+ ...base,
131
+ ...(hasFieldMeta ? { _sourceData: fieldMeta } : {}),
132
+ ...rulesSpread,
133
+ fieldType: 'text',
134
+ };
135
+ }
136
+ }
137
+ function buildSectionField(base, item, path, warnings, options, fieldMeta) {
138
+ const nestedFields = item.item
139
+ ? item.item.map((child, i) => convertItemToField(child, `${path}.item[${i}]`, warnings, options))
140
+ : [];
141
+ return {
142
+ ...base,
143
+ ...(fieldMeta ? { _sourceData: fieldMeta } : {}),
144
+ fieldType: 'section',
145
+ title: item.text,
146
+ fields: nestedFields,
147
+ };
148
+ }
149
+ function buildFieldMeta(item, typeResult, options) {
150
+ const meta = {};
151
+ const preserve = options?.preserveExtensions !== false;
152
+ if (item.definition)
153
+ meta.definition = item.definition;
154
+ if (item.code)
155
+ meta.code = item.code;
156
+ if (item.prefix)
157
+ meta.prefix = item.prefix;
158
+ if (item.readOnly)
159
+ meta.readOnly = item.readOnly;
160
+ if (item.repeats)
161
+ meta.repeats = item.repeats;
162
+ // Store original FHIR type for export
163
+ meta.fhirItemType = item.type;
164
+ // Extract validation extensions
165
+ const minVal = getExtensionValue(item.extension, FHIR_EXT.MIN_VALUE);
166
+ const maxVal = getExtensionValue(item.extension, FHIR_EXT.MAX_VALUE);
167
+ const minLen = getExtensionValue(item.extension, FHIR_EXT.MIN_LENGTH);
168
+ const regex = getExtensionValue(item.extension, FHIR_EXT.REGEX);
169
+ if (minVal !== undefined)
170
+ meta.minValue = minVal;
171
+ if (maxVal !== undefined)
172
+ meta.maxValue = maxVal;
173
+ if (minLen !== undefined)
174
+ meta.minLength = minLen;
175
+ if (regex !== undefined)
176
+ meta.regex = regex;
177
+ // Preserve unknown extensions
178
+ if (preserve && item.extension) {
179
+ meta.fhirExtensions =
180
+ item.extension;
181
+ }
182
+ // Preserve answerValueSet URL for client-side resolution
183
+ if (item.answerValueSet) {
184
+ meta.answerValueSet = item.answerValueSet;
185
+ }
186
+ return meta;
187
+ }
188
+ function convertAnswerOptions(answerOptions, existingIds) {
189
+ if (!answerOptions)
190
+ return [];
191
+ return answerOptions.map((opt, index) => convertAnswerOptionToFieldOption(opt, existingIds, index));
192
+ }
193
+ function convertEnableWhenToRules(enableWhen, enableBehavior) {
194
+ if (!enableWhen || enableWhen.length === 0)
195
+ return [];
196
+ const conditions = [];
197
+ for (const ew of enableWhen) {
198
+ const operator = mapFhirOperatorToEsheet(ew.operator, ew.answerBoolean);
199
+ if (!operator)
200
+ continue;
201
+ conditions.push({
202
+ conditionType: 'field',
203
+ targetId: ew.question,
204
+ operator,
205
+ expected: extractEnableWhenAnswer(ew),
206
+ });
207
+ }
208
+ if (conditions.length === 0)
209
+ return [];
210
+ return [
211
+ {
212
+ effect: 'visible',
213
+ logic: mapEnableBehaviorToLogic(enableBehavior),
214
+ conditions,
215
+ },
216
+ ];
217
+ }
218
+ function addConversionWarnings(item, path, typeResult, warnings) {
219
+ // Reference type warning
220
+ if (typeResult.subType === 'reference') {
221
+ warnings.push({
222
+ path,
223
+ code: 'UNSUPPORTED_TYPE',
224
+ message: `FHIR reference type converted to text field. Type safety lost.`,
225
+ });
226
+ }
227
+ // Quantity type warning
228
+ if (typeResult.subType === 'quantity') {
229
+ warnings.push({
230
+ path,
231
+ code: 'UNSUPPORTED_TYPE',
232
+ message: `FHIR quantity type converted to number field. Unit choices lost.`,
233
+ });
234
+ }
235
+ // ValueSet reference (informational)
236
+ if (item.answerValueSet) {
237
+ warnings.push({
238
+ path,
239
+ code: 'VALUESET_NOT_EXPANDED',
240
+ severity: 'info',
241
+ message: `ValueSet "${item.answerValueSet}" requires expansion. URL preserved in _sourceData.answerValueSet for client-side $expand.`,
242
+ });
243
+ }
244
+ }
245
+ // ---------------------------------------------------------------------------
246
+ // Export: FormDefinition → FHIR Questionnaire
247
+ // ---------------------------------------------------------------------------
248
+ /**
249
+ * Convert an eSheet FormDefinition to a FHIR R4 Questionnaire.
250
+ */
251
+ export function exportToFhir(form, options) {
252
+ const sourceMeta = form._sourceData;
253
+ const questionnaire = {
254
+ resourceType: 'Questionnaire',
255
+ id: options?.resourceId ?? form.id,
256
+ url: options?.canonicalUrl
257
+ ? `${options.canonicalUrl}/Questionnaire/${form.id}`
258
+ : sourceMeta?.url ?? `urn:uuid:${generateUUID()}`,
259
+ status: options?.status ?? sourceMeta?.status ?? 'draft',
260
+ title: form.title,
261
+ description: form.description,
262
+ ...(sourceMeta?.version ? { version: sourceMeta.version } : {}),
263
+ ...(options?.publisher ?? sourceMeta?.publisher
264
+ ? { publisher: options?.publisher ?? sourceMeta?.publisher }
265
+ : {}),
266
+ ...(sourceMeta?.date ? { date: sourceMeta.date } : {}),
267
+ item: form.fields.map(convertFieldToItem),
268
+ };
269
+ // DTR compliance
270
+ if (options?.dtrCompliant) {
271
+ questionnaire.subjectType = [
272
+ 'Patient',
273
+ ];
274
+ }
275
+ // Restore preserved extensions
276
+ if (sourceMeta?.fhirExtensions) {
277
+ questionnaire.extension = sourceMeta.fhirExtensions;
278
+ }
279
+ return questionnaire;
280
+ }
281
+ function convertFieldToItem(field) {
282
+ const fieldMeta = field._sourceData;
283
+ const inputType = 'inputType' in field ? field.inputType : undefined;
284
+ const typeResult = mapEsheetTypeToFhir(field.fieldType, inputType);
285
+ // For sections, use title if question is not set
286
+ const text = field.question ??
287
+ (field.fieldType === 'section' && 'title' in field
288
+ ? field.title
289
+ : undefined);
290
+ const item = {
291
+ linkId: field.id,
292
+ text,
293
+ type: typeResult.type,
294
+ ...(field.required ? { required: true } : {}),
295
+ ...(typeResult.repeats ? { repeats: true } : {}),
296
+ ...(fieldMeta?.readOnly ? { readOnly: true } : {}),
297
+ };
298
+ // Build extensions array
299
+ const extensions = [];
300
+ // Add itemControl extension
301
+ if (typeResult.itemControl) {
302
+ extensions.push(createItemControlExtension(typeResult.itemControl));
303
+ }
304
+ // Add signature extension for signature fields
305
+ if (field.fieldType === 'signature') {
306
+ extensions.push(createSignatureRequiredExtension());
307
+ }
308
+ // Add preserved extensions
309
+ if (fieldMeta?.fhirExtensions) {
310
+ // Filter out extensions we're generating ourselves
311
+ const preserved = fieldMeta.fhirExtensions.filter((e) => e.url !== FHIR_EXT.ITEM_CONTROL && e.url !== FHIR_EXT.SIGNATURE_REQUIRED);
312
+ extensions.push(...preserved);
313
+ }
314
+ if (extensions.length > 0) {
315
+ item.extension = extensions;
316
+ }
317
+ // Add answer options
318
+ if ('options' in field && field.options && field.options.length > 0) {
319
+ item.answerOption =
320
+ field.options.map(convertOptionToFhirAnswerOption);
321
+ }
322
+ // Add enableWhen for visibility rules
323
+ const enableWhen = convertRulesToEnableWhen(field.rules);
324
+ if (enableWhen.length > 0) {
325
+ item.enableWhen =
326
+ enableWhen;
327
+ if (enableWhen.length > 1) {
328
+ // Check original logic mode
329
+ const visibleRule = field.rules?.find((r) => r.effect === 'visible');
330
+ item.enableBehavior =
331
+ visibleRule?.logic === 'OR' ? 'any' : 'all';
332
+ }
333
+ }
334
+ // Handle nested fields for sections
335
+ if (field.fieldType === 'section' && 'fields' in field && field.fields) {
336
+ item.item =
337
+ field.fields.map(convertFieldToItem);
338
+ }
339
+ // Restore definition, code, prefix from metadata
340
+ if (fieldMeta?.definition)
341
+ item.definition = fieldMeta.definition;
342
+ if (fieldMeta?.code)
343
+ item.code = fieldMeta.code;
344
+ if (fieldMeta?.prefix)
345
+ item.prefix = fieldMeta.prefix;
346
+ return item;
347
+ }
348
+ function convertRulesToEnableWhen(rules) {
349
+ if (!rules)
350
+ return [];
351
+ // Only convert 'visible' rules to enableWhen
352
+ const visibleRule = rules.find((r) => r.effect === 'visible');
353
+ if (!visibleRule)
354
+ return [];
355
+ return visibleRule.conditions
356
+ .map((cond) => {
357
+ if (cond.conditionType === 'expression')
358
+ return null;
359
+ if (!cond.targetId || !cond.operator)
360
+ return null;
361
+ const fhirOp = mapEsheetOperatorToFhir(cond.operator);
362
+ if (!fhirOp)
363
+ return null;
364
+ const ew = {
365
+ question: cond.targetId,
366
+ operator: fhirOp.operator,
367
+ };
368
+ // Set the answer value
369
+ if (fhirOp.answerBoolean !== undefined) {
370
+ ew.answerBoolean = fhirOp.answerBoolean;
371
+ }
372
+ else if (cond.expected !== undefined) {
373
+ // Determine answer type based on expected value
374
+ if (cond.expected === 'true' || cond.expected === 'false') {
375
+ ew.answerBoolean =
376
+ cond.expected === 'true';
377
+ }
378
+ else if (!isNaN(Number(cond.expected))) {
379
+ ew.answerDecimal = Number(cond.expected);
380
+ }
381
+ else {
382
+ ew.answerString = cond.expected;
383
+ }
384
+ }
385
+ return ew;
386
+ })
387
+ .filter((ew) => ew !== null);
388
+ }
389
+ // ---------------------------------------------------------------------------
390
+ // Import: FHIR QuestionnaireResponse → Answers
391
+ // ---------------------------------------------------------------------------
392
+ /**
393
+ * Convert a FHIR QuestionnaireResponse to an eSheet answer map.
394
+ */
395
+ export function importResponseFromFhir(response, options) {
396
+ const answers = {};
397
+ if (response.item) {
398
+ collectResponseAnswers(response.item, answers);
399
+ }
400
+ return answers;
401
+ }
402
+ function collectResponseAnswers(items, answers) {
403
+ for (const item of items) {
404
+ if (item.answer && item.answer.length > 0) {
405
+ answers[item.linkId] = extractAnswerValue(item.answer);
406
+ }
407
+ // Recurse into nested items
408
+ if (item.item) {
409
+ collectResponseAnswers(item.item, answers);
410
+ }
411
+ // Also recurse into answer-nested items
412
+ for (const ans of item.answer ?? []) {
413
+ if (ans.item) {
414
+ collectResponseAnswers(ans.item, answers);
415
+ }
416
+ }
417
+ }
418
+ }
419
+ function extractAnswerValue(answers) {
420
+ // Multiple answers → array (for repeating items)
421
+ if (answers.length > 1) {
422
+ return answers.map(extractSingleAnswerValue);
423
+ }
424
+ return extractSingleAnswerValue(answers[0]);
425
+ }
426
+ function extractSingleAnswerValue(answer) {
427
+ if (answer.valueBoolean !== undefined)
428
+ return answer.valueBoolean;
429
+ if (answer.valueDecimal !== undefined)
430
+ return answer.valueDecimal;
431
+ if (answer.valueInteger !== undefined)
432
+ return answer.valueInteger;
433
+ if (answer.valueDate !== undefined)
434
+ return answer.valueDate;
435
+ if (answer.valueDateTime !== undefined)
436
+ return answer.valueDateTime;
437
+ if (answer.valueTime !== undefined)
438
+ return answer.valueTime;
439
+ if (answer.valueString !== undefined)
440
+ return answer.valueString;
441
+ if (answer.valueUri !== undefined)
442
+ return answer.valueUri;
443
+ if (answer.valueCoding)
444
+ return answer.valueCoding.code;
445
+ if (answer.valueQuantity)
446
+ return answer.valueQuantity.value;
447
+ if (answer.valueAttachment)
448
+ return answer.valueAttachment.data;
449
+ if (answer.valueReference)
450
+ return answer.valueReference.reference;
451
+ return undefined;
452
+ }
453
+ // ---------------------------------------------------------------------------
454
+ // Export: Answers → FHIR QuestionnaireResponse
455
+ // ---------------------------------------------------------------------------
456
+ /**
457
+ * Convert eSheet answers to a FHIR QuestionnaireResponse.
458
+ */
459
+ export function exportResponseToFhir(form, answers, options) {
460
+ const response = {
461
+ resourceType: 'QuestionnaireResponse',
462
+ ...(options.resourceId ? { id: options.resourceId } : {}),
463
+ questionnaire: options.questionnaireUrl,
464
+ status: options.status ?? 'completed',
465
+ ...(options.subject ? { subject: options.subject } : {}),
466
+ ...(options.author ? { author: options.author } : {}),
467
+ authored: new Date().toISOString(),
468
+ item: convertFieldsToResponseItems(form.fields, answers),
469
+ };
470
+ return response;
471
+ }
472
+ function convertFieldsToResponseItems(fields, answers) {
473
+ const items = [];
474
+ for (const field of fields) {
475
+ const item = convertFieldToResponseItem(field, answers);
476
+ if (item) {
477
+ items.push(item);
478
+ }
479
+ }
480
+ return items;
481
+ }
482
+ function convertFieldToResponseItem(field, answers) {
483
+ const value = answers[field.id];
484
+ // Handle sections recursively
485
+ if (field.fieldType === 'section' && 'fields' in field && field.fields) {
486
+ const nestedItems = convertFieldsToResponseItems(field.fields, answers);
487
+ if (nestedItems.length === 0)
488
+ return null;
489
+ return {
490
+ linkId: field.id,
491
+ text: field.question,
492
+ item: nestedItems,
493
+ };
494
+ }
495
+ // Skip fields with no answer
496
+ if (value === undefined || value === null || value === '') {
497
+ return null;
498
+ }
499
+ const item = {
500
+ linkId: field.id,
501
+ text: field.question,
502
+ answer: convertValueToAnswers(field, value),
503
+ };
504
+ return item;
505
+ }
506
+ function convertValueToAnswers(field, value) {
507
+ const inputType = 'inputType' in field ? field.inputType : undefined;
508
+ switch (field.fieldType) {
509
+ case 'text':
510
+ case 'longtext':
511
+ return [convertTextAnswer(value, inputType)];
512
+ case 'boolean':
513
+ return [{ valueBoolean: Boolean(value) }];
514
+ case 'radio':
515
+ case 'dropdown':
516
+ return [createCodingAnswer(field, value)];
517
+ case 'check':
518
+ case 'multiselectdropdown': {
519
+ const selected = Array.isArray(value) ? value : [value];
520
+ return selected.map((v) => createCodingAnswer(field, v));
521
+ }
522
+ case 'rating':
523
+ case 'slider':
524
+ return [{ valueInteger: Number(value) }];
525
+ case 'signature':
526
+ case 'diagram':
527
+ return [
528
+ {
529
+ valueAttachment: {
530
+ contentType: 'image/png',
531
+ data: value,
532
+ },
533
+ },
534
+ ];
535
+ default:
536
+ return [{ valueString: String(value) }];
537
+ }
538
+ }
539
+ function convertTextAnswer(value, inputType) {
540
+ switch (inputType) {
541
+ case 'number':
542
+ return { valueDecimal: Number(value) };
543
+ case 'date':
544
+ return { valueDate: value };
545
+ case 'datetime-local':
546
+ return { valueDateTime: value };
547
+ case 'time':
548
+ return { valueTime: value };
549
+ case 'url':
550
+ return { valueUri: value };
551
+ default:
552
+ return { valueString: String(value) };
553
+ }
554
+ }
555
+ function createCodingAnswer(field, selectedId) {
556
+ const options = 'options' in field ? field.options : undefined;
557
+ const option = options?.find((o) => o.id === selectedId);
558
+ return {
559
+ valueCoding: {
560
+ code: option?.value ?? selectedId,
561
+ display: option?.text,
562
+ },
563
+ };
564
+ }
@@ -0,0 +1,5 @@
1
+ export type { FhirCoding, FhirCodeableConcept, FhirReference, FhirIdentifier, FhirPeriod, FhirAttachment, FhirQuantity, FhirExpression, FhirExpressionLanguage, FhirExtension, FhirMeta, FhirContactDetail, FhirContactPoint, FhirUsageContext, FhirRange, FhirQuestionnaireStatus, FhirQuestionnaireItemType, FhirEnableWhenOperator, FhirEnableBehavior, FhirEnableWhen, FhirAnswerOption, FhirInitialValue, FhirQuestionnaireItem, FhirQuestionnaire, FhirResponseStatus, FhirResponseAnswer, FhirQuestionnaireResponseItem, FhirQuestionnaireResponse, FhirImportOptions, FhirExportOptions, ResponseImportOptions, ResponseExportOptions, ImportWarning, ImportWarningCode, ImportWarningSeverity, FhirFieldMeta, FhirFormMeta, } from './types.js';
2
+ export { isFhirQuestionnaire, isFhirQuestionnaireResponse } from './utils.js';
3
+ export { importFromFhir, exportToFhir, importResponseFromFhir, exportResponseToFhir, } from './fhir-adapter.js';
4
+ export { mapFhirTypeToEsheet, mapEsheetTypeToFhir, convertAnswerOptionToFieldOption, convertOptionToFhirAnswerOption, mapFhirOperatorToEsheet, mapEsheetOperatorToFhir, getExtensionValue, createItemControlExtension, FHIR_EXT, ITEM_CONTROL_SYSTEM, } from './utils.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fhir/index.ts"],"names":[],"mappings":"AAKA,YAAY,EAEV,UAAU,EACV,mBAAmB,EACnB,aAAa,EACb,cAAc,EACd,UAAU,EACV,cAAc,EACd,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,aAAa,EACb,QAAQ,EACR,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,SAAS,EAET,uBAAuB,EACvB,yBAAyB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EAEjB,kBAAkB,EAClB,kBAAkB,EAClB,6BAA6B,EAC7B,yBAAyB,EAEzB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EAErB,aAAa,EACb,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAG9E,OAAO,EACL,cAAc,EACd,YAAY,EACZ,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gCAAgC,EAChC,+BAA+B,EAC/B,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,0BAA0B,EAC1B,QAAQ,EACR,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,9 @@
1
+ // ---------------------------------------------------------------------------
2
+ // FHIR Adapter - Public API
3
+ // ---------------------------------------------------------------------------
4
+ // Type guards
5
+ export { isFhirQuestionnaire, isFhirQuestionnaireResponse } from './utils.js';
6
+ // Adapter functions
7
+ export { importFromFhir, exportToFhir, importResponseFromFhir, exportResponseToFhir, } from './fhir-adapter.js';
8
+ // Utility functions (for advanced users)
9
+ export { mapFhirTypeToEsheet, mapEsheetTypeToFhir, convertAnswerOptionToFieldOption, convertOptionToFhirAnswerOption, mapFhirOperatorToEsheet, mapEsheetOperatorToFhir, getExtensionValue, createItemControlExtension, FHIR_EXT, ITEM_CONTROL_SYSTEM, } from './utils.js';