@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.
Files changed (147) hide show
  1. package/README.md +716 -238
  2. package/dist/index.d.ts +226 -120
  3. package/dist/index.js +11552 -5580
  4. package/dist/index.js.map +1 -1
  5. package/package.json +12 -5
  6. package/src/analyzer/augmentor.ts +242 -0
  7. package/src/analyzer/cursor-services.ts +75 -0
  8. package/src/analyzer/scope-manager.ts +57 -0
  9. package/src/analyzer/trivia-indexer.ts +58 -0
  10. package/src/analyzer/type-compat.ts +157 -0
  11. package/src/analyzer/utils.ts +132 -0
  12. package/src/analyzer.ts +939 -1204
  13. package/src/completion-provider.ts +209 -191
  14. package/src/complex-types/quantity-value.ts +410 -0
  15. package/src/complex-types/temporal.ts +1776 -0
  16. package/src/errors.ts +25 -3
  17. package/src/index.ts +17 -104
  18. package/src/inspect.ts +4 -4
  19. package/src/{boxing.ts → interpreter/boxing.ts} +1 -1
  20. package/src/interpreter/navigator.ts +94 -0
  21. package/src/interpreter/runtime-context.ts +273 -0
  22. package/src/interpreter.ts +506 -468
  23. package/src/lexer.ts +192 -211
  24. package/src/model-provider.ts +71 -43
  25. package/src/operations/abs-function.ts +1 -1
  26. package/src/operations/aggregate-function.ts +84 -5
  27. package/src/operations/all-function.ts +4 -3
  28. package/src/operations/allFalse-function.ts +2 -1
  29. package/src/operations/allTrue-function.ts +2 -1
  30. package/src/operations/and-operator.ts +2 -1
  31. package/src/operations/anyFalse-function.ts +2 -1
  32. package/src/operations/anyTrue-function.ts +2 -1
  33. package/src/operations/as-function.ts +99 -0
  34. package/src/operations/as-operator.ts +57 -19
  35. package/src/operations/ceiling-function.ts +1 -1
  36. package/src/operations/children-function.ts +14 -5
  37. package/src/operations/combine-function.ts +6 -3
  38. package/src/operations/combine-operator.ts +6 -7
  39. package/src/operations/comparison.ts +744 -0
  40. package/src/operations/contains-function.ts +1 -1
  41. package/src/operations/contains-operator.ts +2 -1
  42. package/src/operations/convertsToBoolean-function.ts +78 -0
  43. package/src/operations/convertsToDecimal-function.ts +82 -0
  44. package/src/operations/convertsToInteger-function.ts +71 -0
  45. package/src/operations/convertsToLong-function.ts +89 -0
  46. package/src/operations/convertsToQuantity-function.ts +132 -0
  47. package/src/operations/convertsToString-function.ts +88 -0
  48. package/src/operations/count-function.ts +2 -1
  49. package/src/operations/dateOf-function.ts +69 -0
  50. package/src/operations/dayOf-function.ts +66 -0
  51. package/src/operations/decimal-boundaries.ts +133 -0
  52. package/src/operations/defineVariable-function.ts +130 -17
  53. package/src/operations/distinct-function.ts +1 -1
  54. package/src/operations/div-operator.ts +1 -1
  55. package/src/operations/divide-operator.ts +12 -7
  56. package/src/operations/dot-operator.ts +1 -1
  57. package/src/operations/empty-function.ts +30 -21
  58. package/src/operations/endsWith-function.ts +6 -1
  59. package/src/operations/equal-operator.ts +23 -32
  60. package/src/operations/equivalent-operator.ts +13 -53
  61. package/src/operations/exclude-function.ts +2 -1
  62. package/src/operations/exists-function.ts +4 -3
  63. package/src/operations/extension-function.ts +84 -0
  64. package/src/operations/first-function.ts +1 -1
  65. package/src/operations/floor-function.ts +1 -1
  66. package/src/operations/greater-operator.ts +7 -9
  67. package/src/operations/greater-or-equal-operator.ts +7 -9
  68. package/src/operations/highBoundary-function.ts +120 -0
  69. package/src/operations/hourOf-function.ts +66 -0
  70. package/src/operations/iif-function.ts +193 -8
  71. package/src/operations/implies-operator.ts +2 -1
  72. package/src/operations/in-operator.ts +2 -1
  73. package/src/operations/index.ts +43 -0
  74. package/src/operations/indexOf-function.ts +1 -1
  75. package/src/operations/intersect-function.ts +1 -1
  76. package/src/operations/is-function.ts +70 -0
  77. package/src/operations/is-operator.ts +176 -13
  78. package/src/operations/isDistinct-function.ts +2 -1
  79. package/src/operations/join-function.ts +1 -1
  80. package/src/operations/last-function.ts +1 -1
  81. package/src/operations/lastIndexOf-function.ts +85 -0
  82. package/src/operations/length-function.ts +1 -1
  83. package/src/operations/less-operator.ts +8 -9
  84. package/src/operations/less-or-equal-operator.ts +7 -9
  85. package/src/operations/less-than.ts +8 -13
  86. package/src/operations/lowBoundary-function.ts +120 -0
  87. package/src/operations/lower-function.ts +1 -1
  88. package/src/operations/matches-function.ts +86 -0
  89. package/src/operations/matchesFull-function.ts +96 -0
  90. package/src/operations/millisecondOf-function.ts +66 -0
  91. package/src/operations/minus-operator.ts +76 -4
  92. package/src/operations/minuteOf-function.ts +66 -0
  93. package/src/operations/mod-operator.ts +8 -2
  94. package/src/operations/monthOf-function.ts +66 -0
  95. package/src/operations/multiply-operator.ts +27 -3
  96. package/src/operations/not-equal-operator.ts +24 -30
  97. package/src/operations/not-equivalent-operator.ts +13 -53
  98. package/src/operations/not-function.ts +10 -3
  99. package/src/operations/ofType-function.ts +43 -12
  100. package/src/operations/or-operator.ts +2 -1
  101. package/src/operations/plus-operator.ts +71 -7
  102. package/src/operations/power-function.ts +35 -10
  103. package/src/operations/precision-function.ts +146 -0
  104. package/src/operations/repeat-function.ts +169 -0
  105. package/src/operations/replace-function.ts +1 -1
  106. package/src/operations/replaceMatches-function.ts +125 -0
  107. package/src/operations/round-function.ts +1 -1
  108. package/src/operations/secondOf-function.ts +66 -0
  109. package/src/operations/select-function.ts +66 -5
  110. package/src/operations/single-function.ts +1 -1
  111. package/src/operations/skip-function.ts +1 -1
  112. package/src/operations/split-function.ts +1 -1
  113. package/src/operations/sqrt-function.ts +15 -8
  114. package/src/operations/startsWith-function.ts +1 -1
  115. package/src/operations/subsetOf-function.ts +6 -2
  116. package/src/operations/substring-function.ts +1 -1
  117. package/src/operations/supersetOf-function.ts +6 -2
  118. package/src/operations/tail-function.ts +1 -1
  119. package/src/operations/take-function.ts +1 -1
  120. package/src/operations/temporal-functions.ts +555 -0
  121. package/src/operations/timeOf-function.ts +67 -0
  122. package/src/operations/timezoneOffsetOf-function.ts +69 -0
  123. package/src/operations/toBoolean-function.ts +27 -8
  124. package/src/operations/toChars-function.ts +56 -0
  125. package/src/operations/toDecimal-function.ts +27 -8
  126. package/src/operations/toInteger-function.ts +15 -3
  127. package/src/operations/toLong-function.ts +98 -0
  128. package/src/operations/toQuantity-function.ts +181 -0
  129. package/src/operations/toString-function.ts +78 -15
  130. package/src/operations/trace-function.ts +1 -1
  131. package/src/operations/trim-function.ts +1 -1
  132. package/src/operations/truncate-function.ts +1 -1
  133. package/src/operations/unary-minus-operator.ts +2 -2
  134. package/src/operations/unary-plus-operator.ts +1 -1
  135. package/src/operations/union-function.ts +1 -1
  136. package/src/operations/union-operator.ts +16 -26
  137. package/src/operations/upper-function.ts +1 -1
  138. package/src/operations/where-function.ts +3 -3
  139. package/src/operations/xor-operator.ts +1 -1
  140. package/src/operations/yearOf-function.ts +66 -0
  141. package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
  142. package/src/parser.ts +262 -503
  143. package/src/registry.ts +53 -42
  144. package/src/types.ts +129 -17
  145. package/src/utils/decimal.ts +76 -0
  146. package/src/utils/pprint.ts +151 -0
  147. package/src/quantity-value.ts +0 -198
