Skip to main content

FHIR Adapter

Convert between FHIR R4 Questionnaire/QuestionnaireResponse resources and eSheet FormDefinition. Supports bidirectional conversion with metadata preservation for round-trip fidelity, conditional logic (enableWhen), and DTR profile compatibility.

Functions

FunctionSignatureDescription
importFromFhir(questionnaire: FhirQuestionnaire, options?: FhirImportOptions) => FormDefinitionImport FHIR Questionnaire to eSheet
exportToFhir(form: FormDefinition, options?: FhirExportOptions) => FhirQuestionnaireExport eSheet to FHIR Questionnaire
importResponseFromFhir(response: FhirQuestionnaireResponse, options?: ResponseImportOptions) => Record<string, unknown>Import QuestionnaireResponse to answers map
exportResponseToFhir(form: FormDefinition, answers: Record<string, unknown>, options: ResponseExportOptions) => FhirQuestionnaireResponseExport answers to QuestionnaireResponse
isFhirQuestionnaire(value: unknown) => value is FhirQuestionnaireType guard for Questionnaire
isFhirQuestionnaireResponse(value: unknown) => value is FhirQuestionnaireResponseType guard for QuestionnaireResponse

Import from FHIR

import { importFromFhir } from '@esheet/adapters';

const fhirQuestionnaire = {
resourceType: 'Questionnaire',
id: 'patient-intake',
status: 'active',
title: 'Patient Intake Form',
item: [
{
linkId: 'name',
text: 'Full Name',
type: 'string',
required: true,
},
{
linkId: 'dob',
text: 'Date of Birth',
type: 'date',
},
{
linkId: 'conditions',
text: 'Pre-existing Conditions',
type: 'choice',
repeats: true,
answerOption: [
{ valueCoding: { code: 'diabetes', display: 'Diabetes' } },
{ valueCoding: { code: 'hypertension', display: 'Hypertension' } },
],
},
],
};

const formDefinition = importFromFhir(fhirQuestionnaire);
// formDefinition.fields contains text, date, and checkbox fields

Import Options

interface FhirImportOptions {
formId?: string; // Override generated form ID
preserveExtensions?: boolean; // Keep extensions in _sourceData (default: true)
strictMode?: boolean; // Fail on unsupported features (default: false)
}

const form = importFromFhir(questionnaire, {
formId: 'custom-form-id',
preserveExtensions: true,
});

Export to FHIR

import { exportToFhir } from '@esheet/adapters';

const fhirQuestionnaire = exportToFhir(formDefinition, {
canonicalUrl: 'https://example.org/fhir',
publisher: 'My Organization',
status: 'active',
});
// Returns valid FHIR R4 Questionnaire resource

Export Options

interface FhirExportOptions {
resourceId?: string; // Override resource ID
canonicalUrl?: string; // Base URL for canonical references
status?: 'draft' | 'active' | 'retired' | 'unknown'; // Resource status (default: draft)
publisher?: string; // Publisher name
dtrCompliant?: boolean; // Apply DTR profile constraints
}

Type Guards

Use type guards to detect FHIR resources when handling unknown input:

import {
isFhirQuestionnaire,
isFhirQuestionnaireResponse,
importFromFhir,
importResponseFromFhir,
} from '@esheet/adapters';

function detectAndConvert(resource: unknown) {
if (isFhirQuestionnaire(resource)) {
return importFromFhir(resource);
}
if (isFhirQuestionnaireResponse(resource)) {
return importResponseFromFhir(resource);
}
return null;
}

Returns true if value has resourceType: 'Questionnaire' or resourceType: 'QuestionnaireResponse'.

Response Conversion

Import QuestionnaireResponse

Convert filled-out responses back to an eSheet answers map:

import { importResponseFromFhir } from '@esheet/adapters';

