@atomic-ehr/fhirpath
Version:
A TypeScript implementation of FHIRPath
90 lines (77 loc) • 2.9 kB
text/typescript
import type { FunctionDefinition, FunctionEvaluator } from '../types';
import { Errors } from '../errors';
import { box, unbox } from '../boxing';
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
// round() takes 0 or 1 argument (precision)
if (args.length > 1) {
throw Errors.wrongArgumentCountRange('round', 0, 1, args.length);
}
// If input is empty, return empty
if (input.length === 0) {
return { value: [], context };
}
// If input has multiple items, error
if (input.length > 1) {
throw Errors.singletonRequired('round', input.length);
}
const boxedValue = input[0];
if (!boxedValue) return { value: [], context };
const value = unbox(boxedValue);
// Must be a number
if (typeof value !== 'number') {
throw Errors.invalidOperandType('round', `${typeof value}`);
}
// Get precision if provided
let precision = 0;
if (args.length === 1) {
const precisionResult = await evaluator(args[0]!, input, context);
if (precisionResult.value.length === 0) {
return { value: [], context };
}
if (precisionResult.value.length > 1) {
throw Errors.invalidOperation('round precision must be a single value');
}
const boxedPrecision = precisionResult.value[0];
if (!boxedPrecision) {
return { value: [], context };
}
precision = unbox(boxedPrecision);
if (!Number.isInteger(precision) || precision < 0) {
throw Errors.invalidOperation('round precision must be a non-negative integer');
}
}
// Round to specified precision
// FHIRPath uses "traditional round" where 0.5 rounds away from zero
const multiplier = Math.pow(10, precision);
const scaled = value * multiplier;
let rounded: number;
if (scaled >= 0) {
rounded = Math.floor(scaled + 0.5);
} else {
rounded = Math.ceil(scaled - 0.5);
}
const result = rounded / multiplier;
// If precision is 0 and result is a whole number, return as integer
if (precision === 0 && Number.isInteger(result)) {
return { value: [box(result, { type: 'Integer', singleton: true })], context };
}
return { value: [box(result, { type: 'Decimal', singleton: true })], context };
};
export const roundFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
name: 'round',
category: ['math'],
description: 'Rounds the decimal to the nearest whole number using a traditional round (i.e. 0.5 or higher will round to 1). If specified, the precision argument determines the decimal place at which the rounding will occur.',
examples: [
'1.round()',
'3.14159.round(3)'
],
signatures: [{
name: 'round',
input: { type: 'Decimal', singleton: true },
parameters: [
{ name: 'precision', type: { type: 'Integer', singleton: true }, optional: true }
],
result: { type: 'Decimal', singleton: true }
}],
evaluate
};