@@ -1,64 +1,23 @@
1
1
  import type { OperatorDefinition } from '../types';
2
2
  import { PRECEDENCE } from '../types';
3
3
  import type { OperationEvaluator } from '../types';
4
- import { box, unbox } from '../boxing';
4
+ import { box } from '../interpreter/boxing';
5
+ import { collectionsEquivalent } from './comparison';
5
6
 
6
7
  export const evaluate: OperationEvaluator = async (input, context, left, right) => {
7
- // Empty collections are equivalent
8
- if (left.length === 0 && right.length === 0) {
9
- return { value: [box(true, { type: 'Boolean', singleton: true })], context };
10
- }
11
-
12
- // Different sizes are not equivalent
13
- if (left.length !== right.length) {
14
- return { value: [box(false, { type: 'Boolean', singleton: true })], context };
15
- }
16
-
17
- // For single items, check type-specific equivalence
18
- if (left.length === 1 && right.length === 1) {
19
- const boxedl = left[0];
20
- if (!boxedl) return { value: [], context };
21
- const l = unbox(boxedl);
22
- const boxedr = right[0];
23
- if (!boxedr) return { value: [], context };
24
- const r = unbox(boxedr);
25
-
26
- // String equivalence: case-insensitive with normalized whitespace
27
- if (typeof l === 'string' && typeof r === 'string') {
28
- const normalizeString = (s: string) => s.trim().toLowerCase().replace(/\s+/g, ' ');
29
- return { value: [box(normalizeString(l) === normalizeString(r), { type: 'Boolean', singleton: true })], context };
30
- }
31
-
32
- // Number equivalence (for Integer and Decimal)
33
- if (typeof l === 'number' && typeof r === 'number') {
34
- // For decimals with different precision, round to least precise
35
- // This is a simplified implementation
36
- return { value: [box(Math.abs(l - r) < Number.EPSILON, { type: 'Boolean', singleton: true })], context };
37
- }
38
-
39
- // Boolean equivalence
40
- if (typeof l === 'boolean' && typeof r === 'boolean') {
41
- return { value: [box(l === r, { type: 'Boolean', singleton: true })], context };
42
- }
43
-
44
- // For complex types and other cases, use equality for now
45
- // TODO: Implement full equivalence logic for Date/DateTime/Time and complex types
46
- return { value: [box(l === r, { type: 'Boolean', singleton: true })], context };
47
- }
8
+ // Use the new collectionsEquivalent function from comparison.ts
9
+ const result = collectionsEquivalent(left, right);
48
10
 
49
- // For multiple items, comparison is order-independent
50
- // Create sorted copies for comparison
51
- // TODO: Implement proper order-independent comparison with recursive equivalence
52
- const leftSorted = [...left].sort();
53
- const rightSorted = [...right].sort();
54
-
55
- for (let i = 0; i < leftSorted.length; i++) {
56
- if (leftSorted[i] !== rightSorted[i]) {
57
- return { value: [box(false, { type: 'Boolean', singleton: true })], context };
58
- }
11
+ // null result means incomparable - return empty collection
12
+ if (result === null) {
13
+ return { value: [], context };
59
14
  }
60
15
 
61
- return { value: [box(true, { type: 'Boolean', singleton: true })], context };
16
+ // Return boolean result
17
+ return {
18
+ value: [box(result, { type: 'Boolean', singleton: true })],
19
+ context
20
+ };
62
21
  };
63
22
 
64
23
  export const equivalentOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
@@ -67,6 +26,7 @@ export const equivalentOperator: OperatorDefinition & { evaluate: OperationEvalu
67
26
  category: ['equality'],
68
27
  precedence: PRECEDENCE.EQUALITY,
69
28
  associativity: 'left',
29
+ doesNotPropagateEmpty: true, // Empty collections are valid operands for equivalence
70
30
  description: 'Returns true if the collections are the same. For single items: strings are compared case-insensitive with normalized whitespace, decimals are rounded to least precision, dates with different precision return false. For collections: order-independent comparison. Empty ~ empty returns true (unlike =)',
71
31
  examples: [
72
32
  "'abc' ~ 'ABC'",
@@ -1,6 +1,6 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
2
  import { Errors } from '../errors';
3
- import { box, unbox } from '../boxing';
3
+ import { box, unbox } from '../interpreter/boxing';
4
4
 
5
5
  export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
6
6
  if (args.length !== 1) {
@@ -44,6 +44,7 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
44
44
 
45
45
  export const excludeFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
46
46
  name: 'exclude',
47
+ doesNotPropagateEmpty: true, // exclude with empty argument returns the original collection
47
48
  category: ['collection'],
48
49
  description: 'Returns the set of elements that are not in the other collection. Duplicate items will not be eliminated by this function, and order will be preserved.',
49
50
  examples: [
@@ -1,8 +1,8 @@
1
1
  import type { FunctionDefinition } from '../types';
2
2
  import { Errors } from '../errors';
3
- import { RuntimeContextManager } from '../interpreter';
3
+ import { RuntimeContextManager } from '../interpreter/runtime-context';
4
4
  import { type FunctionEvaluator } from '../types';
5
- import { box, unbox } from '../boxing';
5
+ import { box, unbox } from '../interpreter/boxing';
6
6
 
7
7
  export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
8
8
  // No arguments - just check if input is not empty
@@ -42,6 +42,7 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
42
42
 
43
43
  export const existsFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
44
44
  name: 'exists',
45
+ doesNotPropagateEmpty: true,
45
46
  category: ['collection', 'logical'],
46
47
  description: 'Returns true if the collection has any items, or if any item satisfies the condition',
47
48
  examples: ['Patient.name.exists()', 'Patient.name.exists(use = "official")'],
@@ -55,4 +56,4 @@ export const existsFunction: FunctionDefinition & { evaluate: FunctionEvaluator
55
56
  result: { type: 'Boolean', singleton: true },
56
57
  }],
57
58
  evaluate
58
- };
59
+ };
@@ -0,0 +1,84 @@
1
+ import type { FunctionDefinition, FunctionEvaluator, ASTNode, RuntimeContext, NodeEvaluator } from '../types';
2
+ import type { FHIRPathValue } from '../interpreter/boxing';
3
+ import { box, unbox } from '../interpreter/boxing';
4
+ import { Errors } from '../errors';
5
+
6
+ const extensionEvaluator: FunctionEvaluator = async (
7
+ input: FHIRPathValue[],
8
+ context: RuntimeContext,
9
+ args: ASTNode[],
10
+ evaluator: NodeEvaluator
11
+ ) => {
12
+ // extension() function takes one argument - the URL to match
13
+ if (args.length !== 1) {
14
+ throw Errors.invalidOperation('extension() requires exactly one argument');
15
+ }
16
+
17
+ // Evaluate the URL argument
18
+ const urlResult = await evaluator(args[0]!, input, context);
19
+ if (urlResult.value.length !== 1) {
20
+ throw Errors.invalidOperation('extension() URL argument must be a single value');
21
+ }
22
+
23
+ const urlToMatch = unbox(urlResult.value[0]);
24
+ if (typeof urlToMatch !== 'string') {
25
+ throw Errors.invalidOperation('extension() URL argument must be a string');
26
+ }
27
+
28
+ const results: FHIRPathValue[] = [];
29
+
30
+ // For each input item, look for extensions
31
+ for (const boxedItem of input) {
32
+ const item = unbox(boxedItem);
33
+
34
+ // First check if this is a primitive value with extensions in primitiveElement
35
+ if (boxedItem.primitiveElement?.extension) {
36
+ const extensions = boxedItem.primitiveElement.extension;
37
+ for (const ext of extensions) {
38
+ if (ext && typeof ext === 'object' && 'url' in ext && ext.url === urlToMatch) {
39
+ const boxedExt = box(ext, { type: 'Extension' as any, singleton: true });
40
+ results.push(boxedExt);
41
+ }
42
+ }
43
+ }
44
+
45
+ // Then check for direct extension property on complex types
46
+ if (item && typeof item === 'object' && 'extension' in item) {
47
+ const extensions = (item as any).extension;
48
+ if (Array.isArray(extensions)) {
49
+ for (const ext of extensions) {
50
+ if (ext && typeof ext === 'object' && 'url' in ext && ext.url === urlToMatch) {
51
+ const boxedExt = box(ext, { type: 'Extension' as any, singleton: true });
52
+ results.push(boxedExt);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ return { value: results, context };
60
+ };
61
+
62
+ export const extensionFunction: FunctionDefinition & { evaluate: typeof extensionEvaluator } = {
63
+ name: 'extension',
64
+ category: ['navigation'],
65
+ description: 'Returns extensions matching the specified URL',
66
+ examples: [
67
+ "Patient.birthDate.extension('http://hl7.org/fhir/StructureDefinition/patient-birthTime')",
68
+ "Observation.extension('http://example.com/fhir/StructureDefinition/patient-age')"
69
+ ],
70
+ signatures: [
71
+ {
72
+ name: 'extension-by-url',
73
+ parameters: [{
74
+ name: 'url',
75
+ type: { type: 'String', singleton: true },
76
+ expression: true
77
+ }],
78
+ input: { type: 'Any', singleton: false },
79
+ result: { type: 'Extension' as any, singleton: false }
80
+ }
81
+ ],
82
+ doesNotPropagateEmpty: false,
83
+ evaluate: extensionEvaluator
84
+ };
@@ -1,6 +1,6 @@
1
1
  import type { FunctionDefinition } from '../types';
2
2
  import type { FunctionEvaluator } from '../types';
3
- import { box, unbox } from '../boxing';
3
+ import { box, unbox } from '../interpreter/boxing';
4
4
 
5
5
  export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
6
6
  if (input.length > 0) {
@@ -1,6 +1,6 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
2
  import { Errors } from '../errors';
3
- import { box, unbox } from '../boxing';
3
+ import { box, unbox } from '../interpreter/boxing';
4
4
 
5
5
  export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
6
6
  // floor() takes no arguments
@@ -1,9 +1,8 @@
1
1
  import type { OperatorDefinition } from '../types';
2
2
  import { PRECEDENCE } from '../types';
3
3
  import type { OperationEvaluator } from '../types';
4
- import { compareQuantities } from '../quantity-value';
5
- import type { QuantityValue } from '../quantity-value';
6
- import { box, unbox } from '../boxing';
4
+ import { box, unbox } from '../interpreter/boxing';
5
+ import { compare } from './comparison';
7
6
 
8
7
  export const evaluate: OperationEvaluator = async (input, context, left, right) => {
9
8
  if (left.length === 0 || right.length === 0) {
@@ -17,14 +16,13 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
17
16
  if (!boxedr) return { value: [], context };
18
17
  const r = unbox(boxedr);
19
18
 
20
- // Check if both are quantities
21
- if (l && typeof l === 'object' && 'unit' in l &&
22
- r && typeof r === 'object' && 'unit' in r) {
23
- const result = compareQuantities(l as QuantityValue, r as QuantityValue);
24
- return { value: result !== null ? [box(result > 0, { type: 'Boolean', singleton: true })] : [], context };
19
+ const comparisonResult = compare(l, r);
20
+
21
+ if (comparisonResult.kind === 'incomparable') {
22
+ return { value: [], context };
25
23
  }
26
24
 
27
- return { value: [box((l as any) > (r as any), { type: 'Boolean', singleton: true })], context };
25
+ return { value: [box(comparisonResult.kind === 'greater', { type: 'Boolean', singleton: true })], context };
28
26
  };
29
27
 
30
28
  export const greaterOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
@@ -1,9 +1,8 @@
1
1
  import type { OperatorDefinition } from '../types';
2
2
  import { PRECEDENCE } from '../types';
3
3
  import type { OperationEvaluator } from '../types';
4
- import { compareQuantities } from '../quantity-value';
5
- import type { QuantityValue } from '../quantity-value';
6
- import { box, unbox } from '../boxing';
4
+ import { box, unbox } from '../interpreter/boxing';
5
+ import { compare } from './comparison';
7
6
 
8
7
  export const evaluate: OperationEvaluator = async (input, context, left, right) => {
9
8
  if (left.length === 0 || right.length === 0) {
@@ -17,14 +16,13 @@ export const evaluate: OperationEvaluator = async (input, context, left, right)
17
16
  if (!boxedr) return { value: [], context };
18
17
  const r = unbox(boxedr);
19
18
 
20
- // Check if both are quantities
21
- if (l && typeof l === 'object' && 'unit' in l &&
22
- r && typeof r === 'object' && 'unit' in r) {
23
- const result = compareQuantities(l as QuantityValue, r as QuantityValue);
24
- return { value: result !== null ? [box(result >= 0, { type: 'Boolean', singleton: true })] : [], context };
19
+ const comparisonResult = compare(l, r);
20
+
21
+ if (comparisonResult.kind === 'incomparable') {
22
+ return { value: [], context };
25
23
  }
26
24
 
27
- return { value: [box((l as any) >= (r as any), { type: 'Boolean', singleton: true })], context };
25
+ return { value: [box(comparisonResult.kind === 'greater' || comparisonResult.kind === 'equal', { type: 'Boolean', singleton: true })], context };
28
26
  };
29
27
 
30
28
  export const greaterOrEqualOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
@@ -0,0 +1,120 @@
1
+ // highBoundary() function - Returns the greatest possible value to the specified precision
2
+ import type { FunctionDefinition, FunctionEvaluator } from '../types';
3
+ import { box, unbox } from '../interpreter/boxing';
4
+ import {
5
+ isFHIRDate, isFHIRDateTime, isFHIRTime,
6
+ getDateHighBoundary, getDateTimeHighBoundary, getTimeHighBoundary
7
+ } from '../complex-types/temporal';
8
+ import { getDecimalHighBoundary } from './decimal-boundaries';
9
+ import { Errors } from '../errors';
10
+
11
+ export const highBoundaryEvaluator: FunctionEvaluator = async (input, context, args, evaluator) => {
12
+ // highBoundary() takes optional precision parameter
13
+ if (args.length > 1) {
14
+ throw Errors.wrongArgumentCountRange('highBoundary', 0, 1, args.length);
15
+ }
16
+
17
+ // Empty input returns empty
18
+ if (input.length === 0) {
19
+ return { value: [], context };
20
+ }
21
+
22
+ // Multiple items throws error
23
+ if (input.length > 1) {
24
+ throw Errors.singletonRequired('highBoundary', input.length);
25
+ }
26
+
27
+ const boxedValue = input[0];
28
+ if (!boxedValue) {
29
+ return { value: [], context };
30
+ }
31
+
32
+ const value = unbox(boxedValue);
33
+
34
+ // Get precision if provided
35
+ let precision: number | undefined;
36
+ if (args.length === 1) {
37
+ const precisionResult = await evaluator(args[0]!, input, context);
38
+ const precisionArg = precisionResult.value;
39
+ if (precisionArg.length === 0) {
40
+ return { value: [], context };
41
+ }
42
+ if (precisionArg.length > 1) {
43
+ throw Errors.singletonRequired('highBoundary precision', precisionArg.length);
44
+ }
45
+ const precisionValue = unbox(precisionArg[0]!);
46
+ if (typeof precisionValue !== 'number' || !Number.isInteger(precisionValue)) {
47
+ throw Errors.invalidOperandType('highBoundary precision', typeof precisionValue);
48
+ }
49
+ precision = precisionValue;
50
+ }
51
+
52
+ // Handle Date
53
+ if (isFHIRDate(value)) {
54
+ const result = getDateHighBoundary(value, precision);
55
+ if (!result) {
56
+ return { value: [], context };
57
+ }
58
+ return { value: [box(result, { type: 'Date', singleton: true })], context };
59
+ }
60
+
61
+ // Handle DateTime
62
+ if (isFHIRDateTime(value)) {
63
+ const result = getDateTimeHighBoundary(value, precision);
64
+ if (!result) {
65
+ return { value: [], context };
66
+ }
67
+ return { value: [box(result, { type: 'DateTime', singleton: true })], context };
68
+ }
69
+
70
+ // Handle Time
71
+ if (isFHIRTime(value)) {
72
+ const result = getTimeHighBoundary(value, precision);
73
+ if (!result) {
74
+ return { value: [], context };
75
+ }
76
+ return { value: [box(result, { type: 'Time', singleton: true })], context };
77
+ }
78
+
79
+ // For Decimal/Integer types
80
+ if (typeof value === 'number') {
81
+ const result = getDecimalHighBoundary(value, precision);
82
+ if (result === null) {
83
+ return { value: [], context };
84
+ }
85
+ // Determine the result type based on whether it's an integer or decimal
86
+ const isInteger = Number.isInteger(result);
87
+ return {
88
+ value: [box(result, { type: isInteger ? 'Integer' : 'Decimal', singleton: true })],
89
+ context
90
+ };
91
+ }
92
+
93
+ // Invalid type returns empty
94
+ return { value: [], context };
95
+ };
96
+
97
+ export const highBoundaryFunction: FunctionDefinition & { evaluate: typeof highBoundaryEvaluator } = {
98
+ name: 'highBoundary',
99
+ category: ['utility'],
100
+ description: 'Returns the greatest possible value of the input to the specified precision',
101
+ examples: [
102
+ '@2014.highBoundary(6)',
103
+ '@2014-01-01T08.highBoundary(17)',
104
+ '@T10:30.highBoundary(9)',
105
+ '1.587.highBoundary()',
106
+ '1.587.highBoundary(2)',
107
+ '1.highBoundary(0)'
108
+ ],
109
+ signatures: [
110
+ {
111
+ name: 'highBoundary',
112
+ input: { type: 'Any' as const, singleton: true },
113
+ parameters: [
114
+ { name: 'precision', type: { type: 'Integer' as const, singleton: true }, optional: true }
115
+ ],
116
+ result: { type: 'Any' as const, singleton: true }
117
+ }
118
+ ],
119
+ evaluate: highBoundaryEvaluator
120
+ };
@@ -0,0 +1,66 @@
1
+ // hourOf() function - Extracts hour component from Time or DateTime
2
+ import type { FunctionDefinition, FunctionEvaluator } from '../types';
3
+ import { box, unbox } from '../interpreter/boxing';
4
+ import { isFHIRTime, isFHIRDateTime } from '../complex-types/temporal';
5
+ import { Errors } from '../errors';
6
+
7
+ export const hourOfEvaluator: FunctionEvaluator = async (input, context, args) => {
8
+ // hourOf() takes no arguments
9
+ if (args.length !== 0) {
10
+ throw Errors.wrongArgumentCount('hourOf', 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('hourOf', 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 Time or DateTime
31
+ if (isFHIRTime(value) || isFHIRDateTime(value)) {
32
+ // Check if hour component is present
33
+ if (value.hour === undefined) {
34
+ return { value: [], context };
35
+ }
36
+
37
+ // Return the hour as an Integer (0-23)
38
+ return {
39
+ value: [box(value.hour, { type: 'Integer', singleton: true })],
40
+ context
41
+ };
42
+ }
43
+
44
+ // Not a Time or DateTime, return empty
45
+ return { value: [], context };
46
+ };
47
+
48
+ export const hourOfFunction: FunctionDefinition & { evaluate: typeof hourOfEvaluator } = {
49
+ name: 'hourOf',
50
+ category: ['temporal'],
51
+ description: 'Returns the hour component of a Time or DateTime value (0-23)',
52
+ examples: [
53
+ '@T10:30:00.hourOf()',
54
+ '@2014-01-05T10:30:00.hourOf()',
55
+ 'Patient.birthDate.hourOf()'
56
+ ],
57
+ signatures: [
58
+ {
59
+ name: 'hourOf',
60
+ input: { type: 'Any', singleton: true },
61
+ parameters: [],
62
+ result: { type: 'Integer', singleton: true }
63
+ }
64
+ ],
65
+ evaluate: hourOfEvaluator
66
+ };