const fhirResponse = {
resourceType: 'QuestionnaireResponse',
questionnaire: 'Questionnaire/patient-intake',
status: 'completed',
item: [
{
linkId: 'name',
answer: [{ valueString: 'John Doe' }],
},
{
linkId: 'conditions',
answer: [
{ valueCoding: { code: 'diabetes' } },
{ valueCoding: { code: 'hypertension' } },
],
},
],
};

const answers = importResponseFromFhir(fhirResponse);
// { name: 'John Doe', conditions: ['diabetes', 'hypertension'] }

Export to QuestionnaireResponse

Convert eSheet answers to a FHIR QuestionnaireResponse:

import { exportResponseToFhir } from '@esheet/adapters';

const answers = {
name: 'Jane Smith',
dob: '1990-05-15',
conditions: ['diabetes'],
};

const fhirResponse = exportResponseToFhir(formDefinition, answers, {
questionnaireUrl: 'https://example.org/fhir/Questionnaire/patient-intake',
subject: { reference: 'Patient/123' },
author: { reference: 'Practitioner/456' },
status: 'completed',
});

Response Export Options

interface ResponseExportOptions {
questionnaireUrl: string; // Canonical reference to questionnaire (required)
subject?: FhirReference; // Patient/subject reference
author?: FhirReference; // Author reference
status?:
| 'in-progress'
| 'completed'
| 'amended'
| 'entered-in-error'
| 'stopped';
resourceId?: string; // Resource ID
}

Field Type Mapping

FHIR → eSheet

FHIR Item TypeitemControl ExtensioneSheet Field Type
stringtext (inputType: string)
textlongtext
booleanboolean
decimaltext (inputType: number)
integertext (inputType: number)
integersliderslider
datetext (inputType: date)
dateTimetext (inputType: datetime-local)
timetext (inputType: time)
urltext (inputType: url)
choiceradio-buttonradio
choicecheck-boxcheck
choicedrop-down / autocompletedropdown
choice— (with repeats: true)check
choice— (without repeats)radio
open-choice(same as choice)(same as choice)
groupsection
displaydisplay
attachment— (with signatureRequired)signature
attachmentdiagram
referencetext ⚠️
quantitytext ⚠️

⚠️ = Lossy conversion (see Warning System)

eSheet → FHIR

eSheet Field TypeFHIR TypeitemControl Extension
text(varies)
text (number)decimal
text (date)date
text (datetime-local)dateTime
text (time)time
text (url)url
longtexttext
booleanboolean
radiochoiceradio-button
checkchoicecheck-box (repeats: true)
dropdownchoicedrop-down
multiselectdropdownchoicedrop-down (repeats: true)
ratingintegerslider
sliderdecimalslider
sectiongroup
displaydisplay
signatureattachment— (signatureRequired ext)
diagramattachment

Extension Handling

itemControl Extension

The questionnaire-itemControl extension determines how choice fields render:

const item = {
linkId: 'priority',
type: 'choice',
extension: [
{
url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl',
valueCodeableConcept: {
coding: [
{
system: 'http://hl7.org/fhir/questionnaire-item-control',
code: 'drop-down', // or 'radio-button', 'check-box', 'autocomplete'
},
],
},
},
],
};

Validation Extensions

The adapter extracts and preserves these validation extensions:

Extension URLPurposeStored in
minValueMinimum allowed value_sourceData.minValue
maxValueMaximum allowed value_sourceData.maxValue
minLengthMinimum string length_sourceData.minLength
regexRegex validation pattern_sourceData.regex
const form = importFromFhir(questionnaire);
const field = form.fields[0];

if (field._sourceData?.minValue !== undefined) {
// Apply validation: value >= minValue
}

Option Scoring (ordinalValue)

Scores from ordinalValue extensions on answerOptions map to FieldOption.score:

