@atomic-ehr/fhirpath 0.0.2 → 0.0.3-canary.2be66fb.20250905161900
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 +716 -238
- package/dist/index.d.ts +226 -120
- package/dist/index.js +11552 -5580
- package/dist/index.js.map +1 -1
- package/package.json +12 -5
- package/src/analyzer/augmentor.ts +242 -0
- package/src/analyzer/cursor-services.ts +75 -0
- package/src/analyzer/scope-manager.ts +57 -0
- package/src/analyzer/trivia-indexer.ts +58 -0
- package/src/analyzer/type-compat.ts +157 -0
- package/src/analyzer/utils.ts +132 -0
- package/src/analyzer.ts +939 -1204
- package/src/completion-provider.ts +209 -191
- package/src/complex-types/quantity-value.ts +410 -0
- package/src/complex-types/temporal.ts +1776 -0
- package/src/errors.ts +25 -3
- package/src/index.ts +17 -104
- package/src/inspect.ts +4 -4
- package/src/{boxing.ts → interpreter/boxing.ts} +1 -1
- package/src/interpreter/navigator.ts +94 -0
- package/src/interpreter/runtime-context.ts +273 -0
- package/src/interpreter.ts +506 -468
- package/src/lexer.ts +192 -211
- package/src/model-provider.ts +71 -43
- package/src/operations/abs-function.ts +1 -1
- package/src/operations/aggregate-function.ts +84 -5
- package/src/operations/all-function.ts +4 -3
- package/src/operations/allFalse-function.ts +2 -1
- package/src/operations/allTrue-function.ts +2 -1
- package/src/operations/and-operator.ts +2 -1
- package/src/operations/anyFalse-function.ts +2 -1
- package/src/operations/anyTrue-function.ts +2 -1
- package/src/operations/as-function.ts +99 -0
- package/src/operations/as-operator.ts +57 -19
- package/src/operations/ceiling-function.ts +1 -1
- package/src/operations/children-function.ts +14 -5
- package/src/operations/combine-function.ts +6 -3
- package/src/operations/combine-operator.ts +6 -7
- package/src/operations/comparison.ts +744 -0
- package/src/operations/contains-function.ts +1 -1
- package/src/operations/contains-operator.ts +2 -1
- package/src/operations/convertsToBoolean-function.ts +78 -0
- package/src/operations/convertsToDecimal-function.ts +82 -0
- package/src/operations/convertsToInteger-function.ts +71 -0
- package/src/operations/convertsToLong-function.ts +89 -0
- package/src/operations/convertsToQuantity-function.ts +132 -0
- package/src/operations/convertsToString-function.ts +88 -0
- package/src/operations/count-function.ts +2 -1
- package/src/operations/dateOf-function.ts +69 -0
- package/src/operations/dayOf-function.ts +66 -0
- package/src/operations/decimal-boundaries.ts +133 -0
- package/src/operations/defineVariable-function.ts +130 -17
- package/src/operations/distinct-function.ts +1 -1
- package/src/operations/div-operator.ts +1 -1
- package/src/operations/divide-operator.ts +12 -7
- package/src/operations/dot-operator.ts +1 -1
- package/src/operations/empty-function.ts +30 -21
- package/src/operations/endsWith-function.ts +6 -1
- package/src/operations/equal-operator.ts +23 -32
- package/src/operations/equivalent-operator.ts +13 -53
- package/src/operations/exclude-function.ts +2 -1
- package/src/operations/exists-function.ts +4 -3
- package/src/operations/extension-function.ts +84 -0
- package/src/operations/first-function.ts +1 -1
- package/src/operations/floor-function.ts +1 -1
- package/src/operations/greater-operator.ts +7 -9
- package/src/operations/greater-or-equal-operator.ts +7 -9
- package/src/operations/highBoundary-function.ts +120 -0
- package/src/operations/hourOf-function.ts +66 -0
- package/src/operations/iif-function.ts +193 -8
- package/src/operations/implies-operator.ts +2 -1
- package/src/operations/in-operator.ts +2 -1
- package/src/operations/index.ts +43 -0
- package/src/operations/indexOf-function.ts +1 -1
- package/src/operations/intersect-function.ts +1 -1
- package/src/operations/is-function.ts +70 -0
- package/src/operations/is-operator.ts +176 -13
- package/src/operations/isDistinct-function.ts +2 -1
- package/src/operations/join-function.ts +1 -1
- package/src/operations/last-function.ts +1 -1
- package/src/operations/lastIndexOf-function.ts +85 -0
- package/src/operations/length-function.ts +1 -1
- package/src/operations/less-operator.ts +8 -9
- package/src/operations/less-or-equal-operator.ts +7 -9
- package/src/operations/less-than.ts +8 -13
- package/src/operations/lowBoundary-function.ts +120 -0
- package/src/operations/lower-function.ts +1 -1
- package/src/operations/matches-function.ts +86 -0
- package/src/operations/matchesFull-function.ts +96 -0
- package/src/operations/millisecondOf-function.ts +66 -0
- package/src/operations/minus-operator.ts +76 -4
- package/src/operations/minuteOf-function.ts +66 -0
- package/src/operations/mod-operator.ts +8 -2
- package/src/operations/monthOf-function.ts +66 -0
- package/src/operations/multiply-operator.ts +27 -3
- package/src/operations/not-equal-operator.ts +24 -30
- package/src/operations/not-equivalent-operator.ts +13 -53
- package/src/operations/not-function.ts +10 -3
- package/src/operations/ofType-function.ts +43 -12
- package/src/operations/or-operator.ts +2 -1
- package/src/operations/plus-operator.ts +71 -7
- package/src/operations/power-function.ts +35 -10
- package/src/operations/precision-function.ts +146 -0
- package/src/operations/repeat-function.ts +169 -0
- package/src/operations/replace-function.ts +1 -1
- package/src/operations/replaceMatches-function.ts +125 -0
- package/src/operations/round-function.ts +1 -1
- package/src/operations/secondOf-function.ts +66 -0
- package/src/operations/select-function.ts +66 -5
- package/src/operations/single-function.ts +1 -1
- package/src/operations/skip-function.ts +1 -1
- package/src/operations/split-function.ts +1 -1
- package/src/operations/sqrt-function.ts +15 -8
- package/src/operations/startsWith-function.ts +1 -1
- package/src/operations/subsetOf-function.ts +6 -2
- package/src/operations/substring-function.ts +1 -1
- package/src/operations/supersetOf-function.ts +6 -2
- package/src/operations/tail-function.ts +1 -1
- package/src/operations/take-function.ts +1 -1
- package/src/operations/temporal-functions.ts +555 -0
- package/src/operations/timeOf-function.ts +67 -0
- package/src/operations/timezoneOffsetOf-function.ts +69 -0
- package/src/operations/toBoolean-function.ts +27 -8
- package/src/operations/toChars-function.ts +56 -0
- package/src/operations/toDecimal-function.ts +27 -8
- package/src/operations/toInteger-function.ts +15 -3
- package/src/operations/toLong-function.ts +98 -0
- package/src/operations/toQuantity-function.ts +181 -0
- package/src/operations/toString-function.ts +78 -15
- package/src/operations/trace-function.ts +1 -1
- package/src/operations/trim-function.ts +1 -1
- package/src/operations/truncate-function.ts +1 -1
- package/src/operations/unary-minus-operator.ts +2 -2
- package/src/operations/unary-plus-operator.ts +1 -1
- package/src/operations/union-function.ts +1 -1
- package/src/operations/union-operator.ts +16 -26
- package/src/operations/upper-function.ts +1 -1
- package/src/operations/where-function.ts +3 -3
- package/src/operations/xor-operator.ts +1 -1
- package/src/operations/yearOf-function.ts +66 -0
- package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
- package/src/parser.ts +262 -503
- package/src/registry.ts +53 -42
- package/src/types.ts +129 -17
- package/src/utils/decimal.ts +76 -0
- package/src/utils/pprint.ts +151 -0
- package/src/quantity-value.ts +0 -198
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
// Built-in temporal functions for FHIRPath
|
|
2
|
+
import type { FunctionDefinition } from '../types';
|
|
3
|
+
import type { FunctionEvaluator } from '../types';
|
|
4
|
+
import { box } from '../interpreter/boxing';
|
|
5
|
+
import { createDate, createDateTime, createTime, isFHIRDate, isFHIRDateTime, isFHIRTime } from '../complex-types/temporal';
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// now() function - Returns current DateTime
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export const nowEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
12
|
+
// Always use cached value if available (set at expression evaluation start)
|
|
13
|
+
const CACHE_KEY = '__fhirpath_now_cache__';
|
|
14
|
+
|
|
15
|
+
if (context.variables[CACHE_KEY]) {
|
|
16
|
+
return {
|
|
17
|
+
value: [context.variables[CACHE_KEY]],
|
|
18
|
+
context
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Fallback: create new value if not cached (shouldn't happen in normal evaluation)
|
|
23
|
+
const now = new Date();
|
|
24
|
+
const dateTime = createDateTime(
|
|
25
|
+
now.getFullYear(),
|
|
26
|
+
now.getMonth() + 1,
|
|
27
|
+
now.getDate(),
|
|
28
|
+
now.getHours(),
|
|
29
|
+
now.getMinutes(),
|
|
30
|
+
now.getSeconds(),
|
|
31
|
+
now.getMilliseconds(),
|
|
32
|
+
-now.getTimezoneOffset() // Convert to minutes from UTC (JS gives minutes to subtract from UTC)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
value: [box(dateTime, { type: 'DateTime', singleton: true })],
|
|
37
|
+
context
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const nowFunction: FunctionDefinition & { evaluate: typeof nowEvaluator } = {
|
|
42
|
+
name: 'now',
|
|
43
|
+
category: ['temporal'],
|
|
44
|
+
description: 'Returns the current date and time as a DateTime value',
|
|
45
|
+
examples: ['now()', 'Patient.birthDate < now()'],
|
|
46
|
+
signatures: [
|
|
47
|
+
{
|
|
48
|
+
name: 'now',
|
|
49
|
+
input: { type: 'Any', singleton: false },
|
|
50
|
+
parameters: [],
|
|
51
|
+
result: { type: 'DateTime', singleton: true }
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
evaluate: nowEvaluator
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// today() function - Returns current Date
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
export const todayEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
62
|
+
// Always use cached value if available (set at expression evaluation start)
|
|
63
|
+
const CACHE_KEY = '__fhirpath_today_cache__';
|
|
64
|
+
|
|
65
|
+
if (context.variables[CACHE_KEY]) {
|
|
66
|
+
return {
|
|
67
|
+
value: [context.variables[CACHE_KEY]],
|
|
68
|
+
context
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Fallback: create new value if not cached (shouldn't happen in normal evaluation)
|
|
73
|
+
const today = new Date();
|
|
74
|
+
const date = createDate(
|
|
75
|
+
today.getFullYear(),
|
|
76
|
+
today.getMonth() + 1,
|
|
77
|
+
today.getDate()
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
value: [box(date, { type: 'Date', singleton: true })],
|
|
82
|
+
context
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const todayFunction: FunctionDefinition & { evaluate: typeof todayEvaluator } = {
|
|
87
|
+
name: 'today',
|
|
88
|
+
category: ['temporal'],
|
|
89
|
+
description: 'Returns the current date (no time component)',
|
|
90
|
+
examples: ['today()', 'Patient.birthDate < today()'],
|
|
91
|
+
signatures: [
|
|
92
|
+
{
|
|
93
|
+
name: 'today',
|
|
94
|
+
input: { type: 'Any', singleton: false },
|
|
95
|
+
parameters: [],
|
|
96
|
+
result: { type: 'Date', singleton: true }
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
evaluate: todayEvaluator
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// timeOfDay() function - Returns current Time
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
export const timeOfDayEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
107
|
+
// Always use cached value if available (set at expression evaluation start)
|
|
108
|
+
const CACHE_KEY = '__fhirpath_timeOfDay_cache__';
|
|
109
|
+
|
|
110
|
+
if (context.variables[CACHE_KEY]) {
|
|
111
|
+
return {
|
|
112
|
+
value: [context.variables[CACHE_KEY]],
|
|
113
|
+
context
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Fallback: create new value if not cached (shouldn't happen in normal evaluation)
|
|
118
|
+
const now = new Date();
|
|
119
|
+
const time = createTime(
|
|
120
|
+
now.getHours(),
|
|
121
|
+
now.getMinutes(),
|
|
122
|
+
now.getSeconds(),
|
|
123
|
+
now.getMilliseconds()
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
value: [box(time, { type: 'Time', singleton: true })],
|
|
128
|
+
context
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const timeOfDayFunction: FunctionDefinition & { evaluate: typeof timeOfDayEvaluator } = {
|
|
133
|
+
name: 'timeOfDay',
|
|
134
|
+
category: ['temporal'],
|
|
135
|
+
description: 'Returns the current time of day',
|
|
136
|
+
examples: ['timeOfDay()', '@T09:00 < timeOfDay()'],
|
|
137
|
+
signatures: [
|
|
138
|
+
{
|
|
139
|
+
name: 'timeOfDay',
|
|
140
|
+
input: { type: 'Any', singleton: false },
|
|
141
|
+
parameters: [],
|
|
142
|
+
result: { type: 'Time', singleton: true }
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
evaluate: timeOfDayEvaluator
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// ============================================================================
|
|
149
|
+
// toDate() function - Convert to Date
|
|
150
|
+
// ============================================================================
|
|
151
|
+
|
|
152
|
+
export const toDateEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
153
|
+
if (input.length === 0) {
|
|
154
|
+
return { value: [], context };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const boxedItem = input[0];
|
|
158
|
+
if (!boxedItem) {
|
|
159
|
+
return { value: [], context };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const item = boxedItem.value;
|
|
163
|
+
const typeInfo = boxedItem.typeInfo;
|
|
164
|
+
|
|
165
|
+
// If it's already a Date, return as is
|
|
166
|
+
if (typeInfo?.type === 'Date') {
|
|
167
|
+
return { value: [boxedItem], context };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If it's a DateTime, extract the date portion
|
|
171
|
+
if (typeInfo?.type === 'DateTime' && item && typeof item === 'object') {
|
|
172
|
+
const dateTime = item as any;
|
|
173
|
+
const date = createDate(dateTime.year, dateTime.month, dateTime.day);
|
|
174
|
+
return {
|
|
175
|
+
value: [box(date, { type: 'Date', singleton: true })],
|
|
176
|
+
context
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// If it's a String, try to parse it
|
|
181
|
+
if (typeof item === 'string') {
|
|
182
|
+
try {
|
|
183
|
+
const { parseTemporalLiteral, isFHIRDate } = await import('../complex-types/temporal');
|
|
184
|
+
const temporal = parseTemporalLiteral('@' + item);
|
|
185
|
+
if (isFHIRDate(temporal)) {
|
|
186
|
+
return {
|
|
187
|
+
value: [box(temporal, { type: 'Date', singleton: true })],
|
|
188
|
+
context
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// Parsing failed, return empty
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// For any other type (including Boolean), return empty
|
|
197
|
+
// This allows expressions like ({}).empty().toDate() to return []
|
|
198
|
+
return { value: [], context };
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export const toDateFunction: FunctionDefinition & { evaluate: typeof toDateEvaluator } = {
|
|
202
|
+
name: 'toDate',
|
|
203
|
+
category: ['conversion', 'temporal'],
|
|
204
|
+
description: 'Converts the input to a Date value',
|
|
205
|
+
examples: ["'2020-01-15'.toDate()", '@2020-01-15T10:30:00.toDate()'],
|
|
206
|
+
signatures: [
|
|
207
|
+
{
|
|
208
|
+
name: 'any-toDate',
|
|
209
|
+
parameters: [],
|
|
210
|
+
input: { type: 'Any', singleton: true },
|
|
211
|
+
result: { type: 'Date', singleton: true }
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
evaluate: toDateEvaluator
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// ============================================================================
|
|
218
|
+
// toDateTime() function - Convert to DateTime
|
|
219
|
+
// ============================================================================
|
|
220
|
+
|
|
221
|
+
export const toDateTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
222
|
+
if (input.length === 0) {
|
|
223
|
+
return { value: [], context };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const boxedItem = input[0];
|
|
227
|
+
if (!boxedItem) {
|
|
228
|
+
return { value: [], context };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const item = boxedItem.value;
|
|
232
|
+
const typeInfo = boxedItem.typeInfo;
|
|
233
|
+
|
|
234
|
+
// If it's already a DateTime, return as is
|
|
235
|
+
if (typeInfo?.type === 'DateTime') {
|
|
236
|
+
return { value: [boxedItem], context };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// If it's a Date, convert to DateTime with time as 00:00:00
|
|
240
|
+
if (typeInfo?.type === 'Date' && item && typeof item === 'object') {
|
|
241
|
+
const date = item as any;
|
|
242
|
+
const dateTime = createDateTime(
|
|
243
|
+
date.year,
|
|
244
|
+
date.month,
|
|
245
|
+
date.day,
|
|
246
|
+
0, 0, 0, 0
|
|
247
|
+
);
|
|
248
|
+
return {
|
|
249
|
+
value: [box(dateTime, { type: 'DateTime', singleton: true })],
|
|
250
|
+
context
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// If it's a String, try to parse it
|
|
255
|
+
if (typeof item === 'string') {
|
|
256
|
+
try {
|
|
257
|
+
const { parseTemporalLiteral, isFHIRDate, isFHIRDateTime } = await import('../complex-types/temporal');
|
|
258
|
+
const temporal = parseTemporalLiteral('@' + item);
|
|
259
|
+
if (isFHIRDateTime(temporal)) {
|
|
260
|
+
return {
|
|
261
|
+
value: [box(temporal, { type: 'DateTime', singleton: true })],
|
|
262
|
+
context
|
|
263
|
+
};
|
|
264
|
+
} else if (isFHIRDate(temporal)) {
|
|
265
|
+
// Convert Date to DateTime (with time as 00:00:00)
|
|
266
|
+
const dateTime = createDateTime(
|
|
267
|
+
temporal.year,
|
|
268
|
+
temporal.month,
|
|
269
|
+
temporal.day,
|
|
270
|
+
0, 0, 0, 0
|
|
271
|
+
);
|
|
272
|
+
return {
|
|
273
|
+
value: [box(dateTime, { type: 'DateTime', singleton: true })],
|
|
274
|
+
context
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
// Parsing failed, return empty
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// For any other type (including Boolean), return empty
|
|
283
|
+
// This allows expressions like ({}).empty().toDateTime() to return []
|
|
284
|
+
return { value: [], context };
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export const toDateTimeFunction: FunctionDefinition & { evaluate: typeof toDateTimeEvaluator } = {
|
|
288
|
+
name: 'toDateTime',
|
|
289
|
+
category: ['conversion', 'temporal'],
|
|
290
|
+
description: 'Converts the input to a DateTime value',
|
|
291
|
+
examples: ["'2020-01-15T10:30:00Z'.toDateTime()", '@2020-01-15.toDateTime()'],
|
|
292
|
+
signatures: [
|
|
293
|
+
{
|
|
294
|
+
name: 'any-toDateTime',
|
|
295
|
+
parameters: [],
|
|
296
|
+
input: { type: 'Any', singleton: true },
|
|
297
|
+
result: { type: 'DateTime', singleton: true }
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
evaluate: toDateTimeEvaluator
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// ============================================================================
|
|
304
|
+
// toTime() function - Convert to Time
|
|
305
|
+
// ============================================================================
|
|
306
|
+
|
|
307
|
+
export const toTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
308
|
+
if (input.length === 0) {
|
|
309
|
+
return { value: [], context };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const boxedItem = input[0];
|
|
313
|
+
if (!boxedItem) {
|
|
314
|
+
return { value: [], context };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const item = boxedItem.value;
|
|
318
|
+
const typeInfo = boxedItem.typeInfo;
|
|
319
|
+
|
|
320
|
+
// If it's already a Time, return as is
|
|
321
|
+
if (typeInfo?.type === 'Time') {
|
|
322
|
+
return { value: [boxedItem], context };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// If it's a DateTime, extract the time portion
|
|
326
|
+
if (typeInfo?.type === 'DateTime' && item && typeof item === 'object') {
|
|
327
|
+
const dateTime = item as any;
|
|
328
|
+
if (dateTime.hour !== undefined) {
|
|
329
|
+
const time = createTime(dateTime.hour, dateTime.minute, dateTime.second, dateTime.millisecond);
|
|
330
|
+
return {
|
|
331
|
+
value: [box(time, { type: 'Time', singleton: true })],
|
|
332
|
+
context
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// If it's a String, try to parse it
|
|
338
|
+
if (typeof item === 'string') {
|
|
339
|
+
try {
|
|
340
|
+
const { parseTemporalLiteral, isFHIRTime } = await import('../complex-types/temporal');
|
|
341
|
+
// For time strings, prepend T if not present
|
|
342
|
+
const timeString = item.startsWith('T') ? '@' + item : '@T' + item;
|
|
343
|
+
const temporal = parseTemporalLiteral(timeString);
|
|
344
|
+
if (isFHIRTime(temporal)) {
|
|
345
|
+
return {
|
|
346
|
+
value: [box(temporal, { type: 'Time', singleton: true })],
|
|
347
|
+
context
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
// Parsing failed, return empty
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// For any other type (including Boolean), return empty
|
|
356
|
+
// This allows expressions like ({}).empty().toTime() to return []
|
|
357
|
+
return { value: [], context };
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
export const toTimeFunction: FunctionDefinition & { evaluate: typeof toTimeEvaluator } = {
|
|
361
|
+
name: 'toTime',
|
|
362
|
+
category: ['conversion', 'temporal'],
|
|
363
|
+
description: 'Converts the input to a Time value',
|
|
364
|
+
examples: ["'14:30:00'.toTime()", '@2020-01-15T10:30:00.toTime()'],
|
|
365
|
+
signatures: [
|
|
366
|
+
{
|
|
367
|
+
name: 'any-toTime',
|
|
368
|
+
parameters: [],
|
|
369
|
+
input: { type: 'Any', singleton: true },
|
|
370
|
+
result: { type: 'Time', singleton: true }
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
evaluate: toTimeEvaluator
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// ============================================================================
|
|
377
|
+
// convertsToDate() function - Check if convertible to Date
|
|
378
|
+
// ============================================================================
|
|
379
|
+
|
|
380
|
+
export const convertsToDateEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
381
|
+
if (input.length === 0) {
|
|
382
|
+
return { value: [], context };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const boxedItem = input[0];
|
|
386
|
+
if (!boxedItem) {
|
|
387
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const item = boxedItem.value;
|
|
391
|
+
const typeInfo = boxedItem.typeInfo;
|
|
392
|
+
|
|
393
|
+
// Already a Date
|
|
394
|
+
if (typeInfo?.type === 'Date') {
|
|
395
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// DateTime can be converted
|
|
399
|
+
if (typeInfo?.type === 'DateTime') {
|
|
400
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Try to parse string
|
|
404
|
+
if (typeof item === 'string') {
|
|
405
|
+
try {
|
|
406
|
+
const { parseTemporalLiteral, isFHIRDate } = await import('../complex-types/temporal');
|
|
407
|
+
const temporal = parseTemporalLiteral('@' + item);
|
|
408
|
+
return {
|
|
409
|
+
value: [box(isFHIRDate(temporal), { type: 'Boolean', singleton: true })],
|
|
410
|
+
context
|
|
411
|
+
};
|
|
412
|
+
} catch {
|
|
413
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
export const convertsToDateFunction: FunctionDefinition & { evaluate: typeof convertsToDateEvaluator } = {
|
|
421
|
+
name: 'convertsToDate',
|
|
422
|
+
category: ['conversion', 'temporal'],
|
|
423
|
+
description: 'Returns true if the input can be converted to a Date',
|
|
424
|
+
examples: ["'2020-01-15'.convertsToDate()", "'invalid'.convertsToDate()"],
|
|
425
|
+
signatures: [
|
|
426
|
+
{
|
|
427
|
+
name: 'convertsToDate',
|
|
428
|
+
parameters: [],
|
|
429
|
+
input: { type: 'Any', singleton: true },
|
|
430
|
+
result: { type: 'Boolean', singleton: true }
|
|
431
|
+
}
|
|
432
|
+
],
|
|
433
|
+
evaluate: convertsToDateEvaluator
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// ============================================================================
|
|
437
|
+
// convertsToDateTime() function - Check if convertible to DateTime
|
|
438
|
+
// ============================================================================
|
|
439
|
+
|
|
440
|
+
export const convertsToDateTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
441
|
+
if (input.length === 0) {
|
|
442
|
+
return { value: [], context };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const boxedItem = input[0];
|
|
446
|
+
if (!boxedItem) {
|
|
447
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const item = boxedItem.value;
|
|
451
|
+
const typeInfo = boxedItem.typeInfo;
|
|
452
|
+
|
|
453
|
+
// Already a DateTime
|
|
454
|
+
if (typeInfo?.type === 'DateTime') {
|
|
455
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Date can be converted
|
|
459
|
+
if (typeInfo?.type === 'Date') {
|
|
460
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Try to parse string
|
|
464
|
+
if (typeof item === 'string') {
|
|
465
|
+
try {
|
|
466
|
+
const { parseTemporalLiteral, isFHIRDate, isFHIRDateTime } = await import('../complex-types/temporal');
|
|
467
|
+
const temporal = parseTemporalLiteral('@' + item);
|
|
468
|
+
return {
|
|
469
|
+
value: [box(isFHIRDateTime(temporal) || isFHIRDate(temporal), { type: 'Boolean', singleton: true })],
|
|
470
|
+
context
|
|
471
|
+
};
|
|
472
|
+
} catch {
|
|
473
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
export const convertsToDateTimeFunction: FunctionDefinition & { evaluate: typeof convertsToDateTimeEvaluator } = {
|
|
481
|
+
name: 'convertsToDateTime',
|
|
482
|
+
category: ['conversion', 'temporal'],
|
|
483
|
+
description: 'Returns true if the input can be converted to a DateTime',
|
|
484
|
+
examples: ["'2020-01-15T10:30:00'.convertsToDateTime()", "'invalid'.convertsToDateTime()"],
|
|
485
|
+
signatures: [
|
|
486
|
+
{
|
|
487
|
+
name: 'convertsToDateTime',
|
|
488
|
+
parameters: [],
|
|
489
|
+
input: { type: 'Any', singleton: true },
|
|
490
|
+
result: { type: 'Boolean', singleton: true }
|
|
491
|
+
}
|
|
492
|
+
],
|
|
493
|
+
evaluate: convertsToDateTimeEvaluator
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// ============================================================================
|
|
497
|
+
// convertsToTime() function - Check if convertible to Time
|
|
498
|
+
// ============================================================================
|
|
499
|
+
|
|
500
|
+
export const convertsToTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
501
|
+
if (input.length === 0) {
|
|
502
|
+
return { value: [], context };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const boxedItem = input[0];
|
|
506
|
+
if (!boxedItem) {
|
|
507
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const item = boxedItem.value;
|
|
511
|
+
const typeInfo = boxedItem.typeInfo;
|
|
512
|
+
|
|
513
|
+
// Already a Time
|
|
514
|
+
if (typeInfo?.type === 'Time') {
|
|
515
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// DateTime can be converted if it has time components
|
|
519
|
+
if (typeInfo?.type === 'DateTime') {
|
|
520
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Try to parse string
|
|
524
|
+
if (typeof item === 'string') {
|
|
525
|
+
try {
|
|
526
|
+
const { parseTemporalLiteral, isFHIRTime } = await import('../complex-types/temporal');
|
|
527
|
+
const timeString = item.startsWith('T') ? '@' + item : '@T' + item;
|
|
528
|
+
const temporal = parseTemporalLiteral(timeString);
|
|
529
|
+
return {
|
|
530
|
+
value: [box(isFHIRTime(temporal), { type: 'Boolean', singleton: true })],
|
|
531
|
+
context
|
|
532
|
+
};
|
|
533
|
+
} catch {
|
|
534
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
export const convertsToTimeFunction: FunctionDefinition & { evaluate: typeof convertsToTimeEvaluator } = {
|
|
542
|
+
name: 'convertsToTime',
|
|
543
|
+
category: ['conversion', 'temporal'],
|
|
544
|
+
description: 'Returns true if the input can be converted to a Time',
|
|
545
|
+
examples: ["'14:30:00'.convertsToTime()", "'invalid'.convertsToTime()"],
|
|
546
|
+
signatures: [
|
|
547
|
+
{
|
|
548
|
+
name: 'convertsToTime',
|
|
549
|
+
parameters: [],
|
|
550
|
+
input: { type: 'Any', singleton: true },
|
|
551
|
+
result: { type: 'Boolean', singleton: true }
|
|
552
|
+
}
|
|
553
|
+
],
|
|
554
|
+
evaluate: convertsToTimeEvaluator
|
|
555
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// timeOf() function - Extracts time component from DateTime
|
|
2
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import { createTime, isFHIRDateTime } from '../complex-types/temporal';
|
|
5
|
+
import { Errors } from '../errors';
|
|
6
|
+
|
|
7
|
+
export const timeOfEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
8
|
+
// timeOf() takes no arguments
|
|
9
|
+
if (args.length !== 0) {
|
|
10
|
+
throw Errors.wrongArgumentCount('timeOf', 0, args.length);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Empty input returns empty
|
|
14
|
+
if (input.length === 0) {
|
|
15
|
+
return { value: [], context };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Multiple items throws error
|
|
19
|
+
if (input.length > 1) {
|
|
20
|
+
throw Errors.singletonRequired('timeOf', input.length);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const boxedValue = input[0];
|
|
24
|
+
if (!boxedValue) {
|
|
25
|
+
return { value: [], context };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const value = unbox(boxedValue);
|
|
29
|
+
|
|
30
|
+
// Check if it's a DateTime
|
|
31
|
+
if (isFHIRDateTime(value)) {
|
|
32
|
+
// Check if time component is present
|
|
33
|
+
if (value.hour === undefined) {
|
|
34
|
+
// No time component present
|
|
35
|
+
return { value: [], context };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Extract time component (preserve precision)
|
|
39
|
+
const time = createTime(value.hour, value.minute, value.second, value.millisecond);
|
|
40
|
+
return {
|
|
41
|
+
value: [box(time, { type: 'Time', singleton: true })],
|
|
42
|
+
context
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Not a DateTime, return empty
|
|
47
|
+
return { value: [], context };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const timeOfFunction: FunctionDefinition & { evaluate: typeof timeOfEvaluator } = {
|
|
51
|
+
name: 'timeOf',
|
|
52
|
+
category: ['temporal'],
|
|
53
|
+
description: 'Returns the time component of a DateTime value',
|
|
54
|
+
examples: [
|
|
55
|
+
'@2012-01-01T12:30:00.timeOf()',
|
|
56
|
+
'Observation.effectiveDateTime.timeOf()'
|
|
57
|
+
],
|
|
58
|
+
signatures: [
|
|
59
|
+
{
|
|
60
|
+
name: 'timeOf',
|
|
61
|
+
input: { type: 'Any', singleton: true },
|
|
62
|
+
parameters: [],
|
|
63
|
+
result: { type: 'Time', singleton: true }
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
evaluate: timeOfEvaluator
|
|
67
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// timezoneOffsetOf() function - Extracts timezone offset component from DateTime
|
|
2
|
+
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
|
+
import { isFHIRDateTime } from '../complex-types/temporal';
|
|
5
|
+
import { Errors } from '../errors';
|
|
6
|
+
|
|
7
|
+
export const timezoneOffsetOfEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
8
|
+
// timezoneOffsetOf() takes no arguments
|
|
9
|
+
if (args.length !== 0) {
|
|
10
|
+
throw Errors.wrongArgumentCount('timezoneOffsetOf', 0, args.length);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Empty input returns empty
|
|
14
|
+
if (input.length === 0) {
|
|
15
|
+
return { value: [], context };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Multiple items throws error
|
|
19
|
+
if (input.length > 1) {
|
|
20
|
+
throw Errors.singletonRequired('timezoneOffsetOf', input.length);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const boxedValue = input[0];
|
|
24
|
+
if (!boxedValue) {
|
|
25
|
+
return { value: [], context };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const value = unbox(boxedValue);
|
|
29
|
+
|
|
30
|
+
// Check if it's a DateTime with timezone
|
|
31
|
+
if (isFHIRDateTime(value)) {
|
|
32
|
+
// Check if timezone offset is present
|
|
33
|
+
if (value.timezoneOffset === undefined) {
|
|
34
|
+
return { value: [], context };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return the timezone offset as a Decimal (hours)
|
|
38
|
+
// The timezoneOffset is stored in minutes, convert to decimal hours
|
|
39
|
+
const offsetInHours = value.timezoneOffset / 60;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
value: [box(offsetInHours, { type: 'Decimal', singleton: true })],
|
|
43
|
+
context
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Not a DateTime, return empty
|
|
48
|
+
return { value: [], context };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const timezoneOffsetOfFunction: FunctionDefinition & { evaluate: typeof timezoneOffsetOfEvaluator } = {
|
|
52
|
+
name: 'timezoneOffsetOf',
|
|
53
|
+
category: ['temporal'],
|
|
54
|
+
description: 'Returns the timezone offset component of a DateTime value as decimal hours',
|
|
55
|
+
examples: [
|
|
56
|
+
'@2012-01-01T12:30:00.000-07:00.timezoneOffsetOf()',
|
|
57
|
+
'@2012-01-01T12:30:00.000+05:30.timezoneOffsetOf()',
|
|
58
|
+
'Patient.lastUpdated.timezoneOffsetOf()'
|
|
59
|
+
],
|
|
60
|
+
signatures: [
|
|
61
|
+
{
|
|
62
|
+
name: 'timezoneOffsetOf',
|
|
63
|
+
input: { type: 'Any', singleton: true },
|
|
64
|
+
parameters: [],
|
|
65
|
+
result: { type: 'Decimal', singleton: true }
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
evaluate: timezoneOffsetOfEvaluator
|
|
69
|
+
};
|