const item = {
linkId: 'pain-level',
type: 'choice',
answerOption: [
{
valueCoding: { code: 'mild', display: 'Mild' },
extension: [
{
url: 'http://hl7.org/fhir/StructureDefinition/ordinalValue',
valueDecimal: 1,
},
],
},
{
valueCoding: { code: 'severe', display: 'Severe' },
extension: [
{
url: 'http://hl7.org/fhir/StructureDefinition/ordinalValue',
valueDecimal: 10,
},
],
},
],
};

// Converts to:
// options: [{ id: 'mild', value: 'mild', text: 'Mild', score: 1 }, ...]

Supported Extension URLs

import { FHIR_EXT } from '@esheet/adapters';

FHIR_EXT.ITEM_CONTROL; // questionnaire-itemControl
FHIR_EXT.MIN_VALUE; // minValue
FHIR_EXT.MAX_VALUE; // maxValue
FHIR_EXT.MIN_LENGTH; // minLength
FHIR_EXT.REGEX; // regex
FHIR_EXT.ORDINAL_VALUE; // ordinalValue
FHIR_EXT.HIDDEN; // questionnaire-hidden
FHIR_EXT.SLIDER_STEP; // questionnaire-sliderStepValue
FHIR_EXT.SIGNATURE_REQUIRED; // questionnaire-signatureRequired

// SDC / DTR Extensions
FHIR_EXT.INITIAL_EXPRESSION; // sdc-questionnaire-initialExpression
FHIR_EXT.CALCULATED_EXPRESSION; // sdc-questionnaire-calculatedExpression
FHIR_EXT.ENABLE_WHEN_EXPRESSION; // sdc-questionnaire-enableWhenExpression
FHIR_EXT.VARIABLE; // variable

Metadata Preservation

FhirFieldMeta

Field-level FHIR metadata preserved in _sourceData:

interface FhirFieldMeta {
definition?: string; // Element definition URL
code?: FhirCoding[]; // Semantic codes
prefix?: string; // Numbering prefix ("1.", "a)")
readOnly?: boolean; // Read-only flag
repeats?: boolean; // Repeating item flag
fhirItemType?: string; // Original FHIR type
fhirExtensions?: FhirExtension[]; // All preserved extensions

// Validation
minValue?: number | string;
maxValue?: number | string;
minLength?: number;
regex?: string;

// DTR expressions (preserved, not evaluated)
initialExpression?: FhirExpression;
calculatedExpression?: FhirExpression;
enableWhenExpression?: FhirExpression;
}

FhirFormMeta

Form-level FHIR metadata preserved in _sourceData:

interface FhirFormMeta {
url?: string; // Canonical URL
version?: string; // Semantic version
name?: string; // Computer-friendly name
status?: string; // Publication status
publisher?: string; // Publisher organization
date?: string; // Publication date
subjectType?: string[]; // Allowed subject types
derivedFrom?: string[]; // Parent questionnaires
code?: FhirCoding[]; // Form-level codes
fhirExtensions?: FhirExtension[]; // Form-level extensions
_conversionWarnings?: ImportWarning[]; // Conversion warnings
}

Warning System

The adapter tracks potentially lossy conversions via warnings:

const form = importFromFhir(questionnaire);
const meta = form._sourceData as FhirFormMeta;

if (meta._conversionWarnings) {
for (const warning of meta._conversionWarnings) {
console.warn(`${warning.code} at ${warning.path}: ${warning.message}`);
}
}

Warning Codes

CodeDescription
UNSUPPORTED_TYPEFHIR type converted to text field (reference, quantity)
VALUESET_NOT_EXPANDEDanswerValueSet reference not expanded — options empty
EXPRESSION_PRESERVEDFHIRPath/CQL expression preserved but not evaluated
NESTED_ANSWERS_FLATTENEDNested answer structure flattened
EXTENSION_PRESERVEDUnknown extension preserved in _sourceData
REPEAT_NOT_SUPPORTEDrepeats on non-choice field ignored

Conditional Logic (enableWhen)

Import: enableWhen → rules

FHIR enableWhen conditions convert to eSheet visibility rules:

// FHIR enableWhen
const item = {
linkId: 'pregnancy-details',
type: 'text',
enableWhen: [
{ question: 'is-pregnant', operator: '=', answerBoolean: true },
],
};

// Converts to eSheet rule:
{
effect: 'visible',
logic: 'AND',
conditions: [{
conditionType: 'field',
targetId: 'is-pregnant',
operator: 'equals',
expected: 'true',
}],
}

Operator Mapping

FHIR OperatoreSheet Operator
=equals
!=notEquals
>greaterThan
>=greaterThanOrEqual
<lessThan
<=lessThanOrEqual
exists (true)notEmpty
exists (false)empty

enableBehavior

FHIR enableBehavioreSheet logic
all (default)AND
anyOR

Export: rules → enableWhen

Only visible effect rules export back to enableWhen. Other effects (enable, required) are not supported in standard FHIR and are preserved in _sourceData.

DTR Profile Support

For Da Vinci DTR (Documentation Templates and Rules) compliance:

const fhirQuestionnaire = exportToFhir(formDefinition, {
dtrCompliant: true,
canonicalUrl: 'https://example.org/fhir',
});
// Sets subjectType: ['Patient'] and applies DTR constraints

DTR Extensions Preserved

The adapter preserves these SDC/DTR extensions in _sourceData without evaluation:

  • initialExpression — FHIRPath expression for initial value
  • calculatedExpression — FHIRPath expression for calculated value
  • enableWhenExpression — FHIRPath expression for visibility
  • variable — Named FHIRPath expressions

These expressions require a FHIRPath evaluation engine to execute at runtime.

Round-Trip Fidelity

Original FHIR metadata is preserved in _sourceData for lossless round-trips:

// Import
const form = importFromFhir(originalQuestionnaire);

// Modify
form.title = 'Updated Title';

// Export — original extensions, codes, definitions restored
const exported = exportToFhir(form);

What Gets Preserved

  • ✅ All standard FHIR properties (url, version, status, publisher, etc.)
  • ✅ All extensions (itemControl, validation, scoring, custom)
  • ✅ Semantic codes and definitions
  • ✅ Nested item structure
  • ✅ enableWhen/enableBehavior
  • ✅ repeats flag
  • ✅ readOnly flag

What Requires Re-mapping

  • ⚠️ answerValueSet — Must be expanded before import
  • ⚠️ Complex expressions — FHIRPath/CQL evaluated externally
  • ⚠️ Nested answers in QuestionnaireResponse — Flattened to simple map

Utility Functions

For advanced use cases, low-level utilities are exported:

import {
mapFhirTypeToEsheet,
mapEsheetTypeToFhir,
convertAnswerOptionToFieldOption,
convertOptionToFhirAnswerOption,
mapFhirOperatorToEsheet,
mapEsheetOperatorToFhir,
getExtensionValue,
createItemControlExtension,
FHIR_EXT,
ITEM_CONTROL_SYSTEM,
} from '@esheet/adapters';

Types

import type {
// Primitives
FhirCoding,
FhirCodeableConcept,
FhirReference,
FhirIdentifier,
FhirPeriod,
FhirAttachment,
FhirQuantity,
FhirExpression,
FhirExtension,

// Questionnaire
FhirQuestionnaire,
FhirQuestionnaireItem,
FhirQuestionnaireStatus,
FhirQuestionnaireItemType,
FhirAnswerOption,
FhirEnableWhen,
FhirEnableWhenOperator,
FhirEnableBehavior,

// Response
FhirQuestionnaireResponse,
FhirQuestionnaireResponseItem,
FhirResponseAnswer,
FhirResponseStatus,

// Options
FhirImportOptions,
FhirExportOptions,
ResponseImportOptions,
ResponseExportOptions,
ImportWarning,
ImportWarningCode,

// Metadata
FhirFieldMeta,
FhirFormMeta,
} from '@esheet/adapters';