From 939349efbad28b7104bf9b6896e92e852d1ae003 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Wed, 12 Aug 2026 14:43:15 -0400 Subject: [PATCH 01/19] WIP of improved CQL Decimal --- src/datatypes/datatypes.ts | 1 + src/datatypes/decimal.ts | 216 ++++++++++++++++++++++++ src/datatypes/interval.ts | 16 +- src/datatypes/quantity.ts | 41 +++-- src/datatypes/uncertainty.ts | 2 + src/elm/aggregate.ts | 95 ++++++++--- src/elm/arithmetic.ts | 148 +++++++++++++---- src/elm/interval.ts | 218 ++++++++++++++----------- src/elm/literal.ts | 3 +- src/elm/quantity.ts | 4 +- src/elm/type.ts | 38 +++-- src/runtime/context.ts | 8 +- src/util/comparison.ts | 14 +- src/util/math.ts | 100 ++++++------ src/util/units.ts | 11 +- test/datatypes/decimal-test.ts | 41 +++++ test/datatypes/interval-data.ts | 3 +- test/datatypes/interval-test.ts | 79 ++++----- test/elm/aggregate/aggregate-test.ts | 49 +++--- test/elm/arithmetic/arithmetic-test.ts | 123 +++++++------- test/elm/convert/convert-test.ts | 35 ++-- test/elm/datetime/datetime-test.ts | 7 +- test/elm/instance/instance-test.ts | 6 +- test/elm/interval/interval-test.ts | 25 +-- test/elm/literal/literal-test.ts | 7 +- test/elm/message/message-test.ts | 5 +- test/elm/parameters/parameters-test.ts | 15 +- test/elm/quantity/quantity-test.ts | 3 +- test/elm/query/query-test.ts | 5 +- test/spec-tests/spec-test.ts | 3 + test/util/math-test.ts | 21 +-- test/util/units-test.ts | 31 ++-- 32 files changed, 926 insertions(+), 447 deletions(-) create mode 100644 src/datatypes/decimal.ts create mode 100644 test/datatypes/decimal-test.ts diff --git a/src/datatypes/datatypes.ts b/src/datatypes/datatypes.ts index ec689cc88..e36a6000c 100644 --- a/src/datatypes/datatypes.ts +++ b/src/datatypes/datatypes.ts @@ -1,4 +1,5 @@ export * from './bigint'; +export * from './decimal'; export * from './logic'; export * from './clinical'; export * from './uncertainty'; diff --git a/src/datatypes/decimal.ts b/src/datatypes/decimal.ts new file mode 100644 index 000000000..815b9fb5a --- /dev/null +++ b/src/datatypes/decimal.ts @@ -0,0 +1,216 @@ + + +export type DecimalInput = Decimal | string | number | bigint; + +export type DecimalRoundingMode = 'down' | 'half-up' | 'half-even' | 'half-ceil' | 'ceil' | 'floor'; + +const MIN_FLOAT_PRECISION_VALUE = Math.pow(10, -8); + +export class Decimal { + public readonly value: number; + + private constructor(value: DecimalInput) { + const numericValue = toNumber(value); + if (!Number.isFinite(numericValue)) { + throw new Error('Cannot create a decimal with a non-finite value'); + } + this.value = numericValue; + } + + static from(value: DecimalInput) { + return value instanceof Decimal ? value : new Decimal(value); + } + + get isDecimal() { + return true; + } + + add(other: DecimalInput) { + return new Decimal(this.value + toNumber(other)); + } + + subtract(other: DecimalInput) { + return new Decimal(this.value - toNumber(other)); + } + + multiplyBy(other: DecimalInput) { + return new Decimal(this.value * toNumber(other)); + } + + divideBy(other: DecimalInput) { + const divisor = toNumber(other); + if (divisor === 0) { + throw new RangeError('Cannot divide a decimal by zero'); + } + return new Decimal(this.value / divisor); + } + + modulo(other: DecimalInput) { + const divisor = toNumber(other); + if (divisor === 0) { + throw new RangeError('Cannot calculate decimal modulo by zero'); + } + return new Decimal(this.value % divisor); + } + + compareTo(other: DecimalInput) { + const otherValue = toNumber(other); + return this.value - otherValue; + } + + greaterThan(other: DecimalInput) { + return this.compareTo(other) > 0; + } + + greaterThanOrEquals(other: DecimalInput) { + return this.compareTo(other) >= 0; + } + + lessThan(other: DecimalInput) { + return this.compareTo(other) < 0; + } + + lessThanOrEquals(other: DecimalInput) { + return this.compareTo(other) <= 0; + } + + equals(other: DecimalInput) { + return this.compareTo(other) === 0; + } + + successor() { + return new Decimal(this.value + MIN_FLOAT_PRECISION_VALUE); + } + + predecessor() { + return new Decimal(this.value - MIN_FLOAT_PRECISION_VALUE); + } + + negate() { + return new Decimal(-this.value); + } + + abs() { + return new Decimal(Math.abs(this.value)); + } + + truncate() { + return Math.trunc(this.value); + } + + ceil() { + return Math.ceil(this.value); + } + + floor() { + return Math.floor(this.value); + } + + isInteger() { + return Number.isInteger(this.value); + } + + round(scale = 0) { + return this.setScale(scale, 'half-ceil'); + } + + power(exponent: DecimalInput) { + return new Decimal(Math.pow(this.value, toNumber(exponent))); + } + + sqrt() { + return new Decimal(Math.sqrt(this.value)); + } + + ln() { + return new Decimal(Math.log(this.value)); + } + + exp() { + return new Decimal(Math.exp(this.value)); + } + + log(base: DecimalInput) { + return this.ln().divideBy(Decimal.from(base).ln()); + } + + /** + * Return a value at the requested number of digits after the decimal point. + * `down` truncates toward zero, matching the current ToDecimal behavior. + */ + setScale(scale: number, roundingMode: DecimalRoundingMode = 'down') { + if (!Number.isInteger(scale) || scale < 0) { + throw new RangeError('Decimal scale must be a non-negative integer'); + } + + const factor = Math.pow(10, scale); + return new Decimal(round(this.value * factor, roundingMode) / factor); + } + + toInteger() { + return this.truncate(); + } + + toNumber() { + return this.value; + } + + toLong() { + // TODO: this is wrong + return BigInt(this.toNumber()); + } + + toString() { + return this.value.toString(); + } + + toJSON() { + return this.toString(); + } +} + +export const MAX_DECIMAL_STRING = "99999999999999999999.99999999"; +export const MIN_DECIMAL_STRING = "-99999999999999999999.99999999"; + +export const MAX_DECIMAL_VALUE = Decimal.from(MAX_DECIMAL_STRING); +export const MIN_DECIMAL_VALUE = Decimal.from(MIN_DECIMAL_STRING); + +function toNumber(value: DecimalInput) { + if (value instanceof Decimal) { + return value.value; + } + if (typeof value === 'string' && value.trim() === '') { + // Number() and Number('') return 0 instead of NaN, so catch that case + return NaN; + } + return Number(value); +} + +function round(value: number, mode: DecimalRoundingMode) { + switch (mode) { + case 'down': + return Math.trunc(value); + case 'half-up': + return value < 0 ? -Math.round(-value) : Math.round(value); + case 'half-even': + return roundHalfEven(value); + case 'half-ceil': + return Math.round(value); + case 'ceil': + return Math.ceil(value); + case 'floor': + return Math.floor(value); + } +} + +function roundHalfEven(value: number) { + const lower = Math.floor(value); + const fraction = value - lower; + if (fraction < 0.5) { + return lower; + } + if (fraction > 0.5) { + return lower + 1; + } + return lower % 2 === 0 ? lower : lower + 1; +} diff --git a/src/datatypes/interval.ts b/src/datatypes/interval.ts index c4e3fa45f..064e3460f 100644 --- a/src/datatypes/interval.ts +++ b/src/datatypes/interval.ts @@ -22,6 +22,7 @@ import { } from '../util/elmTypes'; import { MIN_FLOAT_VALUE } from '../util/limits'; import { Quantity } from './quantity'; +import { Decimal, MIN_DECIMAL_VALUE } from './decimal'; export class Interval { constructor( @@ -40,9 +41,11 @@ export class Interval { } if (point != null) { if (typeof point === 'number') { - this.pointType = Number.isInteger(point) ? ELM_INTEGER_TYPE : ELM_DECIMAL_TYPE; + this.pointType = ELM_INTEGER_TYPE; } else if (typeof point === 'bigint') { this.pointType = ELM_LONG_TYPE; + } else if (point.isDecimal) { + this.pointType = ELM_DECIMAL_TYPE; } else if (point.isTime && point.isTime()) { this.pointType = ELM_TIME_TYPE; } else if (point.isDate) { @@ -704,10 +707,11 @@ export class Interval { let minValue = minValueForType(this.pointType, getQuantityInstanceForMinMax(this)); // due to floating point issues in JS, we must use 0.0 for Decimal/Quantity instead of min - if (minValue === MIN_FLOAT_VALUE) { - minValue = 0.0; + // TODO: remove this when changing to decimal.js + if (minValue === MIN_DECIMAL_VALUE) { + minValue = Decimal.from(0.0); } else if ((minValue as any)?.isQuantity) { - (minValue as Quantity).value = 0.0; + minValue = new Quantity(0.0, (minValue as Quantity)?.unit); } if (minValue != null) { @@ -776,7 +780,9 @@ export class Interval { toString() { const start = this.lowClosed ? '[' : '('; const end = this.highClosed ? ']' : ')'; - return start + this.low.toString() + ', ' + this.high.toString() + end; + const lowString = this.low == null ? "null" : this.low.toString(); + const highString = this.high == null ? "null" : this.high.toString(); + return start + lowString + ', ' + highString + end; } } diff --git a/src/datatypes/quantity.ts b/src/datatypes/quantity.ts index b865d2147..ec98aba2b 100644 --- a/src/datatypes/quantity.ts +++ b/src/datatypes/quantity.ts @@ -1,5 +1,6 @@ import { ELM_DECIMAL_TYPE } from '../util/elmTypes'; import { decimalAdjust, add, subtract, isValidDecimal, overflowsOrUnderflows } from '../util/math'; +import { Decimal } from './decimal'; import { checkUnit, convertUnit, @@ -9,13 +10,17 @@ import { } from '../util/units'; export class Quantity { + public readonly value: Decimal; + constructor( - public value: any, + value: Decimal | string | number | bigint, public unit?: any ) { - if (this.value == null || isNaN(this.value)) { + if (value == null || typeof value === 'number' && isNaN(value)) { throw new Error('Cannot create a quantity with an undefined value'); - } else if (!isValidDecimal(this.value)) { + } + this.value = Decimal.from(value); + if (!isValidDecimal(this.value)) { throw new Error('Cannot create a quantity with an invalid decimal value'); } @@ -46,7 +51,7 @@ export class Quantity { if (otherVal == null) { return null; } else { - return this.value <= otherVal; + return this.value.lessThanOrEquals(otherVal); } } } @@ -57,7 +62,7 @@ export class Quantity { if (otherVal == null) { return null; } else { - return this.value >= otherVal; + return this.value.greaterThanOrEquals(otherVal); } } } @@ -68,7 +73,7 @@ export class Quantity { if (otherVal == null) { return null; } else { - return this.value > otherVal; + return this.value.greaterThan(otherVal); } } } @@ -79,7 +84,7 @@ export class Quantity { if (otherVal == null) { return null; } else { - return this.value < otherVal; + return this.value.lessThan(otherVal); } } } @@ -95,7 +100,7 @@ export class Quantity { if (otherVal == null) { return null; } else { - return decimalAdjust('round', this.value, -8) === otherVal; + return this.value.round(8).equals(Decimal.from(otherVal)); } } } @@ -108,7 +113,7 @@ export class Quantity { } dividedBy(other: any) { - if (other == null || other === 0 || other.value === 0) { + if (other == null || other === 0 || (other.value != null && Decimal.from(other.value).equals(0))) { return null; } else if (!other.isQuantity) { // convert it to a quantity w/ unit 1 @@ -116,19 +121,19 @@ export class Quantity { } const [val1, unit1, val2, unit2] = normalizeUnitsWhenPossible( - this.value, + this.value.toNumber(), this.unit, - other.value, + Decimal.from(other.value).toNumber(), other.unit ); - const resultValue = val1 / val2; + const resultValue = Decimal.from(val1 / val2); const resultUnit = getQuotientOfUnits(unit1, unit2); // Check for invalid unit or value if (resultUnit == null || overflowsOrUnderflows(resultValue, ELM_DECIMAL_TYPE)) { return null; } - return new Quantity(decimalAdjust('round', resultValue, -8), resultUnit); + return new Quantity(resultValue.round(8), resultUnit); } multiplyBy(other: any) { @@ -140,26 +145,26 @@ export class Quantity { } const [val1, unit1, val2, unit2] = normalizeUnitsWhenPossible( - this.value, + this.value.toNumber(), this.unit, - other.value, + Decimal.from(other.value).toNumber(), other.unit ); - const resultValue = val1 * val2; + const resultValue = Decimal.from(val1 * val2); const resultUnit = getProductOfUnits(unit1, unit2); // Check for invalid unit or value if (resultUnit == null || overflowsOrUnderflows(resultValue, ELM_DECIMAL_TYPE)) { return null; } - return new Quantity(decimalAdjust('round', resultValue, -8), resultUnit); + return new Quantity(resultValue.round(8), resultUnit); } } export function parseQuantity(str: string) { const components = /([+|-]?\d+\.?\d*)\s*('(.+)')?/.exec(str); if (components != null && components[1] != null) { - const value = parseFloat(components[1]); + const value = Decimal.from(components[1]); if (!isValidDecimal(value)) { return null; } diff --git a/src/datatypes/uncertainty.ts b/src/datatypes/uncertainty.ts index 44de60b3d..e986a581f 100644 --- a/src/datatypes/uncertainty.ts +++ b/src/datatypes/uncertainty.ts @@ -143,6 +143,8 @@ export class Uncertainty { if (typeof a.before === 'function') { return a.before(b, precision); + } else if (a.isDecimal) { + return a.lessThan(b); } else { return a < b; } diff --git a/src/elm/aggregate.ts b/src/elm/aggregate.ts index 72aa736a0..08cd7dbdc 100644 --- a/src/elm/aggregate.ts +++ b/src/elm/aggregate.ts @@ -1,6 +1,7 @@ import { Expression } from './expression'; import { typeIsArray, allTrue, anyTrue, removeNulls, numerical_sort } from '../util/util'; import { Quantity } from '../datatypes/datatypes'; +import { Decimal } from '../datatypes/decimal'; import { Context } from '../runtime/context'; import { Exception } from '../datatypes/exception'; import { greaterThan, lessThan } from '../util/comparison'; @@ -17,6 +18,32 @@ class AggregateExpression extends Expression { } } +function hasDecimals(values: any[]) { + return values.some(value => value && value.isDecimal); +} + +function isDecimal(value: any): value is Decimal { + return value != null && value.isDecimal; +} + +function numberValue(value: any) { + return value && value.isDecimal ? value.toNumber() : value; +} + +function sumDecimals(values: Decimal[]) { + return values.reduce((sum, value) => sum.add(value)).setScale(8, 'half-up'); +} + +function productDecimals(values: Decimal[]) { + return values.reduce((product, value) => product.multiplyBy(value)).setScale(8, 'half-up'); +} + +function decimalResult(value: number, values: any[], resultTypeName?: string) { + return hasDecimals(values) || resultTypeName === ELM_DECIMAL_TYPE + ? Decimal.from(value).setScale(8, 'half-up') + : value; +} + export class Count extends AggregateExpression { constructor(json: any) { super(json); @@ -53,11 +80,12 @@ export class Sum extends AggregateExpression { } if (hasOnlyQuantities(items)) { - const values = getValuesFromQuantities(items); - const sum = values.reduce((x, y) => x + y); + const sum = sumDecimals(getValuesFromQuantities(items)); return overflowsOrUnderflows(sum, ELM_DECIMAL_TYPE) ? null : new Quantity(sum, items[0].unit); } else { - const sum = items.reduce((x: any, y: any) => x + y); + const sum = hasDecimals(items) + ? sumDecimals(items.map(Decimal.from)) + : items.reduce((x: any, y: any) => x + y); return overflowsOrUnderflows(sum, this.resultTypeName) ? null : sum; } } @@ -153,12 +181,14 @@ export class Avg extends AggregateExpression { } if (hasOnlyQuantities(items)) { - const values = getValuesFromQuantities(items); - const sum = values.reduce((x, y) => x + y); - return new Quantity(sum / values.length, items[0].unit); + const sum = sumDecimals(getValuesFromQuantities(items)); + return new Quantity(sum.divideBy(items.length).setScale(8, 'half-up'), items[0].unit); } else { + if (hasDecimals(items)) { + return sumDecimals(items.map(Decimal.from)).divideBy(items.length).setScale(8, 'half-up'); + } const sum = items.reduce((x: number, y: number) => x + y); - return sum / items.length; + return decimalResult(sum / items.length, items, this.resultTypeName); } } } @@ -184,11 +214,12 @@ export class Median extends AggregateExpression { } if (!hasOnlyQuantities(items)) { - return medianOfNumbers(items); + return hasDecimals(items) + ? medianOfDecimals(items.map(Decimal.from)) + : decimalResult(medianOfNumbers(items), items, this.resultTypeName); } - const values = getValuesFromQuantities(items); - const median = medianOfNumbers(values); + const median = medianOfDecimals(getValuesFromQuantities(items)); return new Quantity(median, items[0].unit); } } @@ -218,9 +249,10 @@ export class Mode extends AggregateExpression { const values = getValuesFromQuantities(filtered); let mode = this.mode(values); if (mode.length === 1) { - mode = mode[0]; + return new Quantity(mode[0], items[0].unit); + } else { + return mode.map(m => new Quantity(m, items[0].unit)); } - return new Quantity(mode, items[0].unit); } else { const mode = this.mode(filtered); if (mode.length === 1) { @@ -278,11 +310,14 @@ export class StdDev extends AggregateExpression { } if (hasOnlyQuantities(items)) { - const values = getValuesFromQuantities(items); + const values = getValuesFromQuantities(items).map(numberValue); const stdDev = this.standardDeviation(values); return new Quantity(stdDev, items[0].unit); } else { - return this.standardDeviation(items); + const standardDeviation = this.standardDeviation(items.map(numberValue)); + return standardDeviation == null + ? null + : decimalResult(standardDeviation, items, this.resultTypeName); } } @@ -336,15 +371,17 @@ export class Product extends AggregateExpression { } if (hasOnlyQuantities(items)) { - const values = getValuesFromQuantities(items); - const product = values.reduce((x, y) => x * y); + const product = productDecimals(getValuesFromQuantities(items)); // Units are not multiplied for the geometric product return overflowsOrUnderflows(product, ELM_DECIMAL_TYPE) ? null : new Quantity(product, items[0].unit); } else { - const product = items.reduce((x: any, y: any) => x * y); - return overflowsOrUnderflows(product, this.resultTypeName) ? null : product; + const product = hasDecimals(items) + ? productDecimals(items.map(Decimal.from)) + : items.reduce((x: number, y: number) => x * y); + const result = isDecimal(product) ? product : decimalResult(product, items, this.resultTypeName); + return overflowsOrUnderflows(result, this.resultTypeName) ? null : result; } } } @@ -371,13 +408,17 @@ export class GeometricMean extends AggregateExpression { } if (hasOnlyQuantities(items)) { - const values = getValuesFromQuantities(items); - const product = values.reduce((x, y) => x * y); - const geoMean = Math.pow(product, 1.0 / items.length); + const product = productDecimals(getValuesFromQuantities(items)); + const geoMean = product.power(1.0 / items.length).setScale(8, 'half-up'); return new Quantity(geoMean, items[0].unit); } else { + if (hasDecimals(items)) { + return productDecimals(items.map(Decimal.from)) + .power(1.0 / items.length) + .setScale(8, 'half-up'); + } const product = items.reduce((x: number, y: number) => x * y); - return Math.pow(product, 1.0 / items.length); + return decimalResult(Math.pow(product, 1.0 / items.length), items, this.resultTypeName); } } } @@ -444,7 +485,7 @@ function processQuantities(values: any[]) { } } -function getValuesFromQuantities(quantities: Quantity[]): number[] { +function getValuesFromQuantities(quantities: Quantity[]): Decimal[] { return quantities.map(quantity => quantity.value); } @@ -471,3 +512,11 @@ function medianOfNumbers(numbers: number[]) { return (items[items.length / 2 - 1] + items[items.length / 2]) / 2; } } + +function medianOfDecimals(decimals: Decimal[]) { + const items = [...decimals].sort((a, b) => a.compareTo(b)); + const middle = Math.floor(items.length / 2); + return items.length % 2 === 1 + ? items[middle] + : items[middle - 1].add(items[middle]).divideBy(2).setScale(8, 'half-up'); +} diff --git a/src/elm/arithmetic.ts b/src/elm/arithmetic.ts index d777121e0..c63fa18f0 100644 --- a/src/elm/arithmetic.ts +++ b/src/elm/arithmetic.ts @@ -13,6 +13,7 @@ import { MIN_DATETIME_VALUE, MIN_TIME_VALUE } from '../datatypes/datetime'; +import { Decimal, MAX_DECIMAL_VALUE, MIN_DECIMAL_VALUE } from '../datatypes/decimal'; import { ELM_DECIMAL_TYPE, ELM_DATETIME_TYPE, @@ -22,14 +23,47 @@ import { ELM_TIME_TYPE } from '../util/elmTypes'; import { - MAX_FLOAT_VALUE, MAX_INT_VALUE, MAX_LONG_VALUE, - MIN_FLOAT_VALUE, MIN_INT_VALUE, MIN_LONG_VALUE } from '../util/limits'; +function isDecimal(value: any): boolean { + return value != null && value.isDecimal; +} + +function decimalResult(value: any, resultTypeName?: string): any { + if (isDecimal(value) || (typeof value === 'number' && !Number.isFinite(value))) { + return value; + } + return resultTypeName === ELM_DECIMAL_TYPE || (typeof value === 'number' && !Number.isInteger(value)) + ? Decimal.from(value).setScale(8, 'half-up') + : value; +} + +function add(x: any, y: any) { + return isDecimal(x) || isDecimal(y) ? Decimal.from(x).add(y).setScale(8, 'half-up') : x + y; +} + +function subtract(x: any, y: any) { + return isDecimal(x) || isDecimal(y) + ? Decimal.from(x).subtract(y).setScale(8, 'half-up') + : x - y; +} + +function multiply(x: any, y: any) { + return isDecimal(x) || isDecimal(y) + ? Decimal.from(x).multiplyBy(y).setScale(8, 'half-up') + : x * y; +} + +function divide(x: any, y: any) { + return isDecimal(x) || isDecimal(y) + ? Decimal.from(x).divideBy(y).setScale(8, 'half-up') + : x / y; +} + export class Add extends Expression { constructor(json: any) { super(json); @@ -84,10 +118,10 @@ export class Multiply extends Expression { if (x.low.isQuantity) { return new Uncertainty(doMultiplication(x.low, y.low), doMultiplication(x.high, y.high)); } else { - return new Uncertainty(x.low * y.low, x.high * y.high); + return new Uncertainty(multiply(x.low, y.low), multiply(x.high, y.high)); } } else { - return x * y; + return multiply(x, y); } }); @@ -109,7 +143,9 @@ export class Divide extends Expression { return null; } - const quotient = args.reduce((x: any, y: any) => { + let quotient; + let [x, y] = args; + try { if (x.isUncertainty && !y.isUncertainty) { y = new Uncertainty(y, y); } else if (y.isUncertainty && !x.isUncertainty) { @@ -117,17 +153,20 @@ export class Divide extends Expression { } if (x.isQuantity) { - return doDivision(x, y); + quotient = doDivision(x, y); } else if (x.isUncertainty && y.isUncertainty) { if (x.low.isQuantity) { - return new Uncertainty(doDivision(x.low, y.high), doDivision(x.high, y.low)); + quotient = new Uncertainty(doDivision(x.low, y.high), doDivision(x.high, y.low)); } else { - return new Uncertainty(x.low / y.high, x.high / y.low); + quotient = new Uncertainty(divide(x.low, y.high), divide(x.high, y.low)); } } else { - return x / y; + quotient = divide(x, y); } - }); + } catch { + // Decimal division by zero throws; CQL defines the result as null. + return null; + } // Note, anything divided by 0 is Infinity in Javascript, which will be // considered as overflow by this check. @@ -149,7 +188,7 @@ export class TruncatedDivide extends Expression { return null; } - let truncatedQuotient: number | bigint; + let truncatedQuotient: number | bigint | Decimal; if (typeof args[0] === 'bigint') { // bigint division always truncates try { @@ -159,8 +198,17 @@ export class TruncatedDivide extends Expression { return null; } } else { - const quotient = args.reduce((x: number, y: number) => x / y); - truncatedQuotient = quotient >= 0 ? Math.floor(quotient) : Math.ceil(quotient); + try { + const quotient = args.reduce((x: any, y: any) => divide(x, y)); + const truncated = isDecimal(quotient) + ? quotient.truncate() + : quotient >= 0 + ? Math.floor(quotient) + : Math.ceil(quotient); + truncatedQuotient = decimalResult(truncated, this.resultTypeName); + } catch { + return null; + } } if (MathUtil.overflowsOrUnderflows(truncatedQuotient, this.resultTypeName)) { @@ -181,15 +229,17 @@ export class Modulo extends Expression { return null; } - let modulo: number | bigint; + let modulo: number | bigint | Decimal; try { - modulo = args.reduce((x: any, y: any) => x % y); + modulo = args.reduce((x: any, y: any) => + isDecimal(x) || isDecimal(y) ? Decimal.from(x).modulo(y) : x % y + ); } catch { // modulo divide by zero results in null according to specification return null; } - return MathUtil.decimalLongOrNull(modulo); + return MathUtil.decimalLongOrNull(decimalResult(modulo, this.resultTypeName)); } } @@ -204,7 +254,7 @@ export class Ceiling extends Expression { return null; } - return Math.ceil(arg); + return isDecimal(arg) ? arg.ceil() : Math.ceil(arg); } } @@ -219,7 +269,7 @@ export class Floor extends Expression { return null; } - return Math.floor(arg); + return isDecimal(arg) ? arg.floor() : Math.floor(arg); } } @@ -234,7 +284,7 @@ export class Truncate extends Expression { return null; } - return arg >= 0 ? Math.floor(arg) : Math.ceil(arg); + return isDecimal(arg) ? arg.truncate() : arg >= 0 ? Math.floor(arg) : Math.ceil(arg); } } export class Abs extends Expression { @@ -247,12 +297,17 @@ export class Abs extends Expression { if (arg == null) { return null; } else if (arg.isQuantity) { - return new Quantity(Math.abs(arg.value), arg.unit); + return new Quantity(arg.value.abs(), arg.unit); } else if (typeof arg === 'bigint') { const absoluteValue = arg < 0n ? -arg : arg; return MathUtil.overflowsOrUnderflows(absoluteValue, this.resultTypeName) ? null : absoluteValue; + } else if (isDecimal(arg)) { + const absoluteValue = arg.abs(); + return MathUtil.overflowsOrUnderflows(absoluteValue, this.resultTypeName) + ? null + : absoluteValue; } else { const absoluteValue = Math.abs(arg); return MathUtil.overflowsOrUnderflows(absoluteValue, this.resultTypeName) @@ -272,12 +327,17 @@ export class Negate extends Expression { if (arg == null) { return null; } else if (arg.isQuantity) { - return new Quantity(arg.value * -1, arg.unit); + return new Quantity(arg.value.negate(), arg.unit); } else if (typeof arg === 'bigint') { const negatedValue = arg * -1n; return MathUtil.overflowsOrUnderflows(negatedValue, this.resultTypeName) ? null : negatedValue; + } else if (isDecimal(arg)) { + const negatedValue = arg.negate(); + return MathUtil.overflowsOrUnderflows(negatedValue, this.resultTypeName) + ? null + : negatedValue; } else { const negatedValue = arg * -1; return MathUtil.overflowsOrUnderflows(negatedValue, this.resultTypeName) @@ -302,7 +362,10 @@ export class Round extends Expression { } const dec = this.precision != null ? await this.precision.execute(ctx) : 0; - return Math.round(arg * Math.pow(10, dec)) / Math.pow(10, dec); + if (isDecimal(arg)) { + return arg.round(dec); + } + return decimalResult(Math.round(arg * Math.pow(10, dec)) / Math.pow(10, dec), this.resultTypeName); } } @@ -317,9 +380,13 @@ export class Ln extends Expression { return null; } - const ln = Math.log(arg); - - return MathUtil.decimalOrNull(ln); + try { + return isDecimal(arg) + ? arg.ln() + : MathUtil.decimalOrNull(decimalResult(Math.log(arg), ELM_DECIMAL_TYPE)); + } catch { + return null; + } } } @@ -334,7 +401,14 @@ export class Exp extends Expression { return null; } - const power = Math.exp(arg); + let power; + try { + power = isDecimal(arg) + ? arg.exp() + : decimalResult(Math.exp(arg), ELM_DECIMAL_TYPE); + } catch { + return null; + } if (MathUtil.overflowsOrUnderflows(power, this.resultTypeName)) { return null; @@ -354,9 +428,16 @@ export class Log extends Expression { return null; } - const log = args.reduce((x: number, y: number) => Math.log(x) / Math.log(y)); - - return MathUtil.decimalOrNull(log); + try { + const log = args.reduce((x: any, y: any) => + isDecimal(x) || isDecimal(y) + ? Decimal.from(x).log(y) + : Math.log(x) / Math.log(y) + ); + return isDecimal(log) ? log : MathUtil.decimalOrNull(decimalResult(log, ELM_DECIMAL_TYPE)); + } catch { + return null; + } } } @@ -371,7 +452,7 @@ export class Power extends Expression { return null; } - const power = args.reduce((x: any, y: any) => doPower(x, y)); + const power = decimalResult(args.reduce((x: any, y: any) => doPower(x, y)), this.resultTypeName); // Note: The resultTypeName may be wrong if the exponent is a negative number. Math.overflowsOrUnderflows // already accounts for this possibility by only considering it an integer if Number.isInteger(value). @@ -384,6 +465,9 @@ export class Power extends Expression { } function doPower(x: any, y: any) { + if (isDecimal(x) || isDecimal(y)) { + return Decimal.from(x).power(y); + } if (typeof x === 'bigint' && typeof y === 'bigint' && y < 0n) { // x ** y does not support negative exponents for bigint, so downgrade to number if possible, otherwise return null if ( @@ -409,7 +493,7 @@ export class MinValue extends Expression { static readonly MIN_VALUES = { [ELM_INTEGER_TYPE]: MIN_INT_VALUE, [ELM_LONG_TYPE]: MIN_LONG_VALUE, - [ELM_DECIMAL_TYPE]: MIN_FLOAT_VALUE, + [ELM_DECIMAL_TYPE]: MIN_DECIMAL_VALUE, [ELM_DATETIME_TYPE]: MIN_DATETIME_VALUE, [ELM_DATE_TYPE]: MIN_DATE_VALUE, [ELM_TIME_TYPE]: MIN_TIME_VALUE @@ -441,7 +525,7 @@ export class MaxValue extends Expression { static readonly MAX_VALUES = { [ELM_INTEGER_TYPE]: MAX_INT_VALUE, [ELM_LONG_TYPE]: MAX_LONG_VALUE, - [ELM_DECIMAL_TYPE]: MAX_FLOAT_VALUE, + [ELM_DECIMAL_TYPE]: MAX_DECIMAL_VALUE, [ELM_DATETIME_TYPE]: MAX_DATETIME_VALUE, [ELM_DATE_TYPE]: MAX_DATE_VALUE, [ELM_TIME_TYPE]: MAX_TIME_VALUE diff --git a/src/elm/interval.ts b/src/elm/interval.ts index 236a814b1..5ef38af84 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -1,13 +1,15 @@ import { Expression } from './expression'; import { MAX_DATETIME_VALUE, MIN_DATETIME_VALUE } from '../datatypes/datetime'; import { Quantity } from '../datatypes/quantity'; -import { add, successor, predecessor } from '../util/math'; +import { add, successor, predecessor, subtract } from '../util/math'; +import { greaterThan, lessThan, lessThanOrEquals } from '../util/comparison'; import { convertUnit, compareUnits, convertToCQLDateUnit } from '../util/units'; import * as dtivl from '../datatypes/interval'; import { Context } from '../runtime/context'; import { build } from './builder'; import { IntervalTypeSpecifier, NamedTypeSpecifier } from '../types/type-specifiers.interfaces'; import { ELM_ANY_TYPE, ELM_NAMED_TYPE_SPECIFIER } from '../util/elmTypes'; +import { Decimal } from '../datatypes/decimal'; export class Interval extends Expression { lowClosed: boolean; @@ -409,16 +411,16 @@ function intervalListType(intervals: any) { } else { return 'mismatch'; } - } else if (Number.isInteger(low) && Number.isInteger(high)) { + } else if (typeof low === 'number' && typeof high === 'number') { if (type == null) { type = 'integer'; - } else if (type === 'integer' || type === 'decimal') { + } else if (type === 'integer') { continue; } else { return 'mismatch'; } - } else if (typeof low === 'number' && typeof high === 'number') { - if (type == null || type === 'integer') { + } else if (low.isDecimal && high.isDecimal) { + if (type == null) { type = 'decimal'; } else if (type === 'decimal') { continue; @@ -445,7 +447,7 @@ export class Expand extends Expression { let defaultPer, expandFunction; let [intervals, per] = await this.execArgs(ctx); - if (per?.value === 0) { + if (per?.value.equals(0)) { // a per of 0 is basically like a divide-by-zero; since spec says divide-by-zero returns null, we'll return null here too return null; } @@ -471,11 +473,17 @@ export class Expand extends Expression { if (['time', 'date', 'datetime'].includes(type)) { expandFunction = this.expandDTishInterval; defaultPer = (interval: any) => new Quantity(1, interval.low.getPrecision()); - } else if (['quantity'].includes(type)) { + } else if (type === 'quantity') { expandFunction = this.expandQuantityInterval; defaultPer = (interval: any) => new Quantity(1, interval.low.unit); - } else if (['long', 'integer', 'decimal'].includes(type)) { - expandFunction = this.expandNumericInterval; + } else if (type === 'integer') { + expandFunction = this.expandIntegerInterval; + defaultPer = (_interval: any) => new Quantity(1, '1'); + } else if (type === 'long') { + expandFunction = this.expandLongInterval; + defaultPer = (_interval: any) => new Quantity(1, '1'); + } else if (type === 'decimal') { + expandFunction = this.expandDecimalInterval; defaultPer = (_interval: any) => new Quantity(1, '1'); } else { throw new Error('Interval list type not yet supported.'); @@ -587,8 +595,28 @@ export class Expand extends Expression { } else { result_units = interval.low.unit; } - const low_value = convertUnit(interval.low.value, interval.low.unit, result_units); - const high_value = convertUnit(interval.high.value, interval.high.unit, result_units); + let low_value = interval.low.value; + let high_value = interval.high.value; + + // Quantity values are always Decimal, but successor is expected to know if the value is an integer + // this needs to happen before converting units + if (!interval.lowClosed) { + if (low_value.isInteger()) { + low_value = low_value.add(1); + } else { + low_value = successor(low_value); + } + } + if (!interval.highClosed) { + if (high_value.isInteger()) { + high_value = high_value.subtract(1); + } else { + high_value = predecessor(high_value); + } + } + + low_value = convertUnit(low_value, interval.low.unit, result_units); + high_value = convertUnit(high_value, interval.high.unit, result_units); const per_value = convertUnit(per.value, per.unit, result_units); // return null if unit conversion failed, must have mismatched units @@ -596,11 +624,9 @@ export class Expand extends Expression { return null; } - const results = this.makeNumericIntervalList( + const results = this.makeDecimalIntervalList( low_value, high_value, - interval.lowClosed, - interval.highClosed, per_value ); @@ -611,103 +637,111 @@ export class Expand extends Expression { return results; } - expandNumericInterval(interval: any, per: any) { + expandIntegerInterval(interval: any, per: any) { if (per.unit !== '1' && per.unit !== '') { return null; } - return this.makeNumericIntervalList( - interval.low, - interval.high, - interval.lowClosed, - interval.highClosed, - per.value + const low = interval.lowClosed ? interval.low : successor(interval.low); + const high = interval.highClosed ? interval.high : predecessor(interval.high); + + return this.makeDecimalIntervalList( + low, high, per.value ); } - makeNumericIntervalList( + expandDecimalInterval(interval: any, per: any) { + if (per.unit !== '1' && per.unit !== '') { + return null; + } + const low = interval.lowClosed ? interval.low : successor(interval.low); + const high = interval.highClosed ? interval.high : predecessor(interval.high); + + return this.makeDecimalIntervalList( + low, high, per.value + ); + } + + expandLongInterval(interval: any, per: any) { + if (per.unit !== '1' && per.unit !== '') { + return null; + } + + const low = interval.lowClosed ? interval.low : successor(interval.low); + const high = interval.highClosed ? interval.high : predecessor(interval.high); + + return this.makeDecimalIntervalList( + low, high, per.value + ); + } + + makeDecimalIntervalList( low: any, high: any, - lowClosed: boolean, - highClosed: boolean, perValue: any ) { // If the per value is a Decimal (has a .), 8 decimal places are appropriate // Integers should have 0 Decimal places - const perIsDecimal = perValue.toString().includes('.'); - const decimalPrecision = perIsDecimal ? 8 : 0; - const hasLongBoundaries = typeof low === 'bigint' || typeof high === 'bigint'; - - low = lowClosed ? low : successor(low); - high = highClosed ? high : predecessor(high); - - if (hasLongBoundaries && !perIsDecimal) { - const longLow = low as bigint; - const longHigh = high as bigint; - - if (longLow > longHigh) { - return []; - } - if (longLow == null || longHigh == null) { - return []; - } - - const perBigInt = BigInt(perValue); - if (perBigInt > longHigh - longLow + 1n) { - return []; - } - - let current_low = longLow; - let current_high = current_low + perBigInt - 1n; - const results = []; - while (current_high <= longHigh) { - results.push(new dtivl.Interval(current_low, current_high, true, true)); - current_low += perBigInt; - current_high = current_low + perBigInt - 1n; - } - - return results; - } else if (hasLongBoundaries) { - low = Number(low); - high = Number(high); + const perIsIntegral = !perValue.toString().includes('.'); + const decimalPrecision = perIsIntegral ? 0 : 8; + + // For the purposes of this function, we'll perform all the arithmetic using Decimals, + // then convert the results back to the required type if necessary + let makeInterval: Function; + if (!perIsIntegral) { + // If per is not an integer value, then regardless of the original point types, the values will be Decimals + makeInterval = (l: Decimal, h: Decimal) => new dtivl.Interval(l, h, true, true); + } else if (typeof low === 'bigint' || typeof high === 'bigint') { + makeInterval = (l: Decimal, h: Decimal) => new dtivl.Interval(l.toLong(), h.toLong(), true, true); + } else if (typeof low === 'number' || typeof high === 'number') { + makeInterval = (l: Decimal, h: Decimal) => new dtivl.Interval(l.toInteger(), h.toInteger(), true, true); + } else { + // per is an integer but the original bounds of the interval were Decimal. + // TODO: for now just make them integers + makeInterval = (l: Decimal, h: Decimal) => new dtivl.Interval(l.toInteger(), h.toInteger(), true, true); } + // treat everything as a Decimal, convert back later if needed + low = Decimal.from(low); + high = Decimal.from(high); + // If the interval boundaries are more precise than the per quantity, the // more precise values will be truncated to the precision specified by the // per quantity. low = truncateDecimal(low, decimalPrecision); high = truncateDecimal(high, decimalPrecision); - if (low > high) { + if (low == null || high == null) { return []; } - if (low == null || high == null) { + if (low.greaterThan(high)) { return []; } - const perUnitSize = perIsDecimal ? 0.00000001 : 1; + const perUnitSize = perIsIntegral ? 1 : 0.00000001; - if ( - low === high && - Number.isInteger(low) && - Number.isInteger(high) && - !Number.isInteger(perValue) - ) { - high = parseFloat((high + 1).toFixed(decimalPrecision)); - } + // TODO: this supports one test case but it's not clear if the test case is correct + // if ( + // low === high && + // Number.isInteger(low) && + // Number.isInteger(high) && + // !Number.isInteger(perValue) + // ) { + // high = parseFloat((high + 1).toFixed(decimalPrecision)); + // } let current_low = low; const results = []; - if (perValue > high - low + perUnitSize) { + if (perValue.greaterThan(high.subtract(low).add(perUnitSize))) { return []; } - let current_high = parseFloat((current_low + perValue - perUnitSize).toFixed(decimalPrecision)); - let intervalToAdd = new dtivl.Interval(current_low, current_high, true, true); - while (intervalToAdd.high <= high) { + let current_high = current_low.add(perValue).subtract(perUnitSize); + let intervalToAdd = makeInterval(current_low, current_high); + while (current_high.lessThanOrEquals(high)) { results.push(intervalToAdd); - current_low = parseFloat((current_low + perValue).toFixed(decimalPrecision)); - current_high = parseFloat((current_low + perValue - perUnitSize).toFixed(decimalPrecision)); - intervalToAdd = new dtivl.Interval(current_low, current_high, true, true); + current_low = current_low.add(perValue); + current_high = current_low.add(perValue).subtract(perUnitSize); + intervalToAdd = makeInterval(current_low, current_high); } return results; @@ -762,10 +796,10 @@ function collapseIntervals(intervals: any, perWidth: any) { return 1; } } else if (a.low != null && b.low != null) { - if (a.low < b.low) { + if (lessThan(a.low, b.low)) { return -1; } - if (a.low > b.low) { + if (greaterThan(a.low, b.low)) { return 1; } } else if (a.low != null && b.low == null) { @@ -782,10 +816,10 @@ function collapseIntervals(intervals: any, perWidth: any) { return 1; } } else if (a.high != null && b.high != null) { - if (a.high < b.high) { + if (lessThan(a.high, b.high)) { return -1; } - if (a.high > b.high) { + if (greaterThan(a.high, b.high)) { return 1; } } else if (a.high != null && b.high == null) { @@ -828,17 +862,15 @@ function collapseIntervals(intervals: any, perWidth: any) { a = b; } } else { - const distance = b.low - a.high; - const comparablePerWidth = - typeof distance === 'bigint' && Number.isInteger(perWidth.value) - ? BigInt(perWidth.value) - : perWidth.value; - const withinPerWidth = - typeof distance === 'bigint' && typeof comparablePerWidth !== 'bigint' - ? Number(distance) <= comparablePerWidth - : distance <= comparablePerWidth; + + const distance = subtract(b.low, a.high); + // TODO: perWidth.value is a Decimal, but distance could be anything + // lessThanOrEquals requires that its args be the same type + // so I guess for now, make distance a Decimal + const distanceDecimal = Decimal.from(distance); + const withinPerWidth = lessThanOrEquals(distanceDecimal, perWidth.value); if (withinPerWidth) { - if (b.high > a.high || b.high == null) { + if (greaterThan(b.high, a.high) || b.high == null) { a.high = b.high; } } else { @@ -857,5 +889,5 @@ function truncateDecimal(decimal: any, decimalPlaces: number) { // like parseFloat().toFixed() but floor rather than round // Needed for when per precision is less than the interval input precision const re = new RegExp('^-?\\d+(?:.\\d{0,' + (decimalPlaces || -1) + '})?'); - return parseFloat(decimal.toString().match(re)[0]); + return Decimal.from(decimal.toString().match(re)[0]); } diff --git a/src/elm/literal.ts b/src/elm/literal.ts index 40d17b393..dea41feb3 100644 --- a/src/elm/literal.ts +++ b/src/elm/literal.ts @@ -7,6 +7,7 @@ import { ELM_STRING_TYPE } from '../util/elmTypes'; import { Expression } from './expression'; +import { Decimal } from '../datatypes/decimal'; export class Literal extends Expression { valueType: string; @@ -96,7 +97,7 @@ export class LongLiteral extends Literal { export class DecimalLiteral extends Literal { constructor(json: any) { super(json); - this.value = parseFloat(this.value); + this.value = Decimal.from(this.value); } // Define a simple getter to allow type-checking of this class without instanceof diff --git a/src/elm/quantity.ts b/src/elm/quantity.ts index cc9b89335..6bd9ec2c5 100644 --- a/src/elm/quantity.ts +++ b/src/elm/quantity.ts @@ -5,12 +5,12 @@ import { Context } from '../runtime/context'; // Unit conversation is currently implemented on for time duration comparison operations // TODO: Implement unit conversation for time duration mathematical operations export class Quantity extends Expression { - value: number; + value: DT.Decimal; unit: any; constructor(json: any) { super(json); - this.value = parseFloat(json.value); + this.value = DT.Decimal.from(json.value); this.unit = json.unit; } diff --git a/src/elm/type.ts b/src/elm/type.ts index 6625b7e84..073a12e59 100644 --- a/src/elm/type.ts +++ b/src/elm/type.ts @@ -5,6 +5,7 @@ import { DateTime, Date } from '../datatypes/datetime'; import { Concept } from '../datatypes/clinical'; import { Interval as dtInterval } from '../datatypes/interval'; import { Quantity, parseQuantity } from '../datatypes/quantity'; +import { Decimal } from '../datatypes/decimal'; import { isValidDecimal, isValidInteger, isValidLong, limitDecimalPrecision } from '../util/math'; import { normalizeMillisecondsField } from '../util/util'; import { Ratio } from '../datatypes/ratio'; @@ -165,13 +166,17 @@ export class ToDecimal extends Expression { const arg = await this.execArgs(ctx); if (arg != null) { if (arg.isUncertainty) { - const low = limitDecimalPrecision(parseFloat(arg.low.toString())); - const high = limitDecimalPrecision(parseFloat(arg.high.toString())); + const low = Decimal.from(arg.low); + const high = Decimal.from(arg.high); return new Uncertainty(low, high); } else { - const decimal = limitDecimalPrecision(parseFloat(arg.toString())); - if (isValidDecimal(decimal)) { - return decimal; + try { + const decimal = Decimal.from(arg.toString()); + if (isValidDecimal(decimal)) { + return decimal; + } + } catch (_e) { + return null; } } } @@ -195,6 +200,11 @@ export class ToInteger extends Expression { if (isValidInteger(integer)) { return integer; } + } else if (arg && arg.isDecimal) { + const integer = (arg as Decimal).toInteger(); + if (isValidInteger(integer)) { + return integer; + } } else if (typeof arg === 'string') { // check for blank string because Number('') and Number(' ') evaluate to 0. if (arg.trim().length === 0) { @@ -232,6 +242,11 @@ export class ToLong extends Expression { } catch { return null; } + } else if (arg && arg.isDecimal) { + const long = (arg as Decimal).toLong(); + if (isValidLong(long)) { + return long; + } } else if (typeof arg === 'string') { // check string format because BigInt throws for invalid strings if (!/^[+-]?\d+$/.test(arg)) { @@ -260,14 +275,8 @@ export class ToQuantity extends Expression { convertValue(val: any): any { if (val == null) { return null; - } else if (typeof val === 'number') { + } else if (typeof val === 'number' || typeof val === 'bigint' || val.isDecimal) { return new Quantity(val, '1'); - } else if (typeof val === 'bigint') { - // By definition, Quantity value is a Decimal in CQL, so we need to convert bigint to number. - // While this isn't perfect, in practice it is probably OK since the range of safer integers - // in JS number is pretty big: -(2^53 - 1) to 2^53 - 1, which is plus/minus 9 quadrillion. - // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger#description - return new Quantity(Number(val), '1'); } else if (val.isRatio) { // numerator and denominator are guaranteed non-null return val.numerator.dividedBy(val.denominator); @@ -734,10 +743,9 @@ function guessSpecifierType(val: any): any { return typeHierarchy[0]; } else if (typeof val === 'boolean') { return { type: ELM_NAMED_TYPE_SPECIFIER, name: ELM_BOOLEAN_TYPE }; - } else if (typeof val === 'number' && Math.floor(val) === val) { - // it could still be a decimal, but we have to just take our best guess! - return { type: ELM_NAMED_TYPE_SPECIFIER, name: ELM_INTEGER_TYPE }; } else if (typeof val === 'number') { + return { type: ELM_NAMED_TYPE_SPECIFIER, name: ELM_INTEGER_TYPE }; + } else if (val.isDecimal) { return { type: ELM_NAMED_TYPE_SPECIFIER, name: ELM_DECIMAL_TYPE }; } else if (typeof val === 'bigint') { return { type: ELM_NAMED_TYPE_SPECIFIER, name: ELM_LONG_TYPE }; diff --git a/src/runtime/context.ts b/src/runtime/context.ts index e28e5ad68..106a53639 100644 --- a/src/runtime/context.ts +++ b/src/runtime/context.ts @@ -341,9 +341,9 @@ export class Context { case ELM_BOOLEAN_TYPE: return typeof val === 'boolean'; case ELM_DECIMAL_TYPE: - return typeof val === 'number'; + return val && val.isDecimal; case ELM_INTEGER_TYPE: - return typeof val === 'number' && Math.floor(val) === val; + return typeof val === 'number'; case ELM_LONG_TYPE: return typeof val === 'bigint'; case ELM_STRING_TYPE: @@ -388,9 +388,9 @@ export class Context { if (inst.isBooleanLiteral) { return typeof val === 'boolean'; } else if (inst.isDecimalLiteral) { - return typeof val === 'number'; + return val && val.isDecimal; } else if (inst.isIntegerLiteral) { - return typeof val === 'number' && Math.floor(val) === val; + return typeof val === 'number'; } else if (inst.isLongLiteral) { return typeof val === 'bigint'; } else if (inst.isStringLiteral) { diff --git a/src/util/comparison.ts b/src/util/comparison.ts index 17d45e240..467bb2e82 100644 --- a/src/util/comparison.ts +++ b/src/util/comparison.ts @@ -12,6 +12,10 @@ function areStrings(a: any, b: any) { return typeof a === 'string' && typeof b === 'string'; } +function areDecimals(a: any, b: any) { + return a && a.isDecimal && b && b.isDecimal; +} + function areDateTimesOrQuantities(a: any, b: any) { return ( (a && a.isDateTime && b && b.isDateTime) || @@ -27,6 +31,8 @@ function isUncertainty(x: any) { export function lessThan(a: any, b: any, precision?: any) { if (areNumbers(a, b) || areBigInts(a, b) || areStrings(a, b)) { return a < b; + } else if (areDecimals(a, b)) { + return a.lessThan(b); } else if (areDateTimesOrQuantities(a, b)) { return a.before(b, precision); } else if (isUncertainty(a)) { @@ -41,7 +47,9 @@ export function lessThan(a: any, b: any, precision?: any) { export function lessThanOrEquals(a: any, b: any, precision?: any) { if (areNumbers(a, b) || areBigInts(a, b) || areStrings(a, b)) { return a <= b; - } else if (areDateTimesOrQuantities(a, b)) { + }else if (areDecimals(a, b)) { + return a.lessThanOrEquals(b); + } else if (areDateTimesOrQuantities(a, b)) { return a.sameOrBefore(b, precision); } else if (isUncertainty(a)) { return a.lessThanOrEquals(b, precision); @@ -55,6 +63,8 @@ export function lessThanOrEquals(a: any, b: any, precision?: any) { export function greaterThan(a: any, b: any, precision?: any) { if (areNumbers(a, b) || areBigInts(a, b) || areStrings(a, b)) { return a > b; + } else if (areDecimals(a, b)) { + return a.greaterThan(b); } else if (areDateTimesOrQuantities(a, b)) { return a.after(b, precision); } else if (isUncertainty(a)) { @@ -69,6 +79,8 @@ export function greaterThan(a: any, b: any, precision?: any) { export function greaterThanOrEquals(a: any, b: any, precision?: any) { if (areNumbers(a, b) || areBigInts(a, b) || areStrings(a, b)) { return a >= b; + } else if (areDecimals(a, b)) { + return a.greaterThanOrEquals(b); } else if (areDateTimesOrQuantities(a, b)) { return a.sameOrAfter(b, precision); } else if (isUncertainty(a)) { diff --git a/src/util/math.ts b/src/util/math.ts index 1f5f508fd..b0fb84436 100644 --- a/src/util/math.ts +++ b/src/util/math.ts @@ -8,6 +8,13 @@ import { MIN_TIME_VALUE, MAX_TIME_VALUE } from '../datatypes/datetime'; + +import { + Decimal, + MAX_DECIMAL_VALUE, + MIN_DECIMAL_VALUE +} from '../datatypes/decimal'; + import { Uncertainty } from '../datatypes/uncertainty'; import { ELM_INTEGER_TYPE, @@ -19,11 +26,8 @@ import { ELM_QUANTITY_TYPE } from './elmTypes'; import { - MAX_FLOAT_VALUE, MAX_INT_VALUE, MAX_LONG_VALUE, - MIN_FLOAT_PRECISION_VALUE, - MIN_FLOAT_VALUE, MIN_INT_VALUE, MIN_LONG_VALUE } from './limits'; @@ -63,15 +67,11 @@ export function overflowsOrUnderflows(value: any, type?: string): boolean { return true; } } else if (typeof value === 'number') { - // Only consider it an integer if it looks like an integer (even if the type says it's an integer). - // We need to do this because the CQL-to-ELM Translator's implementation of Power may incorrectly tag - // a result as an Integer when it really is a decimal (e.g., when the exponent is a negative number). - const isInteger = Number.isInteger(value) && (type === ELM_INTEGER_TYPE || type == null); - if (isInteger) { if (!isValidInteger(value)) { return true; } - } else if (!isValidDecimal(value)) { + } else if (value.isDecimal) { + if (!isValidDecimal(value)) { return true; } } else if (value.isUncertainty) { @@ -107,16 +107,13 @@ export function isValidLong(long: any) { } export function isValidDecimal(decimal: any) { - if (isNaN(decimal)) { + if (!decimal.isDecimal) { return false; } - if (typeof decimal !== 'number') { + if (decimal.greaterThan(MAX_DECIMAL_VALUE)) { return false; } - if (decimal > MAX_FLOAT_VALUE) { - return false; - } - if (decimal < MIN_FLOAT_VALUE) { + if (decimal.lessThan(MIN_DECIMAL_VALUE)) { return false; } return true; @@ -146,9 +143,11 @@ export function add(a: any, b: any, type?: string): any { } if (typeof a === 'number' && typeof b === 'number') { const sum = a + b; - const numberType = - type ?? (Number.isInteger(a) && Number.isInteger(b) ? ELM_INTEGER_TYPE : ELM_DECIMAL_TYPE); - return overflowsOrUnderflows(sum, numberType) ? null : sum; + return overflowsOrUnderflows(sum, ELM_INTEGER_TYPE) ? null : sum; + } + if (a?.isDecimal && b?.isDecimal) { + const sum = a.add(b); + return overflowsOrUnderflows(sum, ELM_DECIMAL_TYPE) ? null : sum; } if (a?.isQuantity && b?.isQuantity) { const [aValue, aUnit, bValue, bUnit] = normalizeUnitsWhenPossible( @@ -160,7 +159,7 @@ export function add(a: any, b: any, type?: string): any { if (aUnit !== bUnit) { return null; } - const sum = aValue + bValue; + const sum = aValue.add(bValue); return overflowsOrUnderflows(sum, ELM_DECIMAL_TYPE) ? null : new Quantity(sum, aUnit); } if (b?.isQuantity && (a?.isDate || a?.isDateTime || (a?.isTime && a.isTime()))) { @@ -188,14 +187,18 @@ export function subtract(a: any, b: any, type?: string): any { if (typeof b === 'number' || typeof b === 'bigint') { return add(a, -b, type); } + if (a?.isDecimal && b?.isDecimal) { + const difference = a.subtract(b); + return overflowsOrUnderflows(difference, ELM_DECIMAL_TYPE) ? null : difference; + } if (b?.isQuantity) { - return add(a, { isQuantity: true, value: -b.value, unit: b.unit }, type); + return add(a, { isQuantity: true, value: b.value.negate(), unit: b.unit }, type); } throw new Error('Unsupported argument types.'); } -export function limitDecimalPrecision( +export function limitDecimalPrecision( val?: T ): T | undefined { if (val == null) { @@ -204,7 +207,7 @@ export function limitDecimalPrecision= MAX_INT_VALUE) { - throw new OverFlowException(); - } else { - return val + 1; - } + if (val >= MAX_INT_VALUE) { + throw new OverFlowException(); } else { - if (val >= MAX_FLOAT_VALUE) { - throw new OverFlowException(); - } else { - return val + MIN_FLOAT_PRECISION_VALUE; - } + return val + 1; } } else if (typeof val === 'bigint') { if (val >= MAX_LONG_VALUE) { @@ -240,6 +234,12 @@ export function successor(val: any, type?: string, precision?: string): any { } else { return val + 1n; } + } else if (val && val.isDecimal) { + if (val.greaterThanOrEquals(MAX_DECIMAL_VALUE)) { + throw new OverFlowException(); + } else { + return val.successor(); + } } else if (val && val.isTime && val.isTime()) { if (val.sameAs(MAX_TIME_VALUE)) { throw new OverFlowException(); @@ -279,19 +279,10 @@ export function successor(val: any, type?: string, precision?: string): any { export function predecessor(val: any, type?: string, precision?: string): any { if (typeof val === 'number') { - const isInteger = type === ELM_INTEGER_TYPE || (type == null && Number.isInteger(val)); - if (isInteger) { - if (val <= MIN_INT_VALUE) { - throw new OverFlowException(); - } else { - return val - 1; - } + if (val <= MIN_INT_VALUE) { + throw new OverFlowException(); } else { - if (val <= MIN_FLOAT_VALUE) { - throw new OverFlowException(); - } else { - return val - MIN_FLOAT_PRECISION_VALUE; - } + return val - 1; } } else if (typeof val === 'bigint') { if (val <= MIN_LONG_VALUE) { @@ -299,6 +290,12 @@ export function predecessor(val: any, type?: string, precision?: string): any { } else { return val - 1n; } + } else if (val && val.isDecimal) { + if (val.lessThanOrEquals(MIN_DECIMAL_VALUE)) { + throw new OverFlowException(); + } else { + return val.predecessor(); + } } else if (val && val.isTime && val.isTime()) { if (val.sameAs(MIN_TIME_VALUE)) { throw new OverFlowException(); @@ -343,7 +340,7 @@ export function maxValueForType(type: string, quantityInstance?: Quantity) { case ELM_LONG_TYPE: return MAX_LONG_VALUE; case ELM_DECIMAL_TYPE: - return MAX_FLOAT_VALUE; + return MAX_DECIMAL_VALUE; case ELM_DATETIME_TYPE: return MAX_DATETIME_VALUE?.copy(); case ELM_DATE_TYPE: @@ -355,7 +352,7 @@ export function maxValueForType(type: string, quantityInstance?: Quantity) { // especially if this is being used in the context of an interval or uncertainty since the // left and right sides need to be comparable in those cases. // See: https://jira.hl7.org/browse/FHIR-57935 - return new Quantity(MAX_FLOAT_VALUE, quantityInstance?.unit || '1'); + return new Quantity(MAX_DECIMAL_VALUE, quantityInstance?.unit || '1'); } } return null; @@ -368,7 +365,7 @@ export function minValueForType(type: string, quantityInstance?: Quantity) { case ELM_LONG_TYPE: return MIN_LONG_VALUE; case ELM_DECIMAL_TYPE: - return MIN_FLOAT_VALUE; + return MIN_DECIMAL_VALUE; case ELM_DATETIME_TYPE: return MIN_DATETIME_VALUE?.copy(); case ELM_DATE_TYPE: @@ -380,7 +377,7 @@ export function minValueForType(type: string, quantityInstance?: Quantity) { // especially if this is being used in the context of an interval or uncertainty since the // left and right sides need to be comparable in those cases. // See: https://jira.hl7.org/browse/FHIR-57935 - return new Quantity(MIN_FLOAT_VALUE, quantityInstance?.unit || '1'); + return new Quantity(MIN_DECIMAL_VALUE, quantityInstance?.unit || '1'); } } return null; @@ -414,7 +411,8 @@ export function decimalOrNull(value: any) { } export function decimalLongOrNull(value: any) { - return (typeof value === 'number' && isValidDecimal(value)) || + return (typeof value === 'number' && Number.isFinite(value)) || + ((value && value.isDecimal) && isValidDecimal(value)) || (typeof value === 'bigint' && isValidLong(value)) ? value : null; diff --git a/src/util/units.ts b/src/util/units.ts index 31f513d6a..553720f42 100644 --- a/src/util/units.ts +++ b/src/util/units.ts @@ -1,5 +1,6 @@ import * as ucum from '@lhncbc/ucum-lhc'; import { decimalAdjust } from './math'; +import { Decimal } from '../datatypes/decimal'; const utils = ucum.UcumLhcUtils.getInstance(); // The CQL specification says that dates are based on the Gregorian calendar, so CQL-based year and month @@ -69,12 +70,16 @@ export function checkUnit(unit: any, allowEmptyUnits = true, allowCQLDateUnits = export function convertUnit(fromVal: any, fromUnit: any, toUnit: any, adjustPrecision = true) { [fromUnit, toUnit] = [fromUnit, toUnit].map(fixUnit); - const result = utils.convertUnitTo(fixUnit(fromUnit), fromVal, fixUnit(toUnit)); + // IMPORTANT: the UCUM library operates on raw JS numbers, not our Decimal + const rawFromVal = fromVal.isDecimal ? fromVal.value : fromVal; + + const result = utils.convertUnitTo(fixUnit(fromUnit), rawFromVal, fixUnit(toUnit)); if (result.status !== 'succeeded') { return; } // note: convert result.toVal to number (by prefixing +) to keep typescript happy - return adjustPrecision ? decimalAdjust('round', result.toVal, -8) : +result.toVal; + const rawRetVal = adjustPrecision ? decimalAdjust('round', result.toVal, -8) : +result.toVal; + return fromVal.isDecimal ? Decimal.from(rawRetVal) : rawRetVal; } export function normalizeUnitsWhenPossible(val1: any, unit1: any, val2: any, unit2: any) { @@ -121,7 +126,7 @@ export function convertToCQLDateUnit(unit: any) { export function compareUnits(unit1: any, unit2: any) { try { - const c = convertUnit(1, unit1, unit2); + const c = convertUnit(1, unit1, unit2) as number; if (c && c > 1) { // unit1 is bigger (less precise) return 1; diff --git a/test/datatypes/decimal-test.ts b/test/datatypes/decimal-test.ts new file mode 100644 index 000000000..780b1ad21 --- /dev/null +++ b/test/datatypes/decimal-test.ts @@ -0,0 +1,41 @@ +import { Decimal } from '../../src/datatypes/decimal'; + +describe('Decimal', () => { + it('should retain Decimal runtime identity for a whole-number value', () => { + const decimal = Decimal.from('2.0'); + + decimal.isDecimal.should.equal(true); + (typeof decimal).should.equal('object'); + decimal.toNumber().should.equal(2); + }); + + it('should expose arithmetic and comparison operations', () => { + const value = Decimal.from('1.5').subtract('0.5'); + + value.compareTo('1').should.equal(0); + value.add(2).toString().should.equal('3'); + value.multiplyBy(2).toString().should.equal('2'); + value.divideBy(2).toString().should.equal('0.5'); + Decimal.from(3).modulo(2).toString().should.equal('1'); + }); + + it('should provide an explicit scale and JSON representation', () => { + Decimal.from('0.444444444').setScale(8).toString().should.equal('0.44444444'); + JSON.stringify({ value: Decimal.from('1.25') }).should.equal('{"value":"1.25"}'); + }); + + it('should provide CQL arithmetic helpers without exposing a number', () => { + Decimal.from('-1.9').truncate().should.equal(-1); + Decimal.from('1.1').ceil().should.equal(2); + Decimal.from('1.9').floor().should.equal(1); + Decimal.from('-0.5').round().should.eql(Decimal.from(0)); + Decimal.from('2').power(3).should.eql(Decimal.from(8)); + Decimal.from('9').sqrt().should.eql(Decimal.from(3)); + Decimal.from('8').log(2).should.eql(Decimal.from(3)); + }); + + it('should reject non-finite and divide-by-zero values', () => { + (() => Decimal.from('not a number')).should.throw(); + (() => Decimal.from(1).divideBy(0)).should.throw(); + }); +}); diff --git a/test/datatypes/interval-data.ts b/test/datatypes/interval-data.ts index f551726ca..4ce09e61c 100644 --- a/test/datatypes/interval-data.ts +++ b/test/datatypes/interval-data.ts @@ -1,6 +1,7 @@ import { Interval } from '../../src/datatypes/interval'; import { DateTime, Date } from '../../src/datatypes/datetime'; import { Quantity } from '../../src/datatypes/quantity'; +import { Decimal } from '../../src/datatypes/decimal'; class TestDateTime { static parse(string: string) { @@ -296,7 +297,7 @@ export default () => { y: new TestInterval(0n, 100n) } }; - data['zeroPointFiveToNinePointFive'] = new TestInterval(0.5, 9.5); + data['zeroPointFiveToNinePointFive'] = new TestInterval(Decimal.from(0.5), Decimal.from(9.5)); data['zeroToHundredMg'] = new TestInterval(new Quantity(0, 'mg'), new Quantity(100, 'mg')); return data; }; diff --git a/test/datatypes/interval-test.ts b/test/datatypes/interval-test.ts index adef8b2f0..29d4ff072 100644 --- a/test/datatypes/interval-test.ts +++ b/test/datatypes/interval-test.ts @@ -11,6 +11,7 @@ import { } from '../../src/datatypes/datetime'; import { Interval } from '../../src/datatypes/interval'; import { Quantity } from '../../src/datatypes/quantity'; +import { Decimal, MAX_DECIMAL_VALUE, MIN_DECIMAL_VALUE } from '../../src/datatypes/decimal'; import { Uncertainty } from '../../src/datatypes/uncertainty'; import { ELM_DATE_TYPE, @@ -133,7 +134,7 @@ describe('Interval', () => { }); it('should return the point size for Decimal intervals', () => { - new Interval(0.5, 9.5).getPointSize().should.equal(0.00000001); + new Interval(Decimal.from(0.5), Decimal.from(9.5)).getPointSize().should.eql(Decimal.from(0.00000001)); }); it('should return the point size for Quantity intervals', () => { @@ -155,7 +156,7 @@ describe('Interval', () => { it('should return low for intervals with closed low', () => { d.zeroToHundred.closed.start().should.equal(0); - d.zeroPointFiveToNinePointFive.closed.start().should.equal(0.5); + d.zeroPointFiveToNinePointFive.closed.start().should.eql(Decimal.from(0.5)); d.zeroToHundredLong.closed.start().should.equal(0n); d.zeroToHundredMg.closed.start().should.eql(new Quantity(0, 'mg')); d.all2012date.closed.start().should.eql(Date.parse('2012-01-01')); @@ -165,7 +166,7 @@ describe('Interval', () => { it('should return successor of low for intervals with open low', () => { d.zeroToHundred.openClosed.start().should.equal(1); - d.zeroPointFiveToNinePointFive.openClosed.start().should.equal(0.50000001); + d.zeroPointFiveToNinePointFive.openClosed.start().should.eql(Decimal.from(0.50000001)); d.zeroToHundredLong.openClosed.start().should.equal(1n); d.zeroToHundredMg.openClosed.start().should.eql(new Quantity(0.00000001, 'mg')); d.all2012date.openClosed.start().should.eql(Date.parse('2012-01-02')); @@ -178,7 +179,7 @@ describe('Interval', () => { it('should return type minimum for closed null low endpoints', () => { d.zeroToHundred.withNullStart.closed.start().should.equal(MIN_INT_VALUE); d.zeroToHundredLong.withNullStart.closed.start().should.equal(MIN_LONG_VALUE); - d.zeroPointFiveToNinePointFive.withNullStart.closed.start().should.equal(MIN_FLOAT_VALUE); + d.zeroPointFiveToNinePointFive.withNullStart.closed.start().should.eql(Decimal.from(MIN_FLOAT_VALUE)); d.zeroToHundredMg.withNullStart.closed .start() .should.eql(new Quantity(MIN_FLOAT_VALUE, 'mg')); @@ -201,10 +202,10 @@ describe('Interval', () => { .should.eql(new Uncertainty(MIN_LONG_VALUE, 100n)); d.zeroPointFiveToNinePointFive.withNullStart.openClosed .start() - .should.eql(new Uncertainty(MIN_FLOAT_VALUE, 9.5)); + .should.eql(new Uncertainty(MIN_DECIMAL_VALUE, Decimal.from(9.5))); d.zeroToHundredMg.withNullStart.openClosed .start() - .should.eql(new Uncertainty(new Quantity(MIN_FLOAT_VALUE, 'mg'), new Quantity(100, 'mg'))); + .should.eql(new Uncertainty(new Quantity(MIN_DECIMAL_VALUE, 'mg'), new Quantity(100, 'mg'))); d.all2012date.withNullStart.openClosed .start() .should.eql(new Uncertainty(MIN_DATE_VALUE, Date.parse('2012-12-31'))); @@ -225,11 +226,11 @@ describe('Interval', () => { .should.eql(new Uncertainty(MIN_LONG_VALUE, 99n)); d.zeroPointFiveToNinePointFive.withNullStart.open .start() - .should.eql(new Uncertainty(MIN_FLOAT_VALUE, 9.49999999)); + .should.eql(new Uncertainty(MIN_DECIMAL_VALUE, Decimal.from(9.49999999))); d.zeroToHundredMg.withNullStart.open .start() .should.eql( - new Uncertainty(new Quantity(MIN_FLOAT_VALUE, 'mg'), new Quantity(99.99999999, 'mg')) + new Uncertainty(new Quantity(MIN_DECIMAL_VALUE, 'mg'), new Quantity(99.99999999, 'mg')) ); d.all2012date.withNullStart.open .start() @@ -252,7 +253,7 @@ describe('Interval', () => { it('should use default point type when both endpoints are null', () => { new Interval(null, null, true, true, ELM_INTEGER_TYPE).start().should.equal(MIN_INT_VALUE); new Interval(null, null, true, true, ELM_LONG_TYPE).start().should.equal(MIN_LONG_VALUE); - new Interval(null, null, true, true, ELM_DECIMAL_TYPE).start().should.equal(MIN_FLOAT_VALUE); + new Interval(null, null, true, true, ELM_DECIMAL_TYPE).start().should.eql(MIN_DECIMAL_VALUE); new Interval(null, null, true, true, ELM_QUANTITY_TYPE) .start() .should.eql(new Quantity(MIN_FLOAT_VALUE, '1')); @@ -272,11 +273,11 @@ describe('Interval', () => { .should.eql(new Uncertainty(MIN_LONG_VALUE, MAX_LONG_VALUE)); new Interval(null, null, false, false, ELM_DECIMAL_TYPE) .start() - .should.eql(new Uncertainty(MIN_FLOAT_VALUE, MAX_FLOAT_VALUE)); + .should.eql(new Uncertainty(MIN_DECIMAL_VALUE, MAX_DECIMAL_VALUE)); new Interval(null, null, false, false, ELM_QUANTITY_TYPE) .start() .should.eql( - new Uncertainty(new Quantity(MIN_FLOAT_VALUE, '1'), new Quantity(MAX_FLOAT_VALUE, '1')) + new Uncertainty(new Quantity(MIN_DECIMAL_VALUE, '1'), new Quantity(MAX_DECIMAL_VALUE, '1')) ); new Interval(null, null, false, false, ELM_DATETIME_TYPE) .start() @@ -295,7 +296,7 @@ describe('Interval', () => { it('should return high for intervals with closed high', () => { d.zeroToHundred.closed.end().should.equal(100); - d.zeroPointFiveToNinePointFive.closed.end().should.equal(9.5); + d.zeroPointFiveToNinePointFive.closed.end().should.eql(Decimal.from(9.5)); d.zeroToHundredLong.closed.end().should.equal(100n); d.zeroToHundredMg.closed.end().should.eql(new Quantity(100, 'mg')); d.all2012date.closed.end().should.eql(Date.parse('2012-12-31')); @@ -305,7 +306,7 @@ describe('Interval', () => { it('should return predecessor of high for intervals with open high', () => { d.zeroToHundred.closedOpen.end().should.equal(99); - d.zeroPointFiveToNinePointFive.closedOpen.end().should.equal(9.49999999); + d.zeroPointFiveToNinePointFive.closedOpen.end().should.eql(Decimal.from(9.49999999)); d.zeroToHundredLong.closedOpen.end().should.equal(99n); d.zeroToHundredMg.closedOpen.end().should.eql(new Quantity(99.99999999, 'mg')); d.all2012date.closedOpen.end().should.eql(Date.parse('2012-12-30')); @@ -316,8 +317,8 @@ describe('Interval', () => { it('should return type maximum for closed null high endpoints', () => { d.zeroToHundred.withNullEnd.closed.end().should.equal(MAX_INT_VALUE); d.zeroToHundredLong.withNullEnd.closed.end().should.equal(MAX_LONG_VALUE); - d.zeroPointFiveToNinePointFive.withNullEnd.closed.end().should.equal(MAX_FLOAT_VALUE); - d.zeroToHundredMg.withNullEnd.closed.end().should.eql(new Quantity(MAX_FLOAT_VALUE, 'mg')); + d.zeroPointFiveToNinePointFive.withNullEnd.closed.end().should.eql(MAX_DECIMAL_VALUE); + d.zeroToHundredMg.withNullEnd.closed.end().should.eql(new Quantity(MAX_DECIMAL_VALUE, 'mg')); d.all2012date.withNullEnd.closed.end().should.eql(MAX_DATE_VALUE); d.all2012.withNullEnd.closed.end().should.eql(MAX_DATETIME_VALUE); d.alldaytime.withNullEnd.closed.end().should.eql(MAX_TIME_VALUE); @@ -335,10 +336,10 @@ describe('Interval', () => { .should.eql(new Uncertainty(0n, MAX_LONG_VALUE)); d.zeroPointFiveToNinePointFive.withNullEnd.closedOpen .end() - .should.eql(new Uncertainty(0.5, MAX_FLOAT_VALUE)); + .should.eql(new Uncertainty(Decimal.from(0.5), MAX_DECIMAL_VALUE)); d.zeroToHundredMg.withNullEnd.closedOpen .end() - .should.eql(new Uncertainty(new Quantity(0, 'mg'), new Quantity(MAX_FLOAT_VALUE, 'mg'))); + .should.eql(new Uncertainty(new Quantity(0, 'mg'), new Quantity(MAX_DECIMAL_VALUE, 'mg'))); d.all2012date.withNullEnd.closedOpen .end() .should.eql(new Uncertainty(Date.parse('2012-01-01'), MAX_DATE_VALUE)); @@ -357,11 +358,11 @@ describe('Interval', () => { d.zeroToHundredLong.withNullEnd.open.end().should.eql(new Uncertainty(1n, MAX_LONG_VALUE)); d.zeroPointFiveToNinePointFive.withNullEnd.open .end() - .should.eql(new Uncertainty(0.50000001, MAX_FLOAT_VALUE)); + .should.eql(new Uncertainty(Decimal.from(0.50000001), MAX_DECIMAL_VALUE)); d.zeroToHundredMg.withNullEnd.open .end() .should.eql( - new Uncertainty(new Quantity(0.00000001, 'mg'), new Quantity(MAX_FLOAT_VALUE, 'mg')) + new Uncertainty(new Quantity(0.00000001, 'mg'), new Quantity(MAX_DECIMAL_VALUE, 'mg')) ); d.all2012date.withNullEnd.open .end() @@ -384,7 +385,7 @@ describe('Interval', () => { it('should use default point type when both endpoints are null', () => { new Interval(null, null, true, true, ELM_INTEGER_TYPE).end().should.equal(MAX_INT_VALUE); new Interval(null, null, true, true, ELM_LONG_TYPE).end().should.equal(MAX_LONG_VALUE); - new Interval(null, null, true, true, ELM_DECIMAL_TYPE).end().should.equal(MAX_FLOAT_VALUE); + new Interval(null, null, true, true, ELM_DECIMAL_TYPE).end().should.eql(MAX_DECIMAL_VALUE); new Interval(null, null, true, true, ELM_QUANTITY_TYPE) .end() .should.eql(new Quantity(MAX_FLOAT_VALUE, '1')); @@ -402,11 +403,11 @@ describe('Interval', () => { .should.eql(new Uncertainty(MIN_LONG_VALUE, MAX_LONG_VALUE)); new Interval(null, null, false, false, ELM_DECIMAL_TYPE) .end() - .should.eql(new Uncertainty(MIN_FLOAT_VALUE, MAX_FLOAT_VALUE)); + .should.eql(new Uncertainty(MIN_DECIMAL_VALUE, MAX_DECIMAL_VALUE)); new Interval(null, null, false, false, ELM_QUANTITY_TYPE) .end() .should.eql( - new Uncertainty(new Quantity(MIN_FLOAT_VALUE, '1'), new Quantity(MAX_FLOAT_VALUE, '1')) + new Uncertainty(new Quantity(MIN_FLOAT_VALUE, '1'), new Quantity(MAX_DECIMAL_VALUE, '1')) ); new Interval(null, null, false, false, ELM_DATETIME_TYPE) .end() @@ -7003,37 +7004,37 @@ describe('DecimalInterval', () => { }); it('should calculate width and size outside the Integer range', () => { - const interval = new Interval(0.0, 3000000000.0, true, true, ELM_DECIMAL_TYPE); + const interval = new Interval(Decimal.from(0.0), Decimal.from(3000000000.0), true, true, ELM_DECIMAL_TYPE); - interval.width().should.equal(3000000000.0); - interval.size().should.equal(3000000000.0); + interval.width().should.eql(Decimal.from(3000000000.0)); + interval.size().should.eql(Decimal.from(3000000000.0)); }); it('should close open decimal uncertainty endpoints using decimal point size', () => { const closed = new Interval( - new Uncertainty(1, 2), - new Uncertainty(3, 4), + new Uncertainty(Decimal.from(1), Decimal.from(2)), + new Uncertainty(Decimal.from(3), Decimal.from(4)), false, false, ELM_DECIMAL_TYPE ).toClosed(); - closed.low.should.eql(new Uncertainty(1.00000001, 2.00000001)); - closed.high.should.eql(new Uncertainty(2.99999999, 3.99999999)); + closed.low.should.eql(new Uncertainty(Decimal.from(1.00000001), Decimal.from(2.00000001))); + closed.high.should.eql(new Uncertainty(Decimal.from(2.99999999), Decimal.from(3.99999999))); closed.lowClosed.should.be.true(); closed.highClosed.should.be.true(); }); it('should use decimal point size for meetsBefore decimal uncertainty bounds', () => { - const earlier = new Interval(1, 1.99999999); - const later = new Interval(new Uncertainty(2, 2), null, true, false, ELM_DECIMAL_TYPE); + const earlier = new Interval(Decimal.from(1), Decimal.from(1.99999999)); + const later = new Interval(new Uncertainty(Decimal.from(2), Decimal.from(2)), null, true, false, ELM_DECIMAL_TYPE); earlier.meetsBefore(later).should.be.true(); }); it('should use decimal point size for meetsAfter decimal uncertainty bounds', () => { - const earlier = new Interval(null, new Uncertainty(1, 1), false, true, ELM_DECIMAL_TYPE); - const later = new Interval(1.00000001, 2); + const earlier = new Interval(null, new Uncertainty(Decimal.from(1), Decimal.from(1)), false, true, ELM_DECIMAL_TYPE); + const later = new Interval(Decimal.from(1.00000001), Decimal.from(2)); later.meetsAfter(earlier).should.be.true(); }); @@ -7099,12 +7100,12 @@ describe('DecimalInterval', () => { }); it('should properly handle null endpoints', () => { - const decimal = 1.5; - const early = -1.5; - const late = 3.5; - const decimalInterval = new Interval(0.5, 1.5); - const earlyInterval = new Interval(early, -0.5); - const lateInterval = new Interval(3.5, late); + const decimal = Decimal.from(1.5); + const early = Decimal.from(-1.5); + const late = Decimal.from(3.5); + const decimalInterval = new Interval(Decimal.from(0.5), Decimal.from(1.5)); + const earlyInterval = new Interval(early, Decimal.from(-0.5)); + const lateInterval = new Interval(Decimal.from(3.5), late); const startsAtDecimal = new Interval(decimal, late); const endsAtDecimal = new Interval(early, decimal); diff --git a/test/elm/aggregate/aggregate-test.ts b/test/elm/aggregate/aggregate-test.ts index 9fa028950..d4da6e1bd 100644 --- a/test/elm/aggregate/aggregate-test.ts +++ b/test/elm/aggregate/aggregate-test.ts @@ -1,9 +1,10 @@ import should from 'should'; import setup from '../../setup'; +import { Decimal } from '../../../src/datatypes/decimal'; const data = require('./data'); const validateQuantity = function (object: any, expectedValue: any, expectedUnit: any) { object.isQuantity.should.be.true(); - object.value.should.equal(expectedValue); + object.value.should.eql(Decimal.from(expectedValue)); object.unit.should.equal(expectedUnit); }; @@ -72,11 +73,11 @@ describe('Sum', () => { }); it('should be able to sum lists with decimals', async function () { - (await this.decimals.exec(this.ctx)).should.equal(16.5); + (await this.decimals.exec(this.ctx)).should.eql(Decimal.from(16.5)); }); it('should be able to sum decimals up to max decimal value', async function () { - (await this.decimals_at_max_value.exec(this.ctx)).should.equal(99999999999999999999.99999999); + (await this.decimals_at_max_value.exec(this.ctx)).should.eql(Decimal.from(99999999999999999999.99999999)); }); it('should return null when overflowing the max decimal value', async function () { @@ -84,7 +85,7 @@ describe('Sum', () => { }); it('should be able to sum decimals down to min decimal value', async function () { - (await this.decimals_at_min_value.exec(this.ctx)).should.equal(-99999999999999999999.99999999); + (await this.decimals_at_min_value.exec(this.ctx)).should.eql(Decimal.from(-99999999999999999999.99999999)); }); it('should return null when underflowing the min decimal value', async function () { @@ -183,7 +184,7 @@ describe('Min', () => { }); it('list of Decimals', async function () { - (await this.decimalMin.exec(this.ctx)).should.equal(-5); + (await this.decimalMin.exec(this.ctx)).should.eql(Decimal.from(-5)); }); it('list of DateTimes', async function () { @@ -260,7 +261,7 @@ describe('Max', () => { }); it('list of Decimals', async function () { - (await this.decimalMax.exec(this.ctx)).should.equal(5.1); + (await this.decimalMax.exec(this.ctx)).should.eql(Decimal.from(5.1)); }); it('list of DateTimes', async function () { @@ -309,11 +310,11 @@ describe('Avg', () => { }); it('should be able to find average for lists without nulls', async function () { - (await this.not_null.exec(this.ctx)).should.equal(3); + (await this.not_null.exec(this.ctx)).should.eql(Decimal.from(3)); }); it('should be able to find average for lists with nulls', async function () { - (await this.has_null.exec(this.ctx)).should.equal(1.5); + (await this.has_null.exec(this.ctx)).should.eql(Decimal.from(1.5)); }); it('should return null for empty list', async function () { @@ -350,19 +351,19 @@ describe('Median', () => { }); it('should be able to find median of odd numbered list', async function () { - (await this.odd.exec(this.ctx)).should.equal(3); + (await this.odd.exec(this.ctx)).should.eql(Decimal.from(3)); }); it('should be able to find median of even numbered list', async function () { - (await this.even.exec(this.ctx)).should.equal(3.5); + (await this.even.exec(this.ctx)).should.eql(Decimal.from(3.5)); }); it('should be able to find median of odd numbered list that contains duplicates', async function () { - (await this.dup_vals_odd.exec(this.ctx)).should.equal(3); + (await this.dup_vals_odd.exec(this.ctx)).should.eql(Decimal.from(3)); }); it('should be able to find median of even numbered list that contians duplicates', async function () { - (await this.dup_vals_even.exec(this.ctx)).should.equal(2.5); + (await this.dup_vals_even.exec(this.ctx)).should.eql(Decimal.from(2.5)); }); it('should return null for empty list', async function () { @@ -437,7 +438,7 @@ describe('PopulationVariance', () => { setup(this, data); }); it('should be able to find PopulationVariance of a list ', async function () { - (await this.v.exec(this.ctx)).should.equal(2); + (await this.v.exec(this.ctx)).should.eql(Decimal.from(2)); }); it('should be able to find PopulationVariance of a list of like quantities', async function () { validateQuantity(await this.v_q.exec(this.ctx), 2, 'ml'); @@ -458,7 +459,7 @@ describe('Variance', () => { setup(this, data); }); it('should be able to find Variance of a list ', async function () { - (await this.v.exec(this.ctx)).should.equal(2.5); + (await this.v.exec(this.ctx)).should.eql(Decimal.from(2.5)); }); it('should be able to find Variance of a list of matched quantities', async function () { validateQuantity(await this.v_q.exec(this.ctx), 2.5, 'ml'); @@ -479,7 +480,7 @@ describe('StdDev', () => { setup(this, data); }); it('should be able to find Standard Dev of a list ', async function () { - (await this.std.exec(this.ctx)).should.equal(1.5811388300841898); + (await this.std.exec(this.ctx)).should.eql(Decimal.from(1.58113883)); }); it('should be able to find Standard Dev of a list of like quantities', async function () { validateQuantity(await this.std_q.exec(this.ctx), 1.5811388300841898, 'ml'); @@ -500,7 +501,7 @@ describe('PopulationStdDev', () => { setup(this, data); }); it('should be able to find Population Standard Dev of a list ', async function () { - (await this.dev.exec(this.ctx)).should.equal(1.4142135623730951); + (await this.dev.exec(this.ctx)).should.eql(Decimal.from(1.41421356)); }); it('should be able to find Population Standard Dev of a list of quantities', async function () { validateQuantity(await this.dev_q.exec(this.ctx), 1.4142135623730951, 'ml'); @@ -562,12 +563,12 @@ describe('Product', () => { }); it('should return a decimal product', async function () { - (await this.decimal_product.exec(this.ctx)).should.equal(24.0); + (await this.decimal_product.exec(this.ctx)).should.eql(Decimal.from(24.0)); }); it('should return decimal product up to max decimal value', async function () { - (await this.decimals_at_max_value_product.exec(this.ctx)).should.equal( - 99999999999999999999.99999999 + (await this.decimals_at_max_value_product.exec(this.ctx)).should.eql( + Decimal.from(99999999999999999999.99999999) ); }); @@ -576,8 +577,8 @@ describe('Product', () => { }); it('should return decimal product down to min decimal value', async function () { - (await this.decimals_at_min_value_product.exec(this.ctx)).should.equal( - -99999999999999999999.99999999 + (await this.decimals_at_min_value_product.exec(this.ctx)).should.eql( + Decimal.from(-99999999999999999999.99999999) ); }); @@ -653,15 +654,15 @@ describe('GeometricMean', () => { }); it('should return decimal geometric mean', async function () { - (await this.decimal_geometric_mean.exec(this.ctx)).should.equal(4.0); + (await this.decimal_geometric_mean.exec(this.ctx)).should.eql(Decimal.from(4.0)); }); it('should retun 0 as a geometric mean', async function () { - (await this.zero_geometric_mean.exec(this.ctx)).should.equal(0); + (await this.zero_geometric_mean.exec(this.ctx)).should.eql(Decimal.from(0)); }); it('should return value when pass in list that contains nulls', async function () { - (await this.null_geometric_mean.exec(this.ctx)).should.equal(1.4142135623730951); + (await this.null_geometric_mean.exec(this.ctx)).should.eql(Decimal.from(1.41421356)); }); it('should return null when list is all null', async function () { diff --git a/test/elm/arithmetic/arithmetic-test.ts b/test/elm/arithmetic/arithmetic-test.ts index b9e866575..618265736 100644 --- a/test/elm/arithmetic/arithmetic-test.ts +++ b/test/elm/arithmetic/arithmetic-test.ts @@ -9,10 +9,8 @@ import { } from '../../../src/datatypes/quantity'; import setup from '../../setup'; import { - MAX_FLOAT_VALUE, MAX_INT_VALUE, MAX_LONG_VALUE, - MIN_FLOAT_VALUE, MIN_INT_VALUE, MIN_LONG_VALUE } from '../../../src/util/limits'; @@ -24,10 +22,11 @@ import { MIN_DATETIME_VALUE, MIN_TIME_VALUE } from '../../../src/datatypes/datetime'; +import { Decimal, MAX_DECIMAL_VALUE, MIN_DECIMAL_VALUE } from '../../../src/datatypes/decimal'; const data = require('./data'); -const validateQuantity = function (object: any, expectedValue: number, expectedUnit: string) { +const validateQuantity = function (object: any, expectedValue: number | Decimal, expectedUnit: string) { object.isQuantity.should.be.true(); const q = new Quantity(expectedValue, expectedUnit); q.equals(object).should.be.true('Expected ' + object + ' to equal ' + q); @@ -212,64 +211,64 @@ describe('Divide', () => { }); it('should divide two numbers', async function () { - (await this.tenDividedByTwo.exec(this.ctx)).should.equal(5); + (await this.tenDividedByTwo.exec(this.ctx)).should.eql(Decimal.from(5)); }); it("should divide two numbers that don't evenly divide", async function () { - (await this.tenDividedByFour.exec(this.ctx)).should.equal(2.5); + (await this.tenDividedByFour.exec(this.ctx)).should.eql(Decimal.from(2.5)); }); it('should divide multiple numbers', async function () { - (await this.divideMultiple.exec(this.ctx)).should.equal(5); + (await this.divideMultiple.exec(this.ctx)).should.eql(Decimal.from(5)); }); it('should divide variables', async function () { - (await this.divideVariables.exec(this.ctx)).should.equal(25); + (await this.divideVariables.exec(this.ctx)).should.eql(Decimal.from(25)); }); it('should divide two longs', async function () { // NOTE: Divide always returns a Decimal - (await this.tenDividedByTwoLong.exec(this.ctx)).should.equal(5); + (await this.tenDividedByTwoLong.exec(this.ctx)).should.eql(Decimal.from(5)); }); it('should divide integer by long', async function () { // NOTE: Divide always returns a Decimal - (await this.tenDividedByTwoMixed.exec(this.ctx)).should.equal(5); + (await this.tenDividedByTwoMixed.exec(this.ctx)).should.eql(Decimal.from(5)); }); it('should divide long by integer', async function () { // NOTE: Divide always returns a Decimal - (await this.tenDividedByTwoReverseMixed.exec(this.ctx)).should.equal(5); + (await this.tenDividedByTwoReverseMixed.exec(this.ctx)).should.eql(Decimal.from(5)); }); it('should divide two longs with decimal result', async function () { - (await this.tenDividedByFourLong.exec(this.ctx)).should.equal(2.5); + (await this.tenDividedByFourLong.exec(this.ctx)).should.eql(Decimal.from(2.5)); }); it('should divide integer by long with decimal result', async function () { - (await this.tenDividedByFourMixed.exec(this.ctx)).should.equal(2.5); + (await this.tenDividedByFourMixed.exec(this.ctx)).should.eql(Decimal.from(2.5)); }); it('should divide long by integer with decimal result', async function () { - (await this.tenDividedByFourReverseMixed.exec(this.ctx)).should.equal(2.5); + (await this.tenDividedByFourReverseMixed.exec(this.ctx)).should.eql(Decimal.from(2.5)); }); it('should divide uncertainty by uncertainty', async function () { const result = await this.divideUncertainties.exec(this.ctx); - result.low.should.equal(6 / 14); - result.high.should.equal(9); + result.low.should.eql(Decimal.from(0.42857143)); // 6/14 + result.high.should.eql(Decimal.from(9)); }); it('should divide uncertainty by number', async function () { const result = await this.divideUncertaintyByNumber.exec(this.ctx); - result.low.should.equal(3); - result.high.should.equal(9); + result.low.should.eql(Decimal.from(3)); + result.high.should.eql(Decimal.from(9)); }); it('should divide number by uncertainty', async function () { const result = await this.divideNumberByUncertainty.exec(this.ctx); - result.low.should.equal(2); - result.high.should.equal(6); + result.low.should.eql(Decimal.from(2)); + result.high.should.eql(Decimal.from(6)); }); }); @@ -301,11 +300,11 @@ describe('MathPrecedence', () => { }); it('should follow order of operations', async function () { - (await this.mixed.exec(this.ctx)).should.equal(46); + (await this.mixed.exec(this.ctx)).should.eql(Decimal.from(46)); }); it('should allow parentheses to override order of operations', async function () { - (await this.parenthetical.exec(this.ctx)).should.equal(-10); + (await this.parenthetical.exec(this.ctx)).should.eql(Decimal.from(-10)); }); }); @@ -315,11 +314,11 @@ describe('Power', () => { }); it('should be able to calculate the power of a number', async function () { - (await this.pow.exec(this.ctx)).should.equal(81); + (await this.pow.exec(this.ctx)).should.eql(81); }); it('should be able to calculate the negative power of a number', async function () { - (await this.negPow.exec(this.ctx)).should.equal(0.1); + (await this.negPow.exec(this.ctx)).should.eql(Decimal.from(0.1)); }); it('should be able to calculate the power of a long', async function () { @@ -335,7 +334,7 @@ describe('Power', () => { }); it('should be able to calculate the negative power of a long', async function () { - (await this.tenLongExpNegativeOneLong.exec(this.ctx)).should.equal(0.1); + (await this.tenLongExpNegativeOneLong.exec(this.ctx)).should.eql(Decimal.from(0.1)); }); it('should return null when a long power exponent is too large (beyond max Long value)', async function () { @@ -552,11 +551,11 @@ describe('Ln', () => { }); it('should be able to return the natural log of a number', async function () { - (await this.ln.exec(this.ctx)).should.equal(Math.log(4)); + (await this.ln.exec(this.ctx)).should.eql(Decimal.from(Math.log(4))); }); it('should be able to return the natural log of a long', async function () { - (await this.lnFourLong.exec(this.ctx)).should.equal(Math.log(4)); + (await this.lnFourLong.exec(this.ctx)).should.eql(Decimal.from(Math.log(4))); }); }); @@ -566,11 +565,11 @@ describe('Log', () => { }); it('should be able to return the log of a number based on an arbitrary base value', async function () { - (await this.log.exec(this.ctx)).should.equal(0.25); + (await this.log.exec(this.ctx)).should.eql(Decimal.from(0.25)); }); it('should be able to return the log of a long based on an arbitrary base value', async function () { - (await this.logLong.exec(this.ctx)).should.equal(0.25); + (await this.logLong.exec(this.ctx)).should.eql(Decimal.from(0.25)); }); }); @@ -639,12 +638,12 @@ describe('Round', () => { }); it('should be able to round a number up or down to the closest integer value', async function () { - (await this.up.exec(this.ctx)).should.equal(5); - (await this.down.exec(this.ctx)).should.equal(4); + (await this.up.exec(this.ctx)).should.eql(Decimal.from(5)); + (await this.down.exec(this.ctx)).should.eql(Decimal.from(4)); }); it('should be able to round a number up or down to the closest decimal place ', async function () { - (await this.up_percent.exec(this.ctx)).should.equal(4.6); - (await this.down_percent.exec(this.ctx)).should.equal(4.4); + (await this.up_percent.exec(this.ctx)).should.eql(Decimal.from(4.6)); + (await this.down_percent.exec(this.ctx)).should.eql(Decimal.from(4.4)); }); }); @@ -662,7 +661,7 @@ describe('Successor', () => { }); it('should be able to get Real Successor', async function () { - (await this.rs.exec(this.ctx)).should.equal(2.2 + Math.pow(10, -8)); + (await this.rs.exec(this.ctx)).should.eql(Decimal.from(2.2 + Math.pow(10, -8))); }); it('should return null for Successor greater than Integer Max value', async function () { @@ -765,7 +764,7 @@ describe('Predecessor', () => { }); it('should be able to get Real Predecessor', async function () { - (await this.rs.exec(this.ctx)).should.equal(2.2 - Math.pow(10, -8)); + (await this.rs.exec(this.ctx)).should.eql(Decimal.from(2.2 - Math.pow(10, -8))); }); it('should return null for Predecessor greater than Integer Max value', async function () { @@ -892,13 +891,13 @@ describe('Quantity', () => { it('should be able to perform Quantity Absolution', async function () { const q = await this.abs.exec(this.ctx); - q.value.should.equal(10); + q.value.should.eql(Decimal.from(10)); q.unit.should.equal('days'); }); it('should be able to perform Quantity Negation', async function () { const q = await this.neg.exec(this.ctx); - q.value.should.equal(-10); + q.value.should.eql(Decimal.from(-10)); q.unit.should.equal('days'); }); @@ -1024,12 +1023,12 @@ describe('OutOfBounds', () => { it('should return value for Divide near overflow', async function () { // not really near overflow, but more than max integer and near JavaScript max safe number - should(await this.integerDivideNearOverflow.exec(this.ctx)).equal(8589934588000000); + should(await this.integerDivideNearOverflow.exec(this.ctx)).eql(Decimal.from(8589934588000000)); }); it('should return value for Divide near underflow', async function () { // not really near underflow, but less than min integer and near JavaScript min safe number - should(await this.integerDivideNearUnderflow.exec(this.ctx)).equal(-8589934592000000); + should(await this.integerDivideNearUnderflow.exec(this.ctx)).eql(Decimal.from(-8589934592000000)); }); it('should return null for Divide By Zero', async function () { @@ -1128,12 +1127,12 @@ describe('OutOfBounds', () => { it('should return value for Divide near overflow', async function () { // not really near overflow, but near JavaScript max safe number - should(await this.longDivideNearOverflow.exec(this.ctx)).equal(9007199254740992); + should(await this.longDivideNearOverflow.exec(this.ctx)).eql(Decimal.from(9007199254740992n)); }); it('should return value for Divide near underflow', async function () { // not really near underflow, but near JavaScript min safe number - should(await this.longDivideNearUnderflow.exec(this.ctx)).equal(-9007199254740992); + should(await this.longDivideNearUnderflow.exec(this.ctx)).eql(Decimal.from(-9007199254740992n)); }); it('should return null for Divide By Zero', async function () { @@ -1183,11 +1182,11 @@ describe('OutOfBounds', () => { }); it('should return value for Add near overflow', async function () { - should(await this.decimalAddNearOverflow.exec(this.ctx)).equal(MAX_FLOAT_VALUE); + should(await this.decimalAddNearOverflow.exec(this.ctx)).eql(MAX_DECIMAL_VALUE); }); it('should return value for Add near underflow', async function () { - should(await this.decimalAddNearUnderflow.exec(this.ctx)).equal(MIN_FLOAT_VALUE); + should(await this.decimalAddNearUnderflow.exec(this.ctx)).eql(MIN_DECIMAL_VALUE); }); it('should return null for Subtract overflow', async function () { @@ -1199,11 +1198,11 @@ describe('OutOfBounds', () => { }); it('should return value for Subtract near overflow', async function () { - should(await this.decimalSubtractNearOverflow.exec(this.ctx)).equal(MAX_FLOAT_VALUE); + should(await this.decimalSubtractNearOverflow.exec(this.ctx)).eql(MAX_DECIMAL_VALUE); }); it('should return value for Subtract near underflow', async function () { - should(await this.decimalSubtractNearUnderflow.exec(this.ctx)).equal(MIN_FLOAT_VALUE); + should(await this.decimalSubtractNearUnderflow.exec(this.ctx)).eql(MIN_DECIMAL_VALUE); }); it('should return null for Multiply overflow', async function () { @@ -1215,11 +1214,11 @@ describe('OutOfBounds', () => { }); it('should return value for Multiply near overflow', async function () { - should(await this.decimalMultiplyNearOverflow.exec(this.ctx)).equal(MAX_FLOAT_VALUE); + should(await this.decimalMultiplyNearOverflow.exec(this.ctx)).eql(MAX_DECIMAL_VALUE); }); it('should return value for Multiply near underflow', async function () { - should(await this.decimalMultiplyNearUnderflow.exec(this.ctx)).equal(MIN_FLOAT_VALUE); + should(await this.decimalMultiplyNearUnderflow.exec(this.ctx)).eql(MIN_DECIMAL_VALUE); }); it('should return null for Divide overflow', async function () { @@ -1231,11 +1230,11 @@ describe('OutOfBounds', () => { }); it('should return value for Divide near overflow', async function () { - should(await this.decimalDivideNearOverflow.exec(this.ctx)).equal(MAX_FLOAT_VALUE); + should(await this.decimalDivideNearOverflow.exec(this.ctx)).eql(MAX_DECIMAL_VALUE); }); it('should return value for Divide near underflow', async function () { - should(await this.decimalDivideNearUnderflow.exec(this.ctx)).equal(MIN_FLOAT_VALUE); + should(await this.decimalDivideNearUnderflow.exec(this.ctx)).eql(MIN_DECIMAL_VALUE); }); it('should return null for Divide By Zero', async function () { @@ -1251,11 +1250,11 @@ describe('OutOfBounds', () => { }); it('should return value for Power near overflow', async function () { - should(await this.decimalPowerNearOverflow.exec(this.ctx)).equal(MAX_FLOAT_VALUE); + should(await this.decimalPowerNearOverflow.exec(this.ctx)).eql(MAX_DECIMAL_VALUE); }); it('should return value for Power near underflow', async function () { - should(await this.decimalPowerNearUnderflow.exec(this.ctx)).equal(MIN_FLOAT_VALUE); + should(await this.decimalPowerNearUnderflow.exec(this.ctx)).eql(MIN_DECIMAL_VALUE); }); it('should return null for successor overflow', async function () { @@ -1268,11 +1267,11 @@ describe('OutOfBounds', () => { // NOTE: skipping successor/predecessor tests near overflow due to JS Number imprecision it.skip('should return value for successor near overflow', async function () { - should(await this.decimalSuccessorNearOverflow.exec(this.ctx)).equal(MAX_FLOAT_VALUE); + should(await this.decimalSuccessorNearOverflow.exec(this.ctx)).equal(MAX_DECIMAL_VALUE); }); it.skip('should return value for predecessor near underflow', async function () { - should(await this.decimalPredecessorNearUnderflow.exec(this.ctx)).equal(MIN_FLOAT_VALUE); + should(await this.decimalPredecessorNearUnderflow.exec(this.ctx)).equal(MIN_DECIMAL_VALUE); }); }); @@ -1288,13 +1287,13 @@ describe('OutOfBounds', () => { it('should return value for Add near overflow', async function () { const result = await this.quantityAddNearOverflow.exec(this.ctx); should(result).not.be.null(); - validateQuantity(result, MAX_FLOAT_VALUE, 'mm'); + validateQuantity(result, MAX_DECIMAL_VALUE, 'mm'); }); it('should return value for Add near underflow', async function () { const result = await this.quantityAddNearUnderflow.exec(this.ctx); should(result).not.be.null(); - validateQuantity(result, MIN_FLOAT_VALUE, 'mm'); + validateQuantity(result, MIN_DECIMAL_VALUE, 'mm'); }); it('should return null for Subtract overflow', async function () { @@ -1308,13 +1307,13 @@ describe('OutOfBounds', () => { it('should return value for Subtract near overflow', async function () { const result = await this.quantitySubtractNearOverflow.exec(this.ctx); should(result).not.be.null(); - validateQuantity(result, MAX_FLOAT_VALUE, 'mm'); + validateQuantity(result, MAX_DECIMAL_VALUE, 'mm'); }); it('should return value for Subtract near underflow', async function () { const result = await this.quantitySubtractNearUnderflow.exec(this.ctx); should(result).not.be.null(); - validateQuantity(result, MIN_FLOAT_VALUE, 'mm'); + validateQuantity(result, MIN_DECIMAL_VALUE, 'mm'); }); it('should return null for Multiply overflow', async function () { @@ -1328,13 +1327,13 @@ describe('OutOfBounds', () => { it('should return value for Multiply near overflow', async function () { const result = await this.quantityMultiplyNearOverflow.exec(this.ctx); should(result).not.be.null(); - validateQuantity(result, MAX_FLOAT_VALUE, 'mm2'); + validateQuantity(result, MAX_DECIMAL_VALUE, 'mm2'); }); it('should return value for Multiply near underflow', async function () { const result = await this.quantityMultiplyNearUnderflow.exec(this.ctx); should(result).not.be.null(); - validateQuantity(result, MIN_FLOAT_VALUE, 'mm2'); + validateQuantity(result, MIN_DECIMAL_VALUE, 'mm2'); }); it('should return null for Divide overflow', async function () { @@ -1348,13 +1347,13 @@ describe('OutOfBounds', () => { it('should return value for Divide near overflow', async function () { const result = await this.quantityDivideNearOverflow.exec(this.ctx); should(result).not.be.null(); - validateQuantity(result, MAX_FLOAT_VALUE, '1'); + validateQuantity(result, MAX_DECIMAL_VALUE, '1'); }); it('should return value for Divide near underflow', async function () { const result = await this.quantityDivideNearUnderflow.exec(this.ctx); should(result).not.be.null(); - validateQuantity(result, MIN_FLOAT_VALUE, '1'); + validateQuantity(result, MIN_DECIMAL_VALUE, '1'); }); it('should return null for Divide By Zero', async function () { @@ -1373,13 +1372,13 @@ describe('OutOfBounds', () => { it.skip('should return value for successor near overflow', async function () { const result = await this.quantitySuccessorNearOverflow.exec(this.ctx); should(result).not.be.null(); - validateQuantity(result, MAX_FLOAT_VALUE, 'mm'); + validateQuantity(result, MAX_DECIMAL_VALUE, 'mm'); }); it.skip('should return value for predecessor near underflow', async function () { const result = await this.quantitPpredecessorNearOverflow.exec(this.ctx); should(result).not.be.null(); - validateQuantity(result, MIN_FLOAT_VALUE, 'mm'); + validateQuantity(result, MIN_DECIMAL_VALUE, 'mm'); }); }); diff --git a/test/elm/convert/convert-test.ts b/test/elm/convert/convert-test.ts index 6548f3e60..72f6c059a 100644 --- a/test/elm/convert/convert-test.ts +++ b/test/elm/convert/convert-test.ts @@ -5,6 +5,7 @@ import { isNull } from '../../../src/util/util'; import { DateTime } from '../../../src/datatypes/datetime'; import { Quantity } from '../../../src/datatypes/quantity'; import { Uncertainty } from '../../../src/datatypes/uncertainty'; +import { Decimal } from '../../../src/datatypes/decimal'; describe('FromString', () => { beforeEach(function () { @@ -28,7 +29,7 @@ describe('FromString', () => { }); it("should convert '10.2' to Decimal", async function () { - (await this.decimalValid.exec(this.ctx)).should.equal(10.2); + (await this.decimalValid.exec(this.ctx)).should.eql(Decimal.from(10.2)); }); it("should be null trying to convert 'abc' to Decimal", async function () { @@ -61,25 +62,25 @@ describe('FromString', () => { it('should convert "10 \'A\'" to Quantity', async function () { const quantity = await this.quantityStr.exec(this.ctx); - quantity.value.should.equal(10); + quantity.value.should.eql(Decimal.from(10)); quantity.unit.should.equal('A'); }); it('should convert "+10 \'A\'" to Quantity', async function () { const quantity = await this.posQuantityStr.exec(this.ctx); - quantity.value.should.equal(10); + quantity.value.should.eql(Decimal.from(10)); quantity.unit.should.equal('A'); }); it('should convert "-10 \'A\'" to Quantity', async function () { const quantity = await this.negQuantityStr.exec(this.ctx); - quantity.value.should.equal(-10); + quantity.value.should.eql(Decimal.from(-10)); quantity.unit.should.equal('A'); }); it('should convert "10.0\'mA\'" to Quantity', async function () { const quantity = await this.quantityStrDecimal.exec(this.ctx); - quantity.value.should.equal(10.0); + quantity.value.should.eql(Decimal.from(10.0)); quantity.unit.should.equal('mA'); }); @@ -128,7 +129,7 @@ describe('FromInteger', () => { }); it('should convert 10 to 10.0', async function () { - (await this.decimal10.exec(this.ctx)).should.equal(10.0); + (await this.decimal10.exec(this.ctx)).should.eql(Decimal.from(10.0)); }); it('should convert null to null', async function () { @@ -154,7 +155,7 @@ describe('FromLong', () => { }); it('should convert 10L to 10.0', async function () { - (await this.decimal10.exec(this.ctx)).should.equal(10.0); + (await this.decimal10.exec(this.ctx)).should.eql(Decimal.from(10.0)); }); it('should convert null to null', async function () { @@ -185,7 +186,7 @@ describe('FromQuantity', () => { it('should convert "10 \'A\'" to "10 \'A\'"', async function () { const quantity = await this.quantityQuantity.exec(this.ctx); - quantity.value.should.equal(10); + quantity.value.should.eql(Decimal.from(10)); quantity.unit.should.equal('A'); }); }); @@ -242,7 +243,7 @@ describe('FromDateTime', () => { dateTime.minute.should.equal(1); dateTime.second.should.equal(2); dateTime.millisecond.should.equal(321); - dateTime.timezoneOffset.should.equal(-6); + dateTime.timezoneOffset.should.eql(Decimal.from(-6)); }); }); @@ -344,19 +345,19 @@ describe('ToDecimal', () => { }); it("should convert '0.0' to 0.0", async function () { - (await this.noSign.exec(this.ctx)).should.equal(0.0); + (await this.noSign.exec(this.ctx)).should.eql(Decimal.from(0.0)); }); it("should convert '+1.1' to 1.1", async function () { - (await this.positiveSign.exec(this.ctx)).should.equal(1.1); + (await this.positiveSign.exec(this.ctx)).should.eql(Decimal.from(1.1)); }); it("should convert '-1.1' to -1.1", async function () { - (await this.negativeSign.exec(this.ctx)).should.equal(-1.1); + (await this.negativeSign.exec(this.ctx)).should.eql(Decimal.from(-1.1)); }); it('should truncate decimal to 8 digits after decimal point', async function () { - (await this.tooPrecise.exec(this.ctx)).should.equal(0.44444444); + (await this.tooPrecise.exec(this.ctx)).should.eql(Decimal.from(0.44444444)); }); it('should be null for decimal that is above max decimal value', async function () { @@ -560,17 +561,17 @@ describe('ToRatio', () => { it('should be valid given quantities with custom UCUM units', async function () { const ratio = await this.isValidWithCustomUCUM.exec(this.ctx); - ratio.numerator.value.should.eql(1.0); + ratio.numerator.value.should.eql(Decimal.from(1.0)); ratio.numerator.unit.should.eql('{foo:bar}'); - ratio.denominator.value.should.eql(2.0); + ratio.denominator.value.should.eql(Decimal.from(2.0)); ratio.denominator.unit.should.eql('mg'); }); it('should create valid ratio', async function () { const ratio = await this.isValid.exec(this.ctx); - ratio.numerator.value.should.eql(1.0); + ratio.numerator.value.should.eql(Decimal.from(1.0)); ratio.numerator.unit.should.eql('mg'); - ratio.denominator.value.should.eql(2.0); + ratio.denominator.value.should.eql(Decimal.from(2.0)); ratio.denominator.unit.should.eql('mg'); }); }); diff --git a/test/elm/datetime/datetime-test.ts b/test/elm/datetime/datetime-test.ts index b02346725..0c87541a8 100644 --- a/test/elm/datetime/datetime-test.ts +++ b/test/elm/datetime/datetime-test.ts @@ -4,6 +4,7 @@ const data = require('./data'); import * as DT from '../../../src/datatypes/datatypes'; import { PatientContext } from '../../../src/cql'; import { Uncertainty } from '../../../src/datatypes/uncertainty'; +import { Decimal } from '../../../src/datatypes/decimal'; describe('DateTime', () => { beforeEach(function () { @@ -99,7 +100,7 @@ describe('DateTime', () => { d.minute.should.equal(10); d.second.should.equal(59); d.millisecond.should.equal(456); - d.timezoneOffset.should.equal(-8); + d.timezoneOffset.should.eql(Decimal.from(-8)); }); }); @@ -408,8 +409,8 @@ describe('TimezoneOffsetFrom', () => { }); it('should return the timezoneoffset from a fully defined DateTime', async function () { - (await this.centralEuropean.exec(this.ctx)).should.equal(1); - (await this.easternStandard.exec(this.ctx)).should.equal(-5); + (await this.centralEuropean.exec(this.ctx)).should.eql(Decimal.from(1)); + (await this.easternStandard.exec(this.ctx)).should.eql(Decimal.from(-5)); }); it('should return the default timezone when not specified', async function () { diff --git a/test/elm/instance/instance-test.ts b/test/elm/instance/instance-test.ts index 70fc00e57..722e7ca05 100644 --- a/test/elm/instance/instance-test.ts +++ b/test/elm/instance/instance-test.ts @@ -3,6 +3,7 @@ import setup from '../../setup'; const data = require('./data'); import { Code, Concept } from '../../../src/datatypes/clinical'; import { Quantity } from '../../../src/datatypes/quantity'; +import { Decimal } from '../../../src/datatypes/decimal'; describe('Instance', () => { beforeEach(function () { @@ -13,9 +14,10 @@ describe('Instance', () => { const q = await this.quantityA.exec(this.ctx); should(q).be.instanceof(Quantity); q.unit.should.eql('a'); - q.value.should.eql(12); + const decimal12 = Decimal.from(12); + q.value.should.eql(decimal12); q.toString().should.equal("12 'a'"); - (await this.val.exec(this.ctx)).should.eql(12); + (await this.val.exec(this.ctx)).should.eql(decimal12); }); it('should be able to construct a Code', async function () { diff --git a/test/elm/interval/interval-test.ts b/test/elm/interval/interval-test.ts index 710afb3c3..45d5b94b3 100644 --- a/test/elm/interval/interval-test.ts +++ b/test/elm/interval/interval-test.ts @@ -4,14 +4,13 @@ const data = require('./data'); import { Interval } from '../../../src/datatypes/interval'; import { DateTime, MIN_DATETIME_VALUE, MAX_DATETIME_VALUE } from '../../../src/datatypes/datetime'; import { Uncertainty } from '../../../src/datatypes/uncertainty'; +import { Decimal, MAX_DECIMAL_VALUE, MIN_DECIMAL_VALUE } from '../../../src/datatypes/decimal'; import { MIN_INT_VALUE, MAX_INT_VALUE, MIN_LONG_VALUE, MAX_LONG_VALUE, - MIN_FLOAT_VALUE, - MIN_FLOAT_PRECISION_VALUE, - MAX_FLOAT_VALUE + MIN_FLOAT_PRECISION_VALUE } from '../../../src/util/limits'; describe('Interval', () => { @@ -1620,9 +1619,9 @@ describe('Width', () => { it('should calculate the width of real intervals', async function () { // define RealWidth: width of Interval[1.23, 4.56] - (await this.realWidth.exec(this.ctx)).should.equal(3.33); + (await this.realWidth.exec(this.ctx)).should.eql(Decimal.from(3.33)); // define RealOpenWidth: width of Interval(1.23, 4.56) - (await this.realOpenWidth.exec(this.ctx)).should.equal(3.32999998); + (await this.realOpenWidth.exec(this.ctx)).should.eql(Decimal.from(3.32999998)); }); it('should calculate the width of infinite intervals', async function () { @@ -1646,7 +1645,7 @@ describe('Width', () => { it('should calculate the width of interval of quantities', async function () { // define WidthOfQuantityInterval: width of Interval[Quantity{value: 1, unit: 'mm'}, Quantity{value: 10, unit: 'mm'}] const width = await this.widthOfQuantityInterval.exec(this.ctx); - width.value.should.equal(9); + width.value.should.eql(Decimal.from(9)); width.unit.should.equal('mm'); }); @@ -1687,9 +1686,9 @@ describe('Size', () => { it('should calculate the size of real intervals', async function () { // define RealSize: Size(Interval[1.23, 4.56]) - (await this.realSize.exec(this.ctx)).should.equal(3.33 + MIN_FLOAT_PRECISION_VALUE); + (await this.realSize.exec(this.ctx)).should.eql(Decimal.from(3.33 + MIN_FLOAT_PRECISION_VALUE)); // define RealOpenSize: Size(Interval(1.23, 4.56)) - (await this.realOpenSize.exec(this.ctx)).should.equal(3.32999998 + MIN_FLOAT_PRECISION_VALUE); + (await this.realOpenSize.exec(this.ctx)).should.eql(Decimal.from(3.32999998 + MIN_FLOAT_PRECISION_VALUE)); }); it('should calculate the size of infinite intervals', async function () { @@ -1723,7 +1722,7 @@ describe('Size', () => { it('should calculate size of interval of quantities', async function () { // define SizeOfQuantityInterval: Size(Interval[Quantity{value: 1, unit: 'mm'}, Quantity{value: 10, unit: 'mm'}]) const size = await this.sizeOfQuantityInterval.exec(this.ctx); - size.value.should.equal(9.00000001); + size.value.should.eql(Decimal.from(9.00000001)); size.unit.should.equal('mm'); }); @@ -1771,7 +1770,7 @@ describe('Start', () => { }); it('should return the minimum possible Decimal', async function () { - (await this.closedNullDecimal.exec(this.ctx)).should.eql(MIN_FLOAT_VALUE); + (await this.closedNullDecimal.exec(this.ctx)).should.eql(MIN_DECIMAL_VALUE); }); it('should return null when the interval is null', async function () { @@ -1821,7 +1820,7 @@ describe('End', () => { }); it('should return the maximum possible Decimal', async function () { - (await this.closedNullDecimal.exec(this.ctx)).should.eql(MAX_FLOAT_VALUE); + (await this.closedNullDecimal.exec(this.ctx)).should.eql(MAX_DECIMAL_VALUE); }); it('should return null when the interval is null', async function () { @@ -3491,6 +3490,9 @@ describe('QuantityIntervalExpand', () => { }); it('returns null when per zero, not applicable, or mismatch interval', async function () { + + console.log('debuggger') + // define perZero: expand { Interval[2 'g', 4 'g'] } per 0 'g' let a = await this.perZero.exec(this.ctx); should.not.exist(a); @@ -3619,6 +3621,7 @@ describe('LongIntervalExpand', () => { it('expands lists of multiple intervals', async function () { let a = await this.longNullInList.exec(this.ctx); prettyList(a).should.equal('{ [2, 2], [3, 3], [4, 4] }'); + // define LongOverlapping: expand { Interval[2L, 4L], Interval[3L, 5L] } per 1 '1' a = await this.longOverlapping.exec(this.ctx); prettyList(a).should.equal('{ [2, 2], [3, 3], [4, 4], [5, 5] }'); a = await this.longNonOverlapping.exec(this.ctx); diff --git a/test/elm/literal/literal-test.ts b/test/elm/literal/literal-test.ts index e46dc7228..87f1d508c 100644 --- a/test/elm/literal/literal-test.ts +++ b/test/elm/literal/literal-test.ts @@ -1,5 +1,6 @@ import should from 'should'; import setup from '../../setup'; +import { Decimal } from '../../../src/datatypes/decimal'; const data = require('./data'); describe('Literal', () => { @@ -40,11 +41,11 @@ describe('Literal', () => { }); it('should convert .1 to decimal .1', function () { - this.decimalTenth.value.should.equal(0.1); + this.decimalTenth.value.should.eql(Decimal.from(0.1)); }); it('should execute .1 as .1', async function () { - (await this.decimalTenth.exec(this.ctx)).should.equal(0.1); + (await this.decimalTenth.exec(this.ctx)).should.eql(Decimal.from(0.1)); }); it("should convert 'true' to string 'true'", function () { @@ -65,7 +66,7 @@ describe('Literal', () => { d.minute.should.equal(10); d.second.should.equal(59); d.millisecond.should.equal(456); - d.timezoneOffset.should.equal(0); + d.timezoneOffset.should.eql(Decimal.from(0)); }); it("should execute '' as correct Time", async function () { diff --git a/test/elm/message/message-test.ts b/test/elm/message/message-test.ts index 6335beca6..c1f2d29db 100644 --- a/test/elm/message/message-test.ts +++ b/test/elm/message/message-test.ts @@ -2,6 +2,7 @@ import should from 'should'; import setup from '../../setup'; const data = require('./data'); import { Repository } from '../../../src/cql'; +import { Decimal } from '../../../src/datatypes/decimal'; describe('Message', () => { let messageCollector: any; @@ -13,7 +14,7 @@ describe('Message', () => { }); it('should always return the first argument as-is', async function () { - (await this.oneOverTwo.exec(this.ctx)).should.equal(0.5); + (await this.oneOverTwo.exec(this.ctx)).should.eql(Decimal.from(0.5)); should(await this.oneOverZero.exec(this.ctx)).be.null(); }); @@ -39,7 +40,7 @@ describe('Retrieve', () => { }); it('should always return the first argument as-is', async function () { - (await this.oneOverTwo.exec(this.ctx)).should.equal(0.5); + (await this.oneOverTwo.exec(this.ctx)).should.eql(Decimal.from(0.5)); should(await this.oneOverZero.exec(this.ctx)).be.null(); }); diff --git a/test/elm/parameters/parameters-test.ts b/test/elm/parameters/parameters-test.ts index cd9210e8a..3a7c80ff4 100644 --- a/test/elm/parameters/parameters-test.ts +++ b/test/elm/parameters/parameters-test.ts @@ -3,6 +3,7 @@ import { Code, Concept } from '../../../src/datatypes/clinical'; import { Date, DateTime } from '../../../src/datatypes/datetime'; import { Interval } from '../../../src/datatypes/interval'; import { Quantity } from '../../../src/datatypes/quantity'; +import { Decimal } from '../../../src/datatypes/decimal'; import setup from '../../setup'; const data = require('./data'); @@ -99,7 +100,7 @@ describe('DecimalParameterTypes', () => { }); it('should execute to provided valid value', async function () { - (await this.foo.exec(this.ctx.withParameters({ FooP: 3.0 }))).should.equal(3.0); + (await this.foo.exec(this.ctx.withParameters({ FooP: Decimal.from(3.0) }))).should.eql(Decimal.from(3.0)); }); it('should throw when provided value is wrong type', function () { @@ -107,11 +108,11 @@ describe('DecimalParameterTypes', () => { }); it('should execute to default value', async function () { - (await this.foo2.exec(this.ctx)).should.equal(1.5); + (await this.foo2.exec(this.ctx)).should.eql(Decimal.from(1.5)); }); it('should execute to overriding valid value', async function () { - (await this.foo2.exec(this.ctx.withParameters({ FooDP: 3.0 }))).should.equal(3.0); + (await this.foo2.exec(this.ctx.withParameters({ FooDP: Decimal.from(3.0) }))).should.eql(Decimal.from(3.0)); }); it('should throw when overriding value is wrong type', function () { @@ -129,7 +130,7 @@ describe('IntegerParameterTypes', () => { }); it('should throw when provided value is wrong type', function () { - should(() => this.foo.exec(this.ctx.withParameters({ FooP: 3.5 }))).throw(/.*wrong type.*/); + should(() => this.foo.exec(this.ctx.withParameters({ FooP: Decimal.from(3.5) }))).throw(/.*wrong type.*/); }); it('should execute to default value', async function () { @@ -141,7 +142,7 @@ describe('IntegerParameterTypes', () => { }); it('should throw when overriding value is wrong type', function () { - should(() => this.foo2.exec(this.ctx.withParameters({ FooDP: 3.5 }))).throw(/.*wrong type.*/); + should(() => this.foo2.exec(this.ctx.withParameters({ FooDP: Decimal.from(3.5) }))).throw(/.*wrong type.*/); }); }); @@ -423,7 +424,7 @@ describe('IntervalParameterTypes', () => { }); it('should throw when interval contains a wrong point type', async function () { - should(() => this.foo.exec(this.ctx.withParameters({ FooP: new Interval(1.5, 5.5) }))).throw( + should(() => this.foo.exec(this.ctx.withParameters({ FooP: new Interval(Decimal.from(1.5), Decimal.from(5.5)) }))).throw( /.*wrong type.*/ ); }); @@ -443,7 +444,7 @@ describe('IntervalParameterTypes', () => { }); it('should throw when overriding interval contains a wrong point type', async function () { - should(() => this.foo2.exec(this.ctx.withParameters({ FooP: new Interval(1.5, 5.5) }))).throw( + should(() => this.foo2.exec(this.ctx.withParameters({ FooP: new Interval(Decimal.from(1.5), Decimal.from(5.5)) }))).throw( /.*wrong type.*/ ); }); diff --git a/test/elm/quantity/quantity-test.ts b/test/elm/quantity/quantity-test.ts index 290c15c8d..602f5239e 100644 --- a/test/elm/quantity/quantity-test.ts +++ b/test/elm/quantity/quantity-test.ts @@ -6,6 +6,7 @@ import { doSubtraction, Quantity } from '../../../src/datatypes/quantity'; +import { Decimal } from '../../../src/datatypes/decimal'; describe('Quantity', () => { it('should allow creation of Quantity with valid ucum units', () => @@ -62,7 +63,7 @@ describe('Quantity', () => { const denominator = new Quantity(2.0, 'mg'); const result = numerator.dividedBy(denominator); result.unit.should.equal('1'); - result.value.should.equal(-2.75); + result.value.should.eql(Decimal.from(-2.75)); }); it('should allow for singular time units', () => { diff --git a/test/elm/query/query-test.ts b/test/elm/query/query-test.ts index 95f8490f0..2ee9e3cb4 100644 --- a/test/elm/query/query-test.ts +++ b/test/elm/query/query-test.ts @@ -7,6 +7,7 @@ import { Interval } from '../../../src/datatypes/interval'; import { DateTime } from '../../../src/datatypes/datetime'; import { Quantity } from '../../../src/datatypes/quantity'; import { getLocalIdByPath } from '../../testHelpers'; +import { Decimal } from '../../../src/datatypes/decimal'; describe('DateRangeOptimizedQuery', () => { beforeEach(function () { @@ -196,12 +197,12 @@ describe('Sorting', () => { it('should correctly sort quantities asc', async function () { const e = await this.quantityListAsc.exec(this.ctx); e.should.have.length(2); - e[0]['value'].should.equal(2); + e[0]['value'].should.eql(Decimal.from(2)); }); it('should correctly sort quantities', async function () { const e = await this.quantityListSort.exec(this.ctx); - e[0]['N']['value'].should.equal(2); + e[0]['N']['value'].should.eql(Decimal.from(2)); }); it('should be able to sort by a tuple field asc', async function () { diff --git a/test/spec-tests/spec-test.ts b/test/spec-tests/spec-test.ts index cf70ead67..e1bcbf3f1 100644 --- a/test/spec-tests/spec-test.ts +++ b/test/spec-tests/spec-test.ts @@ -44,6 +44,9 @@ describe('CQL Spec Tests (from XML)', () => { } suite.expression.element.forEach((t: any) => { it(`should properly evaluate ${t.name}`, async function () { + if (t.name === 'beans') { + debugger; + } const testCaseMap = convertTupleToMap(t.value); if (testCaseMap.has('skipped')) { this.skip(); diff --git a/test/util/math-test.ts b/test/util/math-test.ts index fa9388dad..16cb165b8 100644 --- a/test/util/math-test.ts +++ b/test/util/math-test.ts @@ -1,5 +1,6 @@ import { Uncertainty } from '../../src/datatypes/uncertainty'; import { MAX_FLOAT_VALUE, MIN_FLOAT_VALUE } from '../../src/util/limits'; +import { Decimal } from '../../src/datatypes/decimal'; import { predecessor, successor } from '../../src/util/math'; import { ELM_DECIMAL_TYPE, ELM_INTEGER_TYPE } from '../../src/util/elmTypes'; @@ -11,14 +12,14 @@ describe('successor', () => { }); it('should preserve decimals in an Uncertainty', () => { - const result = successor(new Uncertainty(1.0, 2.0), ELM_DECIMAL_TYPE); - result.low.should.equal(1.00000001); - result.high.should.equal(2.00000001); + const result = successor(new Uncertainty(Decimal.from(1.0), Decimal.from(2.0)), ELM_DECIMAL_TYPE); + result.low.should.eql(Decimal.from(1.00000001)); + result.high.should.eql(Decimal.from(2.00000001)); }); it('should leave the uncertainty high unchanged when it overflows', () => { - const result = successor(new Uncertainty(1, MAX_FLOAT_VALUE), ELM_DECIMAL_TYPE); - result.should.eql(new Uncertainty(1.00000001, MAX_FLOAT_VALUE)); + const result = successor(new Uncertainty(Decimal.from(1), MAX_FLOAT_VALUE), ELM_DECIMAL_TYPE); + result.should.eql(new Uncertainty(Decimal.from(1.00000001), MAX_FLOAT_VALUE)); }); }); @@ -30,13 +31,13 @@ describe('predecessor', () => { }); it('should preserve decimals in an Uncertainty', () => { - const result = successor(new Uncertainty(1.0, 2.0), ELM_DECIMAL_TYPE); - result.low.should.equal(1.00000001); - result.high.should.equal(2.00000001); + const result = successor(new Uncertainty(Decimal.from(1.0), Decimal.from(2.0)), ELM_DECIMAL_TYPE); + result.low.should.eql(Decimal.from(1.00000001)); + result.high.should.eql(Decimal.from(2.00000001)); }); it('should leave the uncertainty low unchanged when it underflows', () => { - const result = predecessor(new Uncertainty(MIN_FLOAT_VALUE, 2), ELM_DECIMAL_TYPE); - result.should.eql(new Uncertainty(MIN_FLOAT_VALUE, 1.99999999)); + const result = predecessor(new Uncertainty(MIN_FLOAT_VALUE, Decimal.from(2)), ELM_DECIMAL_TYPE); + result.should.eql(new Uncertainty(MIN_FLOAT_VALUE, Decimal.from(1.99999999))); }); }); diff --git a/test/util/units-test.ts b/test/util/units-test.ts index f98dbb546..b3c046065 100644 --- a/test/util/units-test.ts +++ b/test/util/units-test.ts @@ -1,4 +1,5 @@ import should from 'should'; +import { Decimal } from '../../src/datatypes/decimal'; import { checkUnit, compareUnits, @@ -108,41 +109,41 @@ describe('checkUnit', () => { describe('convertUnit', () => { it('should convert compatible units', () => { - convertUnit(18, '[in_i]', '[ft_i]').should.eql(1.5); + convertUnit(Decimal.from(18), '[in_i]', '[ft_i]').should.eql(Decimal.from(1.5)); }); it('should return same value for same units', () => { - convertUnit(18, '[in_i]', '[in_i]').should.eql(18); + convertUnit(Decimal.from(18), '[in_i]', '[in_i]').should.eql(Decimal.from(18)); }); it('should consider empty as 1 during conversion', () => { - convertUnit(18, '', '').should.eql(18); - convertUnit(18, null, null).should.eql(18); - convertUnit(18, '', null).should.eql(18); - convertUnit(18, null, '').should.eql(18); + convertUnit(Decimal.from(18), '', '').should.eql(Decimal.from(18)); + convertUnit(Decimal.from(18), null, null).should.eql(Decimal.from(18)); + convertUnit(Decimal.from(18), '', null).should.eql(Decimal.from(18)); + convertUnit(Decimal.from(18), null, '').should.eql(Decimal.from(18)); }); it('should support CQL date units during conversion', () => { - convertUnit(18, 'months', 'years').should.eql(1.5); - convertUnit(1.5, 'years', 'months').should.eql(18); - convertUnit(2, 'seconds', 'milliseconds').should.eql(2000); - convertUnit(2000, 'milliseconds', 'seconds').should.eql(2); + convertUnit(Decimal.from(18), 'months', 'years').should.eql(Decimal.from(1.5)); + convertUnit(Decimal.from(1.5), 'years', 'months').should.eql(Decimal.from(18)); + convertUnit(Decimal.from(2), 'seconds', 'milliseconds').should.eql(Decimal.from(2000)); + convertUnit(Decimal.from(2000), 'milliseconds', 'seconds').should.eql(Decimal.from(2)); }); it('should truncate precision to 8 decimals by default', () => { - const result = convertUnit(1, '[ft_i]', '[mi_i]'); - result.should.equal(0.00018939); + const result = convertUnit(Decimal.from(1), '[ft_i]', '[mi_i]'); + result.should.eql(Decimal.from("0.00018939")); }); it('should note truncate precision to 8 decimals when adjustPrecision is false', () => { - const result = convertUnit(1, '[ft_i]', '[mi_i]', false); - result.should.not.equal(0.00018939); + const result = convertUnit(Decimal.from(1), '[ft_i]', '[mi_i]', false); + result.should.not.eql(Decimal.from("0.00018939")); result.toString().length.should.be.greaterThan(10); result.toString().should.startWith('0.000189393939393'); }); it('should return undefined for incompatible units', () => { - should(convertUnit(18, '[in_i]', '[in_i]2')).be.undefined(); + should(convertUnit(Decimal.from(18), '[in_i]', '[in_i]2')).be.undefined(); }); }); From cc050e9ed36db7907a8cd8be1ff6701ec2e3ba97 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Wed, 19 Aug 2026 15:33:32 -0400 Subject: [PATCH 02/19] WIP of better CQL Decimal support, checkpoint 2 --- package-lock.json | 7 + package.json | 1 + src/datatypes/datetime.ts | 36 +- src/datatypes/decimal.ts | 167 +-- src/datatypes/quantity.ts | 27 +- src/elm/aggregate.ts | 64 +- src/elm/arithmetic.ts | 201 ++- src/elm/clinical.ts | 2 +- src/elm/interval.ts | 18 +- src/elm/type.ts | 6 +- src/runtime/context.ts | 2 +- src/util/immutableUtil.ts | 16 +- src/util/math.ts | 66 +- src/util/units.ts | 31 +- test/datatypes/date-test.ts | 7 +- test/datatypes/datetime-test.ts | 5 +- test/datatypes/decimal-test.ts | 8 +- test/datatypes/interval-test.ts | 26 +- test/elm/aggregate/aggregate-test.ts | 68 +- test/elm/aggregate/data.cql | 24 +- test/elm/aggregate/data.js | 1267 ++++++++++------- test/elm/arithmetic/arithmetic-test.ts | 122 +- test/elm/arithmetic/data.cql | 2 +- test/elm/arithmetic/data.js | 6 +- test/elm/clinical/clinical-test.ts | 4 +- test/elm/convert/convert-test.ts | 42 +- test/elm/datetime/datetime-test.ts | 34 +- test/elm/interval/interval-test.ts | 16 +- test/elm/literal/literal-test.ts | 6 +- test/elm/message/message-test.ts | 4 +- test/elm/parameters/parameters-test.ts | 6 +- test/elm/quantity/quantity-test.ts | 2 +- test/elm/query/query-test.ts | 4 +- test/should-extensions.ts | 11 + .../cql/CqlArithmeticFunctionsTest.cql | 6 +- .../cql/CqlArithmeticFunctionsTest.json | 6 +- .../cql/ValueLiteralsAndSelectors.cql | 4 +- .../cql/ValueLiteralsAndSelectors.json | 4 +- test/spec-tests/skip-list.txt | 10 +- test/spec-tests/spec-test.ts | 15 +- test/util/math-test.ts | 8 +- test/util/units-test.ts | 53 +- 42 files changed, 1333 insertions(+), 1081 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e3cd80d1..e3c125fad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@lhncbc/ucum-lhc": "^7.1.9", + "decimal.js": "^10.6.0", "immutable": "^5.1.6", "luxon": "^3.7.2" }, @@ -2035,6 +2036,12 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/default-require-extensions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", diff --git a/package.json b/package.json index d7e5bd2ed..c06af919c 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ }, "dependencies": { "@lhncbc/ucum-lhc": "^7.1.9", + "decimal.js": "^10.6.0", "immutable": "^5.1.6", "luxon": "^3.7.2" }, diff --git a/src/datatypes/datetime.ts b/src/datatypes/datetime.ts index 23fcb4ed2..b8f288895 100644 --- a/src/datatypes/datetime.ts +++ b/src/datatypes/datetime.ts @@ -19,6 +19,7 @@ import { MIN_DATETIME_VALUE_STRING, MIN_TIME_VALUE_STRING } from '../util/limits'; +import { Decimal } from './decimal'; // It's easiest and most performant to organize formats by length of the supported strings. // This way we can test strings only against the formats that have a chance of working. @@ -529,7 +530,7 @@ export class DateTime extends AbstractDate { minute: number | null; second: number | null; millisecond: number | null; - timezoneOffset: number | null; + timezoneOffset: Decimal | null; static readonly Unit = { YEAR: 'year', @@ -599,13 +600,19 @@ export class DateTime extends AbstractDate { } // TODO: Note: using the jsDate type causes issues, fix later - static fromJSDate(date: any, timezoneOffset?: any) { + static fromJSDate(date: any, timezoneOffset?: number | string | Decimal) { //This is from a JS Date, not a CQL Date if (date instanceof DateTime) { return date; } if (timezoneOffset != null) { - date = new jsDate(date.getTime() + timezoneOffset * 60 * 60 * 1000); + let tzOffset: number; + if (timezoneOffset instanceof Decimal) { + tzOffset = timezoneOffset.toNumber(); + } else { + tzOffset = +timezoneOffset; + } + date = new jsDate(date.getTime() + tzOffset * 60 * 60 * 1000); return new DateTime( date.getUTCFullYear(), date.getUTCMonth() + 1, @@ -614,7 +621,7 @@ export class DateTime extends AbstractDate { date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds(), - timezoneOffset + tzOffset ); } else { return new DateTime( @@ -641,7 +648,7 @@ export class DateTime extends AbstractDate { luxonDT.minute, luxonDT.second, luxonDT.millisecond, - luxonDT.offset / 60 + Decimal.from(luxonDT.offset / 60) ); } @@ -653,7 +660,7 @@ export class DateTime extends AbstractDate { minute: number | null = null, second: number | null = null, millisecond: number | null = null, - timezoneOffset?: number | null + timezoneOffset?: Decimal | number | null ) { // from the spec: If no timezone is specified, the timezone of the evaluation request timestamp is used. // NOTE: timezoneOffset will be explicitly null for the Time overload, whereas @@ -664,9 +671,11 @@ export class DateTime extends AbstractDate { this.second = second; this.millisecond = millisecond; if (timezoneOffset === undefined) { - this.timezoneOffset = (new jsDate().getTimezoneOffset() / 60) * -1; + this.timezoneOffset = Decimal.from((new jsDate().getTimezoneOffset() / 60) * -1); + } else if (timezoneOffset === null) { + this.timezoneOffset = null; } else { - this.timezoneOffset = timezoneOffset; + this.timezoneOffset = Decimal.from(timezoneOffset); } } @@ -869,7 +878,7 @@ export class DateTime extends AbstractDate { toLuxonDateTime() { const offsetMins = this.timezoneOffset != null - ? this.timezoneOffset * 60 + ? this.timezoneOffset.toNumber() * 60 : new jsDate().getTimezoneOffset() * -1; return LuxonDateTime.fromObject( { @@ -958,10 +967,11 @@ export class DateTime extends AbstractDate { } if (str.indexOf('T') !== -1 && this.timezoneOffset != null) { - str += this.timezoneOffset < 0 ? '-' : '+'; - const offsetHours = Math.floor(Math.abs(this.timezoneOffset)); + const tzOffset = this.timezoneOffset.toNumber(); + str += tzOffset < 0 ? '-' : '+'; + const offsetHours = Math.floor(Math.abs(tzOffset)); str += String(offsetHours).padStart(2, '0'); - const offsetMin = (Math.abs(this.timezoneOffset) - offsetHours) * 60; + const offsetMin = (Math.abs(tzOffset) - offsetHours) * 60; str += ':' + String(offsetMin).padStart(2, '0'); } @@ -1202,7 +1212,7 @@ export class Date extends AbstractDate { return str; } - getDateTime(timeZoneOffset?: number | null) { + getDateTime(timeZoneOffset?: Decimal | null) { // from the spec: the result will be a DateTime with the time components unspecified, // except for the timezone offset, which will be set to the timezone offset of the evaluation // request timestamp. (this last part is achieved by passing in the timeZoneOffset from the context) diff --git a/src/datatypes/decimal.ts b/src/datatypes/decimal.ts index 815b9fb5a..55b60c82a 100644 --- a/src/datatypes/decimal.ts +++ b/src/datatypes/decimal.ts @@ -1,48 +1,75 @@ +import { Decimal as DecimalJS } from 'decimal.js'; + +// Default precision is set to 30 significant figures. (Not decimal places) +// MAX_DECIMAL_VALUE = 99999999999999999999.99999999 is 28 significant figures, +// 30 is just a cleaner number. +DecimalJS.set({ precision: 30 }); export type DecimalInput = Decimal | string | number | bigint; -export type DecimalRoundingMode = 'down' | 'half-up' | 'half-even' | 'half-ceil' | 'ceil' | 'floor'; +export type DecimalRoundingMode = DecimalJS.Rounding; + +const MIN_FLOAT_PRECISION_VALUE = DecimalJS.pow(10, -8); -const MIN_FLOAT_PRECISION_VALUE = Math.pow(10, -8); +const CQL_IMPLICIT_SCALE = 8; +const CQL_IMPLICIT_ROUNDING = DecimalJS.ROUND_HALF_UP; export class Decimal { - public readonly value: number; + private value: DecimalJS; - private constructor(value: DecimalInput) { - const numericValue = toNumber(value); - if (!Number.isFinite(numericValue)) { + private constructor(value: string | number | bigint | DecimalJS) { + this.value = new DecimalJS(value); + if (!this.value.isFinite()) { throw new Error('Cannot create a decimal with a non-finite value'); } - this.value = numericValue; } static from(value: DecimalInput) { - return value instanceof Decimal ? value : new Decimal(value); + if (value instanceof Decimal) { + return value; + } + + return new Decimal(value); } get isDecimal() { return true; } - add(other: DecimalInput) { - return new Decimal(this.value + toNumber(other)); + normalized() { + if (this.value.decimalPlaces() <= CQL_IMPLICIT_SCALE) { + return this; + } + return this.setScale(CQL_IMPLICIT_SCALE, CQL_IMPLICIT_ROUNDING); } - subtract(other: DecimalInput) { - return new Decimal(this.value - toNumber(other)); + private applyWrapper( + operation: (value: any) => DecimalJS, + other: DecimalInput + ): Decimal { + const operand = other instanceof Decimal ? other.value : other; + + return new Decimal(operation.call(this.value, operand)); } - multiplyBy(other: DecimalInput) { - return new Decimal(this.value * toNumber(other)); + add(other: DecimalInput) : Decimal { + return this.applyWrapper(this.value.add, other); } - divideBy(other: DecimalInput) { - const divisor = toNumber(other); - if (divisor === 0) { + subtract(other: DecimalInput) : Decimal { + return this.applyWrapper(this.value.minus, other); + } + + multiplyBy(other: DecimalInput) : Decimal { + return this.applyWrapper(this.value.times, other); + } + + divideBy(other: DecimalInput) : Decimal { + if (toNumber(other) === 0) { throw new RangeError('Cannot divide a decimal by zero'); } - return new Decimal(this.value / divisor); + return this.applyWrapper(this.value.dividedBy, other); } modulo(other: DecimalInput) { @@ -50,12 +77,14 @@ export class Decimal { if (divisor === 0) { throw new RangeError('Cannot calculate decimal modulo by zero'); } - return new Decimal(this.value % divisor); + return this.applyWrapper(this.value.mod, other); } compareTo(other: DecimalInput) { - const otherValue = toNumber(other); - return this.value - otherValue; + if (other instanceof Decimal) { + return this.value.comparedTo(other.value) + } + return this.value.comparedTo(other); } greaterThan(other: DecimalInput) { @@ -79,72 +108,77 @@ export class Decimal { } successor() { - return new Decimal(this.value + MIN_FLOAT_PRECISION_VALUE); + return new Decimal(this.value.add(MIN_FLOAT_PRECISION_VALUE)); } predecessor() { - return new Decimal(this.value - MIN_FLOAT_PRECISION_VALUE); + return new Decimal(this.value.minus(MIN_FLOAT_PRECISION_VALUE)); } negate() { - return new Decimal(-this.value); + return new Decimal(this.value.neg()); } abs() { - return new Decimal(Math.abs(this.value)); + return new Decimal(this.value.abs()); } - truncate() { - return Math.trunc(this.value); + truncate() : number { + return this.value.truncated().toNumber(); } - ceil() { - return Math.ceil(this.value); + truncated() : Decimal { + return new Decimal(this.value.truncated()); } - floor() { - return Math.floor(this.value); + ceil() : number { + return this.value.ceil().toNumber(); } - isInteger() { - return Number.isInteger(this.value); + floor() : number { + return this.value.floor().toNumber(); } - round(scale = 0) { - return this.setScale(scale, 'half-ceil'); + isInteger() { + return this.value.isInteger(); } power(exponent: DecimalInput) { - return new Decimal(Math.pow(this.value, toNumber(exponent))); + return this.applyWrapper(this.value.toPower, exponent); } sqrt() { - return new Decimal(Math.sqrt(this.value)); + return new Decimal(this.value.sqrt()); } ln() { - return new Decimal(Math.log(this.value)); + return new Decimal(this.value.ln()); } exp() { - return new Decimal(Math.exp(this.value)); + return new Decimal(this.value.exp()); } log(base: DecimalInput) { - return this.ln().divideBy(Decimal.from(base).ln()); + return this.applyWrapper(this.value.log, base); + } + + round(scale: number) { + // notes on rounding modes + // ROUND_HALF_UP "Rounds towards nearest neighbour. If equidistant, rounds away from zero" + // rounds 0.5 -> 1.0, -0.5 -> -1.0 + // ROUND_HALF_CEIL "Rounds towards nearest neighbour. If equidistant, rounds towards Infinity" + // rounds 0.5 -> 1.0, -0.5 -> 0.0 + // https://mikemcl.github.io/decimal.js/#modes + return this.setScale(scale, DecimalJS.ROUND_HALF_CEIL); } - /** - * Return a value at the requested number of digits after the decimal point. - * `down` truncates toward zero, matching the current ToDecimal behavior. - */ - setScale(scale: number, roundingMode: DecimalRoundingMode = 'down') { + setScale(scale: number, roundingMode: DecimalRoundingMode = DecimalJS.ROUND_DOWN) { if (!Number.isInteger(scale) || scale < 0) { throw new RangeError('Decimal scale must be a non-negative integer'); } - - const factor = Math.pow(10, scale); - return new Decimal(round(this.value * factor, roundingMode) / factor); + + return new Decimal(this.value.toDecimalPlaces(scale, roundingMode)); } toInteger() { @@ -152,12 +186,12 @@ export class Decimal { } toNumber() { - return this.value; + return this.value.toNumber(); } toLong() { - // TODO: this is wrong - return BigInt(this.toNumber()); + // TODO + return BigInt(this.toString()); } toString() { @@ -177,7 +211,7 @@ export const MIN_DECIMAL_VALUE = Decimal.from(MIN_DECIMAL_STRING); function toNumber(value: DecimalInput) { if (value instanceof Decimal) { - return value.value; + return value.toNumber(); } if (typeof value === 'string' && value.trim() === '') { // Number() and Number('') return 0 instead of NaN, so catch that case @@ -185,32 +219,3 @@ function toNumber(value: DecimalInput) { } return Number(value); } - -function round(value: number, mode: DecimalRoundingMode) { - switch (mode) { - case 'down': - return Math.trunc(value); - case 'half-up': - return value < 0 ? -Math.round(-value) : Math.round(value); - case 'half-even': - return roundHalfEven(value); - case 'half-ceil': - return Math.round(value); - case 'ceil': - return Math.ceil(value); - case 'floor': - return Math.floor(value); - } -} - -function roundHalfEven(value: number) { - const lower = Math.floor(value); - const fraction = value - lower; - if (fraction < 0.5) { - return lower; - } - if (fraction > 0.5) { - return lower + 1; - } - return lower % 2 === 0 ? lower : lower + 1; -} diff --git a/src/datatypes/quantity.ts b/src/datatypes/quantity.ts index ec98aba2b..62c56b7ec 100644 --- a/src/datatypes/quantity.ts +++ b/src/datatypes/quantity.ts @@ -13,13 +13,13 @@ export class Quantity { public readonly value: Decimal; constructor( - value: Decimal | string | number | bigint, + value?: Decimal | string | number | bigint, public unit?: any ) { if (value == null || typeof value === 'number' && isNaN(value)) { throw new Error('Cannot create a quantity with an undefined value'); } - this.value = Decimal.from(value); + this.value = Decimal.from(value).normalized(); if (!isValidDecimal(this.value)) { throw new Error('Cannot create a quantity with an invalid decimal value'); } @@ -93,14 +93,15 @@ export class Quantity { if (other != null && other.isQuantity) { if ((!this.unit && other.unit) || (this.unit && !other.unit)) { return false; - } else if (!this.unit && !other.unit) { - return this.value === other.value; + } else if (this.unit === other.unit) { + // same unit, or both are null + return this.value.equals(other.value); } else { const otherVal = convertUnit(other.value, other.unit, this.unit); if (otherVal == null) { return null; } else { - return this.value.round(8).equals(Decimal.from(otherVal)); + return this.value.equals(otherVal); } } } @@ -121,19 +122,19 @@ export class Quantity { } const [val1, unit1, val2, unit2] = normalizeUnitsWhenPossible( - this.value.toNumber(), + this.value, this.unit, - Decimal.from(other.value).toNumber(), + other.value, other.unit ); - const resultValue = Decimal.from(val1 / val2); + const resultValue = val1.divideBy(val2); const resultUnit = getQuotientOfUnits(unit1, unit2); // Check for invalid unit or value if (resultUnit == null || overflowsOrUnderflows(resultValue, ELM_DECIMAL_TYPE)) { return null; } - return new Quantity(resultValue.round(8), resultUnit); + return new Quantity(resultValue, resultUnit); } multiplyBy(other: any) { @@ -145,19 +146,19 @@ export class Quantity { } const [val1, unit1, val2, unit2] = normalizeUnitsWhenPossible( - this.value.toNumber(), + this.value, this.unit, - Decimal.from(other.value).toNumber(), + other.value, other.unit ); - const resultValue = Decimal.from(val1 * val2); + const resultValue = val1.multiplyBy(val2); const resultUnit = getProductOfUnits(unit1, unit2); // Check for invalid unit or value if (resultUnit == null || overflowsOrUnderflows(resultValue, ELM_DECIMAL_TYPE)) { return null; } - return new Quantity(resultValue.round(8), resultUnit); + return new Quantity(resultValue, resultUnit); } } diff --git a/src/elm/aggregate.ts b/src/elm/aggregate.ts index 08cd7dbdc..e94784810 100644 --- a/src/elm/aggregate.ts +++ b/src/elm/aggregate.ts @@ -26,21 +26,17 @@ function isDecimal(value: any): value is Decimal { return value != null && value.isDecimal; } -function numberValue(value: any) { - return value && value.isDecimal ? value.toNumber() : value; -} - function sumDecimals(values: Decimal[]) { - return values.reduce((sum, value) => sum.add(value)).setScale(8, 'half-up'); + return values.reduce((sum, value) => sum.add(value)); } function productDecimals(values: Decimal[]) { - return values.reduce((product, value) => product.multiplyBy(value)).setScale(8, 'half-up'); + return values.reduce((product, value) => product.multiplyBy(value)); } function decimalResult(value: number, values: any[], resultTypeName?: string) { return hasDecimals(values) || resultTypeName === ELM_DECIMAL_TYPE - ? Decimal.from(value).setScale(8, 'half-up') + ? Decimal.from(value).normalized() : value; } @@ -182,13 +178,10 @@ export class Avg extends AggregateExpression { if (hasOnlyQuantities(items)) { const sum = sumDecimals(getValuesFromQuantities(items)); - return new Quantity(sum.divideBy(items.length).setScale(8, 'half-up'), items[0].unit); + return new Quantity(sum.divideBy(items.length), items[0].unit); } else { - if (hasDecimals(items)) { - return sumDecimals(items.map(Decimal.from)).divideBy(items.length).setScale(8, 'half-up'); - } - const sum = items.reduce((x: number, y: number) => x + y); - return decimalResult(sum / items.length, items, this.resultTypeName); + // return type is always Decimal, so just map everything to Decimals + return sumDecimals(items.map(Decimal.from)).divideBy(items.length).normalized(); } } } @@ -310,37 +303,35 @@ export class StdDev extends AggregateExpression { } if (hasOnlyQuantities(items)) { - const values = getValuesFromQuantities(items).map(numberValue); + const values = getValuesFromQuantities(items); const stdDev = this.standardDeviation(values); return new Quantity(stdDev, items[0].unit); } else { - const standardDeviation = this.standardDeviation(items.map(numberValue)); - return standardDeviation == null - ? null - : decimalResult(standardDeviation, items, this.resultTypeName); + const standardDeviation = this.standardDeviation(items.map(Decimal.from)); + return standardDeviation?.normalized(); // TODO: review function signatures. always return Decimal makes sense but is it correct? } } - standardDeviation(list: any[]) { + standardDeviation(list: Decimal[]) { const val = this.stats(list); if (val) { return val[this.type]; } } - stats(list: any[]) { - const sum = list.reduce((x, y) => x + y); - const mean = sum / list.length; - let sumOfSquares = 0; + stats(list: Decimal[]) { + const sum = list.reduce((x, y) => x.add(y), Decimal.from(0)); + const mean = sum.divideBy(list.length); - for (const sq of list) { - sumOfSquares += Math.pow(sq - mean, 2); - } + const sumOfSquares = list.reduce((total, value) => { + const difference = value.subtract(mean); + return total.add(difference.power(2)); + }, Decimal.from(0)); - const std_var = (1 / (list.length - 1)) * sumOfSquares; - const pop_var = (1 / list.length) * sumOfSquares; - const std_dev = Math.sqrt(std_var); - const pop_dev = Math.sqrt(pop_var); + const std_var = sumOfSquares.divideBy(list.length - 1); + const pop_var = sumOfSquares.divideBy(list.length); + const std_dev = std_var.sqrt(); + const pop_dev = pop_var.sqrt(); return { standard_variance: std_var, population_variance: pop_var, @@ -409,16 +400,11 @@ export class GeometricMean extends AggregateExpression { if (hasOnlyQuantities(items)) { const product = productDecimals(getValuesFromQuantities(items)); - const geoMean = product.power(1.0 / items.length).setScale(8, 'half-up'); + const geoMean = product.power(1.0 / items.length); return new Quantity(geoMean, items[0].unit); } else { - if (hasDecimals(items)) { - return productDecimals(items.map(Decimal.from)) - .power(1.0 / items.length) - .setScale(8, 'half-up'); - } - const product = items.reduce((x: number, y: number) => x * y); - return decimalResult(Math.pow(product, 1.0 / items.length), items, this.resultTypeName); + return productDecimals(items.map(Decimal.from)) + .power(1.0 / items.length).normalized(); } } } @@ -518,5 +504,5 @@ function medianOfDecimals(decimals: Decimal[]) { const middle = Math.floor(items.length / 2); return items.length % 2 === 1 ? items[middle] - : items[middle - 1].add(items[middle]).divideBy(2).setScale(8, 'half-up'); + : items[middle - 1].add(items[middle]).divideBy(2); } diff --git a/src/elm/arithmetic.ts b/src/elm/arithmetic.ts index c63fa18f0..c14a01eca 100644 --- a/src/elm/arithmetic.ts +++ b/src/elm/arithmetic.ts @@ -29,39 +29,22 @@ import { MIN_LONG_VALUE } from '../util/limits'; -function isDecimal(value: any): boolean { - return value != null && value.isDecimal; -} - -function decimalResult(value: any, resultTypeName?: string): any { - if (isDecimal(value) || (typeof value === 'number' && !Number.isFinite(value))) { - return value; - } - return resultTypeName === ELM_DECIMAL_TYPE || (typeof value === 'number' && !Number.isInteger(value)) - ? Decimal.from(value).setScale(8, 'half-up') - : value; -} - -function add(x: any, y: any) { - return isDecimal(x) || isDecimal(y) ? Decimal.from(x).add(y).setScale(8, 'half-up') : x + y; -} - -function subtract(x: any, y: any) { - return isDecimal(x) || isDecimal(y) - ? Decimal.from(x).subtract(y).setScale(8, 'half-up') - : x - y; -} - -function multiply(x: any, y: any) { - return isDecimal(x) || isDecimal(y) - ? Decimal.from(x).multiplyBy(y).setScale(8, 'half-up') - : x * y; -} +function finalizeNumericResult(result: any, type?: string) { + + if (result instanceof Decimal) { + return result.normalized(); + } else if (result instanceof Quantity) { + return new Quantity(result.value.normalized(), result.unit); + } else if (result instanceof Uncertainty) { + if (result.low instanceof Quantity || result.low instanceof Decimal) { + result.low = finalizeNumericResult(result.low); + } + if (result.high instanceof Quantity || result.high instanceof Decimal) { + result.high = finalizeNumericResult(result.high); + } + } -function divide(x: any, y: any) { - return isDecimal(x) || isDecimal(y) - ? Decimal.from(x).divideBy(y).setScale(8, 'half-up') - : x / y; + return result; } export class Add extends Expression { @@ -75,7 +58,8 @@ export class Add extends Expression { return null; } - return MathUtil.add(args[0], args[1], this.resultTypeName); + const sum = MathUtil.add(args[0], args[1], this.resultTypeName); + return finalizeNumericResult(sum, this.resultTypeName); } } @@ -90,7 +74,8 @@ export class Subtract extends Expression { return null; } - return MathUtil.subtract(args[0], args[1], this.resultTypeName); + const difference = MathUtil.subtract(args[0], args[1], this.resultTypeName); + return finalizeNumericResult(difference, this.resultTypeName); } } @@ -105,30 +90,32 @@ export class Multiply extends Expression { return null; } - const product = args.reduce((x: any, y: any) => { - if (x.isUncertainty && !y.isUncertainty) { - y = new Uncertainty(y, y); - } else if (y.isUncertainty && !x.isUncertainty) { - x = new Uncertainty(x, x); - } - - if (x.isQuantity || y.isQuantity) { - return doMultiplication(x, y); - } else if (x.isUncertainty && y.isUncertainty) { - if (x.low.isQuantity) { - return new Uncertainty(doMultiplication(x.low, y.low), doMultiplication(x.high, y.high)); - } else { - return new Uncertainty(multiply(x.low, y.low), multiply(x.high, y.high)); - } + let [x, y] = args; + + if (x.isUncertainty && !y.isUncertainty) { + y = new Uncertainty(y, y); + } else if (y.isUncertainty && !x.isUncertainty) { + x = new Uncertainty(x, x); + } + + let product; + if (x.isQuantity || y.isQuantity) { + product = doMultiplication(x, y); + } else if (x.isUncertainty && y.isUncertainty) { + if (x.low.isQuantity) { + product = new Uncertainty(doMultiplication(x.low, y.low), doMultiplication(x.high, y.high)); } else { - return multiply(x, y); + product = new Uncertainty(MathUtil.multiply(x.low, y.low), MathUtil.multiply(x.high, y.high)); } - }); + } else { + product = MathUtil.multiply(x, y); + } if (MathUtil.overflowsOrUnderflows(product, this.resultTypeName)) { return null; } - return product; + + return finalizeNumericResult(product, this.resultTypeName); } } @@ -155,25 +142,28 @@ export class Divide extends Expression { if (x.isQuantity) { quotient = doDivision(x, y); } else if (x.isUncertainty && y.isUncertainty) { + let low, high; + // TODO change this section back if (x.low.isQuantity) { - quotient = new Uncertainty(doDivision(x.low, y.high), doDivision(x.high, y.low)); + low = doDivision(x.low, y.high); + high = doDivision(x.high, y.low); } else { - quotient = new Uncertainty(divide(x.low, y.high), divide(x.high, y.low)); + low = MathUtil.divide(x.low, y.high); + high = MathUtil.divide(x.high, y.low); } + quotient = new Uncertainty(low, high); } else { - quotient = divide(x, y); + quotient = MathUtil.divide(x, y); } } catch { // Decimal division by zero throws; CQL defines the result as null. return null; } - // Note, anything divided by 0 is Infinity in Javascript, which will be - // considered as overflow by this check. if (MathUtil.overflowsOrUnderflows(quotient, this.resultTypeName)) { return null; } - return quotient; + return finalizeNumericResult(quotient, this.resultTypeName); } } @@ -187,34 +177,27 @@ export class TruncatedDivide extends Expression { if (args == null || args.some((x: any) => x == null)) { return null; } - - let truncatedQuotient: number | bigint | Decimal; - if (typeof args[0] === 'bigint') { - // bigint division always truncates - try { - truncatedQuotient = args.reduce((x: bigint, y: bigint) => x / y); - } catch { - // bigint divide by 0 throws an error - return null; + + let [x, y] = args; + let quotient; + if (x.isQuantity) { + quotient = doDivision(x, y); + if (quotient instanceof Quantity) { + quotient = new Quantity(quotient.value.truncated(), quotient.unit); } } else { - try { - const quotient = args.reduce((x: any, y: any) => divide(x, y)); - const truncated = isDecimal(quotient) - ? quotient.truncate() - : quotient >= 0 - ? Math.floor(quotient) - : Math.ceil(quotient); - truncatedQuotient = decimalResult(truncated, this.resultTypeName); - } catch { - return null; + quotient = MathUtil.divide(x, y); + + // MathUtil.divide performs truncated division for Integers and Longs implicitly + if (quotient != null && (x.isDecimal || y.isDecimal || this.resultTypeName === ELM_DECIMAL_TYPE)) { + quotient = (quotient as Decimal).truncated(); } } - if (MathUtil.overflowsOrUnderflows(truncatedQuotient, this.resultTypeName)) { + if (MathUtil.overflowsOrUnderflows(quotient, this.resultTypeName)) { return null; } - return truncatedQuotient; + return quotient; } } @@ -230,16 +213,16 @@ export class Modulo extends Expression { } let modulo: number | bigint | Decimal; + const [x, y] = args; try { - modulo = args.reduce((x: any, y: any) => - isDecimal(x) || isDecimal(y) ? Decimal.from(x).modulo(y) : x % y - ); + modulo = + x.isDecimal || y.isDecimal ? Decimal.from(x).modulo(y) : x % y; } catch { // modulo divide by zero results in null according to specification return null; } - return MathUtil.decimalLongOrNull(decimalResult(modulo, this.resultTypeName)); + return MathUtil.decimalLongOrNull(finalizeNumericResult(modulo, this.resultTypeName)) } } @@ -254,7 +237,7 @@ export class Ceiling extends Expression { return null; } - return isDecimal(arg) ? arg.ceil() : Math.ceil(arg); + return arg.isDecimal ? arg.ceil() : Math.ceil(arg); } } @@ -269,7 +252,7 @@ export class Floor extends Expression { return null; } - return isDecimal(arg) ? arg.floor() : Math.floor(arg); + return arg.isDecimal ? arg.floor() : Math.floor(arg); } } @@ -284,7 +267,7 @@ export class Truncate extends Expression { return null; } - return isDecimal(arg) ? arg.truncate() : arg >= 0 ? Math.floor(arg) : Math.ceil(arg); + return arg.isDecimal ? arg.truncate() : arg >= 0 ? Math.floor(arg) : Math.ceil(arg); } } export class Abs extends Expression { @@ -303,7 +286,7 @@ export class Abs extends Expression { return MathUtil.overflowsOrUnderflows(absoluteValue, this.resultTypeName) ? null : absoluteValue; - } else if (isDecimal(arg)) { + } else if (arg.isDecimal) { const absoluteValue = arg.abs(); return MathUtil.overflowsOrUnderflows(absoluteValue, this.resultTypeName) ? null @@ -333,7 +316,7 @@ export class Negate extends Expression { return MathUtil.overflowsOrUnderflows(negatedValue, this.resultTypeName) ? null : negatedValue; - } else if (isDecimal(arg)) { + } else if (arg.isDecimal) { const negatedValue = arg.negate(); return MathUtil.overflowsOrUnderflows(negatedValue, this.resultTypeName) ? null @@ -362,10 +345,7 @@ export class Round extends Expression { } const dec = this.precision != null ? await this.precision.execute(ctx) : 0; - if (isDecimal(arg)) { - return arg.round(dec); - } - return decimalResult(Math.round(arg * Math.pow(10, dec)) / Math.pow(10, dec), this.resultTypeName); + return Decimal.from(arg).round(dec); } } @@ -381,9 +361,8 @@ export class Ln extends Expression { } try { - return isDecimal(arg) - ? arg.ln() - : MathUtil.decimalOrNull(decimalResult(Math.log(arg), ELM_DECIMAL_TYPE)); + const ln = Decimal.from(arg).ln().normalized(); + return MathUtil.decimalOrNull(ln); } catch { return null; } @@ -403,9 +382,7 @@ export class Exp extends Expression { let power; try { - power = isDecimal(arg) - ? arg.exp() - : decimalResult(Math.exp(arg), ELM_DECIMAL_TYPE); + power = Decimal.from(arg).exp().normalized(); } catch { return null; } @@ -429,12 +406,8 @@ export class Log extends Expression { } try { - const log = args.reduce((x: any, y: any) => - isDecimal(x) || isDecimal(y) - ? Decimal.from(x).log(y) - : Math.log(x) / Math.log(y) - ); - return isDecimal(log) ? log : MathUtil.decimalOrNull(decimalResult(log, ELM_DECIMAL_TYPE)); + const log = Decimal.from(args[0]).log(args[1]); + return MathUtil.decimalOrNull(log); } catch { return null; } @@ -451,8 +424,9 @@ export class Power extends Expression { if (args == null || args.some((x: any) => x == null)) { return null; } - - const power = decimalResult(args.reduce((x: any, y: any) => doPower(x, y)), this.resultTypeName); + // TODO: cql spec shows the return type is always Decimal, but that's not true + const [x, y] = args; + const power = doPower(x, y); // Note: The resultTypeName may be wrong if the exponent is a negative number. Math.overflowsOrUnderflows // already accounts for this possibility by only considering it an integer if Number.isInteger(value). @@ -465,21 +439,10 @@ export class Power extends Expression { } function doPower(x: any, y: any) { - if (isDecimal(x) || isDecimal(y)) { + if (x.isDecimal || y.isDecimal || (typeof y == 'number' && y < 0) || (typeof y === 'bigint' && y < 0n)) { + // Decimal values or negative powers always produce Decimal result return Decimal.from(x).power(y); } - if (typeof x === 'bigint' && typeof y === 'bigint' && y < 0n) { - // x ** y does not support negative exponents for bigint, so downgrade to number if possible, otherwise return null - if ( - x < BigInt(Number.MIN_SAFE_INTEGER) || - x > BigInt(Number.MAX_SAFE_INTEGER) || - y < BigInt(Number.MIN_SAFE_INTEGER) - ) { - // can't safely convert to number so just return null - return null; - } - return Number(x) ** Number(y); - } try { return x ** y; diff --git a/src/elm/clinical.ts b/src/elm/clinical.ts index 26a9c0019..5d0acd4f5 100644 --- a/src/elm/clinical.ts +++ b/src/elm/clinical.ts @@ -344,7 +344,7 @@ function calculateAge( precision: string, birthDate?: dt.Date | dt.DateTime, asOf?: dt.Date | dt.DateTime, - timeZoneOffset?: number | null + timeZoneOffset?: dt.Decimal | null ) { if (birthDate != null && asOf != null) { // Ensure we use like types (Date or DateTime) based on asOf type diff --git a/src/elm/interval.ts b/src/elm/interval.ts index 5ef38af84..14a109f44 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -516,12 +516,11 @@ export class Expand extends Expression { return results; } - expandDTishInterval(interval: any, per: any) { + expandDTishInterval(interval: any, per: Quantity) { per.unit = convertToCQLDateUnit(per.unit); if (per.unit === 'week') { - per.value *= 7; - per.unit = 'day'; + per = new Quantity(per.value.multiplyBy(7), 'day'); } // Precision Checks @@ -679,9 +678,9 @@ export class Expand extends Expression { high: any, perValue: any ) { - // If the per value is a Decimal (has a .), 8 decimal places are appropriate + // If the per value is a decimal, 8 decimal places are appropriate // Integers should have 0 Decimal places - const perIsIntegral = !perValue.toString().includes('.'); + const perIsIntegral = perValue.isInteger(); const decimalPrecision = perIsIntegral ? 0 : 8; // For the purposes of this function, we'll perform all the arithmetic using Decimals, @@ -707,8 +706,8 @@ export class Expand extends Expression { // If the interval boundaries are more precise than the per quantity, the // more precise values will be truncated to the precision specified by the // per quantity. - low = truncateDecimal(low, decimalPrecision); - high = truncateDecimal(high, decimalPrecision); + low = low.setScale(decimalPrecision); + high = high.setScale(decimalPrecision); if (low == null || high == null) { return []; @@ -783,7 +782,7 @@ function collapseIntervals(intervals: any, perWidth: any) { // width equal to the result of the successor function for the point type). if (perWidth == null) { const pointSize = intervalsClone[0].getPointSize(); - perWidth = pointSize.isQuantity ? pointSize : new Quantity(Number(pointSize), '1'); + perWidth = pointSize.isQuantity ? pointSize : new Quantity(pointSize, '1'); } // sort intervalsClone by start @@ -844,8 +843,7 @@ function collapseIntervals(intervals: any, perWidth: any) { a.high = b.high; } } else if ( - (a.high != null ? a.high.durationBetween(b.low, perWidth.unit).high : undefined) <= - perWidth.value + perWidth.value.greaterThanOrEquals(a.high != null ? a.high.durationBetween(b.low, perWidth.unit).high : undefined) ) { a.high = b.high; } else { diff --git a/src/elm/type.ts b/src/elm/type.ts index 073a12e59..2215be8b4 100644 --- a/src/elm/type.ts +++ b/src/elm/type.ts @@ -166,14 +166,14 @@ export class ToDecimal extends Expression { const arg = await this.execArgs(ctx); if (arg != null) { if (arg.isUncertainty) { - const low = Decimal.from(arg.low); - const high = Decimal.from(arg.high); + const low = Decimal.from(arg.low).normalized() + const high = Decimal.from(arg.high).normalized(); return new Uncertainty(low, high); } else { try { const decimal = Decimal.from(arg.toString()); if (isValidDecimal(decimal)) { - return decimal; + return decimal.normalized(); } } catch (_e) { return null; diff --git a/src/runtime/context.ts b/src/runtime/context.ts index 106a53639..84ec265a0 100644 --- a/src/runtime/context.ts +++ b/src/runtime/context.ts @@ -126,7 +126,7 @@ export class Context { } } - getTimezoneOffset(): number | null { + getTimezoneOffset(): dt.Decimal | null { if (this.executionDateTime != null) { return this.executionDateTime.timezoneOffset; } else if (this.parent && this.parent.getTimezoneOffset != null) { diff --git a/src/util/immutableUtil.ts b/src/util/immutableUtil.ts index 8f77acfda..d57e80772 100644 --- a/src/util/immutableUtil.ts +++ b/src/util/immutableUtil.ts @@ -1,6 +1,6 @@ import * as ucum from '@lhncbc/ucum-lhc'; import { type Collection, Map as ImmutableMap, Seq as ImmutableSeq } from 'immutable'; -import { Code, DateTime, Interval, Quantity, Ratio, Uncertainty } from '../datatypes/datatypes'; +import { Code, DateTime, Decimal, Interval, Quantity, Ratio, Uncertainty } from '../datatypes/datatypes'; import { decimalAdjust } from './math'; import { convertUnit } from './units'; @@ -56,7 +56,7 @@ export const toNormalizedKey = (js: any): NormalizedKey => { }); case DateTime: - if (typeof js.timezoneOffset === 'number' && js.timezoneOffset !== 0) { + if (js.timezoneOffset?.isDecimal && !js.timezoneOffset.equals(0)) { return ImmutableSeq(js.convertToTimezoneOffset(0)) .map((x: any) => toNormalizedKey(x)) .toMap() @@ -68,6 +68,12 @@ export const toNormalizedKey = (js: any): NormalizedKey => { .set('__instance', js.constructor); } + case Decimal: + return ImmutableMap({ + value: js.toString(), + __instance: js.constructor + }); + case Interval: return ImmutableSeq(js.toClosed()) .map((x: any) => toNormalizedKey(x)) @@ -77,7 +83,7 @@ export const toNormalizedKey = (js: any): NormalizedKey => { case Quantity: if (!js.unit) { return ImmutableMap({ - value: js.value ?? null, + value: js.value ? toNormalizedKey(js.value) : null, unit: null, __instance: js.constructor }); @@ -89,7 +95,7 @@ export const toNormalizedKey = (js: any): NormalizedKey => { if (!baseUnitKey) { // No units found - normalization not possible and use provided values return ImmutableMap({ - value: js.value ?? null, + value: js.value ? toNormalizedKey(js.value) : null, unit: js.unit ?? null, __instance: js.constructor }); @@ -99,7 +105,7 @@ export const toNormalizedKey = (js: any): NormalizedKey => { const conversionValue = convertUnit(js.value, js.unit, baseUnitKeyCode); const finalValue = conversionValue ? decimalAdjust('round', conversionValue, -8) : null; return ImmutableMap({ - value: finalValue ?? null, + value: finalValue ? toNormalizedKey(finalValue) : null, unit: baseUnitKeyCode ?? null, __instance: js.constructor }); diff --git a/src/util/math.ts b/src/util/math.ts index b0fb84436..d70db1bc3 100644 --- a/src/util/math.ts +++ b/src/util/math.ts @@ -133,22 +133,18 @@ export function add(a: any, b: any, type?: string): any { return low == null || high == null ? null : new Uncertainty(low, high); } - if (typeof a === 'bigint') { - const sum = a + (typeof b === 'bigint' ? b : BigInt(b)); - return overflowsOrUnderflows(sum, ELM_LONG_TYPE) ? null : sum; + if (a.isDecimal || b.isDecimal || type === ELM_DECIMAL_TYPE) { + const sum = Decimal.from(a).add(Decimal.from(b)); + return overflowsOrUnderflows(sum, ELM_DECIMAL_TYPE) ? null : sum; } - if (typeof b === 'bigint') { - const sum = BigInt(a) + b; + if (typeof a === 'bigint' || typeof b === 'bigint' || type === ELM_LONG_TYPE) { + const sum = BigInt(a) + BigInt(b); return overflowsOrUnderflows(sum, ELM_LONG_TYPE) ? null : sum; } if (typeof a === 'number' && typeof b === 'number') { const sum = a + b; return overflowsOrUnderflows(sum, ELM_INTEGER_TYPE) ? null : sum; } - if (a?.isDecimal && b?.isDecimal) { - const sum = a.add(b); - return overflowsOrUnderflows(sum, ELM_DECIMAL_TYPE) ? null : sum; - } if (a?.isQuantity && b?.isQuantity) { const [aValue, aUnit, bValue, bUnit] = normalizeUnitsWhenPossible( a.value, @@ -187,17 +183,63 @@ export function subtract(a: any, b: any, type?: string): any { if (typeof b === 'number' || typeof b === 'bigint') { return add(a, -b, type); } - if (a?.isDecimal && b?.isDecimal) { - const difference = a.subtract(b); - return overflowsOrUnderflows(difference, ELM_DECIMAL_TYPE) ? null : difference; + if (b?.isDecimal) { + return add(a, (b as Decimal).negate(), type); } if (b?.isQuantity) { + // Note - this path uses a fake Quantity object to defer validation of the unit return add(a, { isQuantity: true, value: b.value.negate(), unit: b.unit }, type); } throw new Error('Unsupported argument types.'); } +export function multiply(a: any, b: any, type?: string) { + if (a.isDecimal || b.isDecimal || type === ELM_DECIMAL_TYPE) { + const product = Decimal.from(a).multiplyBy(b); + return overflowsOrUnderflows(product, ELM_DECIMAL_TYPE) ? null : product; + } + if (typeof a === 'bigint' || typeof b === 'bigint' || type === ELM_LONG_TYPE) { + const product = BigInt(a) * BigInt(b); + return overflowsOrUnderflows(product, ELM_LONG_TYPE) ? null : product; + } + if (typeof a === 'number' && typeof b === 'number') { + const product = a * b; + return overflowsOrUnderflows(product, ELM_INTEGER_TYPE) ? null : product; + } + + throw new Error('Unsupported argument types.'); +} + +export function divide(a: any, b: any, type?: string) { + if (a.isDecimal || b.isDecimal || type === ELM_DECIMAL_TYPE) { + b = Decimal.from(b); + if (b.equals(0)) { + return null; + } + const quotient = Decimal.from(a).divideBy(b); + return overflowsOrUnderflows(quotient, ELM_DECIMAL_TYPE) ? null : quotient; + } + if (typeof a === 'bigint' || typeof b === 'bigint' || type === ELM_LONG_TYPE) { + if (b === 0 || b === 0n) { + return null; + } + // BigInt division is inherently truncated, eg 10n / 3n = 3n + const quotient = BigInt(a) / BigInt(b); + return overflowsOrUnderflows(quotient, ELM_LONG_TYPE) ? null : quotient; + } + if (typeof a === 'number' && typeof b === 'number') { + if (b === 0) { + return null; + } + // here we need to truncate manually to ensure the value is an integer + const quotient = Math.trunc(a / b); + return overflowsOrUnderflows(quotient, ELM_INTEGER_TYPE) ? null : quotient; + } + + throw new Error('Unsupported argument types.'); +} + export function limitDecimalPrecision( val?: T ): T | undefined { diff --git a/src/util/units.ts b/src/util/units.ts index 553720f42..688ef2f6f 100644 --- a/src/util/units.ts +++ b/src/util/units.ts @@ -68,21 +68,24 @@ export function checkUnit(unit: any, allowEmptyUnits = true, allowCQLDateUnits = return unitValidityCache.get(unit); } -export function convertUnit(fromVal: any, fromUnit: any, toUnit: any, adjustPrecision = true) { +export function convertUnit(fromVal: Decimal, fromUnit: any, toUnit: any) { [fromUnit, toUnit] = [fromUnit, toUnit].map(fixUnit); + if (fromUnit === toUnit) { + return fromVal; + } // IMPORTANT: the UCUM library operates on raw JS numbers, not our Decimal - const rawFromVal = fromVal.isDecimal ? fromVal.value : fromVal; - - const result = utils.convertUnitTo(fixUnit(fromUnit), rawFromVal, fixUnit(toUnit)); + // this means that extremely large or extremely small numbers would lose precision via this function. + // To prevent this, instead of converting fromVal directly, convert 1 unit to get the conversion factor, + // and manually multiply the fromVal by it. + const result = utils.convertUnitTo(fromUnit, 1, toUnit); if (result.status !== 'succeeded') { return; } - // note: convert result.toVal to number (by prefixing +) to keep typescript happy - const rawRetVal = adjustPrecision ? decimalAdjust('round', result.toVal, -8) : +result.toVal; - return fromVal.isDecimal ? Decimal.from(rawRetVal) : rawRetVal; + const conversionFactor = result.toVal; + return fromVal.multiplyBy(conversionFactor).normalized(); } -export function normalizeUnitsWhenPossible(val1: any, unit1: any, val2: any, unit2: any) { +export function normalizeUnitsWhenPossible(val1: Decimal, unit1: any, val2: Decimal, unit2: any) { // If both units are CQL date units, return CQL date units const useCQLDateUnits = unit1 in CQL_TO_UCUM_DATE_UNITS && unit2 in CQL_TO_UCUM_DATE_UNITS; const resultConverter = (unit: any) => { @@ -100,8 +103,8 @@ export function normalizeUnitsWhenPossible(val1: any, unit1: any, val2: any, uni // it was not convertible, so just return the quantities as-is return [val1, resultConverter(unit1), val2, resultConverter(unit2)]; } - // If the new val2 > old val2, return since we prefer conversion to smaller units - if (newVal2 >= val2) { + // If the new val2 >= old val2, return since we prefer conversion to smaller units + if (newVal2.greaterThanOrEquals(val2)) { return [val1, resultConverter(unit1), newVal2, resultConverter(newUnit2)]; } // else it was a conversion to a larger unit, so go the other way around @@ -126,11 +129,11 @@ export function convertToCQLDateUnit(unit: any) { export function compareUnits(unit1: any, unit2: any) { try { - const c = convertUnit(1, unit1, unit2) as number; - if (c && c > 1) { + const c = convertUnit(Decimal.from(1), unit1, unit2); + if (c && c.greaterThan(1)) { // unit1 is bigger (less precise) return 1; - } else if (c && c < 1) { + } else if (c && c.lessThan(1)) { // unit1 is smaller return -1; } @@ -245,7 +248,7 @@ export function getQuotientOfUnits(unit1: any, unit2: any) { // UNEXPORTED FUNCTIONS -function convertToBaseUnit(fromVal: any, fromUnit: any, toBaseUnit: any) { +function convertToBaseUnit(fromVal: Decimal, fromUnit: any, toBaseUnit: any) { const fromPower = getBaseUnitAndPower(fromUnit)[1]; const toUnit = fromPower === 1 ? toBaseUnit : `${toBaseUnit}${fromPower}`; const newVal = convertUnit(fromVal, fromUnit, toUnit); diff --git a/test/datatypes/date-test.ts b/test/datatypes/date-test.ts index 4fb294092..90080282f 100644 --- a/test/datatypes/date-test.ts +++ b/test/datatypes/date-test.ts @@ -3,6 +3,7 @@ import should from 'should'; import { Date, DateTime, MAX_DATE_VALUE, MIN_DATE_VALUE } from '../../src/datatypes/datetime'; import { Uncertainty } from '../../src/datatypes/uncertainty'; import { jsDate } from '../../src/util/util'; +import { Decimal } from '../../src/datatypes/decimal'; describe('Date', () => { it('should properly set all properties when constructed', () => { @@ -884,11 +885,11 @@ describe('Date.getPrecisionValue', () => { describe('Date.getDateTime', () => { it('should return a DateTime that has the passed in timeZoneOffset', () => { const d = new Date(2000, 12, 1); - const dateTime = d.getDateTime(2); + const dateTime = d.getDateTime(Decimal.from(2)); dateTime.year.should.equal(2000); dateTime.month.should.equal(12); dateTime.day.should.equal(1); - dateTime.timezoneOffset.should.equal(2); + dateTime.timezoneOffset.should.equalDecimal(Decimal.from(2)); }); it('should return a DateTime with a timeZoneOffset when one is not passed in', () => { @@ -897,7 +898,7 @@ describe('Date.getDateTime', () => { dateTime.year.should.equal(2000); dateTime.month.should.equal(12); dateTime.day.should.equal(1); - dateTime.timezoneOffset.should.equal((new jsDate().getTimezoneOffset() / 60) * -1); + dateTime.timezoneOffset.should.equalDecimal(Decimal.from((new jsDate().getTimezoneOffset() / 60) * -1)); }); it('should return a DateTime without a timeZoneOffset when a null timeZoneOffset is passed in', () => { diff --git a/test/datatypes/datetime-test.ts b/test/datatypes/datetime-test.ts index cd4e71f24..e771a8aca 100644 --- a/test/datatypes/datetime-test.ts +++ b/test/datatypes/datetime-test.ts @@ -2,6 +2,7 @@ import * as luxon from 'luxon'; import should from 'should'; import { DateTime, MAX_DATETIME_VALUE, MIN_DATETIME_VALUE } from '../../src/datatypes/datetime'; import { Uncertainty } from '../../src/datatypes/uncertainty'; +import { Decimal } from '../../src/datatypes/decimal'; const tzDate = function ( y: number, @@ -59,13 +60,13 @@ describe('DateTime', () => { d.minute.should.equal(25); d.second.should.equal(59); d.millisecond.should.equal(246); - d.timezoneOffset.should.equal(5.5); + d.timezoneOffset.should.equalDecimal(Decimal.from(5.5)); }); it('should leave unset properties as undefined', () => { const d = new DateTime(2000); d.year.should.equal(2000); - d.timezoneOffset.should.equal((new Date().getTimezoneOffset() / 60) * -1); + d.timezoneOffset.should.equalDecimal(Decimal.from((new Date().getTimezoneOffset() / 60) * -1)); should.not.exist(d.month); should.not.exist(d.day); should.not.exist(d.hour); diff --git a/test/datatypes/decimal-test.ts b/test/datatypes/decimal-test.ts index 780b1ad21..54e693207 100644 --- a/test/datatypes/decimal-test.ts +++ b/test/datatypes/decimal-test.ts @@ -28,10 +28,10 @@ describe('Decimal', () => { Decimal.from('-1.9').truncate().should.equal(-1); Decimal.from('1.1').ceil().should.equal(2); Decimal.from('1.9').floor().should.equal(1); - Decimal.from('-0.5').round().should.eql(Decimal.from(0)); - Decimal.from('2').power(3).should.eql(Decimal.from(8)); - Decimal.from('9').sqrt().should.eql(Decimal.from(3)); - Decimal.from('8').log(2).should.eql(Decimal.from(3)); + Decimal.from('-0.5').setScale(0).should.equalDecimal(Decimal.from(0)); + Decimal.from('2').power(3).should.equalDecimal(Decimal.from(8)); + Decimal.from('9').sqrt().should.equalDecimal(Decimal.from(3)); + Decimal.from('8').log(2).should.equalDecimal(Decimal.from(3)); }); it('should reject non-finite and divide-by-zero values', () => { diff --git a/test/datatypes/interval-test.ts b/test/datatypes/interval-test.ts index 29d4ff072..57f1e15de 100644 --- a/test/datatypes/interval-test.ts +++ b/test/datatypes/interval-test.ts @@ -23,10 +23,8 @@ import { ELM_TIME_TYPE } from '../../src/util/elmTypes'; import { - MAX_FLOAT_VALUE, MAX_INT_VALUE, MAX_LONG_VALUE, - MIN_FLOAT_VALUE, MIN_INT_VALUE, MIN_LONG_VALUE } from '../../src/util/limits'; @@ -134,7 +132,7 @@ describe('Interval', () => { }); it('should return the point size for Decimal intervals', () => { - new Interval(Decimal.from(0.5), Decimal.from(9.5)).getPointSize().should.eql(Decimal.from(0.00000001)); + new Interval(Decimal.from(0.5), Decimal.from(9.5)).getPointSize().should.equalDecimal(Decimal.from(0.00000001)); }); it('should return the point size for Quantity intervals', () => { @@ -156,7 +154,7 @@ describe('Interval', () => { it('should return low for intervals with closed low', () => { d.zeroToHundred.closed.start().should.equal(0); - d.zeroPointFiveToNinePointFive.closed.start().should.eql(Decimal.from(0.5)); + d.zeroPointFiveToNinePointFive.closed.start().should.equalDecimal(Decimal.from(0.5)); d.zeroToHundredLong.closed.start().should.equal(0n); d.zeroToHundredMg.closed.start().should.eql(new Quantity(0, 'mg')); d.all2012date.closed.start().should.eql(Date.parse('2012-01-01')); @@ -166,7 +164,7 @@ describe('Interval', () => { it('should return successor of low for intervals with open low', () => { d.zeroToHundred.openClosed.start().should.equal(1); - d.zeroPointFiveToNinePointFive.openClosed.start().should.eql(Decimal.from(0.50000001)); + d.zeroPointFiveToNinePointFive.openClosed.start().should.equalDecimal(Decimal.from("0.50000001")); d.zeroToHundredLong.openClosed.start().should.equal(1n); d.zeroToHundredMg.openClosed.start().should.eql(new Quantity(0.00000001, 'mg')); d.all2012date.openClosed.start().should.eql(Date.parse('2012-01-02')); @@ -179,10 +177,10 @@ describe('Interval', () => { it('should return type minimum for closed null low endpoints', () => { d.zeroToHundred.withNullStart.closed.start().should.equal(MIN_INT_VALUE); d.zeroToHundredLong.withNullStart.closed.start().should.equal(MIN_LONG_VALUE); - d.zeroPointFiveToNinePointFive.withNullStart.closed.start().should.eql(Decimal.from(MIN_FLOAT_VALUE)); + d.zeroPointFiveToNinePointFive.withNullStart.closed.start().should.equalDecimal(MIN_DECIMAL_VALUE); d.zeroToHundredMg.withNullStart.closed .start() - .should.eql(new Quantity(MIN_FLOAT_VALUE, 'mg')); + .should.eql(new Quantity(MIN_DECIMAL_VALUE, 'mg')); d.all2012date.withNullStart.closed.start().should.eql(MIN_DATE_VALUE); d.all2012.withNullStart.closed.start().should.eql(MIN_DATETIME_VALUE); d.alldaytime.withNullStart.closed.start().should.eql(MIN_TIME_VALUE); @@ -256,7 +254,7 @@ describe('Interval', () => { new Interval(null, null, true, true, ELM_DECIMAL_TYPE).start().should.eql(MIN_DECIMAL_VALUE); new Interval(null, null, true, true, ELM_QUANTITY_TYPE) .start() - .should.eql(new Quantity(MIN_FLOAT_VALUE, '1')); + .should.eql(new Quantity(MIN_DECIMAL_VALUE, '1')); new Interval(null, null, true, true, ELM_DATETIME_TYPE) .start() .should.eql(MIN_DATETIME_VALUE); @@ -296,7 +294,7 @@ describe('Interval', () => { it('should return high for intervals with closed high', () => { d.zeroToHundred.closed.end().should.equal(100); - d.zeroPointFiveToNinePointFive.closed.end().should.eql(Decimal.from(9.5)); + d.zeroPointFiveToNinePointFive.closed.end().should.equalDecimal(Decimal.from(9.5)); d.zeroToHundredLong.closed.end().should.equal(100n); d.zeroToHundredMg.closed.end().should.eql(new Quantity(100, 'mg')); d.all2012date.closed.end().should.eql(Date.parse('2012-12-31')); @@ -306,7 +304,7 @@ describe('Interval', () => { it('should return predecessor of high for intervals with open high', () => { d.zeroToHundred.closedOpen.end().should.equal(99); - d.zeroPointFiveToNinePointFive.closedOpen.end().should.eql(Decimal.from(9.49999999)); + d.zeroPointFiveToNinePointFive.closedOpen.end().should.equalDecimal(Decimal.from(9.49999999)); d.zeroToHundredLong.closedOpen.end().should.equal(99n); d.zeroToHundredMg.closedOpen.end().should.eql(new Quantity(99.99999999, 'mg')); d.all2012date.closedOpen.end().should.eql(Date.parse('2012-12-30')); @@ -388,7 +386,7 @@ describe('Interval', () => { new Interval(null, null, true, true, ELM_DECIMAL_TYPE).end().should.eql(MAX_DECIMAL_VALUE); new Interval(null, null, true, true, ELM_QUANTITY_TYPE) .end() - .should.eql(new Quantity(MAX_FLOAT_VALUE, '1')); + .should.eql(new Quantity(MAX_DECIMAL_VALUE, '1')); new Interval(null, null, true, true, ELM_DATETIME_TYPE).end().should.eql(MAX_DATETIME_VALUE); new Interval(null, null, true, true, ELM_DATE_TYPE).end().should.eql(MAX_DATE_VALUE); new Interval(null, null, true, true, ELM_TIME_TYPE).end().should.eql(MAX_TIME_VALUE); @@ -407,7 +405,7 @@ describe('Interval', () => { new Interval(null, null, false, false, ELM_QUANTITY_TYPE) .end() .should.eql( - new Uncertainty(new Quantity(MIN_FLOAT_VALUE, '1'), new Quantity(MAX_DECIMAL_VALUE, '1')) + new Uncertainty(new Quantity(MIN_DECIMAL_VALUE, '1'), new Quantity(MAX_DECIMAL_VALUE, '1')) ); new Interval(null, null, false, false, ELM_DATETIME_TYPE) .end() @@ -7006,8 +7004,8 @@ describe('DecimalInterval', () => { it('should calculate width and size outside the Integer range', () => { const interval = new Interval(Decimal.from(0.0), Decimal.from(3000000000.0), true, true, ELM_DECIMAL_TYPE); - interval.width().should.eql(Decimal.from(3000000000.0)); - interval.size().should.eql(Decimal.from(3000000000.0)); + interval.width().should.equalDecimal(Decimal.from("3000000000.0")); + interval.size().should.equalDecimal(Decimal.from("3000000000.00000001")); }); it('should close open decimal uncertainty endpoints using decimal point size', () => { diff --git a/test/elm/aggregate/aggregate-test.ts b/test/elm/aggregate/aggregate-test.ts index d4da6e1bd..6adc191e6 100644 --- a/test/elm/aggregate/aggregate-test.ts +++ b/test/elm/aggregate/aggregate-test.ts @@ -1,10 +1,10 @@ import should from 'should'; import setup from '../../setup'; -import { Decimal } from '../../../src/datatypes/decimal'; +import { Decimal, MAX_DECIMAL_VALUE, MIN_DECIMAL_VALUE } from '../../../src/datatypes/decimal'; const data = require('./data'); const validateQuantity = function (object: any, expectedValue: any, expectedUnit: any) { object.isQuantity.should.be.true(); - object.value.should.eql(Decimal.from(expectedValue)); + object.value.should.equalDecimal(expectedValue); object.unit.should.equal(expectedUnit); }; @@ -73,11 +73,11 @@ describe('Sum', () => { }); it('should be able to sum lists with decimals', async function () { - (await this.decimals.exec(this.ctx)).should.eql(Decimal.from(16.5)); + (await this.decimals.exec(this.ctx)).should.equalDecimal(Decimal.from(16.5)); }); it('should be able to sum decimals up to max decimal value', async function () { - (await this.decimals_at_max_value.exec(this.ctx)).should.eql(Decimal.from(99999999999999999999.99999999)); + (await this.decimals_at_max_value.exec(this.ctx)).should.equalDecimal(MAX_DECIMAL_VALUE); }); it('should return null when overflowing the max decimal value', async function () { @@ -85,7 +85,7 @@ describe('Sum', () => { }); it('should be able to sum decimals down to min decimal value', async function () { - (await this.decimals_at_min_value.exec(this.ctx)).should.eql(Decimal.from(-99999999999999999999.99999999)); + (await this.decimals_at_min_value.exec(this.ctx)).should.equalDecimal(MIN_DECIMAL_VALUE); }); it('should return null when underflowing the min decimal value', async function () { @@ -100,7 +100,7 @@ describe('Sum', () => { it('should be able to sum quantities up to max decimal value', async function () { validateQuantity( await this.quantities_at_max_value.exec(this.ctx), - 99999999999999999999.99999999, + MAX_DECIMAL_VALUE, 'ml' ); }); @@ -112,7 +112,7 @@ describe('Sum', () => { it('should be able to sum quantities down to min decimal value', async function () { validateQuantity( await this.quantities_at_min_value.exec(this.ctx), - -99999999999999999999.99999999, + MIN_DECIMAL_VALUE, 'ml' ); }); @@ -184,7 +184,7 @@ describe('Min', () => { }); it('list of Decimals', async function () { - (await this.decimalMin.exec(this.ctx)).should.eql(Decimal.from(-5)); + (await this.decimalMin.exec(this.ctx)).should.equalDecimal(Decimal.from(-5)); }); it('list of DateTimes', async function () { @@ -261,7 +261,7 @@ describe('Max', () => { }); it('list of Decimals', async function () { - (await this.decimalMax.exec(this.ctx)).should.eql(Decimal.from(5.1)); + (await this.decimalMax.exec(this.ctx)).should.equalDecimal(Decimal.from(5.1)); }); it('list of DateTimes', async function () { @@ -310,28 +310,28 @@ describe('Avg', () => { }); it('should be able to find average for lists without nulls', async function () { - (await this.not_null.exec(this.ctx)).should.eql(Decimal.from(3)); + (await this.not_null.exec(this.ctx)).should.equalDecimal(Decimal.from(3)); }); it('should be able to find average for lists with nulls', async function () { - (await this.has_null.exec(this.ctx)).should.eql(Decimal.from(1.5)); + (await this.has_null.exec(this.ctx)).should.equalDecimal(Decimal.from(1.5)); }); it('should return null for empty list', async function () { should(await this.empty.exec(this.ctx)).be.null(); }); - it('should be able to find average for lists of quantiies without nulls', async function () { + it('should be able to find average for lists of quantities without nulls', async function () { const q = await this.not_null_q.exec(this.ctx); validateQuantity(q, 3, 'ml'); }); - it('should be able to find average for lists of quantiies with nulls', async function () { + it('should be able to find average for lists of quantities with nulls', async function () { const q = await this.has_null_q.exec(this.ctx); validateQuantity(q, 1.5, 'ml'); }); - it('should be able to find average for lists of quantiies with related units', async function () { + it('should be able to find average for lists of quantities with related units', async function () { const q = await this.q_diff_units.exec(this.ctx); validateQuantity(q, 3, 'ml'); }); @@ -351,19 +351,19 @@ describe('Median', () => { }); it('should be able to find median of odd numbered list', async function () { - (await this.odd.exec(this.ctx)).should.eql(Decimal.from(3)); + (await this.odd.exec(this.ctx)).should.equalDecimal(Decimal.from(3)); }); it('should be able to find median of even numbered list', async function () { - (await this.even.exec(this.ctx)).should.eql(Decimal.from(3.5)); + (await this.even.exec(this.ctx)).should.equalDecimal(Decimal.from(3.5)); }); it('should be able to find median of odd numbered list that contains duplicates', async function () { - (await this.dup_vals_odd.exec(this.ctx)).should.eql(Decimal.from(3)); + (await this.dup_vals_odd.exec(this.ctx)).should.equalDecimal(Decimal.from(3)); }); it('should be able to find median of even numbered list that contians duplicates', async function () { - (await this.dup_vals_even.exec(this.ctx)).should.eql(Decimal.from(2.5)); + (await this.dup_vals_even.exec(this.ctx)).should.equalDecimal(Decimal.from(2.5)); }); it('should return null for empty list', async function () { @@ -438,7 +438,7 @@ describe('PopulationVariance', () => { setup(this, data); }); it('should be able to find PopulationVariance of a list ', async function () { - (await this.v.exec(this.ctx)).should.eql(Decimal.from(2)); + (await this.v.exec(this.ctx)).should.equalDecimal(Decimal.from(2)); }); it('should be able to find PopulationVariance of a list of like quantities', async function () { validateQuantity(await this.v_q.exec(this.ctx), 2, 'ml'); @@ -459,7 +459,7 @@ describe('Variance', () => { setup(this, data); }); it('should be able to find Variance of a list ', async function () { - (await this.v.exec(this.ctx)).should.eql(Decimal.from(2.5)); + (await this.v.exec(this.ctx)).should.equalDecimal(Decimal.from(2.5)); }); it('should be able to find Variance of a list of matched quantities', async function () { validateQuantity(await this.v_q.exec(this.ctx), 2.5, 'ml'); @@ -480,13 +480,13 @@ describe('StdDev', () => { setup(this, data); }); it('should be able to find Standard Dev of a list ', async function () { - (await this.std.exec(this.ctx)).should.eql(Decimal.from(1.58113883)); + (await this.std.exec(this.ctx)).should.equalDecimal(Decimal.from("1.58113883")); }); it('should be able to find Standard Dev of a list of like quantities', async function () { - validateQuantity(await this.std_q.exec(this.ctx), 1.5811388300841898, 'ml'); + validateQuantity(await this.std_q.exec(this.ctx), "1.58113883", 'ml'); }); it('should be able to find Standard Dev of a list of related quantities', async function () { - validateQuantity(await this.q_diff_units.exec(this.ctx), 1.5811388300841898, 'ml'); + validateQuantity(await this.q_diff_units.exec(this.ctx), "1.58113883", 'ml'); }); it('should be null if some are numbers and some are quantities', async function () { should(await this.numbersAndQuantities.exec(this.ctx)).be.null(); @@ -501,13 +501,13 @@ describe('PopulationStdDev', () => { setup(this, data); }); it('should be able to find Population Standard Dev of a list ', async function () { - (await this.dev.exec(this.ctx)).should.eql(Decimal.from(1.41421356)); + (await this.dev.exec(this.ctx)).should.equalDecimal(Decimal.from("1.41421356")); }); it('should be able to find Population Standard Dev of a list of quantities', async function () { - validateQuantity(await this.dev_q.exec(this.ctx), 1.4142135623730951, 'ml'); + validateQuantity(await this.dev_q.exec(this.ctx), "1.41421356", 'ml'); }); it('should be able to find Population Standard Dev of a list of related quantities', async function () { - validateQuantity(await this.q_diff_units.exec(this.ctx), 1.4142135623730951, 'ml'); + validateQuantity(await this.q_diff_units.exec(this.ctx), "1.41421356", 'ml'); }); it('should be null if some are numbers and some are quantities', async function () { should(await this.numbersAndQuantities.exec(this.ctx)).be.null(); @@ -563,12 +563,12 @@ describe('Product', () => { }); it('should return a decimal product', async function () { - (await this.decimal_product.exec(this.ctx)).should.eql(Decimal.from(24.0)); + (await this.decimal_product.exec(this.ctx)).should.equalDecimal(Decimal.from(24.0)); }); it('should return decimal product up to max decimal value', async function () { (await this.decimals_at_max_value_product.exec(this.ctx)).should.eql( - Decimal.from(99999999999999999999.99999999) + MAX_DECIMAL_VALUE ); }); @@ -578,7 +578,7 @@ describe('Product', () => { it('should return decimal product down to min decimal value', async function () { (await this.decimals_at_min_value_product.exec(this.ctx)).should.eql( - Decimal.from(-99999999999999999999.99999999) + MIN_DECIMAL_VALUE ); }); @@ -597,7 +597,7 @@ describe('Product', () => { it('should return quantity product up to max decimal value', async function () { validateQuantity( await this.quantities_at_max_value_product.exec(this.ctx), - 99999999999999999999.99999999, + MAX_DECIMAL_VALUE, 'g' ); }); @@ -609,7 +609,7 @@ describe('Product', () => { it('should return quantity product down to min decimal value', async function () { validateQuantity( await this.quantities_at_min_value_product.exec(this.ctx), - -99999999999999999999.99999999, + MIN_DECIMAL_VALUE, 'g' ); }); @@ -654,15 +654,15 @@ describe('GeometricMean', () => { }); it('should return decimal geometric mean', async function () { - (await this.decimal_geometric_mean.exec(this.ctx)).should.eql(Decimal.from(4.0)); + (await this.decimal_geometric_mean.exec(this.ctx)).should.equalDecimal(Decimal.from(4.0)); }); it('should retun 0 as a geometric mean', async function () { - (await this.zero_geometric_mean.exec(this.ctx)).should.eql(Decimal.from(0)); + (await this.zero_geometric_mean.exec(this.ctx)).should.equalDecimal(Decimal.from(0)); }); it('should return value when pass in list that contains nulls', async function () { - (await this.null_geometric_mean.exec(this.ctx)).should.eql(Decimal.from(1.41421356)); + (await this.null_geometric_mean.exec(this.ctx)).should.equalDecimal(Decimal.from("1.41421356")); }); it('should return null when list is all null', async function () { diff --git a/test/elm/aggregate/data.cql b/test/elm/aggregate/data.cql index 40968e0b3..c82e2527e 100644 --- a/test/elm/aggregate/data.cql +++ b/test/elm/aggregate/data.cql @@ -21,10 +21,14 @@ define decimals_above_max_value: Sum({99999999999999999999.99999999, 99999999999 define decimals_at_min_value: Sum({-99999999999999999999.99999999}) define decimals_below_min_value: Sum({-99999999999999999999.99999999, -99999999999999999999.99999999}) define quantities: Sum({1 'ml',2 'ml',3 'ml',4 'ml',5 'ml'}) -define quantities_at_max_value: Sum({99999999999999999999.99999999 'ml'}) -define quantities_above_max_value: Sum({99999999999999999999.99999999 'ml', 99999999999999999999.99999999 'ml'}) -define quantities_at_min_value: Sum({-99999999999999999999.99999999 'ml'}) -define quantities_below_min_value: Sum({-99999999999999999999.99999999 'ml', -99999999999999999999.99999999 'ml'}) +// Max/Min-valued quantities are described using the "maximum" and "minimum" operators +// to avoid the translator rounding them to +/-1.0e20, which is not a legal Decimal +define MaxValueMLQuantity: Quantity { value: maximum Decimal, unit: 'ml' } +define MinValueMLQuantity: Quantity { value: minimum Decimal, unit: 'ml' } +define quantities_at_max_value: Sum({MaxValueMLQuantity}) +define quantities_above_max_value: Sum({MaxValueMLQuantity, MaxValueMLQuantity}) +define quantities_at_min_value: Sum({MinValueMLQuantity}) +define quantities_below_min_value: Sum({MinValueMLQuantity, MinValueMLQuantity}) define has_null: Sum({1,null,null,null,2}) define has_null_q: Sum({1 'ml',null,null,null,2 'ml'}) define unmatched_units_q: Min({1 'ml',2 'm',3 'ml',4 'ml',5 'ml',0 'ml'}) @@ -156,10 +160,14 @@ define decimals_above_max_value_product: Product({99999999999999999999.99999999, define decimals_at_min_value_product: Product({-99999999999999999999.99999999, 1.0}) define decimals_below_min_value_product: Product({-99999999999999999999.99999999, 2.0}) define quantity_product: Product({1.0 'g', 2.0 'g', 3.0 'g', 4.0 'g'}) -define quantities_at_max_value_product: Product({99999999999999999999.99999999 'g', 1.0 'g'}) -define quantities_above_max_value_product: Product({99999999999999999999.99999999 'g', 2.0 'g'}) -define quantities_at_min_value_product: Product({-99999999999999999999.99999999 'g', 1.0 'g'}) -define quantities_below_min_value_product: Product({-99999999999999999999.99999999 'g', 2.0 'g'}) +// Max/Min-valued quantities are described using the "maximum" and "minimum" operators +// to avoid the translator rounding them to +/-1.0e20, which is not a legal Decimal +define MaxValueGramQuantity: Quantity { value: maximum Decimal, unit: 'g' } +define MinValueGramQuantity: Quantity { value: minimum Decimal, unit: 'g' } +define quantities_at_max_value_product: Product({MaxValueGramQuantity, 1.0 'g'}) +define quantities_above_max_value_product: Product({MaxValueGramQuantity, 2.0 'g'}) +define quantities_at_min_value_product: Product({MinValueGramQuantity, 1.0 'g'}) +define quantities_below_min_value_product: Product({MinValueGramQuantity, 2.0 'g'}) define quantity_zero_product: Product({1.0 'g', 2.0 'g', 0 'g'}) define zero_product: Product({0, 5, 10}) define product_with_null: Product({5, 4, null}) diff --git a/test/elm/aggregate/data.js b/test/elm/aggregate/data.js index f3231bca3..9199f9626 100644 --- a/test/elm/aggregate/data.js +++ b/test/elm/aggregate/data.js @@ -499,10 +499,14 @@ define decimals_above_max_value: Sum({99999999999999999999.99999999, 99999999999 define decimals_at_min_value: Sum({-99999999999999999999.99999999}) define decimals_below_min_value: Sum({-99999999999999999999.99999999, -99999999999999999999.99999999}) define quantities: Sum({1 'ml',2 'ml',3 'ml',4 'ml',5 'ml'}) -define quantities_at_max_value: Sum({99999999999999999999.99999999 'ml'}) -define quantities_above_max_value: Sum({99999999999999999999.99999999 'ml', 99999999999999999999.99999999 'ml'}) -define quantities_at_min_value: Sum({-99999999999999999999.99999999 'ml'}) -define quantities_below_min_value: Sum({-99999999999999999999.99999999 'ml', -99999999999999999999.99999999 'ml'}) +// Max/Min-valued quantities are described using the "maximum" and "minimum" operators +// to avoid the translator rounding them to +/-1.0e20, which is not a legal Decimal +define MaxValueMLQuantity: Quantity { value: maximum Decimal, unit: 'ml' } +define MinValueMLQuantity: Quantity { value: minimum Decimal, unit: 'ml' } +define quantities_at_max_value: Sum({MaxValueMLQuantity}) +define quantities_above_max_value: Sum({MaxValueMLQuantity, MaxValueMLQuantity}) +define quantities_at_min_value: Sum({MinValueMLQuantity}) +define quantities_below_min_value: Sum({MinValueMLQuantity, MinValueMLQuantity}) define has_null: Sum({1,null,null,null,2}) define has_null_q: Sum({1 'ml',null,null,null,2 'ml'}) define unmatched_units_q: Min({1 'ml',2 'm',3 'ml',4 'ml',5 'ml',0 'ml'}) @@ -524,7 +528,7 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "682", + "r" : "694", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -2285,7 +2289,7 @@ module.exports['Sum'] = { }, { "localId" : "502", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "name" : "quantities_at_max_value", + "name" : "MaxValueMLQuantity", "context" : "Patient", "accessLevel" : "Public", "annotation" : [ { @@ -2293,20 +2297,170 @@ module.exports['Sum'] = { "t" : [ ], "s" : { "r" : "502", + "s" : [ { + "value" : [ "// Max/Min-valued quantities are described using the \"maximum\" and \"minimum\" operators\n// to avoid the translator rounding them to +/-1.0e20, which is not a legal Decimal\n", "define ", "MaxValueMLQuantity", ": " ] + }, { + "r" : "503", + "s" : [ { + "value" : [ "Quantity", " { " ] + }, { + "s" : [ { + "value" : [ "value", ": " ] + }, { + "r" : "506", + "s" : [ { + "value" : [ "maximum", " " ] + }, { + "r" : "505", + "s" : [ { + "value" : [ "Decimal" ] + } ] + } ] + } ] + }, { + "value" : [ ", " ] + }, { + "s" : [ { + "value" : [ "unit", ": " ] + }, { + "r" : "507", + "s" : [ { + "value" : [ "'ml'" ] + } ] + } ] + }, { + "value" : [ " }" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Instance", + "localId" : "503", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "classType" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ], + "element" : [ { + "name" : "value", + "value" : { + "type" : "MaxValue", + "localId" : "506", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + }, { + "name" : "unit", + "value" : { + "type" : "Literal", + "localId" : "507", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "ml", + "annotation" : [ ] + } + } ] + } + }, { + "localId" : "511", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "name" : "MinValueMLQuantity", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "511", + "s" : [ { + "value" : [ "", "define ", "MinValueMLQuantity", ": " ] + }, { + "r" : "512", + "s" : [ { + "value" : [ "Quantity", " { " ] + }, { + "s" : [ { + "value" : [ "value", ": " ] + }, { + "r" : "515", + "s" : [ { + "value" : [ "minimum", " " ] + }, { + "r" : "514", + "s" : [ { + "value" : [ "Decimal" ] + } ] + } ] + } ] + }, { + "value" : [ ", " ] + }, { + "s" : [ { + "value" : [ "unit", ": " ] + }, { + "r" : "516", + "s" : [ { + "value" : [ "'ml'" ] + } ] + } ] + }, { + "value" : [ " }" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Instance", + "localId" : "512", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "classType" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ], + "element" : [ { + "name" : "value", + "value" : { + "type" : "MinValue", + "localId" : "515", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + }, { + "name" : "unit", + "value" : { + "type" : "Literal", + "localId" : "516", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "ml", + "annotation" : [ ] + } + } ] + } + }, { + "localId" : "520", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "name" : "quantities_at_max_value", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "520", "s" : [ { "value" : [ "", "define ", "quantities_at_max_value", ": " ] }, { - "r" : "511", + "r" : "529", "s" : [ { "value" : [ "Sum", "(" ] }, { - "r" : "503", + "r" : "521", "s" : [ { "value" : [ "{" ] }, { - "r" : "504", + "r" : "522", "s" : [ { - "value" : [ "99999999999999999999.99999999 ", "'ml'" ] + "value" : [ "MaxValueMLQuantity" ] } ] }, { "value" : [ "}" ] @@ -2319,47 +2473,46 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Sum", - "localId" : "511", + "localId" : "529", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "512", + "localId" : "530", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "513", + "localId" : "531", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "503", + "localId" : "521", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "505", + "localId" : "523", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "506", + "localId" : "524", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { - "type" : "Quantity", - "localId" : "504", + "type" : "ExpressionRef", + "localId" : "522", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "value" : 1.0E20, - "unit" : "ml", + "name" : "MaxValueMLQuantity", "annotation" : [ ] } ] } } }, { - "localId" : "516", + "localId" : "534", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "quantities_above_max_value", "context" : "Patient", @@ -2368,28 +2521,28 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "516", + "r" : "534", "s" : [ { "value" : [ "", "define ", "quantities_above_max_value", ": " ] }, { - "r" : "526", + "r" : "544", "s" : [ { "value" : [ "Sum", "(" ] }, { - "r" : "517", + "r" : "535", "s" : [ { "value" : [ "{" ] }, { - "r" : "518", + "r" : "536", "s" : [ { - "value" : [ "99999999999999999999.99999999 ", "'ml'" ] + "value" : [ "MaxValueMLQuantity" ] } ] }, { "value" : [ ", " ] }, { - "r" : "519", + "r" : "537", "s" : [ { - "value" : [ "99999999999999999999.99999999 ", "'ml'" ] + "value" : [ "MaxValueMLQuantity" ] } ] }, { "value" : [ "}" ] @@ -2402,54 +2555,52 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Sum", - "localId" : "526", + "localId" : "544", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "527", + "localId" : "545", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "528", + "localId" : "546", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "517", + "localId" : "535", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "520", + "localId" : "538", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "521", + "localId" : "539", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { - "type" : "Quantity", - "localId" : "518", + "type" : "ExpressionRef", + "localId" : "536", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "value" : 1.0E20, - "unit" : "ml", + "name" : "MaxValueMLQuantity", "annotation" : [ ] }, { - "type" : "Quantity", - "localId" : "519", + "type" : "ExpressionRef", + "localId" : "537", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "value" : 1.0E20, - "unit" : "ml", + "name" : "MaxValueMLQuantity", "annotation" : [ ] } ] } } }, { - "localId" : "531", + "localId" : "549", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "quantities_at_min_value", "context" : "Patient", @@ -2458,26 +2609,21 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "531", + "r" : "549", "s" : [ { "value" : [ "", "define ", "quantities_at_min_value", ": " ] }, { - "r" : "542", + "r" : "558", "s" : [ { "value" : [ "Sum", "(" ] }, { - "r" : "532", + "r" : "550", "s" : [ { "value" : [ "{" ] }, { - "r" : "533", + "r" : "551", "s" : [ { - "value" : [ "-" ] - }, { - "r" : "534", - "s" : [ { - "value" : [ "99999999999999999999.99999999 ", "'ml'" ] - } ] + "value" : [ "MinValueMLQuantity" ] } ] }, { "value" : [ "}" ] @@ -2490,59 +2636,46 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Sum", - "localId" : "542", + "localId" : "558", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "543", + "localId" : "559", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "544", + "localId" : "560", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "532", + "localId" : "550", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "536", + "localId" : "552", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "537", + "localId" : "553", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { - "type" : "Negate", - "localId" : "533", + "type" : "ExpressionRef", + "localId" : "551", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "annotation" : [ ], - "signature" : [ { - "type" : "NamedTypeSpecifier", - "localId" : "535", - "name" : "{urn:hl7-org:elm-types:r1}Quantity", - "annotation" : [ ] - } ], - "operand" : { - "type" : "Quantity", - "localId" : "534", - "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "value" : 1.0E20, - "unit" : "ml", - "annotation" : [ ] - } + "name" : "MinValueMLQuantity", + "annotation" : [ ] } ] } } }, { - "localId" : "547", + "localId" : "563", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "quantities_below_min_value", "context" : "Patient", @@ -2551,38 +2684,28 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "547", + "r" : "563", "s" : [ { "value" : [ "", "define ", "quantities_below_min_value", ": " ] }, { - "r" : "561", + "r" : "573", "s" : [ { "value" : [ "Sum", "(" ] }, { - "r" : "548", + "r" : "564", "s" : [ { "value" : [ "{" ] }, { - "r" : "549", + "r" : "565", "s" : [ { - "value" : [ "-" ] - }, { - "r" : "550", - "s" : [ { - "value" : [ "99999999999999999999.99999999 ", "'ml'" ] - } ] + "value" : [ "MinValueMLQuantity" ] } ] }, { "value" : [ ", " ] }, { - "r" : "552", + "r" : "566", "s" : [ { - "value" : [ "-" ] - }, { - "r" : "553", - "s" : [ { - "value" : [ "99999999999999999999.99999999 ", "'ml'" ] - } ] + "value" : [ "MinValueMLQuantity" ] } ] }, { "value" : [ "}" ] @@ -2595,78 +2718,52 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Sum", - "localId" : "561", + "localId" : "573", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "562", + "localId" : "574", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "563", + "localId" : "575", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "548", + "localId" : "564", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "555", + "localId" : "567", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "556", + "localId" : "568", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { - "type" : "Negate", - "localId" : "549", + "type" : "ExpressionRef", + "localId" : "565", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "annotation" : [ ], - "signature" : [ { - "type" : "NamedTypeSpecifier", - "localId" : "551", - "name" : "{urn:hl7-org:elm-types:r1}Quantity", - "annotation" : [ ] - } ], - "operand" : { - "type" : "Quantity", - "localId" : "550", - "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "value" : 1.0E20, - "unit" : "ml", - "annotation" : [ ] - } + "name" : "MinValueMLQuantity", + "annotation" : [ ] }, { - "type" : "Negate", - "localId" : "552", + "type" : "ExpressionRef", + "localId" : "566", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "annotation" : [ ], - "signature" : [ { - "type" : "NamedTypeSpecifier", - "localId" : "554", - "name" : "{urn:hl7-org:elm-types:r1}Quantity", - "annotation" : [ ] - } ], - "operand" : { - "type" : "Quantity", - "localId" : "553", - "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "value" : 1.0E20, - "unit" : "ml", - "annotation" : [ ] - } + "name" : "MinValueMLQuantity", + "annotation" : [ ] } ] } } }, { - "localId" : "566", + "localId" : "578", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "name" : "has_null", "context" : "Patient", @@ -2675,17 +2772,17 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "566", + "r" : "578", "s" : [ { "value" : [ "", "define ", "has_null", ": " ] }, { - "r" : "582", + "r" : "594", "s" : [ { "value" : [ "Sum", "(" ] }, { - "r" : "567", + "r" : "579", "s" : [ { - "r" : "568", + "r" : "580", "value" : [ "{", "1", ",", "null", ",", "null", ",", "null", ",", "2", "}" ] } ] }, { @@ -2696,81 +2793,81 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Sum", - "localId" : "582", + "localId" : "594", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "583", + "localId" : "595", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "584", + "localId" : "596", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "567", + "localId" : "579", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "576", + "localId" : "588", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "577", + "localId" : "589", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } }, "element" : [ { "type" : "Literal", - "localId" : "568", + "localId" : "580", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "1", "annotation" : [ ] }, { "type" : "As", - "localId" : "573", + "localId" : "585", "asType" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "569", + "localId" : "581", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] } }, { "type" : "As", - "localId" : "574", + "localId" : "586", "asType" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "570", + "localId" : "582", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] } }, { "type" : "As", - "localId" : "575", + "localId" : "587", "asType" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "571", + "localId" : "583", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] } }, { "type" : "Literal", - "localId" : "572", + "localId" : "584", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "2", @@ -2779,7 +2876,7 @@ module.exports['Sum'] = { } } }, { - "localId" : "587", + "localId" : "599", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "has_null_q", "context" : "Patient", @@ -2788,27 +2885,27 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "587", + "r" : "599", "s" : [ { "value" : [ "", "define ", "has_null_q", ": " ] }, { - "r" : "603", + "r" : "615", "s" : [ { "value" : [ "Sum", "(" ] }, { - "r" : "588", + "r" : "600", "s" : [ { "value" : [ "{" ] }, { - "r" : "589", + "r" : "601", "s" : [ { "value" : [ "1 ", "'ml'" ] } ] }, { - "r" : "590", + "r" : "602", "value" : [ ",", "null", ",", "null", ",", "null", "," ] }, { - "r" : "593", + "r" : "605", "s" : [ { "value" : [ "2 ", "'ml'" ] } ] @@ -2823,81 +2920,81 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Sum", - "localId" : "603", + "localId" : "615", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "604", + "localId" : "616", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "605", + "localId" : "617", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "588", + "localId" : "600", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "597", + "localId" : "609", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "598", + "localId" : "610", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "Quantity", - "localId" : "589", + "localId" : "601", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 1, "unit" : "ml", "annotation" : [ ] }, { "type" : "As", - "localId" : "594", + "localId" : "606", "asType" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "590", + "localId" : "602", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] } }, { "type" : "As", - "localId" : "595", + "localId" : "607", "asType" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "591", + "localId" : "603", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] } }, { "type" : "As", - "localId" : "596", + "localId" : "608", "asType" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "592", + "localId" : "604", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] } }, { "type" : "Quantity", - "localId" : "593", + "localId" : "605", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 2, "unit" : "ml", @@ -2906,7 +3003,7 @@ module.exports['Sum'] = { } } }, { - "localId" : "608", + "localId" : "620", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "unmatched_units_q", "context" : "Patient", @@ -2915,54 +3012,54 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "608", + "r" : "620", "s" : [ { "value" : [ "", "define ", "unmatched_units_q", ": " ] }, { - "r" : "622", + "r" : "634", "s" : [ { "value" : [ "Min", "(" ] }, { - "r" : "609", + "r" : "621", "s" : [ { "value" : [ "{" ] }, { - "r" : "610", + "r" : "622", "s" : [ { "value" : [ "1 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "611", + "r" : "623", "s" : [ { "value" : [ "2 ", "'m'" ] } ] }, { "value" : [ "," ] }, { - "r" : "612", + "r" : "624", "s" : [ { "value" : [ "3 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "613", + "r" : "625", "s" : [ { "value" : [ "4 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "614", + "r" : "626", "s" : [ { "value" : [ "5 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "615", + "r" : "627", "s" : [ { "value" : [ "0 ", "'ml'" ] } ] @@ -2977,73 +3074,73 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Min", - "localId" : "622", + "localId" : "634", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "623", + "localId" : "635", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "624", + "localId" : "636", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "609", + "localId" : "621", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "616", + "localId" : "628", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "617", + "localId" : "629", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "Quantity", - "localId" : "610", + "localId" : "622", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 1, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "611", + "localId" : "623", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 2, "unit" : "m", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "612", + "localId" : "624", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 3, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "613", + "localId" : "625", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 4, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "614", + "localId" : "626", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 5, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "615", + "localId" : "627", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0, "unit" : "ml", @@ -3052,7 +3149,7 @@ module.exports['Sum'] = { } } }, { - "localId" : "627", + "localId" : "639", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "name" : "empty", "context" : "Patient", @@ -3061,19 +3158,19 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "627", + "r" : "639", "s" : [ { "value" : [ "", "define ", "empty", ": " ] }, { - "r" : "637", + "r" : "649", "s" : [ { "value" : [ "Sum", "(" ] }, { - "r" : "629", + "r" : "641", "s" : [ { "value" : [ "List<" ] }, { - "r" : "628", + "r" : "640", "s" : [ { "value" : [ "Integer" ] } ] @@ -3088,31 +3185,31 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Sum", - "localId" : "637", + "localId" : "649", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "638", + "localId" : "650", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "639", + "localId" : "651", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "629", + "localId" : "641", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "631", + "localId" : "643", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "632", + "localId" : "644", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } @@ -3121,7 +3218,7 @@ module.exports['Sum'] = { } } }, { - "localId" : "642", + "localId" : "654", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "q_diff_units", "context" : "Patient", @@ -3130,47 +3227,47 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "642", + "r" : "654", "s" : [ { "value" : [ "", "define ", "q_diff_units", ": " ] }, { - "r" : "655", + "r" : "667", "s" : [ { "value" : [ "Sum", "(" ] }, { - "r" : "643", + "r" : "655", "s" : [ { "value" : [ "{" ] }, { - "r" : "644", + "r" : "656", "s" : [ { "value" : [ "1 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "645", + "r" : "657", "s" : [ { "value" : [ "0.002 ", "'l'" ] } ] }, { "value" : [ "," ] }, { - "r" : "646", + "r" : "658", "s" : [ { "value" : [ "0.03 ", "'dl'" ] } ] }, { "value" : [ "," ] }, { - "r" : "647", + "r" : "659", "s" : [ { "value" : [ "4 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "648", + "r" : "660", "s" : [ { "value" : [ "0.005 ", "'l'" ] } ] @@ -3185,66 +3282,66 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Sum", - "localId" : "655", + "localId" : "667", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "656", + "localId" : "668", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "657", + "localId" : "669", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "643", + "localId" : "655", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "649", + "localId" : "661", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "650", + "localId" : "662", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "Quantity", - "localId" : "644", + "localId" : "656", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 1, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "645", + "localId" : "657", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0.002, "unit" : "l", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "646", + "localId" : "658", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0.03, "unit" : "dl", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "647", + "localId" : "659", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 4, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "648", + "localId" : "660", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0.005, "unit" : "l", @@ -3253,7 +3350,7 @@ module.exports['Sum'] = { } } }, { - "localId" : "660", + "localId" : "672", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "NumbersAndQuantities", "context" : "Patient", @@ -3262,48 +3359,48 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "660", + "r" : "672", "s" : [ { "value" : [ "", "define ", "NumbersAndQuantities", ": " ] }, { - "r" : "677", + "r" : "689", "s" : [ { "value" : [ "Sum", "(" ] }, { - "r" : "661", + "r" : "673", "s" : [ { - "r" : "662", + "r" : "674", "value" : [ "{", "1", " ," ] }, { - "r" : "663", + "r" : "675", "s" : [ { "value" : [ "2 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "664", + "r" : "676", "s" : [ { "value" : [ "3 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "665", + "r" : "677", "s" : [ { "value" : [ "4 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "666", + "r" : "678", "s" : [ { "value" : [ "5 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "667", + "r" : "679", "s" : [ { "value" : [ "0 ", "'ml'" ] } ] @@ -3318,48 +3415,48 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Sum", - "localId" : "677", + "localId" : "689", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "678", + "localId" : "690", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "679", + "localId" : "691", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "661", + "localId" : "673", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "671", + "localId" : "683", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "672", + "localId" : "684", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "ToQuantity", - "localId" : "669", + "localId" : "681", "annotation" : [ ], "signature" : [ { "type" : "NamedTypeSpecifier", - "localId" : "670", + "localId" : "682", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } ], "operand" : { "type" : "Literal", - "localId" : "662", + "localId" : "674", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "1", @@ -3367,35 +3464,35 @@ module.exports['Sum'] = { } }, { "type" : "Quantity", - "localId" : "663", + "localId" : "675", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 2, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "664", + "localId" : "676", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 3, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "665", + "localId" : "677", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 4, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "666", + "localId" : "678", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 5, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "667", + "localId" : "679", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0, "unit" : "ml", @@ -3404,7 +3501,7 @@ module.exports['Sum'] = { } } }, { - "localId" : "682", + "localId" : "694", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "IncompatibleUnitsNull", "context" : "Patient", @@ -3413,26 +3510,26 @@ module.exports['Sum'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "682", + "r" : "694", "s" : [ { "value" : [ "", "define ", "IncompatibleUnitsNull", ": " ] }, { - "r" : "692", + "r" : "704", "s" : [ { "value" : [ "Sum", "(" ] }, { - "r" : "683", + "r" : "695", "s" : [ { "value" : [ "{" ] }, { - "r" : "684", + "r" : "696", "s" : [ { "value" : [ "1 ", "'mg/d'" ] } ] }, { "value" : [ ", " ] }, { - "r" : "685", + "r" : "697", "s" : [ { "value" : [ "0.002 ", "'/d'" ] } ] @@ -3447,45 +3544,45 @@ module.exports['Sum'] = { } ], "expression" : { "type" : "Sum", - "localId" : "692", + "localId" : "704", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "693", + "localId" : "705", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "694", + "localId" : "706", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "683", + "localId" : "695", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "686", + "localId" : "698", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "687", + "localId" : "699", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "Quantity", - "localId" : "684", + "localId" : "696", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 1, "unit" : "mg/d", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "685", + "localId" : "697", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0.002, "unit" : "/d", @@ -15140,10 +15237,14 @@ define decimals_above_max_value_product: Product({99999999999999999999.99999999, define decimals_at_min_value_product: Product({-99999999999999999999.99999999, 1.0}) define decimals_below_min_value_product: Product({-99999999999999999999.99999999, 2.0}) define quantity_product: Product({1.0 'g', 2.0 'g', 3.0 'g', 4.0 'g'}) -define quantities_at_max_value_product: Product({99999999999999999999.99999999 'g', 1.0 'g'}) -define quantities_above_max_value_product: Product({99999999999999999999.99999999 'g', 2.0 'g'}) -define quantities_at_min_value_product: Product({-99999999999999999999.99999999 'g', 1.0 'g'}) -define quantities_below_min_value_product: Product({-99999999999999999999.99999999 'g', 2.0 'g'}) +// Max/Min-valued quantities are described using the "maximum" and "minimum" operators +// to avoid the translator rounding them to +/-1.0e20, which is not a legal Decimal +define MaxValueGramQuantity: Quantity { value: maximum Decimal, unit: 'g' } +define MinValueGramQuantity: Quantity { value: minimum Decimal, unit: 'g' } +define quantities_at_max_value_product: Product({MaxValueGramQuantity, 1.0 'g'}) +define quantities_above_max_value_product: Product({MaxValueGramQuantity, 2.0 'g'}) +define quantities_at_min_value_product: Product({MinValueGramQuantity, 1.0 'g'}) +define quantities_below_min_value_product: Product({MinValueGramQuantity, 2.0 'g'}) define quantity_zero_product: Product({1.0 'g', 2.0 'g', 0 'g'}) define zero_product: Product({0, 5, 10}) define product_with_null: Product({5, 4, null}) @@ -15166,7 +15267,7 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "670", + "r" : "684", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -16683,7 +16784,7 @@ module.exports['Product'] = { }, { "localId" : "475", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "name" : "quantities_at_max_value_product", + "name" : "MaxValueGramQuantity", "context" : "Patient", "accessLevel" : "Public", "annotation" : [ { @@ -16692,24 +16793,174 @@ module.exports['Product'] = { "s" : { "r" : "475", "s" : [ { - "value" : [ "", "define ", "quantities_at_max_value_product", ": " ] + "value" : [ "// Max/Min-valued quantities are described using the \"maximum\" and \"minimum\" operators\n// to avoid the translator rounding them to +/-1.0e20, which is not a legal Decimal\n", "define ", "MaxValueGramQuantity", ": " ] + }, { + "r" : "476", + "s" : [ { + "value" : [ "Quantity", " { " ] + }, { + "s" : [ { + "value" : [ "value", ": " ] + }, { + "r" : "479", + "s" : [ { + "value" : [ "maximum", " " ] + }, { + "r" : "478", + "s" : [ { + "value" : [ "Decimal" ] + } ] + } ] + } ] + }, { + "value" : [ ", " ] + }, { + "s" : [ { + "value" : [ "unit", ": " ] + }, { + "r" : "480", + "s" : [ { + "value" : [ "'g'" ] + } ] + } ] + }, { + "value" : [ " }" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Instance", + "localId" : "476", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "classType" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ], + "element" : [ { + "name" : "value", + "value" : { + "type" : "MaxValue", + "localId" : "479", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + }, { + "name" : "unit", + "value" : { + "type" : "Literal", + "localId" : "480", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "g", + "annotation" : [ ] + } + } ] + } + }, { + "localId" : "484", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "name" : "MinValueGramQuantity", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "484", + "s" : [ { + "value" : [ "", "define ", "MinValueGramQuantity", ": " ] }, { "r" : "485", + "s" : [ { + "value" : [ "Quantity", " { " ] + }, { + "s" : [ { + "value" : [ "value", ": " ] + }, { + "r" : "488", + "s" : [ { + "value" : [ "minimum", " " ] + }, { + "r" : "487", + "s" : [ { + "value" : [ "Decimal" ] + } ] + } ] + } ] + }, { + "value" : [ ", " ] + }, { + "s" : [ { + "value" : [ "unit", ": " ] + }, { + "r" : "489", + "s" : [ { + "value" : [ "'g'" ] + } ] + } ] + }, { + "value" : [ " }" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Instance", + "localId" : "485", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "classType" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ], + "element" : [ { + "name" : "value", + "value" : { + "type" : "MinValue", + "localId" : "488", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + }, { + "name" : "unit", + "value" : { + "type" : "Literal", + "localId" : "489", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "g", + "annotation" : [ ] + } + } ] + } + }, { + "localId" : "493", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "name" : "quantities_at_max_value_product", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "493", + "s" : [ { + "value" : [ "", "define ", "quantities_at_max_value_product", ": " ] + }, { + "r" : "503", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "476", + "r" : "494", "s" : [ { "value" : [ "{" ] }, { - "r" : "477", + "r" : "495", "s" : [ { - "value" : [ "99999999999999999999.99999999 ", "'g'" ] + "value" : [ "MaxValueGramQuantity" ] } ] }, { "value" : [ ", " ] }, { - "r" : "478", + "r" : "496", "s" : [ { "value" : [ "1.0 ", "'g'" ] } ] @@ -16724,45 +16975,44 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "485", + "localId" : "503", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "486", + "localId" : "504", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "487", + "localId" : "505", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "476", + "localId" : "494", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "479", + "localId" : "497", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "480", + "localId" : "498", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { - "type" : "Quantity", - "localId" : "477", + "type" : "ExpressionRef", + "localId" : "495", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "value" : 1.0E20, - "unit" : "g", + "name" : "MaxValueGramQuantity", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "478", + "localId" : "496", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 1.0, "unit" : "g", @@ -16771,7 +17021,7 @@ module.exports['Product'] = { } } }, { - "localId" : "490", + "localId" : "508", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "quantities_above_max_value_product", "context" : "Patient", @@ -16780,26 +17030,26 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "490", + "r" : "508", "s" : [ { "value" : [ "", "define ", "quantities_above_max_value_product", ": " ] }, { - "r" : "500", + "r" : "518", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "491", + "r" : "509", "s" : [ { "value" : [ "{" ] }, { - "r" : "492", + "r" : "510", "s" : [ { - "value" : [ "99999999999999999999.99999999 ", "'g'" ] + "value" : [ "MaxValueGramQuantity" ] } ] }, { "value" : [ ", " ] }, { - "r" : "493", + "r" : "511", "s" : [ { "value" : [ "2.0 ", "'g'" ] } ] @@ -16814,45 +17064,44 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "500", + "localId" : "518", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "501", + "localId" : "519", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "502", + "localId" : "520", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "491", + "localId" : "509", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "494", + "localId" : "512", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "495", + "localId" : "513", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { - "type" : "Quantity", - "localId" : "492", + "type" : "ExpressionRef", + "localId" : "510", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "value" : 1.0E20, - "unit" : "g", + "name" : "MaxValueGramQuantity", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "493", + "localId" : "511", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 2.0, "unit" : "g", @@ -16861,7 +17110,7 @@ module.exports['Product'] = { } } }, { - "localId" : "505", + "localId" : "523", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "quantities_at_min_value_product", "context" : "Patient", @@ -16870,31 +17119,26 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "505", + "r" : "523", "s" : [ { "value" : [ "", "define ", "quantities_at_min_value_product", ": " ] }, { - "r" : "517", + "r" : "533", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "506", + "r" : "524", "s" : [ { "value" : [ "{" ] }, { - "r" : "507", + "r" : "525", "s" : [ { - "value" : [ "-" ] - }, { - "r" : "508", - "s" : [ { - "value" : [ "99999999999999999999.99999999 ", "'g'" ] - } ] + "value" : [ "MinValueGramQuantity" ] } ] }, { "value" : [ ", " ] }, { - "r" : "510", + "r" : "526", "s" : [ { "value" : [ "1.0 ", "'g'" ] } ] @@ -16909,57 +17153,44 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "517", + "localId" : "533", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "518", + "localId" : "534", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "519", + "localId" : "535", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "506", + "localId" : "524", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "511", + "localId" : "527", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "512", + "localId" : "528", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { - "type" : "Negate", - "localId" : "507", + "type" : "ExpressionRef", + "localId" : "525", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "annotation" : [ ], - "signature" : [ { - "type" : "NamedTypeSpecifier", - "localId" : "509", - "name" : "{urn:hl7-org:elm-types:r1}Quantity", - "annotation" : [ ] - } ], - "operand" : { - "type" : "Quantity", - "localId" : "508", - "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "value" : 1.0E20, - "unit" : "g", - "annotation" : [ ] - } + "name" : "MinValueGramQuantity", + "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "510", + "localId" : "526", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 1.0, "unit" : "g", @@ -16968,7 +17199,7 @@ module.exports['Product'] = { } } }, { - "localId" : "522", + "localId" : "538", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "quantities_below_min_value_product", "context" : "Patient", @@ -16977,31 +17208,26 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "522", + "r" : "538", "s" : [ { "value" : [ "", "define ", "quantities_below_min_value_product", ": " ] }, { - "r" : "534", + "r" : "548", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "523", + "r" : "539", "s" : [ { "value" : [ "{" ] }, { - "r" : "524", + "r" : "540", "s" : [ { - "value" : [ "-" ] - }, { - "r" : "525", - "s" : [ { - "value" : [ "99999999999999999999.99999999 ", "'g'" ] - } ] + "value" : [ "MinValueGramQuantity" ] } ] }, { "value" : [ ", " ] }, { - "r" : "527", + "r" : "541", "s" : [ { "value" : [ "2.0 ", "'g'" ] } ] @@ -17016,57 +17242,44 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "534", + "localId" : "548", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "535", + "localId" : "549", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "536", + "localId" : "550", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "523", + "localId" : "539", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "528", + "localId" : "542", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "529", + "localId" : "543", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { - "type" : "Negate", - "localId" : "524", + "type" : "ExpressionRef", + "localId" : "540", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "annotation" : [ ], - "signature" : [ { - "type" : "NamedTypeSpecifier", - "localId" : "526", - "name" : "{urn:hl7-org:elm-types:r1}Quantity", - "annotation" : [ ] - } ], - "operand" : { - "type" : "Quantity", - "localId" : "525", - "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "value" : 1.0E20, - "unit" : "g", - "annotation" : [ ] - } + "name" : "MinValueGramQuantity", + "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "527", + "localId" : "541", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 2.0, "unit" : "g", @@ -17075,7 +17288,7 @@ module.exports['Product'] = { } } }, { - "localId" : "539", + "localId" : "553", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "quantity_zero_product", "context" : "Patient", @@ -17084,33 +17297,33 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "539", + "r" : "553", "s" : [ { "value" : [ "", "define ", "quantity_zero_product", ": " ] }, { - "r" : "550", + "r" : "564", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "540", + "r" : "554", "s" : [ { "value" : [ "{" ] }, { - "r" : "541", + "r" : "555", "s" : [ { "value" : [ "1.0 ", "'g'" ] } ] }, { "value" : [ ", " ] }, { - "r" : "542", + "r" : "556", "s" : [ { "value" : [ "2.0 ", "'g'" ] } ] }, { "value" : [ ", " ] }, { - "r" : "543", + "r" : "557", "s" : [ { "value" : [ "0 ", "'g'" ] } ] @@ -17125,52 +17338,52 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "550", + "localId" : "564", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "551", + "localId" : "565", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "552", + "localId" : "566", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "540", + "localId" : "554", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "544", + "localId" : "558", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "545", + "localId" : "559", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "Quantity", - "localId" : "541", + "localId" : "555", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 1.0, "unit" : "g", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "542", + "localId" : "556", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 2.0, "unit" : "g", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "543", + "localId" : "557", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0, "unit" : "g", @@ -17179,7 +17392,7 @@ module.exports['Product'] = { } } }, { - "localId" : "555", + "localId" : "569", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "name" : "zero_product", "context" : "Patient", @@ -17188,17 +17401,17 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "555", + "r" : "569", "s" : [ { "value" : [ "", "define ", "zero_product", ": " ] }, { - "r" : "566", + "r" : "580", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "556", + "r" : "570", "s" : [ { - "r" : "557", + "r" : "571", "value" : [ "{", "0", ", ", "5", ", ", "10", "}" ] } ] }, { @@ -17209,52 +17422,52 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "566", + "localId" : "580", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "567", + "localId" : "581", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "568", + "localId" : "582", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "556", + "localId" : "570", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "560", + "localId" : "574", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "561", + "localId" : "575", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } }, "element" : [ { "type" : "Literal", - "localId" : "557", + "localId" : "571", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "0", "annotation" : [ ] }, { "type" : "Literal", - "localId" : "558", + "localId" : "572", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "5", "annotation" : [ ] }, { "type" : "Literal", - "localId" : "559", + "localId" : "573", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "10", @@ -17263,7 +17476,7 @@ module.exports['Product'] = { } } }, { - "localId" : "571", + "localId" : "585", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "name" : "product_with_null", "context" : "Patient", @@ -17272,17 +17485,17 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "571", + "r" : "585", "s" : [ { "value" : [ "", "define ", "product_with_null", ": " ] }, { - "r" : "583", + "r" : "597", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "572", + "r" : "586", "s" : [ { - "r" : "573", + "r" : "587", "value" : [ "{", "5", ", ", "4", ", ", "null", "}" ] } ] }, { @@ -17293,58 +17506,58 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "583", + "localId" : "597", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "584", + "localId" : "598", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "585", + "localId" : "599", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "572", + "localId" : "586", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "577", + "localId" : "591", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "578", + "localId" : "592", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } }, "element" : [ { "type" : "Literal", - "localId" : "573", + "localId" : "587", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "5", "annotation" : [ ] }, { "type" : "Literal", - "localId" : "574", + "localId" : "588", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "4", "annotation" : [ ] }, { "type" : "As", - "localId" : "576", + "localId" : "590", "asType" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "575", + "localId" : "589", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] } @@ -17352,7 +17565,7 @@ module.exports['Product'] = { } } }, { - "localId" : "588", + "localId" : "602", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "name" : "product_of_nulls", "context" : "Patient", @@ -17361,30 +17574,30 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "588", + "r" : "602", "s" : [ { "value" : [ "", "define ", "product_of_nulls", ": " ] }, { - "r" : "603", + "r" : "617", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "589", + "r" : "603", "s" : [ { "value" : [ "{" ] }, { - "r" : "590", + "r" : "604", "s" : [ { - "r" : "591", + "r" : "605", "value" : [ "null", " as " ] }, { - "r" : "592", + "r" : "606", "s" : [ { "value" : [ "Integer" ] } ] } ] }, { - "r" : "593", + "r" : "607", "value" : [ ", ", "null", ", ", "null", "}" ] } ] }, { @@ -17395,76 +17608,76 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "603", + "localId" : "617", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "604", + "localId" : "618", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "605", + "localId" : "619", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "589", + "localId" : "603", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "597", + "localId" : "611", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "598", + "localId" : "612", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } }, "element" : [ { "type" : "As", - "localId" : "590", + "localId" : "604", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "strict" : false, "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "591", + "localId" : "605", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] }, "asTypeSpecifier" : { "type" : "NamedTypeSpecifier", - "localId" : "592", + "localId" : "606", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } }, { "type" : "As", - "localId" : "595", + "localId" : "609", "asType" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "593", + "localId" : "607", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] } }, { "type" : "As", - "localId" : "596", + "localId" : "610", "asType" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "594", + "localId" : "608", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] } @@ -17472,7 +17685,7 @@ module.exports['Product'] = { } } }, { - "localId" : "608", + "localId" : "622", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", "name" : "product_null", "context" : "Patient", @@ -17481,24 +17694,24 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "608", + "r" : "622", "s" : [ { "value" : [ "", "define ", "product_null", ": " ] }, { - "r" : "621", + "r" : "635", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "609", + "r" : "623", "s" : [ { - "r" : "610", + "r" : "624", "value" : [ "null", " as " ] }, { - "r" : "611", + "r" : "625", "s" : [ { "value" : [ "List<" ] }, { - "r" : "612", + "r" : "626", "s" : [ { "value" : [ "Decimal" ] } ] @@ -17514,32 +17727,32 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "621", + "localId" : "635", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "622", + "localId" : "636", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "623", + "localId" : "637", "name" : "{urn:hl7-org:elm-types:r1}Decimal", "annotation" : [ ] } } ], "source" : { "type" : "As", - "localId" : "609", + "localId" : "623", "strict" : false, "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "615", + "localId" : "629", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "616", + "localId" : "630", "name" : "{urn:hl7-org:elm-types:r1}Decimal", "annotation" : [ ] } @@ -17547,28 +17760,28 @@ module.exports['Product'] = { "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "610", + "localId" : "624", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] }, "asTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "611", + "localId" : "625", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "613", + "localId" : "627", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "614", + "localId" : "628", "name" : "{urn:hl7-org:elm-types:r1}Decimal", "annotation" : [ ] } }, "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "612", + "localId" : "626", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", "name" : "{urn:hl7-org:elm-types:r1}Decimal", "annotation" : [ ] @@ -17577,7 +17790,7 @@ module.exports['Product'] = { } } }, { - "localId" : "626", + "localId" : "640", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "product_quantity_null", "context" : "Patient", @@ -17586,24 +17799,24 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "626", + "r" : "640", "s" : [ { "value" : [ "", "define ", "product_quantity_null", ": " ] }, { - "r" : "643", + "r" : "657", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "627", + "r" : "641", "s" : [ { "value" : [ "{" ] }, { - "r" : "628", + "r" : "642", "s" : [ { - "r" : "629", + "r" : "643", "value" : [ "null", " as " ] }, { - "r" : "630", + "r" : "644", "s" : [ { "value" : [ "Quantity" ] } ] @@ -17611,12 +17824,12 @@ module.exports['Product'] = { }, { "value" : [ ", " ] }, { - "r" : "631", + "r" : "645", "s" : [ { - "r" : "632", + "r" : "646", "value" : [ "null", " as " ] }, { - "r" : "633", + "r" : "647", "s" : [ { "value" : [ "Quantity" ] } ] @@ -17624,12 +17837,12 @@ module.exports['Product'] = { }, { "value" : [ ", " ] }, { - "r" : "634", + "r" : "648", "s" : [ { - "r" : "635", + "r" : "649", "value" : [ "null", " as " ] }, { - "r" : "636", + "r" : "650", "s" : [ { "value" : [ "Quantity" ] } ] @@ -17645,91 +17858,91 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "643", + "localId" : "657", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "644", + "localId" : "658", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "645", + "localId" : "659", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "627", + "localId" : "641", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "637", + "localId" : "651", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "638", + "localId" : "652", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "As", - "localId" : "628", + "localId" : "642", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "strict" : false, "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "629", + "localId" : "643", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] }, "asTypeSpecifier" : { "type" : "NamedTypeSpecifier", - "localId" : "630", + "localId" : "644", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, { "type" : "As", - "localId" : "631", + "localId" : "645", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "strict" : false, "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "632", + "localId" : "646", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] }, "asTypeSpecifier" : { "type" : "NamedTypeSpecifier", - "localId" : "633", + "localId" : "647", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, { "type" : "As", - "localId" : "634", + "localId" : "648", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "strict" : false, "annotation" : [ ], "signature" : [ ], "operand" : { "type" : "Null", - "localId" : "635", + "localId" : "649", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", "annotation" : [ ] }, "asTypeSpecifier" : { "type" : "NamedTypeSpecifier", - "localId" : "636", + "localId" : "650", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] @@ -17738,7 +17951,7 @@ module.exports['Product'] = { } } }, { - "localId" : "648", + "localId" : "662", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "NumbersAndQuantities", "context" : "Patient", @@ -17747,48 +17960,48 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "648", + "r" : "662", "s" : [ { "value" : [ "", "define ", "NumbersAndQuantities", ": " ] }, { - "r" : "665", + "r" : "679", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "649", + "r" : "663", "s" : [ { - "r" : "650", + "r" : "664", "value" : [ "{", "1", " ," ] }, { - "r" : "651", + "r" : "665", "s" : [ { "value" : [ "2 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "652", + "r" : "666", "s" : [ { "value" : [ "3 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "653", + "r" : "667", "s" : [ { "value" : [ "4 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "654", + "r" : "668", "s" : [ { "value" : [ "5 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "655", + "r" : "669", "s" : [ { "value" : [ "0 ", "'ml'" ] } ] @@ -17803,48 +18016,48 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "665", + "localId" : "679", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "666", + "localId" : "680", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "667", + "localId" : "681", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "649", + "localId" : "663", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "659", + "localId" : "673", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "660", + "localId" : "674", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "ToQuantity", - "localId" : "657", + "localId" : "671", "annotation" : [ ], "signature" : [ { "type" : "NamedTypeSpecifier", - "localId" : "658", + "localId" : "672", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } ], "operand" : { "type" : "Literal", - "localId" : "650", + "localId" : "664", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "1", @@ -17852,35 +18065,35 @@ module.exports['Product'] = { } }, { "type" : "Quantity", - "localId" : "651", + "localId" : "665", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 2, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "652", + "localId" : "666", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 3, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "653", + "localId" : "667", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 4, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "654", + "localId" : "668", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 5, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "655", + "localId" : "669", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0, "unit" : "ml", @@ -17889,7 +18102,7 @@ module.exports['Product'] = { } } }, { - "localId" : "670", + "localId" : "684", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "IncompatibleUnitsNull", "context" : "Patient", @@ -17898,26 +18111,26 @@ module.exports['Product'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "670", + "r" : "684", "s" : [ { "value" : [ "", "define ", "IncompatibleUnitsNull", ": " ] }, { - "r" : "680", + "r" : "694", "s" : [ { "value" : [ "Product", "(" ] }, { - "r" : "671", + "r" : "685", "s" : [ { "value" : [ "{" ] }, { - "r" : "672", + "r" : "686", "s" : [ { "value" : [ "1 ", "'mg/d'" ] } ] }, { "value" : [ ", " ] }, { - "r" : "673", + "r" : "687", "s" : [ { "value" : [ "0.002 ", "'/d'" ] } ] @@ -17932,45 +18145,45 @@ module.exports['Product'] = { } ], "expression" : { "type" : "Product", - "localId" : "680", + "localId" : "694", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "681", + "localId" : "695", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "682", + "localId" : "696", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "671", + "localId" : "685", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "674", + "localId" : "688", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "675", + "localId" : "689", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "Quantity", - "localId" : "672", + "localId" : "686", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 1, "unit" : "mg/d", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "673", + "localId" : "687", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0.002, "unit" : "/d", diff --git a/test/elm/arithmetic/arithmetic-test.ts b/test/elm/arithmetic/arithmetic-test.ts index 618265736..774aa91bd 100644 --- a/test/elm/arithmetic/arithmetic-test.ts +++ b/test/elm/arithmetic/arithmetic-test.ts @@ -211,64 +211,64 @@ describe('Divide', () => { }); it('should divide two numbers', async function () { - (await this.tenDividedByTwo.exec(this.ctx)).should.eql(Decimal.from(5)); + (await this.tenDividedByTwo.exec(this.ctx)).should.equalDecimal(Decimal.from(5)); }); it("should divide two numbers that don't evenly divide", async function () { - (await this.tenDividedByFour.exec(this.ctx)).should.eql(Decimal.from(2.5)); + (await this.tenDividedByFour.exec(this.ctx)).should.equalDecimal(Decimal.from(2.5)); }); it('should divide multiple numbers', async function () { - (await this.divideMultiple.exec(this.ctx)).should.eql(Decimal.from(5)); + (await this.divideMultiple.exec(this.ctx)).should.equalDecimal(Decimal.from(5)); }); it('should divide variables', async function () { - (await this.divideVariables.exec(this.ctx)).should.eql(Decimal.from(25)); + (await this.divideVariables.exec(this.ctx)).should.equalDecimal(Decimal.from(25)); }); it('should divide two longs', async function () { // NOTE: Divide always returns a Decimal - (await this.tenDividedByTwoLong.exec(this.ctx)).should.eql(Decimal.from(5)); + (await this.tenDividedByTwoLong.exec(this.ctx)).should.equalDecimal(Decimal.from(5)); }); it('should divide integer by long', async function () { // NOTE: Divide always returns a Decimal - (await this.tenDividedByTwoMixed.exec(this.ctx)).should.eql(Decimal.from(5)); + (await this.tenDividedByTwoMixed.exec(this.ctx)).should.equalDecimal(Decimal.from(5)); }); it('should divide long by integer', async function () { // NOTE: Divide always returns a Decimal - (await this.tenDividedByTwoReverseMixed.exec(this.ctx)).should.eql(Decimal.from(5)); + (await this.tenDividedByTwoReverseMixed.exec(this.ctx)).should.equalDecimal(Decimal.from(5)); }); it('should divide two longs with decimal result', async function () { - (await this.tenDividedByFourLong.exec(this.ctx)).should.eql(Decimal.from(2.5)); + (await this.tenDividedByFourLong.exec(this.ctx)).should.equalDecimal(Decimal.from(2.5)); }); it('should divide integer by long with decimal result', async function () { - (await this.tenDividedByFourMixed.exec(this.ctx)).should.eql(Decimal.from(2.5)); + (await this.tenDividedByFourMixed.exec(this.ctx)).should.equalDecimal(Decimal.from(2.5)); }); it('should divide long by integer with decimal result', async function () { - (await this.tenDividedByFourReverseMixed.exec(this.ctx)).should.eql(Decimal.from(2.5)); + (await this.tenDividedByFourReverseMixed.exec(this.ctx)).should.equalDecimal(Decimal.from(2.5)); }); it('should divide uncertainty by uncertainty', async function () { const result = await this.divideUncertainties.exec(this.ctx); - result.low.should.eql(Decimal.from(0.42857143)); // 6/14 - result.high.should.eql(Decimal.from(9)); + result.low.should.equalDecimal(Decimal.from("0.42857143")); // 6/14 + result.high.should.equalDecimal(Decimal.from(9)); }); it('should divide uncertainty by number', async function () { const result = await this.divideUncertaintyByNumber.exec(this.ctx); - result.low.should.eql(Decimal.from(3)); - result.high.should.eql(Decimal.from(9)); + result.low.should.equalDecimal(Decimal.from(3)); + result.high.should.equalDecimal(Decimal.from(9)); }); it('should divide number by uncertainty', async function () { const result = await this.divideNumberByUncertainty.exec(this.ctx); - result.low.should.eql(Decimal.from(2)); - result.high.should.eql(Decimal.from(6)); + result.low.should.equalDecimal(Decimal.from(2)); + result.high.should.equalDecimal(Decimal.from(6)); }); }); @@ -300,11 +300,11 @@ describe('MathPrecedence', () => { }); it('should follow order of operations', async function () { - (await this.mixed.exec(this.ctx)).should.eql(Decimal.from(46)); + (await this.mixed.exec(this.ctx)).should.equalDecimal(Decimal.from(46)); }); it('should allow parentheses to override order of operations', async function () { - (await this.parenthetical.exec(this.ctx)).should.eql(Decimal.from(-10)); + (await this.parenthetical.exec(this.ctx)).should.equalDecimal(Decimal.from(-10)); }); }); @@ -318,7 +318,7 @@ describe('Power', () => { }); it('should be able to calculate the negative power of a number', async function () { - (await this.negPow.exec(this.ctx)).should.eql(Decimal.from(0.1)); + (await this.negPow.exec(this.ctx)).should.equalDecimal(Decimal.from(0.1)); }); it('should be able to calculate the power of a long', async function () { @@ -334,7 +334,7 @@ describe('Power', () => { }); it('should be able to calculate the negative power of a long', async function () { - (await this.tenLongExpNegativeOneLong.exec(this.ctx)).should.eql(Decimal.from(0.1)); + (await this.tenLongExpNegativeOneLong.exec(this.ctx)).should.equalDecimal(Decimal.from(0.1)); }); it('should return null when a long power exponent is too large (beyond max Long value)', async function () { @@ -368,19 +368,12 @@ describe('MinValue', () => { String(minLongResult).should.equal(minLongStringValue); }); - // JS number doesn't handle limits of decimal precisely, but this ensures we are in the ballpark - it('of Decimal should return approximate minimum representable Decimal value', async function () { - const minDecimalValue = -99999999999999999999.99999999; - const minDecimalResult = await this.minDecimal.exec(this.ctx); - minDecimalResult.should.be.approximately(minDecimalValue, 0.000000001); - }); - - it.skip('of Decimal should return exact minimum representable Decimal value', async function () { - const minDecimalValue = -99999999999999999999.99999999; + it('of Decimal should return exact minimum representable Decimal value', async function () { const minDecimalStringValue = '-99999999999999999999.99999999'; + const minDecimalValue = Decimal.from(minDecimalStringValue); const minDecimalResult = await this.minDecimal.exec(this.ctx); - minDecimalResult.should.equal(minDecimalValue); - String(minDecimalResult).should.equal(minDecimalStringValue); + minDecimalResult.should.equalDecimal(minDecimalValue); + minDecimalResult.toString().should.equal(minDecimalStringValue); }); it('of DateTime should return minimum representable DateTime value', async function () { @@ -428,19 +421,12 @@ describe('MaxValue', () => { String(maxLongResult).should.equal(maxLongStringValue); }); - // JS number doesn't handle limits of decimal precisely, but this ensures we are in the ballpark - it('of Decimal should return approximate maximum representable Decimal value', async function () { - const maxDecimalValue = 99999999999999999999.99999999; - const maxDecimalResult = await this.maxDecimal.exec(this.ctx); - maxDecimalResult.should.be.approximately(maxDecimalValue, 0.000000001); - }); - - it.skip('of Decimal should return exact maximum representable Decimal value', async function () { - const maxDecimalValue = 99999999999999999999.99999999; + it('of Decimal should return exact maximum representable Decimal value', async function () { const maxDecimalStringValue = '99999999999999999999.99999999'; + const maxDecimalValue = Decimal.from(maxDecimalStringValue); const maxDecimalResult = await this.maxDecimal.exec(this.ctx); - maxDecimalResult.should.equal(maxDecimalValue, 0.000000001); - String(maxDecimalResult).should.equal(maxDecimalStringValue); + maxDecimalResult.should.equalDecimal(maxDecimalValue); + maxDecimalResult.toString().should.equal(maxDecimalStringValue); }); it('of DateTime should return maximum representable DateTime value', async function () { @@ -551,11 +537,13 @@ describe('Ln', () => { }); it('should be able to return the natural log of a number', async function () { - (await this.ln.exec(this.ctx)).should.eql(Decimal.from(Math.log(4))); + const log4 = Decimal.from("1.3862943611198906").normalized(); + (await this.ln.exec(this.ctx)).should.equalDecimal(log4); }); it('should be able to return the natural log of a long', async function () { - (await this.lnFourLong.exec(this.ctx)).should.eql(Decimal.from(Math.log(4))); + const log4 = Decimal.from("1.3862943611198906").normalized(); + (await this.lnFourLong.exec(this.ctx)).should.equalDecimal(log4); }); }); @@ -565,11 +553,11 @@ describe('Log', () => { }); it('should be able to return the log of a number based on an arbitrary base value', async function () { - (await this.log.exec(this.ctx)).should.eql(Decimal.from(0.25)); + (await this.log.exec(this.ctx)).should.equalDecimal(Decimal.from(0.25)); }); it('should be able to return the log of a long based on an arbitrary base value', async function () { - (await this.logLong.exec(this.ctx)).should.eql(Decimal.from(0.25)); + (await this.logLong.exec(this.ctx)).should.equalDecimal(Decimal.from(0.25)); }); }); @@ -638,12 +626,12 @@ describe('Round', () => { }); it('should be able to round a number up or down to the closest integer value', async function () { - (await this.up.exec(this.ctx)).should.eql(Decimal.from(5)); - (await this.down.exec(this.ctx)).should.eql(Decimal.from(4)); + (await this.up.exec(this.ctx)).should.equalDecimal(Decimal.from(5)); + (await this.down.exec(this.ctx)).should.equalDecimal(Decimal.from(4)); }); it('should be able to round a number up or down to the closest decimal place ', async function () { - (await this.up_percent.exec(this.ctx)).should.eql(Decimal.from(4.6)); - (await this.down_percent.exec(this.ctx)).should.eql(Decimal.from(4.4)); + (await this.up_percent.exec(this.ctx)).should.equalDecimal(Decimal.from(4.6)); + (await this.down_percent.exec(this.ctx)).should.equalDecimal(Decimal.from(4.4)); }); }); @@ -661,7 +649,7 @@ describe('Successor', () => { }); it('should be able to get Real Successor', async function () { - (await this.rs.exec(this.ctx)).should.eql(Decimal.from(2.2 + Math.pow(10, -8))); + (await this.rs.exec(this.ctx)).should.equalDecimal(Decimal.from(2.2 + Math.pow(10, -8))); }); it('should return null for Successor greater than Integer Max value', async function () { @@ -764,7 +752,7 @@ describe('Predecessor', () => { }); it('should be able to get Real Predecessor', async function () { - (await this.rs.exec(this.ctx)).should.eql(Decimal.from(2.2 - Math.pow(10, -8))); + (await this.rs.exec(this.ctx)).should.equalDecimal(Decimal.from("2.19999999")); }); it('should return null for Predecessor greater than Integer Max value', async function () { @@ -891,13 +879,13 @@ describe('Quantity', () => { it('should be able to perform Quantity Absolution', async function () { const q = await this.abs.exec(this.ctx); - q.value.should.eql(Decimal.from(10)); + q.value.should.equalDecimal(Decimal.from(10)); q.unit.should.equal('days'); }); it('should be able to perform Quantity Negation', async function () { const q = await this.neg.exec(this.ctx); - q.value.should.eql(Decimal.from(-10)); + q.value.should.equalDecimal(Decimal.from(-10)); q.unit.should.equal('days'); }); @@ -1023,12 +1011,12 @@ describe('OutOfBounds', () => { it('should return value for Divide near overflow', async function () { // not really near overflow, but more than max integer and near JavaScript max safe number - should(await this.integerDivideNearOverflow.exec(this.ctx)).eql(Decimal.from(8589934588000000)); + should(await this.integerDivideNearOverflow.exec(this.ctx)).equalDecimal(Decimal.from(8589934588000000)); }); it('should return value for Divide near underflow', async function () { // not really near underflow, but less than min integer and near JavaScript min safe number - should(await this.integerDivideNearUnderflow.exec(this.ctx)).eql(Decimal.from(-8589934592000000)); + should(await this.integerDivideNearUnderflow.exec(this.ctx)).equalDecimal(Decimal.from(-8589934592000000)); }); it('should return null for Divide By Zero', async function () { @@ -1127,12 +1115,15 @@ describe('OutOfBounds', () => { it('should return value for Divide near overflow', async function () { // not really near overflow, but near JavaScript max safe number - should(await this.longDivideNearOverflow.exec(this.ctx)).eql(Decimal.from(9007199254740992n)); + // note that all division in CQL (except truncated division) is really decimal division + // note also that MAX_LONG_VALUE is (2^63)-1, + // 9223372036854775807 = 7^2 * 73 * 127 * 337 * 92737 * 649657 + should(await this.longDivideNearOverflow.exec(this.ctx)).equalDecimal(Decimal.from(99457304386111n)); }); it('should return value for Divide near underflow', async function () { // not really near underflow, but near JavaScript min safe number - should(await this.longDivideNearUnderflow.exec(this.ctx)).eql(Decimal.from(-9007199254740992n)); + should(await this.longDivideNearUnderflow.exec(this.ctx)).equalDecimal(Decimal.from(-9007199254740992n)); }); it('should return null for Divide By Zero', async function () { @@ -1265,13 +1256,12 @@ describe('OutOfBounds', () => { should(await this.decimalPredecessorUnderflow.exec(this.ctx)).be.null(); }); - // NOTE: skipping successor/predecessor tests near overflow due to JS Number imprecision - it.skip('should return value for successor near overflow', async function () { - should(await this.decimalSuccessorNearOverflow.exec(this.ctx)).equal(MAX_DECIMAL_VALUE); + it('should return value for successor near overflow', async function () { + should(await this.decimalSuccessorNearOverflow.exec(this.ctx)).equalDecimal(MAX_DECIMAL_VALUE); }); - it.skip('should return value for predecessor near underflow', async function () { - should(await this.decimalPredecessorNearUnderflow.exec(this.ctx)).equal(MIN_DECIMAL_VALUE); + it('should return value for predecessor near underflow', async function () { + should(await this.decimalPredecessorNearUnderflow.exec(this.ctx)).equalDecimal(MIN_DECIMAL_VALUE); }); }); @@ -1369,14 +1359,14 @@ describe('OutOfBounds', () => { }); // NOTE: skipping successor/predecessor tests near overflow due to JS Number imprecision - it.skip('should return value for successor near overflow', async function () { + it('should return value for successor near overflow', async function () { const result = await this.quantitySuccessorNearOverflow.exec(this.ctx); should(result).not.be.null(); validateQuantity(result, MAX_DECIMAL_VALUE, 'mm'); }); - it.skip('should return value for predecessor near underflow', async function () { - const result = await this.quantitPpredecessorNearOverflow.exec(this.ctx); + it('should return value for predecessor near underflow', async function () { + const result = await this.quantityPredecessorNearUnderflow.exec(this.ctx); should(result).not.be.null(); validateQuantity(result, MIN_DECIMAL_VALUE, 'mm'); }); diff --git a/test/elm/arithmetic/data.cql b/test/elm/arithmetic/data.cql index 883ad6e33..5f621470a 100644 --- a/test/elm/arithmetic/data.cql +++ b/test/elm/arithmetic/data.cql @@ -250,7 +250,7 @@ define LongMultiplyNearUnderflow: minimum Long * 1L // NOTE: Long division results in decimal, so it must overflow/underflow decimal define LongDivideOverflow: maximum Long / 0.05 define LongDivideUnderflow: minimum Long / 0.05 -define LongDivideNearOverflow: maximum Long / 1024L +define LongDivideNearOverflow: maximum Long / 92737L define LongDivideNearUnderflow: minimum Long / 1024L define LongDivideByZero: 1L / 0L define LongPowerOverflow: (maximum Long)^3L diff --git a/test/elm/arithmetic/data.js b/test/elm/arithmetic/data.js index dfc83788a..fd1503036 100644 --- a/test/elm/arithmetic/data.js +++ b/test/elm/arithmetic/data.js @@ -13277,7 +13277,7 @@ define LongMultiplyNearUnderflow: minimum Long * 1L // NOTE: Long division results in decimal, so it must overflow/underflow decimal define LongDivideOverflow: maximum Long / 0.05 define LongDivideUnderflow: minimum Long / 0.05 -define LongDivideNearOverflow: maximum Long / 1024L +define LongDivideNearOverflow: maximum Long / 92737L define LongDivideNearUnderflow: minimum Long / 1024L define LongDivideByZero: 1L / 0L define LongPowerOverflow: (maximum Long)^3L @@ -16148,7 +16148,7 @@ module.exports['OutOfBounds'] = { } ] }, { "r" : "600", - "value" : [ " / ", "1024L" ] + "value" : [ " / ", "92737L" ] } ] } ] } @@ -16201,7 +16201,7 @@ module.exports['OutOfBounds'] = { "localId" : "600", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Long", "valueType" : "{urn:hl7-org:elm-types:r1}Long", - "value" : "1024", + "value" : "92737", "annotation" : [ ] } } ] diff --git a/test/elm/clinical/clinical-test.ts b/test/elm/clinical/clinical-test.ts index e7eb48279..bdb99d29f 100644 --- a/test/elm/clinical/clinical-test.ts +++ b/test/elm/clinical/clinical-test.ts @@ -484,7 +484,7 @@ describe('CalculateAge: Date-Only Birth Date as DateTime', () => { // Execute these tests as if it is 2020-10-01 at 12:01:02.003 GMT this.ctx.executionDateTime = new DT.DateTime(2020, 10, 1, 12, 1, 2, 3, 0); // Fix the timezone offset to 0 to make things more predictable - this.ctx.patient.birthDate.timezoneOffset = 0; + this.ctx.patient.birthDate.timezoneOffset = DT.Decimal.from(0); }); it('should execute age in years', async function () { @@ -529,7 +529,7 @@ describe('CalculateAge: Date-Only Birth Date as DateTime on Today', () => { // Execute these tests as if it is 2020-10-01 at 12:01:02.003 GMT this.ctx.executionDateTime = new DT.DateTime(2020, 10, 1, 12, 1, 2, 3, 0); // Fix the timezone offset to 0 to make things more predictable - this.ctx.patient.birthDate.timezoneOffset = 0; + this.ctx.patient.birthDate.timezoneOffset = DT.Decimal.from(0); }); it('should execute age in years', async function () { diff --git a/test/elm/convert/convert-test.ts b/test/elm/convert/convert-test.ts index 72f6c059a..7e0ac943a 100644 --- a/test/elm/convert/convert-test.ts +++ b/test/elm/convert/convert-test.ts @@ -29,7 +29,7 @@ describe('FromString', () => { }); it("should convert '10.2' to Decimal", async function () { - (await this.decimalValid.exec(this.ctx)).should.eql(Decimal.from(10.2)); + (await this.decimalValid.exec(this.ctx)).should.equalDecimal(Decimal.from(10.2)); }); it("should be null trying to convert 'abc' to Decimal", async function () { @@ -62,25 +62,25 @@ describe('FromString', () => { it('should convert "10 \'A\'" to Quantity', async function () { const quantity = await this.quantityStr.exec(this.ctx); - quantity.value.should.eql(Decimal.from(10)); + quantity.value.should.equalDecimal(Decimal.from(10)); quantity.unit.should.equal('A'); }); it('should convert "+10 \'A\'" to Quantity', async function () { const quantity = await this.posQuantityStr.exec(this.ctx); - quantity.value.should.eql(Decimal.from(10)); + quantity.value.should.equalDecimal(Decimal.from(10)); quantity.unit.should.equal('A'); }); it('should convert "-10 \'A\'" to Quantity', async function () { const quantity = await this.negQuantityStr.exec(this.ctx); - quantity.value.should.eql(Decimal.from(-10)); + quantity.value.should.equalDecimal(Decimal.from(-10)); quantity.unit.should.equal('A'); }); it('should convert "10.0\'mA\'" to Quantity', async function () { const quantity = await this.quantityStrDecimal.exec(this.ctx); - quantity.value.should.eql(Decimal.from(10.0)); + quantity.value.should.equalDecimal(Decimal.from(10.0)); quantity.unit.should.equal('mA'); }); @@ -105,7 +105,7 @@ describe('FromString', () => { }); it('should convert DateTime string with Z', async function () { - const expectedDateTime = new DateTime(2014, 1, 1, 14, 30, 0, 0, 0); + const expectedDateTime = new DateTime(2014, 1, 1, 14, 30, 0, 0, Decimal.from(0)); (await this.zDateTime.exec(this.ctx)).equals(expectedDateTime).should.be.true(); }); @@ -129,7 +129,7 @@ describe('FromInteger', () => { }); it('should convert 10 to 10.0', async function () { - (await this.decimal10.exec(this.ctx)).should.eql(Decimal.from(10.0)); + (await this.decimal10.exec(this.ctx)).should.equalDecimal(Decimal.from(10.0)); }); it('should convert null to null', async function () { @@ -155,7 +155,7 @@ describe('FromLong', () => { }); it('should convert 10L to 10.0', async function () { - (await this.decimal10.exec(this.ctx)).should.eql(Decimal.from(10.0)); + (await this.decimal10.exec(this.ctx)).should.equalDecimal(Decimal.from(10.0)); }); it('should convert null to null', async function () { @@ -186,7 +186,7 @@ describe('FromQuantity', () => { it('should convert "10 \'A\'" to "10 \'A\'"', async function () { const quantity = await this.quantityQuantity.exec(this.ctx); - quantity.value.should.eql(Decimal.from(10)); + quantity.value.should.equalDecimal(Decimal.from(10)); quantity.unit.should.equal('A'); }); }); @@ -243,7 +243,7 @@ describe('FromDateTime', () => { dateTime.minute.should.equal(1); dateTime.second.should.equal(2); dateTime.millisecond.should.equal(321); - dateTime.timezoneOffset.should.eql(Decimal.from(-6)); + dateTime.timezoneOffset.should.equalDecimal(Decimal.from(-6)); }); }); @@ -261,7 +261,7 @@ describe('FromDate', () => { should.not.exist(dateTime.minute); should.not.exist(dateTime.second); should.not.exist(dateTime.millisecond); - dateTime.timezoneOffset.should.equal(this.ctx.getTimezoneOffset()); + dateTime.timezoneOffset.should.equalDecimal(this.ctx.getTimezoneOffset()); dateTime.isDateTime.should.equal(true); }); @@ -274,7 +274,7 @@ describe('FromDate', () => { for (field of ['hour', 'minute', 'second', 'millisecond']) { should.not.exist(dateTime[field]); } - dateTime.timezoneOffset.should.equal(this.ctx.getTimezoneOffset()); + dateTime.timezoneOffset.should.equalDecimal(this.ctx.getTimezoneOffset()); dateTime.isDateTime.should.equal(true); }); @@ -287,7 +287,7 @@ describe('FromDate', () => { for (field of ['hour', 'minute', 'second', 'millisecond']) { should.not.exist(dateTime[field]); } - dateTime.timezoneOffset.should.equal(this.ctx.getTimezoneOffset()); + dateTime.timezoneOffset.should.equalDecimal(this.ctx.getTimezoneOffset()); dateTime.isDateTime.should.equal(true); }); @@ -345,19 +345,19 @@ describe('ToDecimal', () => { }); it("should convert '0.0' to 0.0", async function () { - (await this.noSign.exec(this.ctx)).should.eql(Decimal.from(0.0)); + (await this.noSign.exec(this.ctx)).should.equalDecimal(Decimal.from(0.0)); }); it("should convert '+1.1' to 1.1", async function () { - (await this.positiveSign.exec(this.ctx)).should.eql(Decimal.from(1.1)); + (await this.positiveSign.exec(this.ctx)).should.equalDecimal(Decimal.from(1.1)); }); it("should convert '-1.1' to -1.1", async function () { - (await this.negativeSign.exec(this.ctx)).should.eql(Decimal.from(-1.1)); + (await this.negativeSign.exec(this.ctx)).should.equalDecimal(Decimal.from(-1.1)); }); it('should truncate decimal to 8 digits after decimal point', async function () { - (await this.tooPrecise.exec(this.ctx)).should.eql(Decimal.from(0.44444444)); + (await this.tooPrecise.exec(this.ctx)).should.equalDecimal(Decimal.from(0.44444444)); }); it('should be null for decimal that is above max decimal value', async function () { @@ -561,17 +561,17 @@ describe('ToRatio', () => { it('should be valid given quantities with custom UCUM units', async function () { const ratio = await this.isValidWithCustomUCUM.exec(this.ctx); - ratio.numerator.value.should.eql(Decimal.from(1.0)); + ratio.numerator.value.should.equalDecimal(Decimal.from(1.0)); ratio.numerator.unit.should.eql('{foo:bar}'); - ratio.denominator.value.should.eql(Decimal.from(2.0)); + ratio.denominator.value.should.equalDecimal(Decimal.from(2.0)); ratio.denominator.unit.should.eql('mg'); }); it('should create valid ratio', async function () { const ratio = await this.isValid.exec(this.ctx); - ratio.numerator.value.should.eql(Decimal.from(1.0)); + ratio.numerator.value.should.equalDecimal(Decimal.from(1.0)); ratio.numerator.unit.should.eql('mg'); - ratio.denominator.value.should.eql(Decimal.from(2.0)); + ratio.denominator.value.should.equalDecimal(Decimal.from(2.0)); ratio.denominator.unit.should.eql('mg'); }); }); diff --git a/test/elm/datetime/datetime-test.ts b/test/elm/datetime/datetime-test.ts index 0c87541a8..95c449818 100644 --- a/test/elm/datetime/datetime-test.ts +++ b/test/elm/datetime/datetime-test.ts @@ -9,14 +9,14 @@ import { Decimal } from '../../../src/datatypes/decimal'; describe('DateTime', () => { beforeEach(function () { setup(this, data); - this.defaultOffset = (new Date().getTimezoneOffset() / 60) * -1; + this.defaultOffset = Decimal.from((new Date().getTimezoneOffset() / 60) * -1); }); it('should execute year precision correctly', async function () { const d = await this.year.exec(this.ctx); d.isTime().should.be.false(); d.year.should.equal(2012); - d.timezoneOffset.should.equal(this.defaultOffset); + d.timezoneOffset.should.equalDecimal(this.defaultOffset); ['month', 'day', 'hour', 'minute', 'second', 'millisecond'].forEach(field => should.not.exist(d[field]) ); @@ -27,7 +27,7 @@ describe('DateTime', () => { d.isTime().should.be.false(); d.year.should.equal(2012); d.month.should.equal(2); - d.timezoneOffset.should.equal(this.defaultOffset); + d.timezoneOffset.should.equalDecimal(this.defaultOffset); ['day', 'hour', 'minute', 'second', 'millisecond'].forEach(field => should.not.exist(d[field])); }); @@ -37,7 +37,7 @@ describe('DateTime', () => { d.year.should.equal(2012); d.month.should.equal(2); d.day.should.equal(15); - d.timezoneOffset.should.equal(this.defaultOffset); + d.timezoneOffset.should.equalDecimal(this.defaultOffset); ['hour', 'minute', 'second', 'millisecond'].forEach(field => should.not.exist(d[field])); }); @@ -48,7 +48,7 @@ describe('DateTime', () => { d.month.should.equal(2); d.day.should.equal(15); d.hour.should.equal(12); - d.timezoneOffset.should.equal(this.defaultOffset); + d.timezoneOffset.should.equalDecimal(this.defaultOffset); ['minute', 'second', 'millisecond'].forEach(field => should.not.exist(d[field])); }); @@ -60,7 +60,7 @@ describe('DateTime', () => { d.day.should.equal(15); d.hour.should.equal(12); d.minute.should.equal(10); - d.timezoneOffset.should.equal(this.defaultOffset); + d.timezoneOffset.should.equalDecimal(this.defaultOffset); ['second', 'millisecond'].forEach(field => should.not.exist(d[field])); }); @@ -73,7 +73,7 @@ describe('DateTime', () => { d.hour.should.equal(12); d.minute.should.equal(10); d.second.should.equal(59); - d.timezoneOffset.should.equal(this.defaultOffset); + d.timezoneOffset.should.equalDecimal(this.defaultOffset); should.not.exist(d.millisecond); }); @@ -87,7 +87,7 @@ describe('DateTime', () => { d.minute.should.equal(10); d.second.should.equal(59); d.millisecond.should.equal(456); - d.timezoneOffset.should.equal(this.defaultOffset); + d.timezoneOffset.should.equalDecimal(this.defaultOffset); }); it('should execute timezone offsets correctly', async function () { @@ -100,7 +100,7 @@ describe('DateTime', () => { d.minute.should.equal(10); d.second.should.equal(59); d.millisecond.should.equal(456); - d.timezoneOffset.should.eql(Decimal.from(-8)); + d.timezoneOffset.should.equalDecimal(Decimal.from(-8)); }); }); @@ -219,7 +219,7 @@ describe('Now', () => { should.exist(now.minute); should.exist(now.second); should.exist(now.millisecond); - now.timezoneOffset.should.equal(this.ctx.getTimezoneOffset()); + now.timezoneOffset.should.equalDecimal(this.ctx.getTimezoneOffset()); }); it('should return all date components representing now using a passed in timezone', async function () { @@ -239,7 +239,7 @@ describe('Now', () => { should.exist(now.minute); should.exist(now.second); should.exist(now.millisecond); - now.timezoneOffset.should.equal('0'); + now.timezoneOffset.should.equalDecimal(Decimal.from(0)); }); it('should return all date components representing now using a passed in timezone using a child context', async function () { @@ -260,8 +260,8 @@ describe('Now', () => { should.exist(now.minute); should.exist(now.second); should.exist(now.millisecond); - now.timezoneOffset.should.equal(this.child_ctx.getTimezoneOffset()); - now.timezoneOffset.should.equal('0'); + now.timezoneOffset.should.equalDecimal(this.child_ctx.getTimezoneOffset()); + now.timezoneOffset.should.equalDecimal(Decimal.from(0)); }); }); @@ -409,13 +409,13 @@ describe('TimezoneOffsetFrom', () => { }); it('should return the timezoneoffset from a fully defined DateTime', async function () { - (await this.centralEuropean.exec(this.ctx)).should.eql(Decimal.from(1)); - (await this.easternStandard.exec(this.ctx)).should.eql(Decimal.from(-5)); + (await this.centralEuropean.exec(this.ctx)).should.equalDecimal(Decimal.from(1)); + (await this.easternStandard.exec(this.ctx)).should.equalDecimal(Decimal.from(-5)); }); it('should return the default timezone when not specified', async function () { - (await this.defaultTimezone.exec(this.ctx)).should.equal( - (new Date().getTimezoneOffset() / 60) * -1 + (await this.defaultTimezone.exec(this.ctx)).should.equalDecimal( + Decimal.from((new Date().getTimezoneOffset() / 60) * -1) ); }); diff --git a/test/elm/interval/interval-test.ts b/test/elm/interval/interval-test.ts index 45d5b94b3..78757f286 100644 --- a/test/elm/interval/interval-test.ts +++ b/test/elm/interval/interval-test.ts @@ -1619,9 +1619,9 @@ describe('Width', () => { it('should calculate the width of real intervals', async function () { // define RealWidth: width of Interval[1.23, 4.56] - (await this.realWidth.exec(this.ctx)).should.eql(Decimal.from(3.33)); + (await this.realWidth.exec(this.ctx)).should.equalDecimal(Decimal.from(3.33)); // define RealOpenWidth: width of Interval(1.23, 4.56) - (await this.realOpenWidth.exec(this.ctx)).should.eql(Decimal.from(3.32999998)); + (await this.realOpenWidth.exec(this.ctx)).should.equalDecimal(Decimal.from(3.32999998)); }); it('should calculate the width of infinite intervals', async function () { @@ -1645,7 +1645,7 @@ describe('Width', () => { it('should calculate the width of interval of quantities', async function () { // define WidthOfQuantityInterval: width of Interval[Quantity{value: 1, unit: 'mm'}, Quantity{value: 10, unit: 'mm'}] const width = await this.widthOfQuantityInterval.exec(this.ctx); - width.value.should.eql(Decimal.from(9)); + width.value.should.equalDecimal(Decimal.from(9)); width.unit.should.equal('mm'); }); @@ -1686,9 +1686,9 @@ describe('Size', () => { it('should calculate the size of real intervals', async function () { // define RealSize: Size(Interval[1.23, 4.56]) - (await this.realSize.exec(this.ctx)).should.eql(Decimal.from(3.33 + MIN_FLOAT_PRECISION_VALUE)); + (await this.realSize.exec(this.ctx)).should.equalDecimal(Decimal.from(3.33 + MIN_FLOAT_PRECISION_VALUE)); // define RealOpenSize: Size(Interval(1.23, 4.56)) - (await this.realOpenSize.exec(this.ctx)).should.eql(Decimal.from(3.32999998 + MIN_FLOAT_PRECISION_VALUE)); + (await this.realOpenSize.exec(this.ctx)).should.equalDecimal(Decimal.from(3.32999998 + MIN_FLOAT_PRECISION_VALUE)); }); it('should calculate the size of infinite intervals', async function () { @@ -1722,7 +1722,7 @@ describe('Size', () => { it('should calculate size of interval of quantities', async function () { // define SizeOfQuantityInterval: Size(Interval[Quantity{value: 1, unit: 'mm'}, Quantity{value: 10, unit: 'mm'}]) const size = await this.sizeOfQuantityInterval.exec(this.ctx); - size.value.should.eql(Decimal.from(9.00000001)); + size.value.should.equalDecimal(Decimal.from(9.00000001)); size.unit.should.equal('mm'); }); @@ -1758,7 +1758,7 @@ describe('Start', () => { it('should return the minimum possible DateTime in timzoneOffset of context', async function () { // set execution timestamp to be +5 this.ctx.executionDateTime = new DateTime(2019, 10, 1, 12, 31, 31, 2, 5); - (await this.closedNullDateTime.exec(this.ctx)).timezoneOffset.should.eql(5); + (await this.closedNullDateTime.exec(this.ctx)).timezoneOffset.should.equalDecimal(Decimal.from(5)); }); it('should return the minimum possible Integer', async function () { @@ -1808,7 +1808,7 @@ describe('End', () => { it('should return the maximum possible DateTime in timzoneOffset of context', async function () { // set execution timestamp to be +5 this.ctx.executionDateTime = new DateTime(2019, 10, 1, 12, 31, 31, 2, 5); - (await this.closedNullDateTime.exec(this.ctx)).timezoneOffset.should.eql(5); + (await this.closedNullDateTime.exec(this.ctx)).timezoneOffset.should.equalDecimal(Decimal.from(5)); }); it('should return the maximum possible Integer', async function () { diff --git a/test/elm/literal/literal-test.ts b/test/elm/literal/literal-test.ts index 87f1d508c..c409a696d 100644 --- a/test/elm/literal/literal-test.ts +++ b/test/elm/literal/literal-test.ts @@ -41,11 +41,11 @@ describe('Literal', () => { }); it('should convert .1 to decimal .1', function () { - this.decimalTenth.value.should.eql(Decimal.from(0.1)); + this.decimalTenth.value.should.equalDecimal(Decimal.from(0.1)); }); it('should execute .1 as .1', async function () { - (await this.decimalTenth.exec(this.ctx)).should.eql(Decimal.from(0.1)); + (await this.decimalTenth.exec(this.ctx)).should.equalDecimal(Decimal.from(0.1)); }); it("should convert 'true' to string 'true'", function () { @@ -66,7 +66,7 @@ describe('Literal', () => { d.minute.should.equal(10); d.second.should.equal(59); d.millisecond.should.equal(456); - d.timezoneOffset.should.eql(Decimal.from(0)); + d.timezoneOffset.should.equalDecimal(Decimal.from(0)); }); it("should execute '' as correct Time", async function () { diff --git a/test/elm/message/message-test.ts b/test/elm/message/message-test.ts index c1f2d29db..67d1243c0 100644 --- a/test/elm/message/message-test.ts +++ b/test/elm/message/message-test.ts @@ -14,7 +14,7 @@ describe('Message', () => { }); it('should always return the first argument as-is', async function () { - (await this.oneOverTwo.exec(this.ctx)).should.eql(Decimal.from(0.5)); + (await this.oneOverTwo.exec(this.ctx)).should.equalDecimal(Decimal.from(0.5)); should(await this.oneOverZero.exec(this.ctx)).be.null(); }); @@ -40,7 +40,7 @@ describe('Retrieve', () => { }); it('should always return the first argument as-is', async function () { - (await this.oneOverTwo.exec(this.ctx)).should.eql(Decimal.from(0.5)); + (await this.oneOverTwo.exec(this.ctx)).should.equalDecimal(Decimal.from(0.5)); should(await this.oneOverZero.exec(this.ctx)).be.null(); }); diff --git a/test/elm/parameters/parameters-test.ts b/test/elm/parameters/parameters-test.ts index 3a7c80ff4..dc2e354a9 100644 --- a/test/elm/parameters/parameters-test.ts +++ b/test/elm/parameters/parameters-test.ts @@ -100,7 +100,7 @@ describe('DecimalParameterTypes', () => { }); it('should execute to provided valid value', async function () { - (await this.foo.exec(this.ctx.withParameters({ FooP: Decimal.from(3.0) }))).should.eql(Decimal.from(3.0)); + (await this.foo.exec(this.ctx.withParameters({ FooP: Decimal.from(3.0) }))).should.equalDecimal(Decimal.from(3.0)); }); it('should throw when provided value is wrong type', function () { @@ -108,11 +108,11 @@ describe('DecimalParameterTypes', () => { }); it('should execute to default value', async function () { - (await this.foo2.exec(this.ctx)).should.eql(Decimal.from(1.5)); + (await this.foo2.exec(this.ctx)).should.equalDecimal(Decimal.from(1.5)); }); it('should execute to overriding valid value', async function () { - (await this.foo2.exec(this.ctx.withParameters({ FooDP: Decimal.from(3.0) }))).should.eql(Decimal.from(3.0)); + (await this.foo2.exec(this.ctx.withParameters({ FooDP: Decimal.from(3.0) }))).should.equalDecimal(Decimal.from(3.0)); }); it('should throw when overriding value is wrong type', function () { diff --git a/test/elm/quantity/quantity-test.ts b/test/elm/quantity/quantity-test.ts index 602f5239e..81a4afb99 100644 --- a/test/elm/quantity/quantity-test.ts +++ b/test/elm/quantity/quantity-test.ts @@ -63,7 +63,7 @@ describe('Quantity', () => { const denominator = new Quantity(2.0, 'mg'); const result = numerator.dividedBy(denominator); result.unit.should.equal('1'); - result.value.should.eql(Decimal.from(-2.75)); + result.value.should.equalDecimal(Decimal.from(-2.75)); }); it('should allow for singular time units', () => { diff --git a/test/elm/query/query-test.ts b/test/elm/query/query-test.ts index 2ee9e3cb4..cc25054c5 100644 --- a/test/elm/query/query-test.ts +++ b/test/elm/query/query-test.ts @@ -197,12 +197,12 @@ describe('Sorting', () => { it('should correctly sort quantities asc', async function () { const e = await this.quantityListAsc.exec(this.ctx); e.should.have.length(2); - e[0]['value'].should.eql(Decimal.from(2)); + e[0]['value'].should.equalDecimal(Decimal.from(2)); }); it('should correctly sort quantities', async function () { const e = await this.quantityListSort.exec(this.ctx); - e[0]['N']['value'].should.eql(Decimal.from(2)); + e[0]['N']['value'].should.equalDecimal(Decimal.from(2)); }); it('should be able to sort by a tuple field asc', async function () { diff --git a/test/should-extensions.ts b/test/should-extensions.ts index 2e48408bd..1d29e8026 100644 --- a/test/should-extensions.ts +++ b/test/should-extensions.ts @@ -1,9 +1,11 @@ import should from 'should'; import { Interval } from '../src/datatypes/interval'; +import { Decimal } from '../src/datatypes/decimal'; declare module 'should' { interface Assertion { equalInterval(expected: Interval): this; + equalDecimal(expected: Decimal): this; } } @@ -28,3 +30,12 @@ declare module 'should' { ); normalizedThis.should.eql(normalizedExpected); }); + +(should as any).Assertion.add('equalDecimal', function (this: any, expected: number | bigint | Decimal) { + this.params = { operator: 'to equal Decimal', expected: expected.toString(), obj: this.obj.toString() }; + + this.assert( + this.obj instanceof Decimal && + this.obj.equals(expected) + ); +}); \ No newline at end of file diff --git a/test/spec-tests/cql/CqlArithmeticFunctionsTest.cql b/test/spec-tests/cql/CqlArithmeticFunctionsTest.cql index 85a57481f..cc94b950e 100644 --- a/test/spec-tests/cql/CqlArithmeticFunctionsTest.cql +++ b/test/spec-tests/cql/CqlArithmeticFunctionsTest.cql @@ -924,19 +924,19 @@ define "Truncated Divide": Tuple{ output: 2.0 }, "TruncatedDivide10d1ByNeg3D1Quantity": Tuple{ - skipped: 'Truncated divide not implemented for Quantity' + skipped: 'Wrong output: The resulting Quantity should have an appropriate unit; \'g\' / \'g\' should be \'1\', not \'g\'. See test Divide1Q1Q which is correct' /* expression: 10.1 'cm' div -3.1 'cm', output: -3.0 'cm' */ }, "TruncatedDivide10By5DQuantity": Tuple{ - skipped: 'Truncated divide not implemented for Quantity' + skipped: 'Wrong output: The resulting Quantity should have an appropriate unit' /* expression: 10.0 'g' div 5.0 'g', output: 2.0 'g' */ }, "TruncatedDivide414By206DQuantity": Tuple{ - skipped: 'Truncated divide not implemented for Quantity' + skipped: 'Wrong output: The resulting Quantity should have an appropriate unit' /* expression: 4.14 'm' div 2.06 'm', output: 2.0 'm' diff --git a/test/spec-tests/cql/CqlArithmeticFunctionsTest.json b/test/spec-tests/cql/CqlArithmeticFunctionsTest.json index fe1b271b4..d09ab84b4 100644 --- a/test/spec-tests/cql/CqlArithmeticFunctionsTest.json +++ b/test/spec-tests/cql/CqlArithmeticFunctionsTest.json @@ -26454,7 +26454,7 @@ "type": "Literal", "resultTypeName": "{urn:hl7-org:elm-types:r1}String", "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Truncated divide not implemented for Quantity", + "value": "Wrong output: The resulting Quantity should have an appropriate unit; 'g' / 'g' should be '1', not 'g'. See test Divide1Q1Q which is correct", "annotation": [] } } @@ -26488,7 +26488,7 @@ "type": "Literal", "resultTypeName": "{urn:hl7-org:elm-types:r1}String", "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Truncated divide not implemented for Quantity", + "value": "Wrong output: The resulting Quantity should have an appropriate unit", "annotation": [] } } @@ -26522,7 +26522,7 @@ "type": "Literal", "resultTypeName": "{urn:hl7-org:elm-types:r1}String", "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Truncated divide not implemented for Quantity", + "value": "Wrong output: The resulting Quantity should have an appropriate unit", "annotation": [] } } diff --git a/test/spec-tests/cql/ValueLiteralsAndSelectors.cql b/test/spec-tests/cql/ValueLiteralsAndSelectors.cql index 7addbb153..1bf057dab 100644 --- a/test/spec-tests/cql/ValueLiteralsAndSelectors.cql +++ b/test/spec-tests/cql/ValueLiteralsAndSelectors.cql @@ -253,13 +253,13 @@ define "Decimal": Tuple{ invalid: true }, "Decimal10Pow28ToZeroOneStepDecimalMaxValue": Tuple{ - skipped: 'Wrong answer (null vs big number)' + skipped: 'Wrong answer (null vs big number); intermediate value exceeds max Decimal' /* expression: 10*1000000000000000000000000000.00000000-0.00000001, output: 9999999999999999999999999999.99999999 */ }, "DecimalPos10Pow28ToZeroOneStepDecimalMaxValue": Tuple{ - skipped: 'Wrong answer (null vs big number)' + skipped: 'Wrong answer (null vs big number); intermediate value exceeds max Decimal' /* expression: +10*1000000000000000000000000000.00000000-0.00000001, output: 9999999999999999999999999999.99999999 diff --git a/test/spec-tests/cql/ValueLiteralsAndSelectors.json b/test/spec-tests/cql/ValueLiteralsAndSelectors.json index ff210f475..b4743e7c5 100644 --- a/test/spec-tests/cql/ValueLiteralsAndSelectors.json +++ b/test/spec-tests/cql/ValueLiteralsAndSelectors.json @@ -8284,7 +8284,7 @@ "type": "Literal", "resultTypeName": "{urn:hl7-org:elm-types:r1}String", "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (null vs big number)", + "value": "Wrong answer (null vs big number); intermediate value exceeds max Decimal", "annotation": [] } } @@ -8318,7 +8318,7 @@ "type": "Literal", "resultTypeName": "{urn:hl7-org:elm-types:r1}String", "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (null vs big number)", + "value": "Wrong answer (null vs big number); intermediate value exceeds max Decimal", "annotation": [] } } diff --git a/test/spec-tests/skip-list.txt b/test/spec-tests/skip-list.txt index e8d2d5152..a939904b0 100644 --- a/test/spec-tests/skip-list.txt +++ b/test/spec-tests/skip-list.txt @@ -13,6 +13,9 @@ CqlListOperatorsTest.Equal.EqualNullNull Wrong output: Ac CqlListOperatorsTest.Sort.simpleSortAsc Wrong output: Queries return distinct lists by default; need to use "all" to retain duplicates CqlListOperatorsTest.Sort.simpleSortDesc Wrong output: Queries return distinct lists by default; need to use "all" to retain duplicates CqlIntervalOperatorsTest.PointFrom.TestPointFromNull Wrong output: Interval[null, null] is not a unit interval, nor is it null +"CqlArithmeticFunctionsTest.Truncated Divide.TruncatedDivide10d1ByNeg3D1Quantity" Wrong output: The resulting Quantity should have an appropriate unit; 'g' / 'g' should be '1', not 'g'. See test Divide1Q1Q which is correct +"CqlArithmeticFunctionsTest.Truncated Divide.TruncatedDivide10By5DQuantity" Wrong output: The resulting Quantity should have an appropriate unit +"CqlArithmeticFunctionsTest.Truncated Divide.TruncatedDivide414By206DQuantity" Wrong output: The resulting Quantity should have an appropriate unit # Potentially Incorrect Expected Output "CqlStringOperatorsTest.toString tests.DateTimeToString2" Answer does not include timezone offset, but default offset depends on test environment @@ -42,8 +45,8 @@ CqlTypeOperatorsTest.Convert.StringToDateTime Wrong answer (di CqlTypeOperatorsTest.ToDateTime.ToDateTime1 Wrong answer (different offsets) CqlTypeOperatorsTest.ToDateTime.ToDateTime2 Wrong answer (different offsets) CqlTypeOperatorsTest.ToDateTime.ToDateTime3 Wrong answer (different offsets) -ValueLiteralsAndSelectors.Decimal.Decimal10Pow28ToZeroOneStepDecimalMaxValue Wrong answer (null vs big number) -ValueLiteralsAndSelectors.Decimal.DecimalPos10Pow28ToZeroOneStepDecimalMaxValue Wrong answer (null vs big number) +ValueLiteralsAndSelectors.Decimal.Decimal10Pow28ToZeroOneStepDecimalMaxValue Wrong answer (null vs big number); intermediate value exceeds max Decimal +ValueLiteralsAndSelectors.Decimal.DecimalPos10Pow28ToZeroOneStepDecimalMaxValue Wrong answer (null vs big number); intermediate value exceeds max Decimal # Unimplemented CqlArithmeticFunctionsTest.HighBoundary HighBoundary not implemented @@ -54,9 +57,6 @@ CqlListOperatorsTest.Descendents Descendents not implemen # Unimplemented (New in CQL 1.5) CqlArithmeticFunctionsTest.Modulo.ModuloQuantity Modulo not implemented for Quantity CqlArithmeticFunctionsTest.Modulo.Modulo10By3Quantity Modulo not implemented for Quantity -"CqlArithmeticFunctionsTest.Truncated Divide.TruncatedDivide10d1ByNeg3D1Quantity" Truncated divide not implemented for Quantity -"CqlArithmeticFunctionsTest.Truncated Divide.TruncatedDivide10By5DQuantity" Truncated divide not implemented for Quantity -"CqlArithmeticFunctionsTest.Truncated Divide.TruncatedDivide414By206DQuantity" Truncated divide not implemented for Quantity # Unimplemented (New in CQL 2.0) CqlListOperatorsTest.Slice Slice not implemented \ No newline at end of file diff --git a/test/spec-tests/spec-test.ts b/test/spec-tests/spec-test.ts index e1bcbf3f1..9809612bf 100644 --- a/test/spec-tests/spec-test.ts +++ b/test/spec-tests/spec-test.ts @@ -6,6 +6,7 @@ import '../../src/elm/expressions'; // Needed for side-effect import { build } from '../../src/elm/builder'; import { Library } from '../../src/elm/library'; import { Uncertainty } from '../../src/datatypes/uncertainty'; +import { Decimal } from '../../src/datatypes/decimal'; describe('CQL Spec Tests (from XML)', () => { fs.readdirSync(path.join(__dirname, 'cql')).forEach(f => { @@ -53,7 +54,7 @@ describe('CQL Spec Tests (from XML)', () => { } if (testCaseMap.has('expression') && testCaseMap.has('output')) { const ctx = new PatientContext(library); - ctx.getExecutionDateTime().timezoneOffset = 0; + ctx.getExecutionDateTime().timezoneOffset = Decimal.from(0); const actualExp = build(testCaseMap.get('expression')) as any; const actual = await actualExp.execute(ctx); const expectedExp = build(testCaseMap.get('output')) as any; @@ -95,11 +96,17 @@ describe('CQL Spec Tests (from XML)', () => { } catch { should.fail(actual, expected, 'Lists are not equal'); } - } else { + } else if (expected instanceof Decimal) { // The tests are somewhat inconsistent w/ number of decimal places used. // To get consistency (and avoid false negatives), always round to 8 places. actual = roundDecimalsWhenApplicable(actual); expected = roundDecimalsWhenApplicable(expected); + if (actual == null) { + should.deepEqual(actual, expected); + } else { + actual.should.equalDecimal(expected); + } + } else { if (actual == null) { should.deepEqual(actual, expected); } else { @@ -121,9 +128,9 @@ describe('CQL Spec Tests (from XML)', () => { } function roundDecimalsWhenApplicable(item: any) { - if (typeof item === 'number') { + if (item instanceof Decimal) { // Round to 8 places since that's the number of places used by expected outputs - item = Math.round(item * 100000000) / 100000000; + item = item.setScale(8); } return item; } diff --git a/test/util/math-test.ts b/test/util/math-test.ts index 16cb165b8..905ac4c17 100644 --- a/test/util/math-test.ts +++ b/test/util/math-test.ts @@ -13,8 +13,8 @@ describe('successor', () => { it('should preserve decimals in an Uncertainty', () => { const result = successor(new Uncertainty(Decimal.from(1.0), Decimal.from(2.0)), ELM_DECIMAL_TYPE); - result.low.should.eql(Decimal.from(1.00000001)); - result.high.should.eql(Decimal.from(2.00000001)); + result.low.should.equalDecimal(Decimal.from(1.00000001)); + result.high.should.equalDecimal(Decimal.from(2.00000001)); }); it('should leave the uncertainty high unchanged when it overflows', () => { @@ -32,8 +32,8 @@ describe('predecessor', () => { it('should preserve decimals in an Uncertainty', () => { const result = successor(new Uncertainty(Decimal.from(1.0), Decimal.from(2.0)), ELM_DECIMAL_TYPE); - result.low.should.eql(Decimal.from(1.00000001)); - result.high.should.eql(Decimal.from(2.00000001)); + result.low.should.equalDecimal(Decimal.from(1.00000001)); + result.high.should.equalDecimal(Decimal.from(2.00000001)); }); it('should leave the uncertainty low unchanged when it underflows', () => { diff --git a/test/util/units-test.ts b/test/util/units-test.ts index b3c046065..9cf5ea45c 100644 --- a/test/util/units-test.ts +++ b/test/util/units-test.ts @@ -109,38 +109,38 @@ describe('checkUnit', () => { describe('convertUnit', () => { it('should convert compatible units', () => { - convertUnit(Decimal.from(18), '[in_i]', '[ft_i]').should.eql(Decimal.from(1.5)); + convertUnit(Decimal.from(18), '[in_i]', '[ft_i]').should.equalDecimal(Decimal.from(1.5)); }); it('should return same value for same units', () => { - convertUnit(Decimal.from(18), '[in_i]', '[in_i]').should.eql(Decimal.from(18)); + convertUnit(Decimal.from(18), '[in_i]', '[in_i]').should.equalDecimal(Decimal.from(18)); }); it('should consider empty as 1 during conversion', () => { - convertUnit(Decimal.from(18), '', '').should.eql(Decimal.from(18)); - convertUnit(Decimal.from(18), null, null).should.eql(Decimal.from(18)); - convertUnit(Decimal.from(18), '', null).should.eql(Decimal.from(18)); - convertUnit(Decimal.from(18), null, '').should.eql(Decimal.from(18)); + convertUnit(Decimal.from(18), '', '').should.equalDecimal(Decimal.from(18)); + convertUnit(Decimal.from(18), null, null).should.equalDecimal(Decimal.from(18)); + convertUnit(Decimal.from(18), '', null).should.equalDecimal(Decimal.from(18)); + convertUnit(Decimal.from(18), null, '').should.equalDecimal(Decimal.from(18)); }); it('should support CQL date units during conversion', () => { - convertUnit(Decimal.from(18), 'months', 'years').should.eql(Decimal.from(1.5)); - convertUnit(Decimal.from(1.5), 'years', 'months').should.eql(Decimal.from(18)); - convertUnit(Decimal.from(2), 'seconds', 'milliseconds').should.eql(Decimal.from(2000)); - convertUnit(Decimal.from(2000), 'milliseconds', 'seconds').should.eql(Decimal.from(2)); + convertUnit(Decimal.from(18), 'months', 'years').should.equalDecimal(Decimal.from(1.5)); + convertUnit(Decimal.from(1.5), 'years', 'months').should.equalDecimal(Decimal.from(18)); + convertUnit(Decimal.from(2), 'seconds', 'milliseconds').should.equalDecimal(Decimal.from(2000)); + convertUnit(Decimal.from(2000), 'milliseconds', 'seconds').should.equalDecimal(Decimal.from(2)); }); it('should truncate precision to 8 decimals by default', () => { const result = convertUnit(Decimal.from(1), '[ft_i]', '[mi_i]'); - result.should.eql(Decimal.from("0.00018939")); + result.should.equalDecimal(Decimal.from("0.00018939")); }); - it('should note truncate precision to 8 decimals when adjustPrecision is false', () => { - const result = convertUnit(Decimal.from(1), '[ft_i]', '[mi_i]', false); - result.should.not.eql(Decimal.from("0.00018939")); - result.toString().length.should.be.greaterThan(10); - result.toString().should.startWith('0.000189393939393'); - }); + // it('should not truncate precision to 8 decimals when adjustPrecision is false', () => { + // const result = convertUnit(Decimal.from(1), '[ft_i]', '[mi_i]', false); + // result.should.not.equalDecimal(Decimal.from("0.00018939")); + // result.toString().length.should.be.greaterThan(10); + // result.toString().should.startWith('0.000189393939393'); + // }); it('should return undefined for incompatible units', () => { should(convertUnit(Decimal.from(18), '[in_i]', '[in_i]2')).be.undefined(); @@ -148,34 +148,35 @@ describe('convertUnit', () => { }); describe('normalizeUnitsWhenPossible', () => { + it('should keep same units', () => { - normalizeUnitsWhenPossible(10, 'm', 1, 'm').should.eql([10, 'm', 1, 'm']); + normalizeUnitsWhenPossible(Decimal.from(10), 'm', Decimal.from(1), 'm').should.eql([Decimal.from(10), 'm', Decimal.from(1), 'm']); }); it('should convert compatible units, preferring smaller units', () => { - normalizeUnitsWhenPossible(10, 'cm', 1, 'm').should.eql([10, 'cm', 100, 'cm']); - normalizeUnitsWhenPossible(1, 'm', 10, 'cm').should.eql([100, 'cm', 10, 'cm']); + normalizeUnitsWhenPossible(Decimal.from(10), 'cm', Decimal.from(1), 'm').should.eql([Decimal.from(10), 'cm', Decimal.from(100), 'cm']); + normalizeUnitsWhenPossible(Decimal.from(1), 'm', Decimal.from(10), 'cm').should.eql([Decimal.from(100), 'cm', Decimal.from(10), 'cm']); }); it('should treat null or empty string units as 1', () => { - normalizeUnitsWhenPossible(10, null, 1, '').should.eql([10, '1', 1, '1']); - normalizeUnitsWhenPossible(1, '', 10, null).should.eql([1, '1', 10, '1']); + normalizeUnitsWhenPossible(Decimal.from(10), null, Decimal.from(1), '').should.eql([Decimal.from(10), '1', Decimal.from(1), '1']); + normalizeUnitsWhenPossible(Decimal.from(1), '', Decimal.from(10), null).should.eql([Decimal.from(1), '1', Decimal.from(10), '1']); }); it('should normalize CQL date units and return CQL date units', () => { - normalizeUnitsWhenPossible(10, 'year', 12, 'month').should.eql([120, 'month', 12, 'month']); + normalizeUnitsWhenPossible(Decimal.from(10), 'year', Decimal.from(12), 'month').should.eql([Decimal.from(120), 'month', Decimal.from(12), 'month']); }); it('should return CQL date units when UCUM units are passed in', () => { - normalizeUnitsWhenPossible(10, 'a_g', 12, 'mo_g').should.eql([120, 'mo_g', 12, 'mo_g']); + normalizeUnitsWhenPossible(Decimal.from(10), 'a_g', Decimal.from(12), 'mo_g').should.eql([Decimal.from(120), 'mo_g', Decimal.from(12), 'mo_g']); }); it('should not convert units of different dimensions', () => { - normalizeUnitsWhenPossible(10, 'm', 1, 'm2').should.eql([10, 'm', 1, 'm2']); + normalizeUnitsWhenPossible(Decimal.from(10), 'm', Decimal.from(1), 'm2').should.eql([Decimal.from(10), 'm', Decimal.from(1), 'm2']); }); it('should not convert incompatible units', () => { - normalizeUnitsWhenPossible(10, 'm', 1, 'mg').should.eql([10, 'm', 1, 'mg']); + normalizeUnitsWhenPossible(Decimal.from(10), 'm', Decimal.from(1), 'mg').should.eql([Decimal.from(10), 'm', Decimal.from(1), 'mg']); }); }); From 2e8cc8a934653532b5a05bf74a81864dca2292ad Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 20 Aug 2026 13:34:37 -0400 Subject: [PATCH 03/19] CQL Decimal improvements, checkpoint 3 --- src/datatypes/decimal.ts | 32 ++++----- src/datatypes/interval.ts | 5 +- src/datatypes/quantity.ts | 10 ++- src/datatypes/uncertainty.ts | 2 +- src/elm/aggregate.ts | 93 +++++++++++++------------- src/elm/arithmetic.ts | 63 +++++++---------- src/elm/interval.ts | 57 ++++++---------- src/elm/type.ts | 6 +- src/util/comparison.ts | 4 +- src/util/immutableUtil.ts | 12 +++- src/util/math.ts | 48 +++++++------ src/util/units.ts | 1 - test/datatypes/date-test.ts | 4 +- test/datatypes/interval-test.ts | 54 ++++++++++++--- test/elm/aggregate/aggregate-test.ts | 34 +++------- test/elm/arithmetic/arithmetic-test.ts | 38 ++++++++--- test/elm/interval/interval-test.ts | 19 ++++-- test/elm/parameters/parameters-test.ts | 32 ++++++--- test/should-extensions.ts | 18 +++-- test/spec-tests/spec-test.ts | 3 - test/util/math-test.ts | 10 ++- test/util/units-test.ts | 66 +++++++++++++++--- 22 files changed, 350 insertions(+), 261 deletions(-) diff --git a/src/datatypes/decimal.ts b/src/datatypes/decimal.ts index 55b60c82a..8e77a9e38 100644 --- a/src/datatypes/decimal.ts +++ b/src/datatypes/decimal.ts @@ -1,4 +1,3 @@ - import { Decimal as DecimalJS } from 'decimal.js'; // Default precision is set to 30 significant figures. (Not decimal places) @@ -44,28 +43,25 @@ export class Decimal { return this.setScale(CQL_IMPLICIT_SCALE, CQL_IMPLICIT_ROUNDING); } - private applyWrapper( - operation: (value: any) => DecimalJS, - other: DecimalInput - ): Decimal { + private applyWrapper(operation: (value: any) => DecimalJS, other: DecimalInput): Decimal { const operand = other instanceof Decimal ? other.value : other; return new Decimal(operation.call(this.value, operand)); } - add(other: DecimalInput) : Decimal { + add(other: DecimalInput): Decimal { return this.applyWrapper(this.value.add, other); } - subtract(other: DecimalInput) : Decimal { + subtract(other: DecimalInput): Decimal { return this.applyWrapper(this.value.minus, other); } - multiplyBy(other: DecimalInput) : Decimal { + multiplyBy(other: DecimalInput): Decimal { return this.applyWrapper(this.value.times, other); } - divideBy(other: DecimalInput) : Decimal { + divideBy(other: DecimalInput): Decimal { if (toNumber(other) === 0) { throw new RangeError('Cannot divide a decimal by zero'); } @@ -82,7 +78,7 @@ export class Decimal { compareTo(other: DecimalInput) { if (other instanceof Decimal) { - return this.value.comparedTo(other.value) + return this.value.comparedTo(other.value); } return this.value.comparedTo(other); } @@ -94,7 +90,7 @@ export class Decimal { greaterThanOrEquals(other: DecimalInput) { return this.compareTo(other) >= 0; } - + lessThan(other: DecimalInput) { return this.compareTo(other) < 0; } @@ -123,19 +119,19 @@ export class Decimal { return new Decimal(this.value.abs()); } - truncate() : number { + truncate(): number { return this.value.truncated().toNumber(); } - truncated() : Decimal { + truncated(): Decimal { return new Decimal(this.value.truncated()); } - ceil() : number { + ceil(): number { return this.value.ceil().toNumber(); } - floor() : number { + floor(): number { return this.value.floor().toNumber(); } @@ -177,7 +173,7 @@ export class Decimal { if (!Number.isInteger(scale) || scale < 0) { throw new RangeError('Decimal scale must be a non-negative integer'); } - + return new Decimal(this.value.toDecimalPlaces(scale, roundingMode)); } @@ -203,8 +199,8 @@ export class Decimal { } } -export const MAX_DECIMAL_STRING = "99999999999999999999.99999999"; -export const MIN_DECIMAL_STRING = "-99999999999999999999.99999999"; +export const MAX_DECIMAL_STRING = '99999999999999999999.99999999'; +export const MIN_DECIMAL_STRING = '-99999999999999999999.99999999'; export const MAX_DECIMAL_VALUE = Decimal.from(MAX_DECIMAL_STRING); export const MIN_DECIMAL_VALUE = Decimal.from(MIN_DECIMAL_STRING); diff --git a/src/datatypes/interval.ts b/src/datatypes/interval.ts index 064e3460f..06045d031 100644 --- a/src/datatypes/interval.ts +++ b/src/datatypes/interval.ts @@ -20,7 +20,6 @@ import { ELM_QUANTITY_TYPE, ELM_ANY_TYPE } from '../util/elmTypes'; -import { MIN_FLOAT_VALUE } from '../util/limits'; import { Quantity } from './quantity'; import { Decimal, MIN_DECIMAL_VALUE } from './decimal'; @@ -780,8 +779,8 @@ export class Interval { toString() { const start = this.lowClosed ? '[' : '('; const end = this.highClosed ? ']' : ')'; - const lowString = this.low == null ? "null" : this.low.toString(); - const highString = this.high == null ? "null" : this.high.toString(); + const lowString = this.low == null ? 'null' : this.low.toString(); + const highString = this.high == null ? 'null' : this.high.toString(); return start + lowString + ', ' + highString + end; } } diff --git a/src/datatypes/quantity.ts b/src/datatypes/quantity.ts index 62c56b7ec..d62110136 100644 --- a/src/datatypes/quantity.ts +++ b/src/datatypes/quantity.ts @@ -1,5 +1,5 @@ import { ELM_DECIMAL_TYPE } from '../util/elmTypes'; -import { decimalAdjust, add, subtract, isValidDecimal, overflowsOrUnderflows } from '../util/math'; +import { add, subtract, isValidDecimal, overflowsOrUnderflows } from '../util/math'; import { Decimal } from './decimal'; import { checkUnit, @@ -16,7 +16,7 @@ export class Quantity { value?: Decimal | string | number | bigint, public unit?: any ) { - if (value == null || typeof value === 'number' && isNaN(value)) { + if (value == null || (typeof value === 'number' && isNaN(value))) { throw new Error('Cannot create a quantity with an undefined value'); } this.value = Decimal.from(value).normalized(); @@ -114,7 +114,11 @@ export class Quantity { } dividedBy(other: any) { - if (other == null || other === 0 || (other.value != null && Decimal.from(other.value).equals(0))) { + if ( + other == null || + other === 0 || + (other.value != null && Decimal.from(other.value).equals(0)) + ) { return null; } else if (!other.isQuantity) { // convert it to a quantity w/ unit 1 diff --git a/src/datatypes/uncertainty.ts b/src/datatypes/uncertainty.ts index e986a581f..8a089e217 100644 --- a/src/datatypes/uncertainty.ts +++ b/src/datatypes/uncertainty.ts @@ -143,7 +143,7 @@ export class Uncertainty { if (typeof a.before === 'function') { return a.before(b, precision); - } else if (a.isDecimal) { + } else if (a.isDecimal) { return a.lessThan(b); } else { return a < b; diff --git a/src/elm/aggregate.ts b/src/elm/aggregate.ts index e94784810..56ec1c620 100644 --- a/src/elm/aggregate.ts +++ b/src/elm/aggregate.ts @@ -6,7 +6,7 @@ import { Context } from '../runtime/context'; import { Exception } from '../datatypes/exception'; import { greaterThan, lessThan } from '../util/comparison'; import { build } from './builder'; -import { overflowsOrUnderflows } from '../util/math'; +import { overflowsOrUnderflows, finalizeNumericResult } from '../util/math'; import { ELM_DECIMAL_TYPE } from '../util/elmTypes'; class AggregateExpression extends Expression { @@ -18,28 +18,6 @@ class AggregateExpression extends Expression { } } -function hasDecimals(values: any[]) { - return values.some(value => value && value.isDecimal); -} - -function isDecimal(value: any): value is Decimal { - return value != null && value.isDecimal; -} - -function sumDecimals(values: Decimal[]) { - return values.reduce((sum, value) => sum.add(value)); -} - -function productDecimals(values: Decimal[]) { - return values.reduce((product, value) => product.multiplyBy(value)); -} - -function decimalResult(value: number, values: any[], resultTypeName?: string) { - return hasDecimals(values) || resultTypeName === ELM_DECIMAL_TYPE - ? Decimal.from(value).normalized() - : value; -} - export class Count extends AggregateExpression { constructor(json: any) { super(json); @@ -76,12 +54,16 @@ export class Sum extends AggregateExpression { } if (hasOnlyQuantities(items)) { - const sum = sumDecimals(getValuesFromQuantities(items)); + const sum = sumOfDecimals(getValuesFromQuantities(items)); return overflowsOrUnderflows(sum, ELM_DECIMAL_TYPE) ? null : new Quantity(sum, items[0].unit); } else { - const sum = hasDecimals(items) - ? sumDecimals(items.map(Decimal.from)) - : items.reduce((x: any, y: any) => x + y); + let sum; + if (hasDecimals(items)) { + sum = sumOfDecimals(items.map(Decimal.from)); + } else { + sum = items.reduce((x: any, y: any) => x + y); + } + sum = finalizeNumericResult(sum); return overflowsOrUnderflows(sum, this.resultTypeName) ? null : sum; } } @@ -177,11 +159,11 @@ export class Avg extends AggregateExpression { } if (hasOnlyQuantities(items)) { - const sum = sumDecimals(getValuesFromQuantities(items)); + const sum = sumOfDecimals(getValuesFromQuantities(items)); return new Quantity(sum.divideBy(items.length), items[0].unit); } else { // return type is always Decimal, so just map everything to Decimals - return sumDecimals(items.map(Decimal.from)).divideBy(items.length).normalized(); + return sumOfDecimals(items.map(Decimal.from)).divideBy(items.length).normalized(); } } } @@ -206,14 +188,17 @@ export class Median extends AggregateExpression { return null; } - if (!hasOnlyQuantities(items)) { - return hasDecimals(items) - ? medianOfDecimals(items.map(Decimal.from)) - : decimalResult(medianOfNumbers(items), items, this.resultTypeName); + if (hasOnlyQuantities(items)) { + const median = medianOfDecimals(getValuesFromQuantities(items)); + return new Quantity(median, items[0].unit); + } + + if (hasDecimals(items)) { + const decimals = items.map(Decimal.from); + return finalizeNumericResult(medianOfDecimals(decimals)); } - const median = medianOfDecimals(getValuesFromQuantities(items)); - return new Quantity(median, items[0].unit); + return medianOfNumbers(items); } } @@ -240,7 +225,7 @@ export class Mode extends AggregateExpression { if (hasOnlyQuantities(filtered)) { const values = getValuesFromQuantities(filtered); - let mode = this.mode(values); + const mode = this.mode(values); if (mode.length === 1) { return new Quantity(mode[0], items[0].unit); } else { @@ -362,16 +347,19 @@ export class Product extends AggregateExpression { } if (hasOnlyQuantities(items)) { - const product = productDecimals(getValuesFromQuantities(items)); + const product = productOfDecimals(getValuesFromQuantities(items)); // Units are not multiplied for the geometric product return overflowsOrUnderflows(product, ELM_DECIMAL_TYPE) ? null : new Quantity(product, items[0].unit); } else { - const product = hasDecimals(items) - ? productDecimals(items.map(Decimal.from)) - : items.reduce((x: number, y: number) => x * y); - const result = isDecimal(product) ? product : decimalResult(product, items, this.resultTypeName); + let result; + if (hasDecimals(items)) { + result = productOfDecimals(items.map(Decimal.from)); + } else { + result = items.reduce((x: number, y: number) => x * y); + } + result = finalizeNumericResult(result); return overflowsOrUnderflows(result, this.resultTypeName) ? null : result; } } @@ -399,12 +387,13 @@ export class GeometricMean extends AggregateExpression { } if (hasOnlyQuantities(items)) { - const product = productDecimals(getValuesFromQuantities(items)); + const product = productOfDecimals(getValuesFromQuantities(items)); const geoMean = product.power(1.0 / items.length); return new Quantity(geoMean, items[0].unit); } else { - return productDecimals(items.map(Decimal.from)) - .power(1.0 / items.length).normalized(); + return productOfDecimals(items.map(Decimal.from)) + .power(1.0 / items.length) + .normalized(); } } } @@ -458,6 +447,10 @@ export class AnyTrue extends AggregateExpression { } } +function hasDecimals(values: any[]) { + return values.some(value => value && value.isDecimal); +} + function processQuantities(values: any[]) { const items = removeNulls(values); if (hasOnlyQuantities(items)) { @@ -502,7 +495,13 @@ function medianOfNumbers(numbers: number[]) { function medianOfDecimals(decimals: Decimal[]) { const items = [...decimals].sort((a, b) => a.compareTo(b)); const middle = Math.floor(items.length / 2); - return items.length % 2 === 1 - ? items[middle] - : items[middle - 1].add(items[middle]).divideBy(2); + return items.length % 2 === 1 ? items[middle] : items[middle - 1].add(items[middle]).divideBy(2); +} + +function sumOfDecimals(values: Decimal[]) { + return values.reduce((sum, value) => sum.add(value)); +} + +function productOfDecimals(values: Decimal[]) { + return values.reduce((product, value) => product.multiplyBy(value)); } diff --git a/src/elm/arithmetic.ts b/src/elm/arithmetic.ts index c14a01eca..3b3170384 100644 --- a/src/elm/arithmetic.ts +++ b/src/elm/arithmetic.ts @@ -22,30 +22,7 @@ import { ELM_LONG_TYPE, ELM_TIME_TYPE } from '../util/elmTypes'; -import { - MAX_INT_VALUE, - MAX_LONG_VALUE, - MIN_INT_VALUE, - MIN_LONG_VALUE -} from '../util/limits'; - -function finalizeNumericResult(result: any, type?: string) { - - if (result instanceof Decimal) { - return result.normalized(); - } else if (result instanceof Quantity) { - return new Quantity(result.value.normalized(), result.unit); - } else if (result instanceof Uncertainty) { - if (result.low instanceof Quantity || result.low instanceof Decimal) { - result.low = finalizeNumericResult(result.low); - } - if (result.high instanceof Quantity || result.high instanceof Decimal) { - result.high = finalizeNumericResult(result.high); - } - } - - return result; -} +import { MAX_INT_VALUE, MAX_LONG_VALUE, MIN_INT_VALUE, MIN_LONG_VALUE } from '../util/limits'; export class Add extends Expression { constructor(json: any) { @@ -59,7 +36,7 @@ export class Add extends Expression { } const sum = MathUtil.add(args[0], args[1], this.resultTypeName); - return finalizeNumericResult(sum, this.resultTypeName); + return MathUtil.finalizeNumericResult(sum, this.resultTypeName); } } @@ -75,7 +52,7 @@ export class Subtract extends Expression { } const difference = MathUtil.subtract(args[0], args[1], this.resultTypeName); - return finalizeNumericResult(difference, this.resultTypeName); + return MathUtil.finalizeNumericResult(difference, this.resultTypeName); } } @@ -91,7 +68,7 @@ export class Multiply extends Expression { } let [x, y] = args; - + if (x.isUncertainty && !y.isUncertainty) { y = new Uncertainty(y, y); } else if (y.isUncertainty && !x.isUncertainty) { @@ -105,7 +82,10 @@ export class Multiply extends Expression { if (x.low.isQuantity) { product = new Uncertainty(doMultiplication(x.low, y.low), doMultiplication(x.high, y.high)); } else { - product = new Uncertainty(MathUtil.multiply(x.low, y.low), MathUtil.multiply(x.high, y.high)); + product = new Uncertainty( + MathUtil.multiply(x.low, y.low), + MathUtil.multiply(x.high, y.high) + ); } } else { product = MathUtil.multiply(x, y); @@ -114,8 +94,8 @@ export class Multiply extends Expression { if (MathUtil.overflowsOrUnderflows(product, this.resultTypeName)) { return null; } - - return finalizeNumericResult(product, this.resultTypeName); + + return MathUtil.finalizeNumericResult(product, this.resultTypeName); } } @@ -163,7 +143,7 @@ export class Divide extends Expression { if (MathUtil.overflowsOrUnderflows(quotient, this.resultTypeName)) { return null; } - return finalizeNumericResult(quotient, this.resultTypeName); + return MathUtil.finalizeNumericResult(quotient, this.resultTypeName); } } @@ -177,8 +157,8 @@ export class TruncatedDivide extends Expression { if (args == null || args.some((x: any) => x == null)) { return null; } - - let [x, y] = args; + + const [x, y] = args; let quotient; if (x.isQuantity) { quotient = doDivision(x, y); @@ -189,7 +169,10 @@ export class TruncatedDivide extends Expression { quotient = MathUtil.divide(x, y); // MathUtil.divide performs truncated division for Integers and Longs implicitly - if (quotient != null && (x.isDecimal || y.isDecimal || this.resultTypeName === ELM_DECIMAL_TYPE)) { + if ( + quotient != null && + (x.isDecimal || y.isDecimal || this.resultTypeName === ELM_DECIMAL_TYPE) + ) { quotient = (quotient as Decimal).truncated(); } } @@ -215,14 +198,13 @@ export class Modulo extends Expression { let modulo: number | bigint | Decimal; const [x, y] = args; try { - modulo = - x.isDecimal || y.isDecimal ? Decimal.from(x).modulo(y) : x % y; + modulo = x.isDecimal || y.isDecimal ? Decimal.from(x).modulo(y) : x % y; } catch { // modulo divide by zero results in null according to specification return null; } - return MathUtil.decimalLongOrNull(finalizeNumericResult(modulo, this.resultTypeName)) + return MathUtil.decimalLongOrNull(MathUtil.finalizeNumericResult(modulo, this.resultTypeName)); } } @@ -439,7 +421,12 @@ export class Power extends Expression { } function doPower(x: any, y: any) { - if (x.isDecimal || y.isDecimal || (typeof y == 'number' && y < 0) || (typeof y === 'bigint' && y < 0n)) { + if ( + x.isDecimal || + y.isDecimal || + (typeof y == 'number' && y < 0) || + (typeof y === 'bigint' && y < 0n) + ) { // Decimal values or negative powers always produce Decimal result return Decimal.from(x).power(y); } diff --git a/src/elm/interval.ts b/src/elm/interval.ts index 14a109f44..234d2ae4a 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -623,11 +623,7 @@ export class Expand extends Expression { return null; } - const results = this.makeDecimalIntervalList( - low_value, - high_value, - per_value - ); + const results = this.makeDecimalIntervalList(low_value, high_value, per_value); for (const itvl of results) { itvl.low = new Quantity(itvl.low, result_units); @@ -636,31 +632,27 @@ export class Expand extends Expression { return results; } - expandIntegerInterval(interval: any, per: any) { + expandIntegerInterval(interval: any, per: any) { if (per.unit !== '1' && per.unit !== '') { return null; } const low = interval.lowClosed ? interval.low : successor(interval.low); const high = interval.highClosed ? interval.high : predecessor(interval.high); - return this.makeDecimalIntervalList( - low, high, per.value - ); + return this.makeDecimalIntervalList(low, high, per.value); } - expandDecimalInterval(interval: any, per: any) { + expandDecimalInterval(interval: any, per: any) { if (per.unit !== '1' && per.unit !== '') { return null; } const low = interval.lowClosed ? interval.low : successor(interval.low); const high = interval.highClosed ? interval.high : predecessor(interval.high); - return this.makeDecimalIntervalList( - low, high, per.value - ); + return this.makeDecimalIntervalList(low, high, per.value); } - expandLongInterval(interval: any, per: any) { + expandLongInterval(interval: any, per: any) { if (per.unit !== '1' && per.unit !== '') { return null; } @@ -668,35 +660,32 @@ export class Expand extends Expression { const low = interval.lowClosed ? interval.low : successor(interval.low); const high = interval.highClosed ? interval.high : predecessor(interval.high); - return this.makeDecimalIntervalList( - low, high, per.value - ); + return this.makeDecimalIntervalList(low, high, per.value); } - makeDecimalIntervalList( - low: any, - high: any, - perValue: any - ) { + makeDecimalIntervalList(low: any, high: any, perValue: any) { // If the per value is a decimal, 8 decimal places are appropriate // Integers should have 0 Decimal places const perIsIntegral = perValue.isInteger(); - const decimalPrecision = perIsIntegral ? 0 : 8; + const decimalPrecision = perIsIntegral ? 0 : 8; // For the purposes of this function, we'll perform all the arithmetic using Decimals, // then convert the results back to the required type if necessary - let makeInterval: Function; + let makeInterval: (l: Decimal, h: Decimal) => dtivl.Interval; if (!perIsIntegral) { // If per is not an integer value, then regardless of the original point types, the values will be Decimals makeInterval = (l: Decimal, h: Decimal) => new dtivl.Interval(l, h, true, true); } else if (typeof low === 'bigint' || typeof high === 'bigint') { - makeInterval = (l: Decimal, h: Decimal) => new dtivl.Interval(l.toLong(), h.toLong(), true, true); + makeInterval = (l: Decimal, h: Decimal) => + new dtivl.Interval(l.toLong(), h.toLong(), true, true); } else if (typeof low === 'number' || typeof high === 'number') { - makeInterval = (l: Decimal, h: Decimal) => new dtivl.Interval(l.toInteger(), h.toInteger(), true, true); + makeInterval = (l: Decimal, h: Decimal) => + new dtivl.Interval(l.toInteger(), h.toInteger(), true, true); } else { // per is an integer but the original bounds of the interval were Decimal. // TODO: for now just make them integers - makeInterval = (l: Decimal, h: Decimal) => new dtivl.Interval(l.toInteger(), h.toInteger(), true, true); + makeInterval = (l: Decimal, h: Decimal) => + new dtivl.Interval(l.toInteger(), h.toInteger(), true, true); } // treat everything as a Decimal, convert back later if needed @@ -843,7 +832,9 @@ function collapseIntervals(intervals: any, perWidth: any) { a.high = b.high; } } else if ( - perWidth.value.greaterThanOrEquals(a.high != null ? a.high.durationBetween(b.low, perWidth.unit).high : undefined) + perWidth.value.greaterThanOrEquals( + a.high != null ? a.high.durationBetween(b.low, perWidth.unit).high : undefined + ) ) { a.high = b.high; } else { @@ -860,13 +851,12 @@ function collapseIntervals(intervals: any, perWidth: any) { a = b; } } else { - const distance = subtract(b.low, a.high); // TODO: perWidth.value is a Decimal, but distance could be anything // lessThanOrEquals requires that its args be the same type // so I guess for now, make distance a Decimal const distanceDecimal = Decimal.from(distance); - const withinPerWidth = lessThanOrEquals(distanceDecimal, perWidth.value); + const withinPerWidth = lessThanOrEquals(distanceDecimal, perWidth.value); if (withinPerWidth) { if (greaterThan(b.high, a.high) || b.high == null) { a.high = b.high; @@ -882,10 +872,3 @@ function collapseIntervals(intervals: any, perWidth: any) { return collapsedIntervals; } } - -function truncateDecimal(decimal: any, decimalPlaces: number) { - // like parseFloat().toFixed() but floor rather than round - // Needed for when per precision is less than the interval input precision - const re = new RegExp('^-?\\d+(?:.\\d{0,' + (decimalPlaces || -1) + '})?'); - return Decimal.from(decimal.toString().match(re)[0]); -} diff --git a/src/elm/type.ts b/src/elm/type.ts index 2215be8b4..c68982d1c 100644 --- a/src/elm/type.ts +++ b/src/elm/type.ts @@ -6,7 +6,7 @@ import { Concept } from '../datatypes/clinical'; import { Interval as dtInterval } from '../datatypes/interval'; import { Quantity, parseQuantity } from '../datatypes/quantity'; import { Decimal } from '../datatypes/decimal'; -import { isValidDecimal, isValidInteger, isValidLong, limitDecimalPrecision } from '../util/math'; +import { isValidDecimal, isValidInteger, isValidLong } from '../util/math'; import { normalizeMillisecondsField } from '../util/util'; import { Ratio } from '../datatypes/ratio'; import { @@ -166,7 +166,7 @@ export class ToDecimal extends Expression { const arg = await this.execArgs(ctx); if (arg != null) { if (arg.isUncertainty) { - const low = Decimal.from(arg.low).normalized() + const low = Decimal.from(arg.low).normalized(); const high = Decimal.from(arg.high).normalized(); return new Uncertainty(low, high); } else { @@ -175,7 +175,7 @@ export class ToDecimal extends Expression { if (isValidDecimal(decimal)) { return decimal.normalized(); } - } catch (_e) { + } catch { return null; } } diff --git a/src/util/comparison.ts b/src/util/comparison.ts index 467bb2e82..ce1cf55ab 100644 --- a/src/util/comparison.ts +++ b/src/util/comparison.ts @@ -47,9 +47,9 @@ export function lessThan(a: any, b: any, precision?: any) { export function lessThanOrEquals(a: any, b: any, precision?: any) { if (areNumbers(a, b) || areBigInts(a, b) || areStrings(a, b)) { return a <= b; - }else if (areDecimals(a, b)) { + } else if (areDecimals(a, b)) { return a.lessThanOrEquals(b); - } else if (areDateTimesOrQuantities(a, b)) { + } else if (areDateTimesOrQuantities(a, b)) { return a.sameOrBefore(b, precision); } else if (isUncertainty(a)) { return a.lessThanOrEquals(b, precision); diff --git a/src/util/immutableUtil.ts b/src/util/immutableUtil.ts index d57e80772..14d6c7d01 100644 --- a/src/util/immutableUtil.ts +++ b/src/util/immutableUtil.ts @@ -1,6 +1,14 @@ import * as ucum from '@lhncbc/ucum-lhc'; import { type Collection, Map as ImmutableMap, Seq as ImmutableSeq } from 'immutable'; -import { Code, DateTime, Decimal, Interval, Quantity, Ratio, Uncertainty } from '../datatypes/datatypes'; +import { + Code, + DateTime, + Decimal, + Interval, + Quantity, + Ratio, + Uncertainty +} from '../datatypes/datatypes'; import { decimalAdjust } from './math'; import { convertUnit } from './units'; @@ -95,7 +103,7 @@ export const toNormalizedKey = (js: any): NormalizedKey => { if (!baseUnitKey) { // No units found - normalization not possible and use provided values return ImmutableMap({ - value: js.value ? toNormalizedKey(js.value) : null, + value: js.value ? toNormalizedKey(js.value) : null, unit: js.unit ?? null, __instance: js.constructor }); diff --git a/src/util/math.ts b/src/util/math.ts index d70db1bc3..62216c002 100644 --- a/src/util/math.ts +++ b/src/util/math.ts @@ -9,11 +9,7 @@ import { MAX_TIME_VALUE } from '../datatypes/datetime'; -import { - Decimal, - MAX_DECIMAL_VALUE, - MIN_DECIMAL_VALUE -} from '../datatypes/decimal'; +import { Decimal, MAX_DECIMAL_VALUE, MIN_DECIMAL_VALUE } from '../datatypes/decimal'; import { Uncertainty } from '../datatypes/uncertainty'; import { @@ -25,12 +21,7 @@ import { ELM_TIME_TYPE, ELM_QUANTITY_TYPE } from './elmTypes'; -import { - MAX_INT_VALUE, - MAX_LONG_VALUE, - MIN_INT_VALUE, - MIN_LONG_VALUE -} from './limits'; +import { MAX_INT_VALUE, MAX_LONG_VALUE, MIN_INT_VALUE, MIN_LONG_VALUE } from './limits'; import { convertToCQLDateUnit, normalizeUnitsWhenPossible } from './units'; export function overflowsOrUnderflows(value: any, type?: string): boolean { @@ -67,10 +58,10 @@ export function overflowsOrUnderflows(value: any, type?: string): boolean { return true; } } else if (typeof value === 'number') { - if (!isValidInteger(value)) { - return true; - } - } else if (value.isDecimal) { + if (!isValidInteger(value)) { + return true; + } + } else if (value.isDecimal) { if (!isValidDecimal(value)) { return true; } @@ -236,13 +227,13 @@ export function divide(a: any, b: any, type?: string) { const quotient = Math.trunc(a / b); return overflowsOrUnderflows(quotient, ELM_INTEGER_TYPE) ? null : quotient; } - + throw new Error('Unsupported argument types.'); } -export function limitDecimalPrecision( - val?: T -): T | undefined { +export function limitDecimalPrecision< + T extends number | bigint | Quantity | Uncertainty | Decimal | undefined +>(val?: T): T | undefined { if (val == null) { return val; } else if (typeof val === 'number') { @@ -454,8 +445,25 @@ export function decimalOrNull(value: any) { export function decimalLongOrNull(value: any) { return (typeof value === 'number' && Number.isFinite(value)) || - ((value && value.isDecimal) && isValidDecimal(value)) || + (value && value.isDecimal && isValidDecimal(value)) || (typeof value === 'bigint' && isValidLong(value)) ? value : null; } + +export function finalizeNumericResult(result: any, _type?: string) { + if (result instanceof Decimal) { + return result.normalized(); + } else if (result instanceof Quantity) { + return new Quantity(result.value.normalized(), result.unit); + } else if (result instanceof Uncertainty) { + if (result.low instanceof Quantity || result.low instanceof Decimal) { + result.low = finalizeNumericResult(result.low); + } + if (result.high instanceof Quantity || result.high instanceof Decimal) { + result.high = finalizeNumericResult(result.high); + } + } + + return result; +} diff --git a/src/util/units.ts b/src/util/units.ts index 688ef2f6f..20abc8435 100644 --- a/src/util/units.ts +++ b/src/util/units.ts @@ -1,5 +1,4 @@ import * as ucum from '@lhncbc/ucum-lhc'; -import { decimalAdjust } from './math'; import { Decimal } from '../datatypes/decimal'; const utils = ucum.UcumLhcUtils.getInstance(); diff --git a/test/datatypes/date-test.ts b/test/datatypes/date-test.ts index 90080282f..dd8150016 100644 --- a/test/datatypes/date-test.ts +++ b/test/datatypes/date-test.ts @@ -898,7 +898,9 @@ describe('Date.getDateTime', () => { dateTime.year.should.equal(2000); dateTime.month.should.equal(12); dateTime.day.should.equal(1); - dateTime.timezoneOffset.should.equalDecimal(Decimal.from((new jsDate().getTimezoneOffset() / 60) * -1)); + dateTime.timezoneOffset.should.equalDecimal( + Decimal.from((new jsDate().getTimezoneOffset() / 60) * -1) + ); }); it('should return a DateTime without a timeZoneOffset when a null timeZoneOffset is passed in', () => { diff --git a/test/datatypes/interval-test.ts b/test/datatypes/interval-test.ts index 57f1e15de..7772a8ac1 100644 --- a/test/datatypes/interval-test.ts +++ b/test/datatypes/interval-test.ts @@ -132,7 +132,9 @@ describe('Interval', () => { }); it('should return the point size for Decimal intervals', () => { - new Interval(Decimal.from(0.5), Decimal.from(9.5)).getPointSize().should.equalDecimal(Decimal.from(0.00000001)); + new Interval(Decimal.from(0.5), Decimal.from(9.5)) + .getPointSize() + .should.equalDecimal(Decimal.from(0.00000001)); }); it('should return the point size for Quantity intervals', () => { @@ -164,7 +166,9 @@ describe('Interval', () => { it('should return successor of low for intervals with open low', () => { d.zeroToHundred.openClosed.start().should.equal(1); - d.zeroPointFiveToNinePointFive.openClosed.start().should.equalDecimal(Decimal.from("0.50000001")); + d.zeroPointFiveToNinePointFive.openClosed + .start() + .should.equalDecimal(Decimal.from('0.50000001')); d.zeroToHundredLong.openClosed.start().should.equal(1n); d.zeroToHundredMg.openClosed.start().should.eql(new Quantity(0.00000001, 'mg')); d.all2012date.openClosed.start().should.eql(Date.parse('2012-01-02')); @@ -177,7 +181,9 @@ describe('Interval', () => { it('should return type minimum for closed null low endpoints', () => { d.zeroToHundred.withNullStart.closed.start().should.equal(MIN_INT_VALUE); d.zeroToHundredLong.withNullStart.closed.start().should.equal(MIN_LONG_VALUE); - d.zeroPointFiveToNinePointFive.withNullStart.closed.start().should.equalDecimal(MIN_DECIMAL_VALUE); + d.zeroPointFiveToNinePointFive.withNullStart.closed + .start() + .should.equalDecimal(MIN_DECIMAL_VALUE); d.zeroToHundredMg.withNullStart.closed .start() .should.eql(new Quantity(MIN_DECIMAL_VALUE, 'mg')); @@ -203,7 +209,9 @@ describe('Interval', () => { .should.eql(new Uncertainty(MIN_DECIMAL_VALUE, Decimal.from(9.5))); d.zeroToHundredMg.withNullStart.openClosed .start() - .should.eql(new Uncertainty(new Quantity(MIN_DECIMAL_VALUE, 'mg'), new Quantity(100, 'mg'))); + .should.eql( + new Uncertainty(new Quantity(MIN_DECIMAL_VALUE, 'mg'), new Quantity(100, 'mg')) + ); d.all2012date.withNullStart.openClosed .start() .should.eql(new Uncertainty(MIN_DATE_VALUE, Date.parse('2012-12-31'))); @@ -275,7 +283,10 @@ describe('Interval', () => { new Interval(null, null, false, false, ELM_QUANTITY_TYPE) .start() .should.eql( - new Uncertainty(new Quantity(MIN_DECIMAL_VALUE, '1'), new Quantity(MAX_DECIMAL_VALUE, '1')) + new Uncertainty( + new Quantity(MIN_DECIMAL_VALUE, '1'), + new Quantity(MAX_DECIMAL_VALUE, '1') + ) ); new Interval(null, null, false, false, ELM_DATETIME_TYPE) .start() @@ -405,7 +416,10 @@ describe('Interval', () => { new Interval(null, null, false, false, ELM_QUANTITY_TYPE) .end() .should.eql( - new Uncertainty(new Quantity(MIN_DECIMAL_VALUE, '1'), new Quantity(MAX_DECIMAL_VALUE, '1')) + new Uncertainty( + new Quantity(MIN_DECIMAL_VALUE, '1'), + new Quantity(MAX_DECIMAL_VALUE, '1') + ) ); new Interval(null, null, false, false, ELM_DATETIME_TYPE) .end() @@ -7002,10 +7016,16 @@ describe('DecimalInterval', () => { }); it('should calculate width and size outside the Integer range', () => { - const interval = new Interval(Decimal.from(0.0), Decimal.from(3000000000.0), true, true, ELM_DECIMAL_TYPE); + const interval = new Interval( + Decimal.from(0.0), + Decimal.from(3000000000.0), + true, + true, + ELM_DECIMAL_TYPE + ); - interval.width().should.equalDecimal(Decimal.from("3000000000.0")); - interval.size().should.equalDecimal(Decimal.from("3000000000.00000001")); + interval.width().should.equalDecimal(Decimal.from('3000000000.0')); + interval.size().should.equalDecimal(Decimal.from('3000000000.00000001')); }); it('should close open decimal uncertainty endpoints using decimal point size', () => { @@ -7025,13 +7045,25 @@ describe('DecimalInterval', () => { it('should use decimal point size for meetsBefore decimal uncertainty bounds', () => { const earlier = new Interval(Decimal.from(1), Decimal.from(1.99999999)); - const later = new Interval(new Uncertainty(Decimal.from(2), Decimal.from(2)), null, true, false, ELM_DECIMAL_TYPE); + const later = new Interval( + new Uncertainty(Decimal.from(2), Decimal.from(2)), + null, + true, + false, + ELM_DECIMAL_TYPE + ); earlier.meetsBefore(later).should.be.true(); }); it('should use decimal point size for meetsAfter decimal uncertainty bounds', () => { - const earlier = new Interval(null, new Uncertainty(Decimal.from(1), Decimal.from(1)), false, true, ELM_DECIMAL_TYPE); + const earlier = new Interval( + null, + new Uncertainty(Decimal.from(1), Decimal.from(1)), + false, + true, + ELM_DECIMAL_TYPE + ); const later = new Interval(Decimal.from(1.00000001), Decimal.from(2)); later.meetsAfter(earlier).should.be.true(); diff --git a/test/elm/aggregate/aggregate-test.ts b/test/elm/aggregate/aggregate-test.ts index 6adc191e6..22292ffdb 100644 --- a/test/elm/aggregate/aggregate-test.ts +++ b/test/elm/aggregate/aggregate-test.ts @@ -98,11 +98,7 @@ describe('Sum', () => { }); it('should be able to sum quantities up to max decimal value', async function () { - validateQuantity( - await this.quantities_at_max_value.exec(this.ctx), - MAX_DECIMAL_VALUE, - 'ml' - ); + validateQuantity(await this.quantities_at_max_value.exec(this.ctx), MAX_DECIMAL_VALUE, 'ml'); }); it('should return null when overflowing the max quantity value', async function () { @@ -110,11 +106,7 @@ describe('Sum', () => { }); it('should be able to sum quantities down to min decimal value', async function () { - validateQuantity( - await this.quantities_at_min_value.exec(this.ctx), - MIN_DECIMAL_VALUE, - 'ml' - ); + validateQuantity(await this.quantities_at_min_value.exec(this.ctx), MIN_DECIMAL_VALUE, 'ml'); }); it('should return null when underflowing the min quantity value', async function () { @@ -480,13 +472,13 @@ describe('StdDev', () => { setup(this, data); }); it('should be able to find Standard Dev of a list ', async function () { - (await this.std.exec(this.ctx)).should.equalDecimal(Decimal.from("1.58113883")); + (await this.std.exec(this.ctx)).should.equalDecimal(Decimal.from('1.58113883')); }); it('should be able to find Standard Dev of a list of like quantities', async function () { - validateQuantity(await this.std_q.exec(this.ctx), "1.58113883", 'ml'); + validateQuantity(await this.std_q.exec(this.ctx), '1.58113883', 'ml'); }); it('should be able to find Standard Dev of a list of related quantities', async function () { - validateQuantity(await this.q_diff_units.exec(this.ctx), "1.58113883", 'ml'); + validateQuantity(await this.q_diff_units.exec(this.ctx), '1.58113883', 'ml'); }); it('should be null if some are numbers and some are quantities', async function () { should(await this.numbersAndQuantities.exec(this.ctx)).be.null(); @@ -501,13 +493,13 @@ describe('PopulationStdDev', () => { setup(this, data); }); it('should be able to find Population Standard Dev of a list ', async function () { - (await this.dev.exec(this.ctx)).should.equalDecimal(Decimal.from("1.41421356")); + (await this.dev.exec(this.ctx)).should.equalDecimal(Decimal.from('1.41421356')); }); it('should be able to find Population Standard Dev of a list of quantities', async function () { - validateQuantity(await this.dev_q.exec(this.ctx), "1.41421356", 'ml'); + validateQuantity(await this.dev_q.exec(this.ctx), '1.41421356', 'ml'); }); it('should be able to find Population Standard Dev of a list of related quantities', async function () { - validateQuantity(await this.q_diff_units.exec(this.ctx), "1.41421356", 'ml'); + validateQuantity(await this.q_diff_units.exec(this.ctx), '1.41421356', 'ml'); }); it('should be null if some are numbers and some are quantities', async function () { should(await this.numbersAndQuantities.exec(this.ctx)).be.null(); @@ -567,9 +559,7 @@ describe('Product', () => { }); it('should return decimal product up to max decimal value', async function () { - (await this.decimals_at_max_value_product.exec(this.ctx)).should.eql( - MAX_DECIMAL_VALUE - ); + (await this.decimals_at_max_value_product.exec(this.ctx)).should.eql(MAX_DECIMAL_VALUE); }); it('should return null when decimal product overflows max decimal value', async function () { @@ -577,9 +567,7 @@ describe('Product', () => { }); it('should return decimal product down to min decimal value', async function () { - (await this.decimals_at_min_value_product.exec(this.ctx)).should.eql( - MIN_DECIMAL_VALUE - ); + (await this.decimals_at_min_value_product.exec(this.ctx)).should.eql(MIN_DECIMAL_VALUE); }); it('should return null when decimal product underflows min decimal value', async function () { @@ -662,7 +650,7 @@ describe('GeometricMean', () => { }); it('should return value when pass in list that contains nulls', async function () { - (await this.null_geometric_mean.exec(this.ctx)).should.equalDecimal(Decimal.from("1.41421356")); + (await this.null_geometric_mean.exec(this.ctx)).should.equalDecimal(Decimal.from('1.41421356')); }); it('should return null when list is all null', async function () { diff --git a/test/elm/arithmetic/arithmetic-test.ts b/test/elm/arithmetic/arithmetic-test.ts index 774aa91bd..1671c64c5 100644 --- a/test/elm/arithmetic/arithmetic-test.ts +++ b/test/elm/arithmetic/arithmetic-test.ts @@ -26,7 +26,11 @@ import { Decimal, MAX_DECIMAL_VALUE, MIN_DECIMAL_VALUE } from '../../../src/data const data = require('./data'); -const validateQuantity = function (object: any, expectedValue: number | Decimal, expectedUnit: string) { +const validateQuantity = function ( + object: any, + expectedValue: number | Decimal, + expectedUnit: string +) { object.isQuantity.should.be.true(); const q = new Quantity(expectedValue, expectedUnit); q.equals(object).should.be.true('Expected ' + object + ' to equal ' + q); @@ -255,7 +259,7 @@ describe('Divide', () => { it('should divide uncertainty by uncertainty', async function () { const result = await this.divideUncertainties.exec(this.ctx); - result.low.should.equalDecimal(Decimal.from("0.42857143")); // 6/14 + result.low.should.equalDecimal(Decimal.from('0.42857143')); // 6/14 result.high.should.equalDecimal(Decimal.from(9)); }); @@ -537,12 +541,12 @@ describe('Ln', () => { }); it('should be able to return the natural log of a number', async function () { - const log4 = Decimal.from("1.3862943611198906").normalized(); + const log4 = Decimal.from('1.3862943611198906').normalized(); (await this.ln.exec(this.ctx)).should.equalDecimal(log4); }); it('should be able to return the natural log of a long', async function () { - const log4 = Decimal.from("1.3862943611198906").normalized(); + const log4 = Decimal.from('1.3862943611198906').normalized(); (await this.lnFourLong.exec(this.ctx)).should.equalDecimal(log4); }); }); @@ -752,7 +756,7 @@ describe('Predecessor', () => { }); it('should be able to get Real Predecessor', async function () { - (await this.rs.exec(this.ctx)).should.equalDecimal(Decimal.from("2.19999999")); + (await this.rs.exec(this.ctx)).should.equalDecimal(Decimal.from('2.19999999')); }); it('should return null for Predecessor greater than Integer Max value', async function () { @@ -1011,12 +1015,16 @@ describe('OutOfBounds', () => { it('should return value for Divide near overflow', async function () { // not really near overflow, but more than max integer and near JavaScript max safe number - should(await this.integerDivideNearOverflow.exec(this.ctx)).equalDecimal(Decimal.from(8589934588000000)); + should(await this.integerDivideNearOverflow.exec(this.ctx)).equalDecimal( + Decimal.from(8589934588000000) + ); }); it('should return value for Divide near underflow', async function () { // not really near underflow, but less than min integer and near JavaScript min safe number - should(await this.integerDivideNearUnderflow.exec(this.ctx)).equalDecimal(Decimal.from(-8589934592000000)); + should(await this.integerDivideNearUnderflow.exec(this.ctx)).equalDecimal( + Decimal.from(-8589934592000000) + ); }); it('should return null for Divide By Zero', async function () { @@ -1118,12 +1126,16 @@ describe('OutOfBounds', () => { // note that all division in CQL (except truncated division) is really decimal division // note also that MAX_LONG_VALUE is (2^63)-1, // 9223372036854775807 = 7^2 * 73 * 127 * 337 * 92737 * 649657 - should(await this.longDivideNearOverflow.exec(this.ctx)).equalDecimal(Decimal.from(99457304386111n)); + should(await this.longDivideNearOverflow.exec(this.ctx)).equalDecimal( + Decimal.from(99457304386111n) + ); }); it('should return value for Divide near underflow', async function () { // not really near underflow, but near JavaScript min safe number - should(await this.longDivideNearUnderflow.exec(this.ctx)).equalDecimal(Decimal.from(-9007199254740992n)); + should(await this.longDivideNearUnderflow.exec(this.ctx)).equalDecimal( + Decimal.from(-9007199254740992n) + ); }); it('should return null for Divide By Zero', async function () { @@ -1257,11 +1269,15 @@ describe('OutOfBounds', () => { }); it('should return value for successor near overflow', async function () { - should(await this.decimalSuccessorNearOverflow.exec(this.ctx)).equalDecimal(MAX_DECIMAL_VALUE); + should(await this.decimalSuccessorNearOverflow.exec(this.ctx)).equalDecimal( + MAX_DECIMAL_VALUE + ); }); it('should return value for predecessor near underflow', async function () { - should(await this.decimalPredecessorNearUnderflow.exec(this.ctx)).equalDecimal(MIN_DECIMAL_VALUE); + should(await this.decimalPredecessorNearUnderflow.exec(this.ctx)).equalDecimal( + MIN_DECIMAL_VALUE + ); }); }); diff --git a/test/elm/interval/interval-test.ts b/test/elm/interval/interval-test.ts index 78757f286..791b951d9 100644 --- a/test/elm/interval/interval-test.ts +++ b/test/elm/interval/interval-test.ts @@ -1686,9 +1686,13 @@ describe('Size', () => { it('should calculate the size of real intervals', async function () { // define RealSize: Size(Interval[1.23, 4.56]) - (await this.realSize.exec(this.ctx)).should.equalDecimal(Decimal.from(3.33 + MIN_FLOAT_PRECISION_VALUE)); + (await this.realSize.exec(this.ctx)).should.equalDecimal( + Decimal.from(3.33 + MIN_FLOAT_PRECISION_VALUE) + ); // define RealOpenSize: Size(Interval(1.23, 4.56)) - (await this.realOpenSize.exec(this.ctx)).should.equalDecimal(Decimal.from(3.32999998 + MIN_FLOAT_PRECISION_VALUE)); + (await this.realOpenSize.exec(this.ctx)).should.equalDecimal( + Decimal.from(3.32999998 + MIN_FLOAT_PRECISION_VALUE) + ); }); it('should calculate the size of infinite intervals', async function () { @@ -1758,7 +1762,9 @@ describe('Start', () => { it('should return the minimum possible DateTime in timzoneOffset of context', async function () { // set execution timestamp to be +5 this.ctx.executionDateTime = new DateTime(2019, 10, 1, 12, 31, 31, 2, 5); - (await this.closedNullDateTime.exec(this.ctx)).timezoneOffset.should.equalDecimal(Decimal.from(5)); + (await this.closedNullDateTime.exec(this.ctx)).timezoneOffset.should.equalDecimal( + Decimal.from(5) + ); }); it('should return the minimum possible Integer', async function () { @@ -1808,7 +1814,9 @@ describe('End', () => { it('should return the maximum possible DateTime in timzoneOffset of context', async function () { // set execution timestamp to be +5 this.ctx.executionDateTime = new DateTime(2019, 10, 1, 12, 31, 31, 2, 5); - (await this.closedNullDateTime.exec(this.ctx)).timezoneOffset.should.equalDecimal(Decimal.from(5)); + (await this.closedNullDateTime.exec(this.ctx)).timezoneOffset.should.equalDecimal( + Decimal.from(5) + ); }); it('should return the maximum possible Integer', async function () { @@ -3490,9 +3498,6 @@ describe('QuantityIntervalExpand', () => { }); it('returns null when per zero, not applicable, or mismatch interval', async function () { - - console.log('debuggger') - // define perZero: expand { Interval[2 'g', 4 'g'] } per 0 'g' let a = await this.perZero.exec(this.ctx); should.not.exist(a); diff --git a/test/elm/parameters/parameters-test.ts b/test/elm/parameters/parameters-test.ts index dc2e354a9..b450a217e 100644 --- a/test/elm/parameters/parameters-test.ts +++ b/test/elm/parameters/parameters-test.ts @@ -100,7 +100,9 @@ describe('DecimalParameterTypes', () => { }); it('should execute to provided valid value', async function () { - (await this.foo.exec(this.ctx.withParameters({ FooP: Decimal.from(3.0) }))).should.equalDecimal(Decimal.from(3.0)); + (await this.foo.exec(this.ctx.withParameters({ FooP: Decimal.from(3.0) }))).should.equalDecimal( + Decimal.from(3.0) + ); }); it('should throw when provided value is wrong type', function () { @@ -112,7 +114,9 @@ describe('DecimalParameterTypes', () => { }); it('should execute to overriding valid value', async function () { - (await this.foo2.exec(this.ctx.withParameters({ FooDP: Decimal.from(3.0) }))).should.equalDecimal(Decimal.from(3.0)); + ( + await this.foo2.exec(this.ctx.withParameters({ FooDP: Decimal.from(3.0) })) + ).should.equalDecimal(Decimal.from(3.0)); }); it('should throw when overriding value is wrong type', function () { @@ -130,7 +134,9 @@ describe('IntegerParameterTypes', () => { }); it('should throw when provided value is wrong type', function () { - should(() => this.foo.exec(this.ctx.withParameters({ FooP: Decimal.from(3.5) }))).throw(/.*wrong type.*/); + should(() => this.foo.exec(this.ctx.withParameters({ FooP: Decimal.from(3.5) }))).throw( + /.*wrong type.*/ + ); }); it('should execute to default value', async function () { @@ -142,7 +148,9 @@ describe('IntegerParameterTypes', () => { }); it('should throw when overriding value is wrong type', function () { - should(() => this.foo2.exec(this.ctx.withParameters({ FooDP: Decimal.from(3.5) }))).throw(/.*wrong type.*/); + should(() => this.foo2.exec(this.ctx.withParameters({ FooDP: Decimal.from(3.5) }))).throw( + /.*wrong type.*/ + ); }); }); @@ -424,9 +432,11 @@ describe('IntervalParameterTypes', () => { }); it('should throw when interval contains a wrong point type', async function () { - should(() => this.foo.exec(this.ctx.withParameters({ FooP: new Interval(Decimal.from(1.5), Decimal.from(5.5)) }))).throw( - /.*wrong type.*/ - ); + should(() => + this.foo.exec( + this.ctx.withParameters({ FooP: new Interval(Decimal.from(1.5), Decimal.from(5.5)) }) + ) + ).throw(/.*wrong type.*/); }); it('should execute to default value', async function () { @@ -444,9 +454,11 @@ describe('IntervalParameterTypes', () => { }); it('should throw when overriding interval contains a wrong point type', async function () { - should(() => this.foo2.exec(this.ctx.withParameters({ FooP: new Interval(Decimal.from(1.5), Decimal.from(5.5)) }))).throw( - /.*wrong type.*/ - ); + should(() => + this.foo2.exec( + this.ctx.withParameters({ FooP: new Interval(Decimal.from(1.5), Decimal.from(5.5)) }) + ) + ).throw(/.*wrong type.*/); }); }); diff --git a/test/should-extensions.ts b/test/should-extensions.ts index 1d29e8026..f57ba3890 100644 --- a/test/should-extensions.ts +++ b/test/should-extensions.ts @@ -31,11 +31,15 @@ declare module 'should' { normalizedThis.should.eql(normalizedExpected); }); -(should as any).Assertion.add('equalDecimal', function (this: any, expected: number | bigint | Decimal) { - this.params = { operator: 'to equal Decimal', expected: expected.toString(), obj: this.obj.toString() }; +(should as any).Assertion.add( + 'equalDecimal', + function (this: any, expected: number | bigint | Decimal) { + this.params = { + operator: 'to equal Decimal', + expected: expected.toString(), + obj: this.obj.toString() + }; - this.assert( - this.obj instanceof Decimal && - this.obj.equals(expected) - ); -}); \ No newline at end of file + this.assert(this.obj instanceof Decimal && this.obj.equals(expected)); + } +); diff --git a/test/spec-tests/spec-test.ts b/test/spec-tests/spec-test.ts index 9809612bf..b68768ded 100644 --- a/test/spec-tests/spec-test.ts +++ b/test/spec-tests/spec-test.ts @@ -45,9 +45,6 @@ describe('CQL Spec Tests (from XML)', () => { } suite.expression.element.forEach((t: any) => { it(`should properly evaluate ${t.name}`, async function () { - if (t.name === 'beans') { - debugger; - } const testCaseMap = convertTupleToMap(t.value); if (testCaseMap.has('skipped')) { this.skip(); diff --git a/test/util/math-test.ts b/test/util/math-test.ts index 905ac4c17..5071fd60d 100644 --- a/test/util/math-test.ts +++ b/test/util/math-test.ts @@ -12,7 +12,10 @@ describe('successor', () => { }); it('should preserve decimals in an Uncertainty', () => { - const result = successor(new Uncertainty(Decimal.from(1.0), Decimal.from(2.0)), ELM_DECIMAL_TYPE); + const result = successor( + new Uncertainty(Decimal.from(1.0), Decimal.from(2.0)), + ELM_DECIMAL_TYPE + ); result.low.should.equalDecimal(Decimal.from(1.00000001)); result.high.should.equalDecimal(Decimal.from(2.00000001)); }); @@ -31,7 +34,10 @@ describe('predecessor', () => { }); it('should preserve decimals in an Uncertainty', () => { - const result = successor(new Uncertainty(Decimal.from(1.0), Decimal.from(2.0)), ELM_DECIMAL_TYPE); + const result = successor( + new Uncertainty(Decimal.from(1.0), Decimal.from(2.0)), + ELM_DECIMAL_TYPE + ); result.low.should.equalDecimal(Decimal.from(1.00000001)); result.high.should.equalDecimal(Decimal.from(2.00000001)); }); diff --git a/test/util/units-test.ts b/test/util/units-test.ts index 9cf5ea45c..3550a6f25 100644 --- a/test/util/units-test.ts +++ b/test/util/units-test.ts @@ -132,7 +132,7 @@ describe('convertUnit', () => { it('should truncate precision to 8 decimals by default', () => { const result = convertUnit(Decimal.from(1), '[ft_i]', '[mi_i]'); - result.should.equalDecimal(Decimal.from("0.00018939")); + result.should.equalDecimal(Decimal.from('0.00018939')); }); // it('should not truncate precision to 8 decimals when adjustPrecision is false', () => { @@ -148,35 +148,79 @@ describe('convertUnit', () => { }); describe('normalizeUnitsWhenPossible', () => { - it('should keep same units', () => { - normalizeUnitsWhenPossible(Decimal.from(10), 'm', Decimal.from(1), 'm').should.eql([Decimal.from(10), 'm', Decimal.from(1), 'm']); + normalizeUnitsWhenPossible(Decimal.from(10), 'm', Decimal.from(1), 'm').should.eql([ + Decimal.from(10), + 'm', + Decimal.from(1), + 'm' + ]); }); it('should convert compatible units, preferring smaller units', () => { - normalizeUnitsWhenPossible(Decimal.from(10), 'cm', Decimal.from(1), 'm').should.eql([Decimal.from(10), 'cm', Decimal.from(100), 'cm']); - normalizeUnitsWhenPossible(Decimal.from(1), 'm', Decimal.from(10), 'cm').should.eql([Decimal.from(100), 'cm', Decimal.from(10), 'cm']); + normalizeUnitsWhenPossible(Decimal.from(10), 'cm', Decimal.from(1), 'm').should.eql([ + Decimal.from(10), + 'cm', + Decimal.from(100), + 'cm' + ]); + normalizeUnitsWhenPossible(Decimal.from(1), 'm', Decimal.from(10), 'cm').should.eql([ + Decimal.from(100), + 'cm', + Decimal.from(10), + 'cm' + ]); }); it('should treat null or empty string units as 1', () => { - normalizeUnitsWhenPossible(Decimal.from(10), null, Decimal.from(1), '').should.eql([Decimal.from(10), '1', Decimal.from(1), '1']); - normalizeUnitsWhenPossible(Decimal.from(1), '', Decimal.from(10), null).should.eql([Decimal.from(1), '1', Decimal.from(10), '1']); + normalizeUnitsWhenPossible(Decimal.from(10), null, Decimal.from(1), '').should.eql([ + Decimal.from(10), + '1', + Decimal.from(1), + '1' + ]); + normalizeUnitsWhenPossible(Decimal.from(1), '', Decimal.from(10), null).should.eql([ + Decimal.from(1), + '1', + Decimal.from(10), + '1' + ]); }); it('should normalize CQL date units and return CQL date units', () => { - normalizeUnitsWhenPossible(Decimal.from(10), 'year', Decimal.from(12), 'month').should.eql([Decimal.from(120), 'month', Decimal.from(12), 'month']); + normalizeUnitsWhenPossible(Decimal.from(10), 'year', Decimal.from(12), 'month').should.eql([ + Decimal.from(120), + 'month', + Decimal.from(12), + 'month' + ]); }); it('should return CQL date units when UCUM units are passed in', () => { - normalizeUnitsWhenPossible(Decimal.from(10), 'a_g', Decimal.from(12), 'mo_g').should.eql([Decimal.from(120), 'mo_g', Decimal.from(12), 'mo_g']); + normalizeUnitsWhenPossible(Decimal.from(10), 'a_g', Decimal.from(12), 'mo_g').should.eql([ + Decimal.from(120), + 'mo_g', + Decimal.from(12), + 'mo_g' + ]); }); it('should not convert units of different dimensions', () => { - normalizeUnitsWhenPossible(Decimal.from(10), 'm', Decimal.from(1), 'm2').should.eql([Decimal.from(10), 'm', Decimal.from(1), 'm2']); + normalizeUnitsWhenPossible(Decimal.from(10), 'm', Decimal.from(1), 'm2').should.eql([ + Decimal.from(10), + 'm', + Decimal.from(1), + 'm2' + ]); }); it('should not convert incompatible units', () => { - normalizeUnitsWhenPossible(Decimal.from(10), 'm', Decimal.from(1), 'mg').should.eql([Decimal.from(10), 'm', Decimal.from(1), 'mg']); + normalizeUnitsWhenPossible(Decimal.from(10), 'm', Decimal.from(1), 'mg').should.eql([ + Decimal.from(10), + 'm', + Decimal.from(1), + 'mg' + ]); }); }); From e94e989e8ad381ebddbd1ec8fbe0e51eb5b9d4f6 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 20 Aug 2026 13:38:25 -0400 Subject: [PATCH 04/19] fix package-lock --- package-lock.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/package-lock.json b/package-lock.json index e3c125fad..4de37c4a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1062,6 +1062,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1079,6 +1082,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1096,6 +1102,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1113,6 +1122,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1130,6 +1142,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1147,6 +1162,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1164,6 +1182,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1181,6 +1202,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ From abd41e374c75cfe9abbacb069e4f4046061ab58c Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 20 Aug 2026 13:44:30 -0400 Subject: [PATCH 05/19] actually fix package-lock --- package-lock.json | 630 ++++++++++++++++++++++++---------------------- 1 file changed, 330 insertions(+), 300 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4de37c4a3..4e391eb81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,14 +109,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -239,13 +239,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -270,18 +270,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", + "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", + "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -289,9 +289,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -310,9 +310,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ "ppc64" ], @@ -327,9 +327,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ "arm" ], @@ -344,9 +344,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], @@ -361,9 +361,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], @@ -378,9 +378,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -395,9 +395,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], @@ -412,9 +412,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -429,9 +429,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -446,9 +446,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -463,9 +463,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -480,9 +480,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ "ia32" ], @@ -497,9 +497,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], @@ -531,9 +531,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], @@ -565,9 +565,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], @@ -582,9 +582,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], @@ -599,9 +599,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], @@ -616,9 +616,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], @@ -633,9 +633,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ "arm64" ], @@ -650,9 +650,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], @@ -667,9 +667,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ "arm64" ], @@ -684,9 +684,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], @@ -701,9 +701,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], @@ -718,9 +718,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ "ia32" ], @@ -735,9 +735,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], @@ -848,20 +848,10 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", "engines": { @@ -1799,9 +1789,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.38", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", - "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "version": "2.11.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.16.tgz", + "integrity": "sha512-H/bNPUFHewJHyCTdjn1n3Pit5+2GmWT6mmeHImPX+8MA9NA6b67jO4gYmi4jTbCJb2otq34KMZnovndDPqJwhQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1832,9 +1822,9 @@ "license": "ISC" }, "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "dev": true, "funding": [ { @@ -1852,11 +1842,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" @@ -1892,9 +1882,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", "dev": true, "funding": [ { @@ -1938,6 +1928,18 @@ "node": ">=6" } }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, "node_modules/coffeescript": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.7.0.tgz", @@ -1978,6 +1980,13 @@ "dev": true, "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -2062,7 +2071,7 @@ }, "node_modules/decimal.js": { "version": "10.6.0", - "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/decimal.js/-/decimal.js-10.6.0.tgz", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "license": "MIT" }, @@ -2093,9 +2102,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.376", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", - "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", + "version": "1.5.411", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.411.tgz", + "integrity": "sha512-gglkxzokjHfawpGxq75XdBV2/l3BAPzrsMs70qgaZdTW5rpV1tC4MdgJVP9fN126bODA4ZJQkn1wryEzJyQXIg==", "dev": true, "license": "ISC" }, @@ -2114,9 +2123,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2127,32 +2136,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escalade": { @@ -2620,16 +2629,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -2862,9 +2861,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.48", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", - "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", "dev": true, "license": "MIT", "engines": { @@ -2913,25 +2912,6 @@ "node": "20 || >=22" } }, - "node_modules/nyc/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/nyc/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" - }, "node_modules/nyc/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -2988,75 +2968,6 @@ "node": ">=8" } }, - "node_modules/nyc/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/oxlint": { "version": "1.79.0", "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.79.0.tgz", @@ -3357,21 +3268,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/readable-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/readdirp": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", @@ -3416,6 +3312,16 @@ "dev": true, "license": "ISC" }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/rimraf": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", @@ -3436,10 +3342,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -3447,9 +3359,9 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3460,9 +3372,9 @@ } }, "node_modules/serialize-javascript": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", - "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.0.tgz", + "integrity": "sha512-RNEqWOyhhUQYN9V1GfHwu9AR/g+NTciH6Z5u3/no6X3/w+04J2lVDL+svFQVXgXrEGBMG2puMVN3gq2SNGuTGw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3593,6 +3505,16 @@ "url": "https://opencollective.com/sinon" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/spawn-wrap": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-3.0.0.tgz", @@ -3639,6 +3561,15 @@ "integrity": "sha512-3HXId/0W8sktQnQM6rOZf2LuDDMbakMgAjpViLk758/h0br+iGqZFFfUxxJSqEvGvT742PyFr4v/TBXUtowdCg==", "license": "BSD-3-Clause" }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string-to-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz", @@ -3738,22 +3669,6 @@ "node": "20 || >=22" } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/tsx": { "version": "4.23.12", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", @@ -3846,9 +3761,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", "dev": true, "funding": [ { @@ -3912,6 +3827,21 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", @@ -3953,6 +3883,13 @@ "integrity": "sha512-8zci48uUQyfqynGDSkUMD7FCJB96hwLnlZOXlgs1l3TX+LW27t3psSWKUxC0fxVgA86i8tL4NwGcY1h/6t3ESg==", "license": "ISC" }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "license": "ISC" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -3960,6 +3897,99 @@ "dev": true, "license": "ISC" }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", From 4d836afdb87f0bcc853ee062b06033b6839851a4 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Tue, 25 Aug 2026 11:30:34 -0400 Subject: [PATCH 06/19] Skip interval expand tests for now --- src/elm/interval.ts | 10 ---------- test/elm/interval/interval-test.ts | 20 ++++++++++++++++++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/elm/interval.ts b/src/elm/interval.ts index 234d2ae4a..c3c4922d2 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -707,16 +707,6 @@ export class Expand extends Expression { const perUnitSize = perIsIntegral ? 1 : 0.00000001; - // TODO: this supports one test case but it's not clear if the test case is correct - // if ( - // low === high && - // Number.isInteger(low) && - // Number.isInteger(high) && - // !Number.isInteger(perValue) - // ) { - // high = parseFloat((high + 1).toFixed(decimalPrecision)); - // } - let current_low = low; const results = []; diff --git a/test/elm/interval/interval-test.ts b/test/elm/interval/interval-test.ts index 791b951d9..4a1660041 100644 --- a/test/elm/interval/interval-test.ts +++ b/test/elm/interval/interval-test.ts @@ -3599,7 +3599,15 @@ describe('IntegerIntervalExpand', () => { should.not.exist(a); }); - it('produces a more precise value for output intervals', async function () { + it.skip('produces a more precise value for output intervals', async function () { + // This example from the spec is incorrect. + // Skip for now until we have more clarity on what the expected result should be + // https://jira.hl7.org/browse/FHIR-58705 and + // https://chat.fhir.org/#narrow/channel/179220-cql/topic/Interval.20Expand.20example/with/619051021 + // Note that as of this writing the produced result is { } (empty list) + // which I believe is the correct result. + // But an empty list doesn't clearly show the intent of the test. + // define PerDecimalMorePrecise: expand { Interval[10, 10] } per 0.1 const a = await this.perDecimalMorePrecise.exec(this.ctx); // JavaScript truncates 10.0 to 10. @@ -3673,7 +3681,15 @@ describe('LongIntervalExpand', () => { should.not.exist(a); }); - it('produces a more precise value for output intervals', async function () { + it.skip('produces a more precise value for output intervals', async function () { + // This example from the spec is incorrect. + // Skip for now until we have more clarity on what the expected result should be + // https://jira.hl7.org/browse/FHIR-58705 and + // https://chat.fhir.org/#narrow/channel/179220-cql/topic/Interval.20Expand.20example/with/619051021 + // Note that as of this writing the produced result is { } (empty list) + // which I believe is the correct result. + // But an empty list doesn't clearly show the intent of the test. + const a = await this.longPerDecimalMorePrecise.exec(this.ctx); prettyList(a).should.equal( '{ [10, 10.09999999], [10.1, 10.19999999], [10.2, 10.29999999], [10.3, 10.39999999], [10.4, 10.49999999], [10.5, 10.59999999], [10.6, 10.69999999], [10.7, 10.79999999], [10.8, 10.89999999], [10.9, 10.99999999] }' From b6f4638a18c5cd0538b8f488242a57931f75f312 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Tue, 25 Aug 2026 12:51:43 -0400 Subject: [PATCH 07/19] fix test-server --- src/cql.ts | 3 +++ test-server/src/convert/convert.ts | 14 ++++++++++---- test-server/src/convert/cqlTypes.ts | 8 ++++---- test-server/tests/convert/convert.test.ts | 5 +++-- test-server/tests/convert/cqlTypes.test.ts | 7 ++++--- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/cql.ts b/src/cql.ts index 33d47d829..e76b08e9f 100644 --- a/src/cql.ts +++ b/src/cql.ts @@ -22,6 +22,7 @@ import { Concept, Date, DateTime, + Decimal, Interval, Quantity, Ratio, @@ -54,6 +55,7 @@ export { Concept, Date, DateTime, + Decimal, Interval, Quantity, Ratio, @@ -81,6 +83,7 @@ export default { Concept, Date, DateTime, + Decimal, Interval, Quantity, Ratio, diff --git a/test-server/src/convert/convert.ts b/test-server/src/convert/convert.ts index eaac8084a..aae941399 100644 --- a/test-server/src/convert/convert.ts +++ b/test-server/src/convert/convert.ts @@ -15,6 +15,7 @@ import { Concept, Date as CqlDate, DateTime as CqlDateTime, + Decimal as CqlDecimal, Quantity as CqlQuantity, Ratio as CqlRatio, Interval, @@ -173,9 +174,9 @@ function toLongParameter(name: string, result: number): ParametersParameter { return { name, valueString: String(result) }; } -function toDecimalParameter(name: string, result: number): ParametersParameter { +function toDecimalParameter(name: string, result: CqlDecimal | number): ParametersParameter { // TODO: use the quantity-precision extension to communicate precision of the value - return { name, valueDecimal: result }; + return { name, valueDecimal: result instanceof CqlDecimal ? result.toNumber() : result }; } function toDateParameter(name: string, result: CqlDate) { @@ -305,15 +306,20 @@ function toChoiceParameter(name: string, result: any, typeSpecifier: AnyTypeSpec return { name }; } -function toFhirQuantity(val: CqlQuantity | number, isIntegerOrLong = false): FhirQuantity { +function toFhirQuantity( + val: CqlQuantity | CqlDecimal | number, + isIntegerOrLong = false +): FhirQuantity { let fq: FhirQuantity; if (typeof val === 'number') { fq = { value: val }; } else if (typeof val === 'bigint') { fq = { value: Number(val) }; + } else if (val instanceof CqlDecimal) { + fq = { value: val.toNumber() }; } else { const cq = val as CqlQuantity; - fq = { value: cq.value } as FhirQuantity; + fq = { value: cq.value.toNumber() }; if (cq.unit != null) { fq.unit = fq.code = cq.unit; if ( diff --git a/test-server/src/convert/cqlTypes.ts b/test-server/src/convert/cqlTypes.ts index dfdba0fae..ebc895803 100644 --- a/test-server/src/convert/cqlTypes.ts +++ b/test-server/src/convert/cqlTypes.ts @@ -6,6 +6,7 @@ import { TupleTypeSpecifier, TupleElementDefinition, AnyTypeSpecifier, + Decimal, Interval } from '../../..'; import { ELM_ANY_TYPE } from '../../../lib/util/elmTypes'; @@ -60,11 +61,10 @@ export function guessSpecifierType(val: any): AnyTypeSpecifier | undefined { return typeHierarchy[0]; } else if (typeof val === 'boolean') { return { type: 'NamedTypeSpecifier', name: '{urn:hl7-org:elm-types:r1}Boolean' }; - } else if (typeof val === 'number' && Math.floor(val) === val) { - // It could still be a decimal, but we have to just take our best guess! - return { type: 'NamedTypeSpecifier', name: '{urn:hl7-org:elm-types:r1}Integer' }; - } else if (typeof val === 'number') { + } else if (val instanceof Decimal) { return { type: 'NamedTypeSpecifier', name: '{urn:hl7-org:elm-types:r1}Decimal' }; + } else if (typeof val === 'number') { + return { type: 'NamedTypeSpecifier', name: '{urn:hl7-org:elm-types:r1}Integer' }; } else if (typeof val === 'string') { return { type: 'NamedTypeSpecifier', name: '{urn:hl7-org:elm-types:r1}String' }; } else if (val.isConcept) { diff --git a/test-server/tests/convert/convert.test.ts b/test-server/tests/convert/convert.test.ts index 47153d6d4..6d2cddb55 100644 --- a/test-server/tests/convert/convert.test.ts +++ b/test-server/tests/convert/convert.test.ts @@ -6,6 +6,7 @@ import { Concept, Date as CqlDate, DateTime, + Decimal, Interval, IntervalTypeSpecifier, ListTypeSpecifier, @@ -58,7 +59,7 @@ describe('convert.toParameters', () => { }); it('converts decimal to valueDecimal', () => { - expect(toParameters(3.14159, 'System.Decimal')).toEqual({ + expect(toParameters(Decimal.from('3.14159'), 'System.Decimal')).toEqual({ resourceType: 'Parameters', parameter: [ { extension: cqlTypeExt('System.Decimal'), name: 'return', valueDecimal: 3.14159 } @@ -670,7 +671,7 @@ describe('convert.toParameters', () => { }); it('guesses type when no type is passed in and converts value (Decimal example)', () => { - expect(toParameters(1.25)).toEqual({ + expect(toParameters(Decimal.from('1.25'))).toEqual({ resourceType: 'Parameters', parameter: [ { diff --git a/test-server/tests/convert/cqlTypes.test.ts b/test-server/tests/convert/cqlTypes.test.ts index 5c8ec76df..7a9dcdb8c 100644 --- a/test-server/tests/convert/cqlTypes.test.ts +++ b/test-server/tests/convert/cqlTypes.test.ts @@ -11,6 +11,7 @@ import { Concept, Date as CqlDate, DateTime, + Decimal, Interval, IntervalTypeSpecifier, ListTypeSpecifier, @@ -137,7 +138,7 @@ describe('guessSpecifierType', () => { type: 'NamedTypeSpecifier', name: '{urn:hl7-org:elm-types:r1}Integer' } as NamedTypeSpecifier); - expect(guessSpecifierType(3.14)).toEqual({ + expect(guessSpecifierType(Decimal.from('3.14'))).toEqual({ type: 'NamedTypeSpecifier', name: '{urn:hl7-org:elm-types:r1}Decimal' } as NamedTypeSpecifier); @@ -178,7 +179,7 @@ describe('guessSpecifierType', () => { }); it('returns the correct type for Uncertainty values', () => { - const spec = guessSpecifierType(new Uncertainty(1.5, 2.5))!; + const spec = guessSpecifierType(new Uncertainty(Decimal.from(1.5), Decimal.from(2.5)))!; expect(spec).toEqual({ type: 'NamedTypeSpecifier', name: '{urn:hl7-org:elm-types:r1}Decimal' @@ -194,7 +195,7 @@ describe('guessSpecifierType', () => { }); it('returns ListTypeSpecifier with Choice for arrays with mixed types', () => { - const spec = guessSpecifierType([1, 2.5, true])!; + const spec = guessSpecifierType([1, Decimal.from(2.5), true])!; expect(spec).toEqual({ type: 'ListTypeSpecifier', elementType: { From af6bee9a0d4425d79d2c60a449fed186b0c2a4d0 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Wed, 26 Aug 2026 11:53:34 -0400 Subject: [PATCH 08/19] clean up some TODOs --- src/datatypes/decimal.ts | 12 +++--- src/datatypes/interval.ts | 11 +---- src/datatypes/quantity.ts | 5 +-- src/elm/aggregate.ts | 24 +++++++---- src/elm/arithmetic.ts | 59 +++++++++++--------------- src/elm/interval.ts | 50 +++++++++++++--------- src/util/math.ts | 36 ++++++++-------- test/elm/arithmetic/arithmetic-test.ts | 5 +-- 8 files changed, 99 insertions(+), 103 deletions(-) diff --git a/src/datatypes/decimal.ts b/src/datatypes/decimal.ts index 8e77a9e38..53c6b66e5 100644 --- a/src/datatypes/decimal.ts +++ b/src/datatypes/decimal.ts @@ -9,7 +9,7 @@ export type DecimalInput = Decimal | string | number | bigint; export type DecimalRoundingMode = DecimalJS.Rounding; -const MIN_FLOAT_PRECISION_VALUE = DecimalJS.pow(10, -8); +const MIN_PRECISION_VALUE = DecimalJS.pow(10, -8); const CQL_IMPLICIT_SCALE = 8; const CQL_IMPLICIT_ROUNDING = DecimalJS.ROUND_HALF_UP; @@ -43,6 +43,8 @@ export class Decimal { return this.setScale(CQL_IMPLICIT_SCALE, CQL_IMPLICIT_ROUNDING); } + // Helper function to reduce repeated boilerplate. + // Apply the given function with the given operand, and wrap the result in a Decimal. private applyWrapper(operation: (value: any) => DecimalJS, other: DecimalInput): Decimal { const operand = other instanceof Decimal ? other.value : other; @@ -104,11 +106,11 @@ export class Decimal { } successor() { - return new Decimal(this.value.add(MIN_FLOAT_PRECISION_VALUE)); + return new Decimal(this.value.add(MIN_PRECISION_VALUE)); } predecessor() { - return new Decimal(this.value.minus(MIN_FLOAT_PRECISION_VALUE)); + return new Decimal(this.value.minus(MIN_PRECISION_VALUE)); } negate() { @@ -186,8 +188,8 @@ export class Decimal { } toLong() { - // TODO - return BigInt(this.toString()); + // note that this is permissive and converts non-integral values + return BigInt(this.value.truncated().toString()); } toString() { diff --git a/src/datatypes/interval.ts b/src/datatypes/interval.ts index 06045d031..2ec03243e 100644 --- a/src/datatypes/interval.ts +++ b/src/datatypes/interval.ts @@ -21,7 +21,6 @@ import { ELM_ANY_TYPE } from '../util/elmTypes'; import { Quantity } from './quantity'; -import { Decimal, MIN_DECIMAL_VALUE } from './decimal'; export class Interval { constructor( @@ -703,15 +702,7 @@ export class Interval { // https://cql.hl7.org/R2/09-b-cqlreference.html#size getPointSize() { // "... point-size is determined by successor of minimum T - minimum T" - let minValue = minValueForType(this.pointType, getQuantityInstanceForMinMax(this)); - - // due to floating point issues in JS, we must use 0.0 for Decimal/Quantity instead of min - // TODO: remove this when changing to decimal.js - if (minValue === MIN_DECIMAL_VALUE) { - minValue = Decimal.from(0.0); - } else if ((minValue as any)?.isQuantity) { - minValue = new Quantity(0.0, (minValue as Quantity)?.unit); - } + const minValue = minValueForType(this.pointType, getQuantityInstanceForMinMax(this)); if (minValue != null) { if ((minValue as any).isDate || (minValue as any).isDatetime || (minValue as any).isTime) { diff --git a/src/datatypes/quantity.ts b/src/datatypes/quantity.ts index d62110136..190cc1e4e 100644 --- a/src/datatypes/quantity.ts +++ b/src/datatypes/quantity.ts @@ -1,4 +1,3 @@ -import { ELM_DECIMAL_TYPE } from '../util/elmTypes'; import { add, subtract, isValidDecimal, overflowsOrUnderflows } from '../util/math'; import { Decimal } from './decimal'; import { @@ -135,7 +134,7 @@ export class Quantity { const resultUnit = getQuotientOfUnits(unit1, unit2); // Check for invalid unit or value - if (resultUnit == null || overflowsOrUnderflows(resultValue, ELM_DECIMAL_TYPE)) { + if (resultUnit == null || overflowsOrUnderflows(resultValue)) { return null; } return new Quantity(resultValue, resultUnit); @@ -159,7 +158,7 @@ export class Quantity { const resultUnit = getProductOfUnits(unit1, unit2); // Check for invalid unit or value - if (resultUnit == null || overflowsOrUnderflows(resultValue, ELM_DECIMAL_TYPE)) { + if (resultUnit == null || overflowsOrUnderflows(resultValue)) { return null; } return new Quantity(resultValue, resultUnit); diff --git a/src/elm/aggregate.ts b/src/elm/aggregate.ts index 56ec1c620..7d233f2ed 100644 --- a/src/elm/aggregate.ts +++ b/src/elm/aggregate.ts @@ -7,7 +7,6 @@ import { Exception } from '../datatypes/exception'; import { greaterThan, lessThan } from '../util/comparison'; import { build } from './builder'; import { overflowsOrUnderflows, finalizeNumericResult } from '../util/math'; -import { ELM_DECIMAL_TYPE } from '../util/elmTypes'; class AggregateExpression extends Expression { source: any; @@ -55,7 +54,7 @@ export class Sum extends AggregateExpression { if (hasOnlyQuantities(items)) { const sum = sumOfDecimals(getValuesFromQuantities(items)); - return overflowsOrUnderflows(sum, ELM_DECIMAL_TYPE) ? null : new Quantity(sum, items[0].unit); + return overflowsOrUnderflows(sum) ? null : new Quantity(sum, items[0].unit); } else { let sum; if (hasDecimals(items)) { @@ -64,7 +63,7 @@ export class Sum extends AggregateExpression { sum = items.reduce((x: any, y: any) => x + y); } sum = finalizeNumericResult(sum); - return overflowsOrUnderflows(sum, this.resultTypeName) ? null : sum; + return overflowsOrUnderflows(sum) ? null : sum; } } } @@ -290,10 +289,13 @@ export class StdDev extends AggregateExpression { if (hasOnlyQuantities(items)) { const values = getValuesFromQuantities(items); const stdDev = this.standardDeviation(values); + if (stdDev === null) { + return null; + } return new Quantity(stdDev, items[0].unit); } else { const standardDeviation = this.standardDeviation(items.map(Decimal.from)); - return standardDeviation?.normalized(); // TODO: review function signatures. always return Decimal makes sense but is it correct? + return standardDeviation?.normalized(); } } @@ -305,6 +307,14 @@ export class StdDev extends AggregateExpression { } stats(list: Decimal[]) { + if (list.length === 1) { + return { + standard_variance: null, + population_variance: Decimal.from(0), + standard_deviation: null, + population_deviation: Decimal.from(0) + }; + } const sum = list.reduce((x, y) => x.add(y), Decimal.from(0)); const mean = sum.divideBy(list.length); @@ -349,9 +359,7 @@ export class Product extends AggregateExpression { if (hasOnlyQuantities(items)) { const product = productOfDecimals(getValuesFromQuantities(items)); // Units are not multiplied for the geometric product - return overflowsOrUnderflows(product, ELM_DECIMAL_TYPE) - ? null - : new Quantity(product, items[0].unit); + return overflowsOrUnderflows(product) ? null : new Quantity(product, items[0].unit); } else { let result; if (hasDecimals(items)) { @@ -360,7 +368,7 @@ export class Product extends AggregateExpression { result = items.reduce((x: number, y: number) => x * y); } result = finalizeNumericResult(result); - return overflowsOrUnderflows(result, this.resultTypeName) ? null : result; + return overflowsOrUnderflows(result) ? null : result; } } } diff --git a/src/elm/arithmetic.ts b/src/elm/arithmetic.ts index 3b3170384..72f00c8be 100644 --- a/src/elm/arithmetic.ts +++ b/src/elm/arithmetic.ts @@ -91,7 +91,7 @@ export class Multiply extends Expression { product = MathUtil.multiply(x, y); } - if (MathUtil.overflowsOrUnderflows(product, this.resultTypeName)) { + if (MathUtil.overflowsOrUnderflows(product)) { return null; } @@ -123,7 +123,6 @@ export class Divide extends Expression { quotient = doDivision(x, y); } else if (x.isUncertainty && y.isUncertainty) { let low, high; - // TODO change this section back if (x.low.isQuantity) { low = doDivision(x.low, y.high); high = doDivision(x.high, y.low); @@ -140,7 +139,7 @@ export class Divide extends Expression { return null; } - if (MathUtil.overflowsOrUnderflows(quotient, this.resultTypeName)) { + if (MathUtil.overflowsOrUnderflows(quotient)) { return null; } return MathUtil.finalizeNumericResult(quotient, this.resultTypeName); @@ -177,7 +176,7 @@ export class TruncatedDivide extends Expression { } } - if (MathUtil.overflowsOrUnderflows(quotient, this.resultTypeName)) { + if (MathUtil.overflowsOrUnderflows(quotient)) { return null; } return quotient; @@ -265,19 +264,13 @@ export class Abs extends Expression { return new Quantity(arg.value.abs(), arg.unit); } else if (typeof arg === 'bigint') { const absoluteValue = arg < 0n ? -arg : arg; - return MathUtil.overflowsOrUnderflows(absoluteValue, this.resultTypeName) - ? null - : absoluteValue; + return MathUtil.overflowsOrUnderflows(absoluteValue) ? null : absoluteValue; } else if (arg.isDecimal) { const absoluteValue = arg.abs(); - return MathUtil.overflowsOrUnderflows(absoluteValue, this.resultTypeName) - ? null - : absoluteValue; + return MathUtil.overflowsOrUnderflows(absoluteValue) ? null : absoluteValue; } else { const absoluteValue = Math.abs(arg); - return MathUtil.overflowsOrUnderflows(absoluteValue, this.resultTypeName) - ? null - : absoluteValue; + return MathUtil.overflowsOrUnderflows(absoluteValue) ? null : absoluteValue; } } } @@ -295,19 +288,13 @@ export class Negate extends Expression { return new Quantity(arg.value.negate(), arg.unit); } else if (typeof arg === 'bigint') { const negatedValue = arg * -1n; - return MathUtil.overflowsOrUnderflows(negatedValue, this.resultTypeName) - ? null - : negatedValue; + return MathUtil.overflowsOrUnderflows(negatedValue) ? null : negatedValue; } else if (arg.isDecimal) { const negatedValue = arg.negate(); - return MathUtil.overflowsOrUnderflows(negatedValue, this.resultTypeName) - ? null - : negatedValue; + return MathUtil.overflowsOrUnderflows(negatedValue) ? null : negatedValue; } else { const negatedValue = arg * -1; - return MathUtil.overflowsOrUnderflows(negatedValue, this.resultTypeName) - ? null - : negatedValue; + return MathUtil.overflowsOrUnderflows(negatedValue) ? null : negatedValue; } } } @@ -343,8 +330,11 @@ export class Ln extends Expression { } try { - const ln = Decimal.from(arg).ln().normalized(); - return MathUtil.decimalOrNull(ln); + const ln = Decimal.from(arg).ln(); + if (MathUtil.overflowsOrUnderflows(ln)) { + return null; + } + return MathUtil.finalizeNumericResult(ln); } catch { return null; } @@ -369,10 +359,10 @@ export class Exp extends Expression { return null; } - if (MathUtil.overflowsOrUnderflows(power, this.resultTypeName)) { + if (MathUtil.overflowsOrUnderflows(power)) { return null; } - return power; + return MathUtil.finalizeNumericResult(power); } } @@ -389,7 +379,7 @@ export class Log extends Expression { try { const log = Decimal.from(args[0]).log(args[1]); - return MathUtil.decimalOrNull(log); + return MathUtil.finalizeNumericResult(log); } catch { return null; } @@ -406,14 +396,13 @@ export class Power extends Expression { if (args == null || args.some((x: any) => x == null)) { return null; } - // TODO: cql spec shows the return type is always Decimal, but that's not true - const [x, y] = args; - const power = doPower(x, y); - // Note: The resultTypeName may be wrong if the exponent is a negative number. Math.overflowsOrUnderflows - // already accounts for this possibility by only considering it an integer if Number.isInteger(value). + // Note: The resultTypeName may be wrong if the exponent is a negative number. // E.g., CQL-to-ELM says 10^-1 is an Integer result type, but the correct result is a 0.1 (a Decimal) - if (MathUtil.overflowsOrUnderflows(power, this.resultTypeName)) { + // doPower handles this scenario + const power = doPower(args[0], args[1]); + + if (MathUtil.overflowsOrUnderflows(power)) { return null; } return power; @@ -525,7 +514,7 @@ export class Successor extends Expression { } } - if (MathUtil.overflowsOrUnderflows(successor, this.resultTypeName)) { + if (MathUtil.overflowsOrUnderflows(successor)) { return null; } return successor; @@ -554,7 +543,7 @@ export class Predecessor extends Expression { } } - if (MathUtil.overflowsOrUnderflows(predecessor, this.resultTypeName)) { + if (MathUtil.overflowsOrUnderflows(predecessor)) { return null; } return predecessor; diff --git a/src/elm/interval.ts b/src/elm/interval.ts index c3c4922d2..b48b26775 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -2,7 +2,7 @@ import { Expression } from './expression'; import { MAX_DATETIME_VALUE, MIN_DATETIME_VALUE } from '../datatypes/datetime'; import { Quantity } from '../datatypes/quantity'; import { add, successor, predecessor, subtract } from '../util/math'; -import { greaterThan, lessThan, lessThanOrEquals } from '../util/comparison'; +import { greaterThan, lessThan } from '../util/comparison'; import { convertUnit, compareUnits, convertToCQLDateUnit } from '../util/units'; import * as dtivl from '../datatypes/interval'; import { Context } from '../runtime/context'; @@ -10,6 +10,7 @@ import { build } from './builder'; import { IntervalTypeSpecifier, NamedTypeSpecifier } from '../types/type-specifiers.interfaces'; import { ELM_ANY_TYPE, ELM_NAMED_TYPE_SPECIFIER } from '../util/elmTypes'; import { Decimal } from '../datatypes/decimal'; +import { MAX_INT_VALUE, MIN_INT_VALUE } from '../util/limits'; export class Interval extends Expression { lowClosed: boolean; @@ -670,27 +671,38 @@ export class Expand extends Expression { const decimalPrecision = perIsIntegral ? 0 : 8; // For the purposes of this function, we'll perform all the arithmetic using Decimals, - // then convert the results back to the required type if necessary - let makeInterval: (l: Decimal, h: Decimal) => dtivl.Interval; + // then convert the results back to the required type as necessary + const origLow = low; + const origHigh = high; + + low = Decimal.from(low); + high = Decimal.from(high); + + let convertBound: (d: Decimal) => Decimal | number | bigint = d => d.toInteger(); if (!perIsIntegral) { // If per is not an integer value, then regardless of the original point types, the values will be Decimals - makeInterval = (l: Decimal, h: Decimal) => new dtivl.Interval(l, h, true, true); - } else if (typeof low === 'bigint' || typeof high === 'bigint') { - makeInterval = (l: Decimal, h: Decimal) => - new dtivl.Interval(l.toLong(), h.toLong(), true, true); - } else if (typeof low === 'number' || typeof high === 'number') { - makeInterval = (l: Decimal, h: Decimal) => - new dtivl.Interval(l.toInteger(), h.toInteger(), true, true); + convertBound = d => d; + } else if (typeof origLow === 'bigint' || typeof origHigh === 'bigint') { + convertBound = d => d.toLong(); + } else if (typeof origLow === 'number' || typeof origHigh === 'number') { + convertBound = d => d.toInteger(); } else { // per is an integer but the original bounds of the interval were Decimal. - // TODO: for now just make them integers - makeInterval = (l: Decimal, h: Decimal) => - new dtivl.Interval(l.toInteger(), h.toInteger(), true, true); + // Make the resulting intervals either Long or Integer based on the original bounds. + if ( + low.lessThan(MIN_INT_VALUE) || + low.greaterThan(MAX_INT_VALUE) || + high.lessThan(MIN_INT_VALUE) || + high.greaterThan(MAX_INT_VALUE) + ) { + convertBound = d => d.toLong(); + } else { + convertBound = d => d.toInteger(); + } } - // treat everything as a Decimal, convert back later if needed - low = Decimal.from(low); - high = Decimal.from(high); + const makeInterval = (l: Decimal, h: Decimal) => + new dtivl.Interval(convertBound(l), convertBound(h), true, true); // If the interval boundaries are more precise than the per quantity, the // more precise values will be truncated to the precision specified by the @@ -842,11 +854,7 @@ function collapseIntervals(intervals: any, perWidth: any) { } } else { const distance = subtract(b.low, a.high); - // TODO: perWidth.value is a Decimal, but distance could be anything - // lessThanOrEquals requires that its args be the same type - // so I guess for now, make distance a Decimal - const distanceDecimal = Decimal.from(distance); - const withinPerWidth = lessThanOrEquals(distanceDecimal, perWidth.value); + const withinPerWidth = perWidth.value.greaterThanOrEquals(distance); if (withinPerWidth) { if (greaterThan(b.high, a.high) || b.high == null) { a.high = b.high; diff --git a/src/util/math.ts b/src/util/math.ts index 62216c002..06d9dcb4f 100644 --- a/src/util/math.ts +++ b/src/util/math.ts @@ -24,7 +24,7 @@ import { import { MAX_INT_VALUE, MAX_LONG_VALUE, MIN_INT_VALUE, MIN_LONG_VALUE } from './limits'; import { convertToCQLDateUnit, normalizeUnitsWhenPossible } from './units'; -export function overflowsOrUnderflows(value: any, type?: string): boolean { +export function overflowsOrUnderflows(value: any): boolean { if (value == null) { return false; } @@ -66,7 +66,7 @@ export function overflowsOrUnderflows(value: any, type?: string): boolean { return true; } } else if (value.isUncertainty) { - return overflowsOrUnderflows(value.low, type) || overflowsOrUnderflows(value.high, type); + return overflowsOrUnderflows(value.low) || overflowsOrUnderflows(value.high); } return false; } @@ -126,15 +126,15 @@ export function add(a: any, b: any, type?: string): any { if (a.isDecimal || b.isDecimal || type === ELM_DECIMAL_TYPE) { const sum = Decimal.from(a).add(Decimal.from(b)); - return overflowsOrUnderflows(sum, ELM_DECIMAL_TYPE) ? null : sum; + return overflowsOrUnderflows(sum) ? null : sum; } if (typeof a === 'bigint' || typeof b === 'bigint' || type === ELM_LONG_TYPE) { const sum = BigInt(a) + BigInt(b); - return overflowsOrUnderflows(sum, ELM_LONG_TYPE) ? null : sum; + return overflowsOrUnderflows(sum) ? null : sum; } if (typeof a === 'number' && typeof b === 'number') { const sum = a + b; - return overflowsOrUnderflows(sum, ELM_INTEGER_TYPE) ? null : sum; + return overflowsOrUnderflows(sum) ? null : sum; } if (a?.isQuantity && b?.isQuantity) { const [aValue, aUnit, bValue, bUnit] = normalizeUnitsWhenPossible( @@ -147,7 +147,7 @@ export function add(a: any, b: any, type?: string): any { return null; } const sum = aValue.add(bValue); - return overflowsOrUnderflows(sum, ELM_DECIMAL_TYPE) ? null : new Quantity(sum, aUnit); + return overflowsOrUnderflows(sum) ? null : new Quantity(sum, aUnit); } if (b?.isQuantity && (a?.isDate || a?.isDateTime || (a?.isTime && a.isTime()))) { const unit = convertToCQLDateUnit(b.unit) || b.unit; @@ -188,15 +188,15 @@ export function subtract(a: any, b: any, type?: string): any { export function multiply(a: any, b: any, type?: string) { if (a.isDecimal || b.isDecimal || type === ELM_DECIMAL_TYPE) { const product = Decimal.from(a).multiplyBy(b); - return overflowsOrUnderflows(product, ELM_DECIMAL_TYPE) ? null : product; + return overflowsOrUnderflows(product) ? null : product; } if (typeof a === 'bigint' || typeof b === 'bigint' || type === ELM_LONG_TYPE) { const product = BigInt(a) * BigInt(b); - return overflowsOrUnderflows(product, ELM_LONG_TYPE) ? null : product; + return overflowsOrUnderflows(product) ? null : product; } if (typeof a === 'number' && typeof b === 'number') { const product = a * b; - return overflowsOrUnderflows(product, ELM_INTEGER_TYPE) ? null : product; + return overflowsOrUnderflows(product) ? null : product; } throw new Error('Unsupported argument types.'); @@ -209,7 +209,7 @@ export function divide(a: any, b: any, type?: string) { return null; } const quotient = Decimal.from(a).divideBy(b); - return overflowsOrUnderflows(quotient, ELM_DECIMAL_TYPE) ? null : quotient; + return overflowsOrUnderflows(quotient) ? null : quotient; } if (typeof a === 'bigint' || typeof b === 'bigint' || type === ELM_LONG_TYPE) { if (b === 0 || b === 0n) { @@ -217,7 +217,7 @@ export function divide(a: any, b: any, type?: string) { } // BigInt division is inherently truncated, eg 10n / 3n = 3n const quotient = BigInt(a) / BigInt(b); - return overflowsOrUnderflows(quotient, ELM_LONG_TYPE) ? null : quotient; + return overflowsOrUnderflows(quotient) ? null : quotient; } if (typeof a === 'number' && typeof b === 'number') { if (b === 0) { @@ -225,7 +225,7 @@ export function divide(a: any, b: any, type?: string) { } // here we need to truncate manually to ensure the value is an integer const quotient = Math.trunc(a / b); - return overflowsOrUnderflows(quotient, ELM_INTEGER_TYPE) ? null : quotient; + return overflowsOrUnderflows(quotient) ? null : quotient; } throw new Error('Unsupported argument types.'); @@ -254,7 +254,7 @@ export function limitDecimalPrecision< export class OverFlowException extends Exception {} -export function successor(val: any, type?: string, precision?: string): any { +export function successor(val: any, _type?: string, precision?: string): any { if (typeof val === 'number') { if (val >= MAX_INT_VALUE) { throw new OverFlowException(); @@ -295,12 +295,12 @@ export function successor(val: any, type?: string, precision?: string): any { // For uncertainties, if the high is the max val, don't increment it const high = (() => { try { - return successor(val.high, type, precision); + return successor(val.high, undefined, precision); } catch { return val.high; } })(); - return new Uncertainty(successor(val.low, type, precision), high); + return new Uncertainty(successor(val.low, undefined, precision), high); } else if (val && val.isQuantity) { const succ = val.clone(); succ.value = successor(val.value, ELM_DECIMAL_TYPE); @@ -310,7 +310,7 @@ export function successor(val: any, type?: string, precision?: string): any { } } -export function predecessor(val: any, type?: string, precision?: string): any { +export function predecessor(val: any, _type?: string, precision?: string): any { if (typeof val === 'number') { if (val <= MIN_INT_VALUE) { throw new OverFlowException(); @@ -351,12 +351,12 @@ export function predecessor(val: any, type?: string, precision?: string): any { // For uncertainties, if the low is the min val, don't decrement it const low = ((): any => { try { - return predecessor(val.low, type, precision); + return predecessor(val.low, undefined, precision); } catch { return val.low; } })(); - return new Uncertainty(low, predecessor(val.high, type, precision)); + return new Uncertainty(low, predecessor(val.high, undefined, precision)); } else if (val && val.isQuantity) { const pred = val.clone(); pred.value = predecessor(val.value, ELM_DECIMAL_TYPE); diff --git a/test/elm/arithmetic/arithmetic-test.ts b/test/elm/arithmetic/arithmetic-test.ts index 1671c64c5..b05c6b580 100644 --- a/test/elm/arithmetic/arithmetic-test.ts +++ b/test/elm/arithmetic/arithmetic-test.ts @@ -345,9 +345,8 @@ describe('Power', () => { should(await this.twoLongExpMaxLong.exec(this.ctx)).be.null(); }); - // TODO: Unskip this test when we properly handle negative Long exponents that can't be safely converted to Number - it.skip('should return an infinitesimally small number when the exponent is the minimum Long value', async function () { - (await this.twoLongExpMinLong.exec(this.ctx)).should.be(0.0); + it('should return an infinitesimally small number when the exponent is the minimum Long value', async function () { + (await this.twoLongExpMinLong.exec(this.ctx)).should.equalDecimal(Decimal.from(0.0)); }); }); From e18f9eb360988875fef53bdb6a9255d89af078e7 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Wed, 26 Aug 2026 11:57:21 -0400 Subject: [PATCH 09/19] remove now-unnecessary param in successor/predecessor --- src/datatypes/interval.ts | 21 +++++++++------------ src/elm/arithmetic.ts | 4 ++-- src/util/math.ts | 16 ++++++++-------- test/util/math-test.ts | 19 ++++++------------- 4 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/datatypes/interval.ts b/src/datatypes/interval.ts index 2ec03243e..120b0b31d 100644 --- a/src/datatypes/interval.ts +++ b/src/datatypes/interval.ts @@ -518,10 +518,10 @@ export class Interval { this.pointType === ELM_DATETIME_TYPE || this.pointType === ELM_TIME_TYPE ) { - return this.start()?.sameAs(successor(other.end(), other.pointType, precision), precision); + return this.start()?.sameAs(successor(other.end(), precision), precision); } - return cmp.equals(this.start(), successor(other.end(), other.pointType)); + return cmp.equals(this.start(), successor(other.end())); } catch { return false; } @@ -543,13 +543,10 @@ export class Interval { this.pointType === ELM_DATETIME_TYPE || this.pointType === ELM_TIME_TYPE ) { - return this.end()?.sameAs( - predecessor(other.start(), other.pointType, precision), - precision - ); + return this.end()?.sameAs(predecessor(other.start(), precision), precision); } - return cmp.equals(this.end(), predecessor(other.start(), other.pointType)); + return cmp.equals(this.end(), predecessor(other.start())); } catch { return false; } @@ -577,7 +574,7 @@ export class Interval { // "If the low boundary of the interval is closed and non-null, this operator returns the low // value of the interval... If the low boundary of the interval is open and non-null, this // operator returns the successor of the low value of the interval." - return this.lowClosed ? this.low : successor(this.low, this.pointType); + return this.lowClosed ? this.low : successor(this.low); } // https://cql.hl7.org/R2/09-b-cqlreference.html#end @@ -602,7 +599,7 @@ export class Interval { // "If the high boundary of the interval is closed and non-null, this operator returns the high // value of the interval... If the high boundary of the interval is open and non-null, this // operator returns the predecessor of the high value of the interval." - return this.highClosed ? this.high : predecessor(this.high, this.pointType); + return this.highClosed ? this.high : predecessor(this.high); } // https://cql.hl7.org/R2/09-b-cqlreference.html#starts @@ -712,7 +709,7 @@ export class Interval { // E.g., point size of Interval[@2012-01, @2012-12] is 1 month, not 1 ms. return new Quantity(1, (this.low ?? this.high).getPrecision()); } - return subtract(successor(minValue, this.pointType), minValue, this.pointType); + return subtract(successor(minValue), minValue, this.pointType); } throw new Error('Point type of interval cannot be determined.'); @@ -743,7 +740,7 @@ export class Interval { if (this.lowClosed && this.low == null) { low = minValueForType(this.pointType, quantityInstance); } else if (!this.lowClosed && this.low != null) { - low = successor(this.low, this.pointType); + low = successor(this.low); } else { low = this.low; } @@ -751,7 +748,7 @@ export class Interval { if (this.highClosed && this.high == null) { high = maxValueForType(this.pointType, quantityInstance); } else if (!this.highClosed && this.high != null) { - high = predecessor(this.high, this.pointType); + high = predecessor(this.high); } else { high = this.high; } diff --git a/src/elm/arithmetic.ts b/src/elm/arithmetic.ts index 72f00c8be..2c3b85e9b 100644 --- a/src/elm/arithmetic.ts +++ b/src/elm/arithmetic.ts @@ -507,7 +507,7 @@ export class Successor extends Expression { try { // MathUtil.successor throws on overflow, and the exception is used in // the logic for evaluating `meets`, so it can't be changed to just return null - successor = MathUtil.successor(arg, this.resultTypeName); + successor = MathUtil.successor(arg); } catch (e) { if (e instanceof MathUtil.OverFlowException) { return null; @@ -536,7 +536,7 @@ export class Predecessor extends Expression { try { // MathUtil.predecessor throws on underflow, and the exception is used in // the logic for evaluating `meets`, so it can't be changed to just return null - predecessor = MathUtil.predecessor(arg, this.resultTypeName); + predecessor = MathUtil.predecessor(arg); } catch (e) { if (e instanceof MathUtil.OverFlowException) { return null; diff --git a/src/util/math.ts b/src/util/math.ts index 06d9dcb4f..365a749b8 100644 --- a/src/util/math.ts +++ b/src/util/math.ts @@ -254,7 +254,7 @@ export function limitDecimalPrecision< export class OverFlowException extends Exception {} -export function successor(val: any, _type?: string, precision?: string): any { +export function successor(val: any, precision?: string): any { if (typeof val === 'number') { if (val >= MAX_INT_VALUE) { throw new OverFlowException(); @@ -295,22 +295,22 @@ export function successor(val: any, _type?: string, precision?: string): any { // For uncertainties, if the high is the max val, don't increment it const high = (() => { try { - return successor(val.high, undefined, precision); + return successor(val.high, precision); } catch { return val.high; } })(); - return new Uncertainty(successor(val.low, undefined, precision), high); + return new Uncertainty(successor(val.low, precision), high); } else if (val && val.isQuantity) { const succ = val.clone(); - succ.value = successor(val.value, ELM_DECIMAL_TYPE); + succ.value = successor(val.value); return succ; } else if (val == null) { return null; } } -export function predecessor(val: any, _type?: string, precision?: string): any { +export function predecessor(val: any, precision?: string): any { if (typeof val === 'number') { if (val <= MIN_INT_VALUE) { throw new OverFlowException(); @@ -351,15 +351,15 @@ export function predecessor(val: any, _type?: string, precision?: string): any { // For uncertainties, if the low is the min val, don't decrement it const low = ((): any => { try { - return predecessor(val.low, undefined, precision); + return predecessor(val.low, precision); } catch { return val.low; } })(); - return new Uncertainty(low, predecessor(val.high, undefined, precision)); + return new Uncertainty(low, predecessor(val.high, precision)); } else if (val && val.isQuantity) { const pred = val.clone(); - pred.value = predecessor(val.value, ELM_DECIMAL_TYPE); + pred.value = predecessor(val.value); return pred; } else if (val == null) { return null; diff --git a/test/util/math-test.ts b/test/util/math-test.ts index 5071fd60d..a6e6d3629 100644 --- a/test/util/math-test.ts +++ b/test/util/math-test.ts @@ -2,48 +2,41 @@ import { Uncertainty } from '../../src/datatypes/uncertainty'; import { MAX_FLOAT_VALUE, MIN_FLOAT_VALUE } from '../../src/util/limits'; import { Decimal } from '../../src/datatypes/decimal'; import { predecessor, successor } from '../../src/util/math'; -import { ELM_DECIMAL_TYPE, ELM_INTEGER_TYPE } from '../../src/util/elmTypes'; describe('successor', () => { it('should preserve integers in an Uncertainty', () => { - const result = successor(new Uncertainty(1.0, 2.0), ELM_INTEGER_TYPE); + const result = successor(new Uncertainty(1.0, 2.0)); result.low.should.equal(2); result.high.should.equal(3); }); it('should preserve decimals in an Uncertainty', () => { - const result = successor( - new Uncertainty(Decimal.from(1.0), Decimal.from(2.0)), - ELM_DECIMAL_TYPE - ); + const result = successor(new Uncertainty(Decimal.from(1.0), Decimal.from(2.0))); result.low.should.equalDecimal(Decimal.from(1.00000001)); result.high.should.equalDecimal(Decimal.from(2.00000001)); }); it('should leave the uncertainty high unchanged when it overflows', () => { - const result = successor(new Uncertainty(Decimal.from(1), MAX_FLOAT_VALUE), ELM_DECIMAL_TYPE); + const result = successor(new Uncertainty(Decimal.from(1), MAX_FLOAT_VALUE)); result.should.eql(new Uncertainty(Decimal.from(1.00000001), MAX_FLOAT_VALUE)); }); }); describe('predecessor', () => { it('should preserve integers in an Uncertainty', () => { - const result = successor(new Uncertainty(1.0, 2.0), ELM_INTEGER_TYPE); + const result = successor(new Uncertainty(1.0, 2.0)); result.low.should.equal(2); result.high.should.equal(3); }); it('should preserve decimals in an Uncertainty', () => { - const result = successor( - new Uncertainty(Decimal.from(1.0), Decimal.from(2.0)), - ELM_DECIMAL_TYPE - ); + const result = successor(new Uncertainty(Decimal.from(1.0), Decimal.from(2.0))); result.low.should.equalDecimal(Decimal.from(1.00000001)); result.high.should.equalDecimal(Decimal.from(2.00000001)); }); it('should leave the uncertainty low unchanged when it underflows', () => { - const result = predecessor(new Uncertainty(MIN_FLOAT_VALUE, Decimal.from(2)), ELM_DECIMAL_TYPE); + const result = predecessor(new Uncertainty(MIN_FLOAT_VALUE, Decimal.from(2))); result.should.eql(new Uncertainty(MIN_FLOAT_VALUE, Decimal.from(1.99999999))); }); }); From f6731b9a95dafa2f6774dbd14c37567452e9236b Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Wed, 26 Aug 2026 15:15:26 -0400 Subject: [PATCH 10/19] additional cleanup and fixes --- src/datatypes/decimal.ts | 28 ++-- src/elm/aggregate.ts | 120 +++++++++--------- src/elm/arithmetic.ts | 89 ++++++------- src/elm/interval.ts | 45 ++----- src/elm/type.ts | 30 +++++ src/util/math.ts | 25 ++-- test/datatypes/decimal-test.ts | 6 +- test/elm/convert/convert-test.ts | 10 +- test/elm/convert/data.cql | 2 +- test/elm/convert/data.js | 6 +- test/elm/instance/instance-test.ts | 4 +- test/elm/interval/interval-test.ts | 46 +++---- .../spec-tests/cql/CqlStringOperatorsTest.cql | 4 +- .../cql/CqlStringOperatorsTest.json | 53 +------- test/spec-tests/skip-list.txt | 1 + 15 files changed, 201 insertions(+), 268 deletions(-) diff --git a/src/datatypes/decimal.ts b/src/datatypes/decimal.ts index 53c6b66e5..5d0a99ec0 100644 --- a/src/datatypes/decimal.ts +++ b/src/datatypes/decimal.ts @@ -64,15 +64,14 @@ export class Decimal { } divideBy(other: DecimalInput): Decimal { - if (toNumber(other) === 0) { + if (Decimal.from(other).equals(0)) { throw new RangeError('Cannot divide a decimal by zero'); } return this.applyWrapper(this.value.dividedBy, other); } modulo(other: DecimalInput) { - const divisor = toNumber(other); - if (divisor === 0) { + if (Decimal.from(other).equals(0)) { throw new RangeError('Cannot calculate decimal modulo by zero'); } return this.applyWrapper(this.value.mod, other); @@ -180,6 +179,7 @@ export class Decimal { } toInteger() { + // note that this is permissive and converts non-integral values return this.truncate(); } @@ -193,7 +193,16 @@ export class Decimal { } toString() { - return this.value.toString(); + // decimal.js toString can return exponential notation, + // toFixed always returns normal notation + // CQL spec expects format (-)?#0.0# + // https://cql.hl7.org/R2/09-b-cqlreference.html#tostring + // meaning, optional minus sign, at least one digit, decimal point, at least one digit + // (# means any number of digits, including none; 0 means a digit must appear) + // a regex for this is -?\d+\.\d+ + // so Decimal.from(1).toString() --> "1.0" + const places = Math.max(1, this.value.decimalPlaces()); + return this.value.toFixed(places); } toJSON() { @@ -206,14 +215,3 @@ export const MIN_DECIMAL_STRING = '-99999999999999999999.99999999'; export const MAX_DECIMAL_VALUE = Decimal.from(MAX_DECIMAL_STRING); export const MIN_DECIMAL_VALUE = Decimal.from(MIN_DECIMAL_STRING); - -function toNumber(value: DecimalInput) { - if (value instanceof Decimal) { - return value.toNumber(); - } - if (typeof value === 'string' && value.trim() === '') { - // Number() and Number('') return 0 instead of NaN, so catch that case - return NaN; - } - return Number(value); -} diff --git a/src/elm/aggregate.ts b/src/elm/aggregate.ts index 7d233f2ed..fe72381f7 100644 --- a/src/elm/aggregate.ts +++ b/src/elm/aggregate.ts @@ -1,12 +1,25 @@ import { Expression } from './expression'; -import { typeIsArray, allTrue, anyTrue, removeNulls, numerical_sort } from '../util/util'; -import { Quantity } from '../datatypes/datatypes'; +import { typeIsArray, allTrue, anyTrue, removeNulls } from '../util/util'; +import { doAddition, Quantity } from '../datatypes/datatypes'; import { Decimal } from '../datatypes/decimal'; import { Context } from '../runtime/context'; import { Exception } from '../datatypes/exception'; import { greaterThan, lessThan } from '../util/comparison'; import { build } from './builder'; -import { overflowsOrUnderflows, finalizeNumericResult } from '../util/math'; +import * as MathUtil from '../util/math'; + +function finalizeAggregateResult(result: any, firstItem: any) { + if (result == null) { + return null; + } + const finalized = MathUtil.finalizeNumericResult(result); + const bounded = MathUtil.overflowsOrUnderflows(finalized) ? null : finalized; + if (bounded && firstItem instanceof Quantity && !(bounded instanceof Quantity)) { + return new Quantity(bounded, firstItem.unit); + } else { + return bounded; + } +} class AggregateExpression extends Expression { source: any; @@ -52,19 +65,18 @@ export class Sum extends AggregateExpression { return null; } + let sum; if (hasOnlyQuantities(items)) { - const sum = sumOfDecimals(getValuesFromQuantities(items)); - return overflowsOrUnderflows(sum) ? null : new Quantity(sum, items[0].unit); + // note doAddition is Quantity addition + sum = items.reduce(doAddition); } else { - let sum; if (hasDecimals(items)) { sum = sumOfDecimals(items.map(Decimal.from)); } else { sum = items.reduce((x: any, y: any) => x + y); } - sum = finalizeNumericResult(sum); - return overflowsOrUnderflows(sum) ? null : sum; } + return finalizeAggregateResult(sum, items[0]); } } @@ -157,13 +169,17 @@ export class Avg extends AggregateExpression { return null; } + let decimals; if (hasOnlyQuantities(items)) { - const sum = sumOfDecimals(getValuesFromQuantities(items)); - return new Quantity(sum.divideBy(items.length), items[0].unit); + decimals = getValuesFromQuantities(items); } else { // return type is always Decimal, so just map everything to Decimals - return sumOfDecimals(items.map(Decimal.from)).divideBy(items.length).normalized(); + decimals = items.map(Decimal.from); } + const sum = sumOfDecimals(decimals); + const avg = finalizeAggregateResult(sum.divideBy(items.length), items[0]); + + return finalizeAggregateResult(avg, items[0]); } } @@ -187,17 +203,22 @@ export class Median extends AggregateExpression { return null; } + let decimals; if (hasOnlyQuantities(items)) { - const median = medianOfDecimals(getValuesFromQuantities(items)); - return new Quantity(median, items[0].unit); + decimals = getValuesFromQuantities(items); + } else { + // Note that the Median signature is Median(argument List) Decimal + // because median on a list of even number of items takes the average of the 2 middle items + // so we can treat all the input as decimals + decimals = items.map(Decimal.from); } - if (hasDecimals(items)) { - const decimals = items.map(Decimal.from); - return finalizeNumericResult(medianOfDecimals(decimals)); - } + const sorted = [...decimals].sort((a, b) => a.compareTo(b)); + const middle = Math.floor(items.length / 2); + const median = + items.length % 2 === 1 ? sorted[middle] : sorted[middle - 1].add(sorted[middle]).divideBy(2); - return medianOfNumbers(items); + return finalizeAggregateResult(median, items[0]); } } @@ -285,18 +306,15 @@ export class StdDev extends AggregateExpression { if (items.length === 0) { return null; } - + let values; if (hasOnlyQuantities(items)) { - const values = getValuesFromQuantities(items); - const stdDev = this.standardDeviation(values); - if (stdDev === null) { - return null; - } - return new Quantity(stdDev, items[0].unit); + values = getValuesFromQuantities(items); } else { - const standardDeviation = this.standardDeviation(items.map(Decimal.from)); - return standardDeviation?.normalized(); + values = items.map(Decimal.from); } + + const stdDev = this.standardDeviation(values); + return finalizeAggregateResult(stdDev, items[0]); } standardDeviation(list: Decimal[]) { @@ -356,20 +374,16 @@ export class Product extends AggregateExpression { return null; } + let product; if (hasOnlyQuantities(items)) { - const product = productOfDecimals(getValuesFromQuantities(items)); - // Units are not multiplied for the geometric product - return overflowsOrUnderflows(product) ? null : new Quantity(product, items[0].unit); + product = productOfDecimals(getValuesFromQuantities(items)); + } else if (hasDecimals(items)) { + product = productOfDecimals(items.map(Decimal.from)); } else { - let result; - if (hasDecimals(items)) { - result = productOfDecimals(items.map(Decimal.from)); - } else { - result = items.reduce((x: number, y: number) => x * y); - } - result = finalizeNumericResult(result); - return overflowsOrUnderflows(result) ? null : result; + product = items.reduce((x: number, y: number) => x * y); } + + return finalizeAggregateResult(product, items[0]); } } @@ -394,15 +408,16 @@ export class GeometricMean extends AggregateExpression { return null; } + let decimals; if (hasOnlyQuantities(items)) { - const product = productOfDecimals(getValuesFromQuantities(items)); - const geoMean = product.power(1.0 / items.length); - return new Quantity(geoMean, items[0].unit); + decimals = getValuesFromQuantities(items); } else { - return productOfDecimals(items.map(Decimal.from)) - .power(1.0 / items.length) - .normalized(); + decimals = items.map(Decimal.from); } + const product = productOfDecimals(decimals); + const oneOverLength = Decimal.from(1).divideBy(items.length); + const geoMean = product.power(oneOverLength); + return finalizeAggregateResult(geoMean, items[0]); } } @@ -489,23 +504,6 @@ function convertAllUnits(arr: any[]) { return arr.map(q => q.convertUnit(arr[0].unit)); } -function medianOfNumbers(numbers: number[]) { - const items = numerical_sort(numbers, 'asc'); - if (items.length % 2 === 1) { - // Odd number of items - return items[(items.length - 1) / 2]; - } else { - // Even number of items - return (items[items.length / 2 - 1] + items[items.length / 2]) / 2; - } -} - -function medianOfDecimals(decimals: Decimal[]) { - const items = [...decimals].sort((a, b) => a.compareTo(b)); - const middle = Math.floor(items.length / 2); - return items.length % 2 === 1 ? items[middle] : items[middle - 1].add(items[middle]).divideBy(2); -} - function sumOfDecimals(values: Decimal[]) { return values.reduce((sum, value) => sum.add(value)); } diff --git a/src/elm/arithmetic.ts b/src/elm/arithmetic.ts index 2c3b85e9b..9f37e676a 100644 --- a/src/elm/arithmetic.ts +++ b/src/elm/arithmetic.ts @@ -24,6 +24,14 @@ import { } from '../util/elmTypes'; import { MAX_INT_VALUE, MAX_LONG_VALUE, MIN_INT_VALUE, MIN_LONG_VALUE } from '../util/limits'; +function finalizeArithmeticResult(result: T): T | null { + if (result == null) { + return null; + } + const finalized = MathUtil.finalizeNumericResult(result); + return MathUtil.overflowsOrUnderflows(finalized) ? null : finalized; +} + export class Add extends Expression { constructor(json: any) { super(json); @@ -36,7 +44,7 @@ export class Add extends Expression { } const sum = MathUtil.add(args[0], args[1], this.resultTypeName); - return MathUtil.finalizeNumericResult(sum, this.resultTypeName); + return finalizeArithmeticResult(sum); } } @@ -52,7 +60,7 @@ export class Subtract extends Expression { } const difference = MathUtil.subtract(args[0], args[1], this.resultTypeName); - return MathUtil.finalizeNumericResult(difference, this.resultTypeName); + return finalizeArithmeticResult(difference); } } @@ -91,11 +99,7 @@ export class Multiply extends Expression { product = MathUtil.multiply(x, y); } - if (MathUtil.overflowsOrUnderflows(product)) { - return null; - } - - return MathUtil.finalizeNumericResult(product, this.resultTypeName); + return finalizeArithmeticResult(product); } } @@ -139,10 +143,7 @@ export class Divide extends Expression { return null; } - if (MathUtil.overflowsOrUnderflows(quotient)) { - return null; - } - return MathUtil.finalizeNumericResult(quotient, this.resultTypeName); + return finalizeArithmeticResult(quotient); } } @@ -176,10 +177,7 @@ export class TruncatedDivide extends Expression { } } - if (MathUtil.overflowsOrUnderflows(quotient)) { - return null; - } - return quotient; + return finalizeArithmeticResult(quotient); } } @@ -203,7 +201,7 @@ export class Modulo extends Expression { return null; } - return MathUtil.decimalLongOrNull(MathUtil.finalizeNumericResult(modulo, this.resultTypeName)); + return finalizeArithmeticResult(modulo); } } @@ -260,18 +258,18 @@ export class Abs extends Expression { const arg = await this.execArgs(ctx); if (arg == null) { return null; - } else if (arg.isQuantity) { - return new Quantity(arg.value.abs(), arg.unit); + } + let absoluteValue; + if (arg.isQuantity) { + absoluteValue = new Quantity(arg.value.abs(), arg.unit); } else if (typeof arg === 'bigint') { - const absoluteValue = arg < 0n ? -arg : arg; - return MathUtil.overflowsOrUnderflows(absoluteValue) ? null : absoluteValue; + absoluteValue = arg < 0n ? -arg : arg; } else if (arg.isDecimal) { - const absoluteValue = arg.abs(); - return MathUtil.overflowsOrUnderflows(absoluteValue) ? null : absoluteValue; + absoluteValue = arg.abs(); } else { - const absoluteValue = Math.abs(arg); - return MathUtil.overflowsOrUnderflows(absoluteValue) ? null : absoluteValue; + absoluteValue = Math.abs(arg); } + return finalizeArithmeticResult(absoluteValue); } } @@ -284,18 +282,18 @@ export class Negate extends Expression { const arg = await this.execArgs(ctx); if (arg == null) { return null; - } else if (arg.isQuantity) { - return new Quantity(arg.value.negate(), arg.unit); + } + let negatedValue; + if (arg.isQuantity) { + negatedValue = new Quantity(arg.value.negate(), arg.unit); } else if (typeof arg === 'bigint') { - const negatedValue = arg * -1n; - return MathUtil.overflowsOrUnderflows(negatedValue) ? null : negatedValue; + negatedValue = arg * -1n; } else if (arg.isDecimal) { - const negatedValue = arg.negate(); - return MathUtil.overflowsOrUnderflows(negatedValue) ? null : negatedValue; + negatedValue = arg.negate(); } else { - const negatedValue = arg * -1; - return MathUtil.overflowsOrUnderflows(negatedValue) ? null : negatedValue; + negatedValue = arg * -1; } + return finalizeArithmeticResult(negatedValue); } } @@ -331,10 +329,7 @@ export class Ln extends Expression { try { const ln = Decimal.from(arg).ln(); - if (MathUtil.overflowsOrUnderflows(ln)) { - return null; - } - return MathUtil.finalizeNumericResult(ln); + return finalizeArithmeticResult(ln); } catch { return null; } @@ -359,10 +354,7 @@ export class Exp extends Expression { return null; } - if (MathUtil.overflowsOrUnderflows(power)) { - return null; - } - return MathUtil.finalizeNumericResult(power); + return finalizeArithmeticResult(power); } } @@ -379,7 +371,7 @@ export class Log extends Expression { try { const log = Decimal.from(args[0]).log(args[1]); - return MathUtil.finalizeNumericResult(log); + return finalizeArithmeticResult(log); } catch { return null; } @@ -402,10 +394,7 @@ export class Power extends Expression { // doPower handles this scenario const power = doPower(args[0], args[1]); - if (MathUtil.overflowsOrUnderflows(power)) { - return null; - } - return power; + return finalizeArithmeticResult(power); } } @@ -514,10 +503,7 @@ export class Successor extends Expression { } } - if (MathUtil.overflowsOrUnderflows(successor)) { - return null; - } - return successor; + return finalizeArithmeticResult(successor); } } @@ -543,9 +529,6 @@ export class Predecessor extends Expression { } } - if (MathUtil.overflowsOrUnderflows(predecessor)) { - return null; - } - return predecessor; + return finalizeArithmeticResult(predecessor); } } diff --git a/src/elm/interval.ts b/src/elm/interval.ts index b48b26775..cea2cf1a7 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -474,18 +474,12 @@ export class Expand extends Expression { if (['time', 'date', 'datetime'].includes(type)) { expandFunction = this.expandDTishInterval; defaultPer = (interval: any) => new Quantity(1, interval.low.getPrecision()); - } else if (type === 'quantity') { + } else if (['integer', 'long', 'decimal'].includes(type)) { + expandFunction = this.expandNumericInterval; + defaultPer = (_interval: any) => new Quantity(1, '1'); + } else if (['quantity'].includes(type)) { expandFunction = this.expandQuantityInterval; defaultPer = (interval: any) => new Quantity(1, interval.low.unit); - } else if (type === 'integer') { - expandFunction = this.expandIntegerInterval; - defaultPer = (_interval: any) => new Quantity(1, '1'); - } else if (type === 'long') { - expandFunction = this.expandLongInterval; - defaultPer = (_interval: any) => new Quantity(1, '1'); - } else if (type === 'decimal') { - expandFunction = this.expandDecimalInterval; - defaultPer = (_interval: any) => new Quantity(1, '1'); } else { throw new Error('Interval list type not yet supported.'); } @@ -624,7 +618,7 @@ export class Expand extends Expression { return null; } - const results = this.makeDecimalIntervalList(low_value, high_value, per_value); + const results = this.makeNumericIntervalList(low_value, high_value, per_value); for (const itvl of results) { itvl.low = new Quantity(itvl.low, result_units); @@ -633,38 +627,17 @@ export class Expand extends Expression { return results; } - expandIntegerInterval(interval: any, per: any) { + expandNumericInterval(interval: any, per: any) { if (per.unit !== '1' && per.unit !== '') { return null; } const low = interval.lowClosed ? interval.low : successor(interval.low); const high = interval.highClosed ? interval.high : predecessor(interval.high); - return this.makeDecimalIntervalList(low, high, per.value); - } - - expandDecimalInterval(interval: any, per: any) { - if (per.unit !== '1' && per.unit !== '') { - return null; - } - const low = interval.lowClosed ? interval.low : successor(interval.low); - const high = interval.highClosed ? interval.high : predecessor(interval.high); - - return this.makeDecimalIntervalList(low, high, per.value); - } - - expandLongInterval(interval: any, per: any) { - if (per.unit !== '1' && per.unit !== '') { - return null; - } - - const low = interval.lowClosed ? interval.low : successor(interval.low); - const high = interval.highClosed ? interval.high : predecessor(interval.high); - - return this.makeDecimalIntervalList(low, high, per.value); + return this.makeNumericIntervalList(low, high, per.value); } - makeDecimalIntervalList(low: any, high: any, perValue: any) { + makeNumericIntervalList(low: any, high: any, perValue: any) { // If the per value is a decimal, 8 decimal places are appropriate // Integers should have 0 Decimal places const perIsIntegral = perValue.isInteger(); @@ -678,7 +651,7 @@ export class Expand extends Expression { low = Decimal.from(low); high = Decimal.from(high); - let convertBound: (d: Decimal) => Decimal | number | bigint = d => d.toInteger(); + let convertBound: (d: Decimal) => Decimal | number | bigint; if (!perIsIntegral) { // If per is not an integer value, then regardless of the original point types, the values will be Decimals convertBound = d => d; diff --git a/src/elm/type.ts b/src/elm/type.ts index c68982d1c..e7184691d 100644 --- a/src/elm/type.ts +++ b/src/elm/type.ts @@ -96,6 +96,21 @@ export class ToBoolean extends Expression { async exec(ctx: Context) { const arg = await this.execArgs(ctx); if (arg != null) { + if (typeof arg === 'boolean') { + return arg; + } else if (typeof arg === 'number' || typeof arg === 'bigint') { + if (arg == 1) { + return true; + } else if (arg == 0) { + return false; + } + } else if (arg instanceof Decimal) { + if (arg.equals('1.0')) { + return true; + } else if (arg.equals('0.0')) { + return false; + } + } const strArg = arg.toString().toLowerCase(); if (['true', 't', 'yes', 'y', '1'].includes(strArg)) { return true; @@ -157,6 +172,14 @@ export class ToDateTime extends Expression { } } +// Described in the CQL spec as (+|-)?#0(.0#)? +// Meaning an optional polarity indicator, +// followed by any number of digits (including none), +// followed by at least one digit, +// followed optionally by a decimal point, +// at least one digit, and any number of additional digits (including none). +const CQL_DECIMAL_STRING = /^[+-]?\d+(\.\d+)?$/; + export class ToDecimal extends Expression { constructor(json: any) { super(json); @@ -170,6 +193,13 @@ export class ToDecimal extends Expression { const high = Decimal.from(arg.high).normalized(); return new Uncertainty(low, high); } else { + if (typeof arg === 'string' && !CQL_DECIMAL_STRING.test(arg)) { + // reject anything that doesn't match the CQL Decimal format + // In particular, our Decimal.from could be more permissive + // and allow things like "1e8", which is not allowed by the spec + return null; + } + try { const decimal = Decimal.from(arg.toString()); if (isValidDecimal(decimal)) { diff --git a/src/util/math.ts b/src/util/math.ts index 365a749b8..76b090a12 100644 --- a/src/util/math.ts +++ b/src/util/math.ts @@ -439,30 +439,21 @@ export function decimalAdjust(type: MathFn, value: any, exp: any) { return +(value[0] + 'e' + v); } -export function decimalOrNull(value: any) { - return isValidDecimal(value) ? value : null; -} - -export function decimalLongOrNull(value: any) { - return (typeof value === 'number' && Number.isFinite(value)) || - (value && value.isDecimal && isValidDecimal(value)) || - (typeof value === 'bigint' && isValidLong(value)) - ? value - : null; -} - -export function finalizeNumericResult(result: any, _type?: string) { +export function finalizeNumericResult(result: any) { if (result instanceof Decimal) { return result.normalized(); } else if (result instanceof Quantity) { return new Quantity(result.value.normalized(), result.unit); } else if (result instanceof Uncertainty) { - if (result.low instanceof Quantity || result.low instanceof Decimal) { - result.low = finalizeNumericResult(result.low); + let low = result.low; + if (low instanceof Quantity || low instanceof Decimal) { + low = finalizeNumericResult(low); } - if (result.high instanceof Quantity || result.high instanceof Decimal) { - result.high = finalizeNumericResult(result.high); + let high = result.high; + if (high instanceof Quantity || high instanceof Decimal) { + high = finalizeNumericResult(high); } + return new Uncertainty(low, high); } return result; diff --git a/test/datatypes/decimal-test.ts b/test/datatypes/decimal-test.ts index 54e693207..1622b5034 100644 --- a/test/datatypes/decimal-test.ts +++ b/test/datatypes/decimal-test.ts @@ -13,10 +13,10 @@ describe('Decimal', () => { const value = Decimal.from('1.5').subtract('0.5'); value.compareTo('1').should.equal(0); - value.add(2).toString().should.equal('3'); - value.multiplyBy(2).toString().should.equal('2'); + value.add(2).toString().should.equal('3.0'); + value.multiplyBy(2).toString().should.equal('2.0'); value.divideBy(2).toString().should.equal('0.5'); - Decimal.from(3).modulo(2).toString().should.equal('1'); + Decimal.from(3).modulo(2).toString().should.equal('1.0'); }); it('should provide an explicit scale and JSON representation', () => { diff --git a/test/elm/convert/convert-test.ts b/test/elm/convert/convert-test.ts index 7e0ac943a..a5f7cc01d 100644 --- a/test/elm/convert/convert-test.ts +++ b/test/elm/convert/convert-test.ts @@ -173,15 +173,15 @@ describe('FromQuantity', () => { }); it('should convert "10 \'A\'" to "10 \'A\'"', async function () { - (await this.quantityStr.exec(this.ctx)).should.equal("10 'A'"); + (await this.quantityStr.exec(this.ctx)).should.equal("10.0 'A'"); }); it('should convert "+10 \'A\'" to "10 \'A\'"', async function () { - (await this.posQuantityStr.exec(this.ctx)).should.equal("10 'A'"); + (await this.posQuantityStr.exec(this.ctx)).should.equal("10.0 'A'"); }); it('should convert "-10 \'A\'" to "10 \'A\'"', async function () { - (await this.negQuantityStr.exec(this.ctx)).should.equal("-10 'A'"); + (await this.negQuantityStr.exec(this.ctx)).should.equal("-10.0 'A'"); }); it('should convert "10 \'A\'" to "10 \'A\'"', async function () { @@ -357,7 +357,7 @@ describe('ToDecimal', () => { }); it('should truncate decimal to 8 digits after decimal point', async function () { - (await this.tooPrecise.exec(this.ctx)).should.equalDecimal(Decimal.from(0.44444444)); + (await this.tooPrecise.exec(this.ctx)).should.equalDecimal(Decimal.from('0.44444444')); }); it('should be null for decimal that is above max decimal value', async function () { @@ -372,7 +372,7 @@ describe('ToDecimal', () => { should.not.exist(await this.nullDecimal.exec(this.ctx)); }); - it.skip('should be null if wrong format (+.1)', async function () { + it('should be null if wrong format (+.1)', async function () { // TODO: parseFloat is more forgiving than the CQL spec, so this does get converted should(await this.wrongFormat.exec(this.ctx)).be.null(); }); diff --git a/test/elm/convert/data.cql b/test/elm/convert/data.cql index eb64cd79f..35c249ba4 100644 --- a/test/elm/convert/data.cql +++ b/test/elm/convert/data.cql @@ -76,7 +76,7 @@ define foo: 'bar' define NoSign: ToDecimal('0.0') define PositiveSign: ToDecimal('+1.1') define NegativeSign: ToDecimal('-1.1') -define TooPrecise: ToDecimal('.444444444') +define TooPrecise: ToDecimal('0.444444444') define TooLargeDec: ToDecimal('444444444444444444444444444444') define TooSmallDec: ToDecimal('-444444444444444444444444444444') define NullDecimal: ToDecimal((null as String)) diff --git a/test/elm/convert/data.js b/test/elm/convert/data.js index 4c259f550..bc8abadd2 100644 --- a/test/elm/convert/data.js +++ b/test/elm/convert/data.js @@ -3849,7 +3849,7 @@ context Patient define NoSign: ToDecimal('0.0') define PositiveSign: ToDecimal('+1.1') define NegativeSign: ToDecimal('-1.1') -define TooPrecise: ToDecimal('.444444444') +define TooPrecise: ToDecimal('0.444444444') define TooLargeDec: ToDecimal('444444444444444444444444444444') define TooSmallDec: ToDecimal('-444444444444444444444444444444') define NullDecimal: ToDecimal((null as String)) @@ -4104,7 +4104,7 @@ module.exports['ToDecimal'] = { }, { "r" : "245", "s" : [ { - "value" : [ "'.444444444'" ] + "value" : [ "'0.444444444'" ] } ] }, { "value" : [ ")" ] @@ -4128,7 +4128,7 @@ module.exports['ToDecimal'] = { "localId" : "245", "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", "valueType" : "{urn:hl7-org:elm-types:r1}String", - "value" : ".444444444", + "value" : "0.444444444", "annotation" : [ ] } } diff --git a/test/elm/instance/instance-test.ts b/test/elm/instance/instance-test.ts index 722e7ca05..7197a395e 100644 --- a/test/elm/instance/instance-test.ts +++ b/test/elm/instance/instance-test.ts @@ -16,8 +16,8 @@ describe('Instance', () => { q.unit.should.eql('a'); const decimal12 = Decimal.from(12); q.value.should.eql(decimal12); - q.toString().should.equal("12 'a'"); - (await this.val.exec(this.ctx)).should.eql(decimal12); + q.toString().should.equal("12.0 'a'"); + (await this.val.exec(this.ctx)).should.equalDecimal(decimal12); }); it('should be able to construct a Code', async function () { diff --git a/test/elm/interval/interval-test.ts b/test/elm/interval/interval-test.ts index 4a1660041..20bceedd0 100644 --- a/test/elm/interval/interval-test.ts +++ b/test/elm/interval/interval-test.ts @@ -3394,83 +3394,83 @@ describe('QuantityIntervalExpand', () => { it('expands single intervals', async function () { // define ClosedSingleGPerG: expand { Interval[2 'g', 4 'g'] } per 1 'g' let a = await this.closedSingleGPerG.exec(this.ctx); - prettyList(a).should.equal("{ [2 'g', 2 'g'], [3 'g', 3 'g'], [4 'g', 4 'g'] }"); + prettyList(a).should.equal("{ [2.0 'g', 2.0 'g'], [3.0 'g', 3.0 'g'], [4.0 'g', 4.0 'g'] }"); // define ClosedSingleGPerGDecimal: expand { Interval[2.1 'g', 4.1 'g'] } per 1 'g' a = await this.closedSingleGPerGDecimal.exec(this.ctx); - prettyList(a).should.equal("{ [2 'g', 2 'g'], [3 'g', 3 'g'], [4 'g', 4 'g'] }"); + prettyList(a).should.equal("{ [2.0 'g', 2.0 'g'], [3.0 'g', 3.0 'g'], [4.0 'g', 4.0 'g'] }"); // define ClosedSingleGPerMG: expand { Interval[2 'g', 2.003 'g'] } per 1 'mg' a = await this.closedSingleGPerMG.exec(this.ctx); prettyList(a).should.equal( - "{ [2000 'mg', 2000 'mg'], [2001 'mg', 2001 'mg'], [2002 'mg', 2002 'mg'], [2003 'mg', 2003 'mg'] }" + "{ [2000.0 'mg', 2000.0 'mg'], [2001.0 'mg', 2001.0 'mg'], [2002.0 'mg', 2002.0 'mg'], [2003.0 'mg', 2003.0 'mg'] }" ); // define ClosedSingleMGPerGTrunc: expand { Interval[2999 'mg', 4200 'mg'] } per 1 'g' a = await this.closedSingleMGPerGTrunc.exec(this.ctx); - prettyList(a).should.equal("{ [2999 'mg', 3998 'mg'] }"); + prettyList(a).should.equal("{ [2999.0 'mg', 3998.0 'mg'] }"); // define ClosedSingleMGPerMGTrunc: expand { Interval[2000 'mg', 4500 'mg'] } per 800 'mg' a = await this.closedSingleMGPerMGTrunc.exec(this.ctx); prettyList(a).should.equal( - "{ [2000 'mg', 2799 'mg'], [2800 'mg', 3599 'mg'], [3600 'mg', 4399 'mg'] }" + "{ [2000.0 'mg', 2799.0 'mg'], [2800.0 'mg', 3599.0 'mg'], [3600.0 'mg', 4399.0 'mg'] }" ); // define ClosedSingleMGPerMGDecimal: expand { Interval[2000.01 'mg', 4500 'mg'] } per 800 'mg' a = await this.closedSingleMGPerMGDecimal.exec(this.ctx); prettyList(a).should.equal( - "{ [2000 'mg', 2799 'mg'], [2800 'mg', 3599 'mg'], [3600 'mg', 4399 'mg'] }" + "{ [2000.0 'mg', 2799.0 'mg'], [2800.0 'mg', 3599.0 'mg'], [3600.0 'mg', 4399.0 'mg'] }" ); }); it('expands lists of multiple intervals', async function () { // define NullInList: expand { Interval[2 'g', 4 'g'], null } per 1 'g' let a = await this.nullInList.exec(this.ctx); - prettyList(a).should.equal("{ [2 'g', 2 'g'], [3 'g', 3 'g'], [4 'g', 4 'g'] }"); + prettyList(a).should.equal("{ [2.0 'g', 2.0 'g'], [3.0 'g', 3.0 'g'], [4.0 'g', 4.0 'g'] }"); // define Overlapping: expand { Interval[2 'g', 4 'g'], Interval[3 'g', 5 'g'] } per 1 'g' a = await this.overlapping.exec(this.ctx); prettyList(a).should.equal( - "{ [2 'g', 2 'g'], [3 'g', 3 'g'], [4 'g', 4 'g'], [5 'g', 5 'g'] }" + "{ [2.0 'g', 2.0 'g'], [3.0 'g', 3.0 'g'], [4.0 'g', 4.0 'g'], [5.0 'g', 5.0 'g'] }" ); // define NonOverlapping: expand { Interval[2 'g', 4 'g'], Interval[6 'g', 6 'g'] } per 1 'g' a = await this.nonOverlapping.exec(this.ctx); prettyList(a).should.equal( - "{ [2 'g', 2 'g'], [3 'g', 3 'g'], [4 'g', 4 'g'], [6 'g', 6 'g'] }" + "{ [2.0 'g', 2.0 'g'], [3.0 'g', 3.0 'g'], [4.0 'g', 4.0 'g'], [6.0 'g', 6.0 'g'] }" ); }); it('expands interval using the first items units if no per provided', async function () { // define NoPerDefaultM: expand { Interval[2 'm', 400 'cm'] } let a = await this.noPerDefaultM.exec(this.ctx); - prettyList(a).should.equal("{ [2 'm', 2 'm'], [3 'm', 3 'm'], [4 'm', 4 'm'] }"); + prettyList(a).should.equal("{ [2.0 'm', 2.0 'm'], [3.0 'm', 3.0 'm'], [4.0 'm', 4.0 'm'] }"); // define NoPerDefaultG: expand { Interval[2 'g', 4 'g'] } a = await this.noPerDefaultG.exec(this.ctx); - prettyList(a).should.equal("{ [2 'g', 2 'g'], [3 'g', 3 'g'], [4 'g', 4 'g'] }"); + prettyList(a).should.equal("{ [2.0 'g', 2.0 'g'], [3.0 'g', 3.0 'g'], [4.0 'g', 4.0 'g'] }"); }); it('expands interval with open ends', async function () { // define OpenStart: expand { Interval(2 'g', 4 'g'] } per 1 'g' let a = await this.openStart.exec(this.ctx); - prettyList(a).should.equal("{ [3 'g', 3 'g'], [4 'g', 4 'g'] }"); + prettyList(a).should.equal("{ [3.0 'g', 3.0 'g'], [4.0 'g', 4.0 'g'] }"); // define OpenEnd: expand { Interval[2 'g', 4 'g') } per 1 'g' a = await this.openEnd.exec(this.ctx); - prettyList(a).should.equal("{ [2 'g', 2 'g'], [3 'g', 3 'g'] }"); + prettyList(a).should.equal("{ [2.0 'g', 2.0 'g'], [3.0 'g', 3.0 'g'] }"); // define OpenBoth: expand { Interval(2 'g', 4 'g') } per 1 'g' a = await this.openBoth.exec(this.ctx); - prettyList(a).should.equal("{ [3 'g', 3 'g'] }"); + prettyList(a).should.equal("{ [3.0 'g', 3.0 'g'] }"); // define OpenBothDecimal: expand { Interval(2.1 'g', 4.1 'g') } per 1 'g' a = await this.openBothDecimal.exec(this.ctx); - prettyList(a).should.equal("{ [2 'g', 2 'g'], [3 'g', 3 'g'], [4 'g', 4 'g'] }"); + prettyList(a).should.equal("{ [2.0 'g', 2.0 'g'], [3.0 'g', 3.0 'g'], [4.0 'g', 4.0 'g'] }"); // define OpenBothDecimalTrunc: expand { Interval(2.1 'g', 4.101 'g') } per 1 'g' a = await this.openBothDecimalTrunc.exec(this.ctx); - prettyList(a).should.equal("{ [2 'g', 2 'g'], [3 'g', 3 'g'], [4 'g', 4 'g'] }"); + prettyList(a).should.equal("{ [2.0 'g', 2.0 'g'], [3.0 'g', 3.0 'g'], [4.0 'g', 4.0 'g'] }"); }); it('returns an empty list if we get an empty list or if there are no results', async function () { @@ -3705,7 +3705,7 @@ describe('DecimalIntervalExpand', () => { it('expands single intervals', async function () { // define ClosedSingle: expand { Interval[2, 5] } per 1.5 '1' let a = await this.closedSingle.exec(this.ctx); - prettyList(a).should.equal('{ [2, 3.49999999], [3.5, 4.99999999] }'); + prettyList(a).should.equal('{ [2.0, 3.49999999], [3.5, 4.99999999] }'); // define ClosedSingle1: expand { Interval[2.5, 10] } per 2 '1' a = await this.closedSingle1.exec(this.ctx); @@ -3714,22 +3714,22 @@ describe('DecimalIntervalExpand', () => { // define ClosedSingle2: expand { Interval[2, 4.5] } per 0.5 '1' a = await this.closedSingle2.exec(this.ctx); prettyList(a).should.equal( - '{ [2, 2.49999999], [2.5, 2.99999999], [3, 3.49999999], [3.5, 3.99999999], [4, 4.49999999] }' + '{ [2.0, 2.49999999], [2.5, 2.99999999], [3.0, 3.49999999], [3.5, 3.99999999], [4.0, 4.49999999] }' ); }); it('expands lists of multiple intervals', async function () { // define NullInList: expand { Interval[2, 5], null } per 1.5 '1' let a = await this.nullInList.exec(this.ctx); - prettyList(a).should.equal('{ [2, 3.49999999], [3.5, 4.99999999] }'); + prettyList(a).should.equal('{ [2.0, 3.49999999], [3.5, 4.99999999] }'); // define Overlapping: expand { Interval[2, 5], Interval[4, 7] } per 1.5 '1' a = await this.overlapping.exec(this.ctx); - prettyList(a).should.equal('{ [2, 3.49999999], [3.5, 4.99999999], [5, 6.49999999] }'); + prettyList(a).should.equal('{ [2.0, 3.49999999], [3.5, 4.99999999], [5.0, 6.49999999] }'); // define NonOverlapping: expand { Interval[2, 4], Interval[6, 8] } per 1.5 '1' a = await this.nonOverlapping.exec(this.ctx); - prettyList(a).should.equal('{ [2, 3.49999999], [6, 7.49999999] }'); + prettyList(a).should.equal('{ [2.0, 3.49999999], [6.0, 7.49999999] }'); }); it('expands interval using default per of 1', async function () { @@ -3741,11 +3741,11 @@ describe('DecimalIntervalExpand', () => { it('expands interval with open ends', async function () { // define OpenStart: expand { Interval(2, 5] } per 1.5 '1' let a = await this.openStart.exec(this.ctx); - prettyList(a).should.equal('{ [3, 4.49999999] }'); + prettyList(a).should.equal('{ [3.0, 4.49999999] }'); // define OpenEnd: expand { Interval[2, 5) } per 1.5 '1' a = await this.openEnd.exec(this.ctx); - prettyList(a).should.equal('{ [2, 3.49999999] }'); + prettyList(a).should.equal('{ [2.0, 3.49999999] }'); // define OpenBoth: expand { Interval(2, 5) } per 1.5 '1' (await this.openBoth.exec(this.ctx)).should.be.empty(); diff --git a/test/spec-tests/cql/CqlStringOperatorsTest.cql b/test/spec-tests/cql/CqlStringOperatorsTest.cql index fbc62f47d..12054e0e6 100644 --- a/test/spec-tests/cql/CqlStringOperatorsTest.cql +++ b/test/spec-tests/cql/CqlStringOperatorsTest.cql @@ -350,9 +350,11 @@ define "Upper": Tuple{ define "toString tests": Tuple{ "QuantityToString": Tuple{ + skipped: 'Wrong output: Spec says Quantity and Decimal ToString must always contain a decimal point and at least 1 digit on each side' + /* expression: ToString(125 'cm'), output: '125 \'cm\'' - }, + */ }, "DateTimeToString1": Tuple{ expression: ToString(DateTime(2000, 1, 1)), output: '2000-01-01' diff --git a/test/spec-tests/cql/CqlStringOperatorsTest.json b/test/spec-tests/cql/CqlStringOperatorsTest.json index 430bcf316..1fd5e49e9 100644 --- a/test/spec-tests/cql/CqlStringOperatorsTest.json +++ b/test/spec-tests/cql/CqlStringOperatorsTest.json @@ -10087,16 +10087,7 @@ "annotation": [], "element": [ { - "name": "expression", - "annotation": [], - "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] - } - }, - { - "name": "output", + "name": "skipped", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", @@ -10227,16 +10218,7 @@ "annotation": [], "element": [ { - "name": "expression", - "annotation": [], - "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] - } - }, - { - "name": "output", + "name": "skipped", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", @@ -10363,16 +10345,7 @@ "annotation": [], "element": [ { - "name": "expression", - "annotation": [], - "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] - } - }, - { - "name": "output", + "name": "skipped", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", @@ -10384,28 +10357,12 @@ }, "element": [ { - "name": "expression", - "value": { - "type": "ToString", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "annotation": [], - "signature": [], - "operand": { - "type": "Quantity", - "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", - "value": 125, - "unit": "cm", - "annotation": [] - } - } - }, - { - "name": "output", + "name": "skipped", "value": { "type": "Literal", "resultTypeName": "{urn:hl7-org:elm-types:r1}String", "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "125 'cm'", + "value": "Wrong output: Spec says Quantity and Decimal ToString must always contain a decimal point and at least 1 digit on each side", "annotation": [] } } diff --git a/test/spec-tests/skip-list.txt b/test/spec-tests/skip-list.txt index a939904b0..6e8072e7b 100644 --- a/test/spec-tests/skip-list.txt +++ b/test/spec-tests/skip-list.txt @@ -16,6 +16,7 @@ CqlIntervalOperatorsTest.PointFrom.TestPointFromNull Wrong output: In "CqlArithmeticFunctionsTest.Truncated Divide.TruncatedDivide10d1ByNeg3D1Quantity" Wrong output: The resulting Quantity should have an appropriate unit; 'g' / 'g' should be '1', not 'g'. See test Divide1Q1Q which is correct "CqlArithmeticFunctionsTest.Truncated Divide.TruncatedDivide10By5DQuantity" Wrong output: The resulting Quantity should have an appropriate unit "CqlArithmeticFunctionsTest.Truncated Divide.TruncatedDivide414By206DQuantity" Wrong output: The resulting Quantity should have an appropriate unit +"CqlStringOperatorsTest.toString tests.QuantityToString" Wrong output: Spec says Quantity and Decimal ToString must always contain a decimal point and at least 1 digit on each side # Potentially Incorrect Expected Output "CqlStringOperatorsTest.toString tests.DateTimeToString2" Answer does not include timezone offset, but default offset depends on test environment From 976ed9a5f436e9be12e458cfd2b33b7fb764da7a Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Wed, 26 Aug 2026 15:31:58 -0400 Subject: [PATCH 11/19] additional cleanup --- src/elm/aggregate.ts | 16 ++++++++++------ src/elm/arithmetic.ts | 7 ++++++- src/util/immutableUtil.ts | 6 ++---- src/util/math.ts | 23 ----------------------- 4 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/elm/aggregate.ts b/src/elm/aggregate.ts index fe72381f7..6d9e91536 100644 --- a/src/elm/aggregate.ts +++ b/src/elm/aggregate.ts @@ -177,8 +177,7 @@ export class Avg extends AggregateExpression { decimals = items.map(Decimal.from); } const sum = sumOfDecimals(decimals); - const avg = finalizeAggregateResult(sum.divideBy(items.length), items[0]); - + const avg = sum.divideBy(items.length); return finalizeAggregateResult(avg, items[0]); } } @@ -414,10 +413,15 @@ export class GeometricMean extends AggregateExpression { } else { decimals = items.map(Decimal.from); } - const product = productOfDecimals(decimals); - const oneOverLength = Decimal.from(1).divideBy(items.length); - const geoMean = product.power(oneOverLength); - return finalizeAggregateResult(geoMean, items[0]); + + try { + const product = productOfDecimals(decimals); + const oneOverLength = Decimal.from(1).divideBy(items.length); + const geoMean = product.power(oneOverLength); + return finalizeAggregateResult(geoMean, items[0]); + } catch { + return null; + } } } diff --git a/src/elm/arithmetic.ts b/src/elm/arithmetic.ts index 9f37e676a..c0d7f796a 100644 --- a/src/elm/arithmetic.ts +++ b/src/elm/arithmetic.ts @@ -392,7 +392,12 @@ export class Power extends Expression { // Note: The resultTypeName may be wrong if the exponent is a negative number. // E.g., CQL-to-ELM says 10^-1 is an Integer result type, but the correct result is a 0.1 (a Decimal) // doPower handles this scenario - const power = doPower(args[0], args[1]); + let power; + try { + power = doPower(args[0], args[1]); + } catch { + return null; + } return finalizeArithmeticResult(power); } diff --git a/src/util/immutableUtil.ts b/src/util/immutableUtil.ts index 14d6c7d01..adeada848 100644 --- a/src/util/immutableUtil.ts +++ b/src/util/immutableUtil.ts @@ -9,7 +9,6 @@ import { Ratio, Uncertainty } from '../datatypes/datatypes'; -import { decimalAdjust } from './math'; import { convertUnit } from './units'; const ucumUtilInstance = ucum.UcumLhcUtils.getInstance(); @@ -108,12 +107,11 @@ export const toNormalizedKey = (js: any): NormalizedKey => { __instance: js.constructor }); } else { - // Unit was found - convert to baseUnit and normalize + // Unit was found - convert to baseUnit const baseUnitKeyCode = baseUnitKey[0].csCode_; const conversionValue = convertUnit(js.value, js.unit, baseUnitKeyCode); - const finalValue = conversionValue ? decimalAdjust('round', conversionValue, -8) : null; return ImmutableMap({ - value: finalValue ? toNormalizedKey(finalValue) : null, + value: conversionValue ? toNormalizedKey(conversionValue) : null, unit: baseUnitKeyCode ?? null, __instance: js.constructor }); diff --git a/src/util/math.ts b/src/util/math.ts index 76b090a12..48d913c00 100644 --- a/src/util/math.ts +++ b/src/util/math.ts @@ -416,29 +416,6 @@ export function minValueForType(type: string, quantityInstance?: Quantity) { return null; } -type MathFn = keyof typeof Math; - -export function decimalAdjust(type: MathFn, value: any, exp: any) { - //If the exp is undefined or zero... - if (typeof exp === 'undefined' || +exp === 0) { - return (Math[type] as (x: number) => number)(value); - } - value = +value; - exp = +exp; - //If the value is not a number or the exp is not an integer... - if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) { - return NaN; - } - //Shift - value = value.toString().split('e'); - let v = value[1] ? +value[1] - exp : -exp; - value = (Math[type] as (x: number) => number)(+(value[0] + 'e' + v)); - //Shift back - value = value.toString().split('e'); - v = value[1] ? +value[1] + exp : exp; - return +(value[0] + 'e' + v); -} - export function finalizeNumericResult(result: any) { if (result instanceof Decimal) { return result.normalized(); From 6426710d2e2f3c83f345ecab3c5c02b6153a85ee Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Wed, 26 Aug 2026 15:51:37 -0400 Subject: [PATCH 12/19] new tests added by codex --- test/datatypes/decimal-test.ts | 11 + test/elm/aggregate/aggregate-test.ts | 26 + test/elm/aggregate/data.cql | 8 + test/elm/aggregate/data.js | 853 +++++++++++++++++++++---- test/elm/arithmetic/arithmetic-test.ts | 16 + test/elm/arithmetic/data.cql | 5 + test/elm/arithmetic/data.js | 336 +++++++++- test/elm/convert/convert-test.ts | 21 + test/elm/convert/data.cql | 9 + test/elm/convert/data.js | 433 ++++++++++++- test/util/immutableUtil-test.ts | 8 + test/util/math-test.ts | 21 +- 12 files changed, 1626 insertions(+), 121 deletions(-) diff --git a/test/datatypes/decimal-test.ts b/test/datatypes/decimal-test.ts index 1622b5034..053584fd3 100644 --- a/test/datatypes/decimal-test.ts +++ b/test/datatypes/decimal-test.ts @@ -24,6 +24,13 @@ describe('Decimal', () => { JSON.stringify({ value: Decimal.from('1.25') }).should.equal('{"value":"1.25"}'); }); + it('should serialize using fixed-point CQL Decimal notation', () => { + Decimal.from(1).toString().should.equal('1.0'); + Decimal.from('-12.5').toString().should.equal('-12.5'); + Decimal.from('0.00000001').toString().should.equal('0.00000001'); + JSON.stringify({ value: Decimal.from(1) }).should.equal('{"value":"1.0"}'); + }); + it('should provide CQL arithmetic helpers without exposing a number', () => { Decimal.from('-1.9').truncate().should.equal(-1); Decimal.from('1.1').ceil().should.equal(2); @@ -38,4 +45,8 @@ describe('Decimal', () => { (() => Decimal.from('not a number')).should.throw(); (() => Decimal.from(1).divideBy(0)).should.throw(); }); + + it('should not coerce a nonzero Decimal divisor through a JavaScript number', () => { + (() => Decimal.from(1).divideBy('1e-1000')).should.not.throw(); + }); }); diff --git a/test/elm/aggregate/aggregate-test.ts b/test/elm/aggregate/aggregate-test.ts index 22292ffdb..5f946e4cc 100644 --- a/test/elm/aggregate/aggregate-test.ts +++ b/test/elm/aggregate/aggregate-test.ts @@ -309,6 +309,10 @@ describe('Avg', () => { (await this.has_null.exec(this.ctx)).should.equalDecimal(Decimal.from(1.5)); }); + it('should normalize repeating Decimal averages at the aggregate boundary', async function () { + (await this.repeating_decimal.exec(this.ctx)).should.equalDecimal(Decimal.from('1.66666667')); + }); + it('should return null for empty list', async function () { should(await this.empty.exec(this.ctx)).be.null(); }); @@ -444,6 +448,11 @@ describe('PopulationVariance', () => { it('should be null if quantity units are not compatible', async function () { should(await this.incompatibleUnitsNull.exec(this.ctx)).be.null(); }); + + it('should return zero for a single-item population variance', async function () { + (await this.single_value.exec(this.ctx)).should.equalDecimal(Decimal.from(0)); + validateQuantity(await this.single_value_q.exec(this.ctx), 0, 'ml'); + }); }); describe('Variance', () => { @@ -465,6 +474,10 @@ describe('Variance', () => { it('should be null if quantity units are not compatible', async function () { should(await this.incompatibleUnitsNull.exec(this.ctx)).be.null(); }); + + it('should return null for a single-item sample variance', async function () { + should(await this.single_value.exec(this.ctx)).be.null(); + }); }); describe('StdDev', () => { @@ -486,6 +499,10 @@ describe('StdDev', () => { it('should be null if quantity units are not compatible', async function () { should(await this.incompatibleUnitsNull.exec(this.ctx)).be.null(); }); + + it('should return null for a single-item sample standard deviation', async function () { + should(await this.single_value.exec(this.ctx)).be.null(); + }); }); describe('PopulationStdDev', () => { @@ -507,6 +524,11 @@ describe('PopulationStdDev', () => { it('should be null if quantity units are not compatible', async function () { should(await this.incompatibleUnitsNull.exec(this.ctx)).be.null(); }); + + it('should return zero for a single-item population standard deviation', async function () { + (await this.single_value.exec(this.ctx)).should.equalDecimal(Decimal.from(0)); + validateQuantity(await this.single_value_q.exec(this.ctx), 0, 'ml'); + }); }); describe('Product', () => { @@ -660,6 +682,10 @@ describe('GeometricMean', () => { it('should return null when pass in list as null', async function () { should(await this.also_null_geometric_mean.exec(this.ctx)).be.null(); }); + + it('should return null when the geometric mean cannot be represented', async function () { + should(await this.negative_geometric_mean.exec(this.ctx)).be.null(); + }); }); describe('AllTrue', () => { diff --git a/test/elm/aggregate/data.cql b/test/elm/aggregate/data.cql index c82e2527e..61eeffe92 100644 --- a/test/elm/aggregate/data.cql +++ b/test/elm/aggregate/data.cql @@ -84,6 +84,7 @@ define not_null_q: Avg({1 'ml',2 'ml',3 'ml',4 'ml',5 'ml'}) define has_null_q: Avg({1 'ml',null,null,2 'ml'}) define empty: Avg(List{}) define q_diff_units: Avg({1 'ml',0.002 'l',0.03 'dl',4 'ml',5 'ml'}) +define repeating_decimal: Avg({1.0, 2.0, 2.0}) define NumbersAndQuantities: Avg({1 ,2 'ml',3 'ml',4 'ml',5 'ml',0 'ml'}) define IncompatibleUnitsNull: Avg({1 'mg/d', 0.002 '/d'}) @@ -120,6 +121,7 @@ define v_q: Variance({1 'ml',2 'ml',3 'ml',4 'ml',5 'ml'}) define q_diff_units: Variance({1.0 'ml',0.002 'l',0.003 'l',0.04 'dl',5.0 'ml'}) define NumbersAndQuantities: Variance({1.0 ,2.0 ,3.0 ,4.0 'ml',5.0 'ml'}) define IncompatibleUnitsNull: Variance({1 'mg/d', 0.002 '/d'}) +define single_value: Variance({2.0}) // @Test: PopulationVariance define v: PopulationVariance({1.0,2.0,3.0,4.0,5.0}) @@ -127,6 +129,8 @@ define v_q: PopulationVariance({1.0 'ml',2.0 'ml',3.0 'ml',4.0 'ml',5.0 'ml'}) define q_diff_units: PopulationVariance({1.0 'ml',0.002 'l',0.003 'l',0.04 'dl',5.0 'ml'}) define NumbersAndQuantities: PopulationVariance({1.0 ,2.0 ,3.0 ,4.0 'ml',5.0 'ml'}) define IncompatibleUnitsNull: PopulationVariance({1 'mg/d', 0.002 '/d'}) +define single_value: PopulationVariance({2.0}) +define single_value_q: PopulationVariance({2.0 'ml'}) // @Test: StdDev define std: StdDev({1,2,3,4,5}) @@ -135,6 +139,7 @@ define q_diff_units: StdDev({1 'ml', 0.002 'l',3 'ml',4 'ml', 0.05 'dl'}) define sq_throw1: StdDev({1 'ml',2 'ml',3 'ml',4 'ml',5 'm'}) define NumbersAndQuantities: StdDev({1 ,2 ,3 ,4 'ml',5 }) define IncompatibleUnitsNull: StdDev({1 'mg/d', 0.002 '/d'}) +define single_value: StdDev({2.0}) // @Test: PopulationStdDev define dev: PopulationStdDev({1,2,3,4,5}) @@ -142,6 +147,8 @@ define dev_q: PopulationStdDev({1 'ml',2 'ml',3 'ml',4 'ml',5 'ml'}) define q_diff_units: PopulationStdDev({1 'ml', 0.002 'l',3 'ml',4 'ml', 0.05 'dl'}) define NumbersAndQuantities: PopulationStdDev({1 ,2 ,3 ,4 'ml',5 }) define IncompatibleUnitsNull: PopulationStdDev({1 'mg/d', 0.002 '/d'}) +define single_value: PopulationStdDev({2.0}) +define single_value_q: PopulationStdDev({2.0 'ml'}) // @Test: Product define integer_product: Product({5, 4, 5}) @@ -183,6 +190,7 @@ define zero_geometric_mean: GeometricMean({2.0, 8.0, 0}) define null_geometric_mean: GeometricMean({1, 2, null}) define all_nulls: GeometricMean({null, null, null}) define also_null_geometric_mean: GeometricMean(null as List) +define negative_geometric_mean: GeometricMean({-1.0, 4.0}) // @Test: AllTrue define at: AllTrue({true,true,true,true}) diff --git a/test/elm/aggregate/data.js b/test/elm/aggregate/data.js index 9199f9626..5f063253c 100644 --- a/test/elm/aggregate/data.js +++ b/test/elm/aggregate/data.js @@ -8157,6 +8157,7 @@ define not_null_q: Avg({1 'ml',2 'ml',3 'ml',4 'ml',5 'ml'}) define has_null_q: Avg({1 'ml',null,null,2 'ml'}) define empty: Avg(List{}) define q_diff_units: Avg({1 'ml',0.002 'l',0.03 'dl',4 'ml',5 'ml'}) +define repeating_decimal: Avg({1.0, 2.0, 2.0}) define NumbersAndQuantities: Avg({1 ,2 'ml',3 'ml',4 'ml',5 'ml',0 'ml'}) define IncompatibleUnitsNull: Avg({1 'mg/d', 0.002 '/d'}) */ @@ -8173,7 +8174,7 @@ module.exports['Avg'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "385", + "r" : "401", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -8996,8 +8997,8 @@ module.exports['Avg'] = { } }, { "localId" : "363", - "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "name" : "NumbersAndQuantities", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "repeating_decimal", "context" : "Patient", "accessLevel" : "Public", "annotation" : [ { @@ -9006,46 +9007,130 @@ module.exports['Avg'] = { "s" : { "r" : "363", "s" : [ { - "value" : [ "", "define ", "NumbersAndQuantities", ": " ] + "value" : [ "", "define ", "repeating_decimal", ": " ] }, { - "r" : "380", + "r" : "374", "s" : [ { "value" : [ "Avg", "(" ] }, { "r" : "364", "s" : [ { "r" : "365", + "value" : [ "{", "1.0", ", ", "2.0", ", ", "2.0", "}" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Avg", + "localId" : "374", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "ListTypeSpecifier", + "localId" : "375", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "376", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + } ], + "source" : { + "type" : "List", + "localId" : "364", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "ListTypeSpecifier", + "localId" : "368", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "369", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + }, + "element" : [ { + "type" : "Literal", + "localId" : "365", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "1.0", + "annotation" : [ ] + }, { + "type" : "Literal", + "localId" : "366", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "2.0", + "annotation" : [ ] + }, { + "type" : "Literal", + "localId" : "367", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "2.0", + "annotation" : [ ] + } ] + } + } + }, { + "localId" : "379", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "name" : "NumbersAndQuantities", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "379", + "s" : [ { + "value" : [ "", "define ", "NumbersAndQuantities", ": " ] + }, { + "r" : "396", + "s" : [ { + "value" : [ "Avg", "(" ] + }, { + "r" : "380", + "s" : [ { + "r" : "381", "value" : [ "{", "1", " ," ] }, { - "r" : "366", + "r" : "382", "s" : [ { "value" : [ "2 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "367", + "r" : "383", "s" : [ { "value" : [ "3 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "368", + "r" : "384", "s" : [ { "value" : [ "4 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "369", + "r" : "385", "s" : [ { "value" : [ "5 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "370", + "r" : "386", "s" : [ { "value" : [ "0 ", "'ml'" ] } ] @@ -9060,48 +9145,48 @@ module.exports['Avg'] = { } ], "expression" : { "type" : "Avg", - "localId" : "380", + "localId" : "396", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "381", + "localId" : "397", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "382", + "localId" : "398", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "364", + "localId" : "380", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "374", + "localId" : "390", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "375", + "localId" : "391", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "ToQuantity", - "localId" : "372", + "localId" : "388", "annotation" : [ ], "signature" : [ { "type" : "NamedTypeSpecifier", - "localId" : "373", + "localId" : "389", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } ], "operand" : { "type" : "Literal", - "localId" : "365", + "localId" : "381", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "1", @@ -9109,35 +9194,35 @@ module.exports['Avg'] = { } }, { "type" : "Quantity", - "localId" : "366", + "localId" : "382", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 2, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "367", + "localId" : "383", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 3, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "368", + "localId" : "384", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 4, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "369", + "localId" : "385", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 5, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "370", + "localId" : "386", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0, "unit" : "ml", @@ -9146,7 +9231,7 @@ module.exports['Avg'] = { } } }, { - "localId" : "385", + "localId" : "401", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "IncompatibleUnitsNull", "context" : "Patient", @@ -9155,26 +9240,26 @@ module.exports['Avg'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "385", + "r" : "401", "s" : [ { "value" : [ "", "define ", "IncompatibleUnitsNull", ": " ] }, { - "r" : "395", + "r" : "411", "s" : [ { "value" : [ "Avg", "(" ] }, { - "r" : "386", + "r" : "402", "s" : [ { "value" : [ "{" ] }, { - "r" : "387", + "r" : "403", "s" : [ { "value" : [ "1 ", "'mg/d'" ] } ] }, { "value" : [ ", " ] }, { - "r" : "388", + "r" : "404", "s" : [ { "value" : [ "0.002 ", "'/d'" ] } ] @@ -9189,45 +9274,45 @@ module.exports['Avg'] = { } ], "expression" : { "type" : "Avg", - "localId" : "395", + "localId" : "411", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "396", + "localId" : "412", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "397", + "localId" : "413", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "386", + "localId" : "402", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "389", + "localId" : "405", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "390", + "localId" : "406", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "Quantity", - "localId" : "387", + "localId" : "403", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 1, "unit" : "mg/d", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "388", + "localId" : "404", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0.002, "unit" : "/d", @@ -12193,6 +12278,7 @@ define v_q: Variance({1 'ml',2 'ml',3 'ml',4 'ml',5 'ml'}) define q_diff_units: Variance({1.0 'ml',0.002 'l',0.003 'l',0.04 'dl',5.0 'ml'}) define NumbersAndQuantities: Variance({1.0 ,2.0 ,3.0 ,4.0 'ml',5.0 'ml'}) define IncompatibleUnitsNull: Variance({1 'mg/d', 0.002 '/d'}) +define single_value: Variance({2.0}) */ module.exports['Variance'] = { @@ -12207,7 +12293,7 @@ module.exports['Variance'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "309", + "r" : "324", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -12910,6 +12996,76 @@ module.exports['Variance'] = { } ] } } + }, { + "localId" : "324", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "single_value", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "324", + "s" : [ { + "value" : [ "", "define ", "single_value", ": " ] + }, { + "r" : "333", + "s" : [ { + "value" : [ "Variance", "(" ] + }, { + "r" : "325", + "s" : [ { + "r" : "326", + "value" : [ "{", "2.0", "}" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Variance", + "localId" : "333", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "ListTypeSpecifier", + "localId" : "334", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "335", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + } ], + "source" : { + "type" : "List", + "localId" : "325", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "ListTypeSpecifier", + "localId" : "327", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "328", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + }, + "element" : [ { + "type" : "Literal", + "localId" : "326", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "2.0", + "annotation" : [ ] + } ] + } + } } ] } } @@ -12924,6 +13080,8 @@ define v_q: PopulationVariance({1.0 'ml',2.0 'ml',3.0 'ml',4.0 'ml',5.0 'ml'}) define q_diff_units: PopulationVariance({1.0 'ml',0.002 'l',0.003 'l',0.04 'dl',5.0 'ml'}) define NumbersAndQuantities: PopulationVariance({1.0 ,2.0 ,3.0 ,4.0 'ml',5.0 'ml'}) define IncompatibleUnitsNull: PopulationVariance({1 'mg/d', 0.002 '/d'}) +define single_value: PopulationVariance({2.0}) +define single_value_q: PopulationVariance({2.0 'ml'}) */ module.exports['PopulationVariance'] = { @@ -12938,7 +13096,7 @@ module.exports['PopulationVariance'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "295", + "r" : "324", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -13607,91 +13765,238 @@ module.exports['PopulationVariance'] = { } ] } } - } ] - } - } -} - -/* StdDev -library TestSnippet version '1' -using Simple version '1.0.0' -context Patient -define std: StdDev({1,2,3,4,5}) -define std_q: StdDev({1 'ml',2 'ml',3 'ml',4 'ml',5 'ml'}) -define q_diff_units: StdDev({1 'ml', 0.002 'l',3 'ml',4 'ml', 0.05 'dl'}) -define sq_throw1: StdDev({1 'ml',2 'ml',3 'ml',4 'ml',5 'm'}) -define NumbersAndQuantities: StdDev({1 ,2 ,3 ,4 'ml',5 }) -define IncompatibleUnitsNull: StdDev({1 'mg/d', 0.002 '/d'}) -*/ - -module.exports['StdDev'] = { - "library" : { - "localId" : "0", - "annotation" : [ { - "type" : "CqlToElmInfo", - "translatorVersion" : "4.2.0", - "translatorOptions" : "EnableDateRangeOptimization,EnableAnnotations,EnableResultTypes", - "signatureLevel" : "All" - }, { - "type" : "Annotation", - "t" : [ ], - "s" : { - "r" : "330", - "s" : [ { - "value" : [ "", "library TestSnippet version '1'" ] - } ] - } - } ], - "identifier" : { - "id" : "TestSnippet", - "version" : "1" - }, - "schemaIdentifier" : { - "id" : "urn:hl7-org:elm", - "version" : "r1" - }, - "usings" : { - "def" : [ { - "localId" : "1", - "localIdentifier" : "System", - "uri" : "urn:hl7-org:elm-types:r1", - "annotation" : [ ] }, { - "localId" : "206", - "localIdentifier" : "Simple", - "uri" : "https://github.com/cqframework/cql-execution/simple", - "version" : "1.0.0", + "localId" : "310", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "single_value", + "context" : "Patient", + "accessLevel" : "Public", "annotation" : [ { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "206", + "r" : "310", "s" : [ { - "value" : [ "", "using " ] + "value" : [ "", "define ", "single_value", ": " ] }, { + "r" : "319", "s" : [ { - "value" : [ "Simple" ] + "value" : [ "PopulationVariance", "(" ] + }, { + "r" : "311", + "s" : [ { + "r" : "312", + "value" : [ "{", "2.0", "}" ] + } ] + }, { + "value" : [ ")" ] } ] - }, { - "value" : [ " version '1.0.0'" ] } ] } - } ] - } ] - }, - "contexts" : { - "def" : [ { - "localId" : "211", - "name" : "Patient", - "annotation" : [ ] - } ] - }, - "statements" : { - "def" : [ { - "localId" : "209", - "name" : "Patient", - "context" : "Patient", - "annotation" : [ ], + } ], + "expression" : { + "type" : "PopulationVariance", + "localId" : "319", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "ListTypeSpecifier", + "localId" : "320", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "321", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + } ], + "source" : { + "type" : "List", + "localId" : "311", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "ListTypeSpecifier", + "localId" : "313", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "314", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + }, + "element" : [ { + "type" : "Literal", + "localId" : "312", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "2.0", + "annotation" : [ ] + } ] + } + } + }, { + "localId" : "324", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "name" : "single_value_q", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "324", + "s" : [ { + "value" : [ "", "define ", "single_value_q", ": " ] + }, { + "r" : "333", + "s" : [ { + "value" : [ "PopulationVariance", "(" ] + }, { + "r" : "325", + "s" : [ { + "value" : [ "{" ] + }, { + "r" : "326", + "s" : [ { + "value" : [ "2.0 ", "'ml'" ] + } ] + }, { + "value" : [ "}" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PopulationVariance", + "localId" : "333", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ], + "signature" : [ { + "type" : "ListTypeSpecifier", + "localId" : "334", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "335", + "name" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ] + } + } ], + "source" : { + "type" : "List", + "localId" : "325", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "ListTypeSpecifier", + "localId" : "327", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "328", + "name" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ] + } + }, + "element" : [ { + "type" : "Quantity", + "localId" : "326", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 2.0, + "unit" : "ml", + "annotation" : [ ] + } ] + } + } + } ] + } + } +} + +/* StdDev +library TestSnippet version '1' +using Simple version '1.0.0' +context Patient +define std: StdDev({1,2,3,4,5}) +define std_q: StdDev({1 'ml',2 'ml',3 'ml',4 'ml',5 'ml'}) +define q_diff_units: StdDev({1 'ml', 0.002 'l',3 'ml',4 'ml', 0.05 'dl'}) +define sq_throw1: StdDev({1 'ml',2 'ml',3 'ml',4 'ml',5 'm'}) +define NumbersAndQuantities: StdDev({1 ,2 ,3 ,4 'ml',5 }) +define IncompatibleUnitsNull: StdDev({1 'mg/d', 0.002 '/d'}) +define single_value: StdDev({2.0}) +*/ + +module.exports['StdDev'] = { + "library" : { + "localId" : "0", + "annotation" : [ { + "type" : "CqlToElmInfo", + "translatorVersion" : "4.2.0", + "translatorOptions" : "EnableDateRangeOptimization,EnableAnnotations,EnableResultTypes", + "signatureLevel" : "All" + }, { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "345", + "s" : [ { + "value" : [ "", "library TestSnippet version '1'" ] + } ] + } + } ], + "identifier" : { + "id" : "TestSnippet", + "version" : "1" + }, + "schemaIdentifier" : { + "id" : "urn:hl7-org:elm", + "version" : "r1" + }, + "usings" : { + "def" : [ { + "localId" : "1", + "localIdentifier" : "System", + "uri" : "urn:hl7-org:elm-types:r1", + "annotation" : [ ] + }, { + "localId" : "206", + "localIdentifier" : "Simple", + "uri" : "https://github.com/cqframework/cql-execution/simple", + "version" : "1.0.0", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "206", + "s" : [ { + "value" : [ "", "using " ] + }, { + "s" : [ { + "value" : [ "Simple" ] + } ] + }, { + "value" : [ " version '1.0.0'" ] + } ] + } + } ] + } ] + }, + "contexts" : { + "def" : [ { + "localId" : "211", + "name" : "Patient", + "annotation" : [ ] + } ] + }, + "statements" : { + "def" : [ { + "localId" : "209", + "name" : "Patient", + "context" : "Patient", + "annotation" : [ ], "expression" : { "type" : "SingletonFrom", "localId" : "210", @@ -14476,6 +14781,76 @@ module.exports['StdDev'] = { } ] } } + }, { + "localId" : "345", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "single_value", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "345", + "s" : [ { + "value" : [ "", "define ", "single_value", ": " ] + }, { + "r" : "354", + "s" : [ { + "value" : [ "StdDev", "(" ] + }, { + "r" : "346", + "s" : [ { + "r" : "347", + "value" : [ "{", "2.0", "}" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "StdDev", + "localId" : "354", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "ListTypeSpecifier", + "localId" : "355", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "356", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + } ], + "source" : { + "type" : "List", + "localId" : "346", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "ListTypeSpecifier", + "localId" : "348", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "349", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + }, + "element" : [ { + "type" : "Literal", + "localId" : "347", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "2.0", + "annotation" : [ ] + } ] + } + } } ] } } @@ -14490,6 +14865,8 @@ define dev_q: PopulationStdDev({1 'ml',2 'ml',3 'ml',4 'ml',5 'ml'}) define q_diff_units: PopulationStdDev({1 'ml', 0.002 'l',3 'ml',4 'ml', 0.05 'dl'}) define NumbersAndQuantities: PopulationStdDev({1 ,2 ,3 ,4 'ml',5 }) define IncompatibleUnitsNull: PopulationStdDev({1 'mg/d', 0.002 '/d'}) +define single_value: PopulationStdDev({2.0}) +define single_value_q: PopulationStdDev({2.0 'ml'}) */ module.exports['PopulationStdDev'] = { @@ -14504,7 +14881,7 @@ module.exports['PopulationStdDev'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "312", + "r" : "341", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -15212,6 +15589,152 @@ module.exports['PopulationStdDev'] = { } ] } } + }, { + "localId" : "327", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "single_value", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "327", + "s" : [ { + "value" : [ "", "define ", "single_value", ": " ] + }, { + "r" : "336", + "s" : [ { + "value" : [ "PopulationStdDev", "(" ] + }, { + "r" : "328", + "s" : [ { + "r" : "329", + "value" : [ "{", "2.0", "}" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PopulationStdDev", + "localId" : "336", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "ListTypeSpecifier", + "localId" : "337", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "338", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + } ], + "source" : { + "type" : "List", + "localId" : "328", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "ListTypeSpecifier", + "localId" : "330", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "331", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + }, + "element" : [ { + "type" : "Literal", + "localId" : "329", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "2.0", + "annotation" : [ ] + } ] + } + } + }, { + "localId" : "341", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "name" : "single_value_q", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "341", + "s" : [ { + "value" : [ "", "define ", "single_value_q", ": " ] + }, { + "r" : "350", + "s" : [ { + "value" : [ "PopulationStdDev", "(" ] + }, { + "r" : "342", + "s" : [ { + "value" : [ "{" ] + }, { + "r" : "343", + "s" : [ { + "value" : [ "2.0 ", "'ml'" ] + } ] + }, { + "value" : [ "}" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PopulationStdDev", + "localId" : "350", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ], + "signature" : [ { + "type" : "ListTypeSpecifier", + "localId" : "351", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "352", + "name" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ] + } + } ], + "source" : { + "type" : "List", + "localId" : "342", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "ListTypeSpecifier", + "localId" : "344", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "345", + "name" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ] + } + }, + "element" : [ { + "type" : "Quantity", + "localId" : "343", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 2.0, + "unit" : "ml", + "annotation" : [ ] + } ] + } + } } ] } } @@ -18205,6 +18728,7 @@ define zero_geometric_mean: GeometricMean({2.0, 8.0, 0}) define null_geometric_mean: GeometricMean({1, 2, null}) define all_nulls: GeometricMean({null, null, null}) define also_null_geometric_mean: GeometricMean(null as List) +define negative_geometric_mean: GeometricMean({-1.0, 4.0}) */ module.exports['GeometricMean'] = { @@ -18219,7 +18743,7 @@ module.exports['GeometricMean'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "307", + "r" : "325", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -18811,6 +19335,103 @@ module.exports['GeometricMean'] = { } } } + }, { + "localId" : "325", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "negative_geometric_mean", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "325", + "s" : [ { + "value" : [ "", "define ", "negative_geometric_mean", ": " ] + }, { + "r" : "337", + "s" : [ { + "value" : [ "GeometricMean", "(" ] + }, { + "r" : "326", + "s" : [ { + "value" : [ "{" ] + }, { + "r" : "327", + "s" : [ { + "r" : "328", + "value" : [ "-", "1.0" ] + } ] + }, { + "r" : "330", + "value" : [ ", ", "4.0", "}" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "GeometricMean", + "localId" : "337", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "ListTypeSpecifier", + "localId" : "338", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "339", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + } ], + "source" : { + "type" : "List", + "localId" : "326", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "ListTypeSpecifier", + "localId" : "331", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "332", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } + }, + "element" : [ { + "type" : "Negate", + "localId" : "327", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "329", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "328", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "1.0", + "annotation" : [ ] + } + }, { + "type" : "Literal", + "localId" : "330", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "4.0", + "annotation" : [ ] + } ] + } + } } ] } } diff --git a/test/elm/arithmetic/arithmetic-test.ts b/test/elm/arithmetic/arithmetic-test.ts index b05c6b580..67c861761 100644 --- a/test/elm/arithmetic/arithmetic-test.ts +++ b/test/elm/arithmetic/arithmetic-test.ts @@ -348,6 +348,17 @@ describe('Power', () => { it('should return an infinitesimally small number when the exponent is the minimum Long value', async function () { (await this.twoLongExpMinLong.exec(this.ctx)).should.equalDecimal(Decimal.from(0.0)); }); + + it('should normalize Decimal power results at the ELM boundary', async function () { + (await this.decimalPowerNeedsNormalization.exec(this.ctx)).should.equalDecimal( + Decimal.from('1.52415788') + ); + }); + + it('should return null for Decimal powers that cannot be represented', async function () { + should(await this.negativeFractionalPower.exec(this.ctx)).be.null(); + should(await this.zeroNegativePower.exec(this.ctx)).be.null(); + }); }); describe('MinValue', () => { @@ -636,6 +647,11 @@ describe('Round', () => { (await this.up_percent.exec(this.ctx)).should.equalDecimal(Decimal.from(4.6)); (await this.down_percent.exec(this.ctx)).should.equalDecimal(Decimal.from(4.4)); }); + + it('should round negative exact-half values toward positive infinity', async function () { + (await this.negativeHalf.exec(this.ctx)).should.equalDecimal(Decimal.from(0)); + (await this.negativeOnePointFive.exec(this.ctx)).should.equalDecimal(Decimal.from(-1)); + }); }); describe('Successor', () => { diff --git a/test/elm/arithmetic/data.cql b/test/elm/arithmetic/data.cql index 5f621470a..8907b069a 100644 --- a/test/elm/arithmetic/data.cql +++ b/test/elm/arithmetic/data.cql @@ -82,6 +82,9 @@ define ThreeExpFourReverseMixed: 3L ^ 4 define TenLongExpNegativeOneLong: 10L ^ -1L define TwoLongExpMaxLong: 2L ^ maximum Long define TwoLongExpMinLong: 2L ^ minimum Long +define DecimalPowerNeedsNormalization: 1.23456789 ^ 2.0 +define NegativeFractionalPower: (-1.0) ^ 0.5 +define ZeroNegativePower: 0.0 ^ -1.0 // @Test: MinValue define MinInteger: minimum Integer @@ -144,6 +147,8 @@ define Up: Round(4.56) define Up_percent: Round(4.56,1) define Down: Round(4.49) define Down_percent: Round(4.43,1) +define NegativeHalf: Round(-0.5) +define NegativeOnePointFive: Round(-1.5) // @Test: Ln define ln: Ln(4) diff --git a/test/elm/arithmetic/data.js b/test/elm/arithmetic/data.js index fd1503036..c87f24008 100644 --- a/test/elm/arithmetic/data.js +++ b/test/elm/arithmetic/data.js @@ -5394,6 +5394,9 @@ define ThreeExpFourReverseMixed: 3L ^ 4 define TenLongExpNegativeOneLong: 10L ^ -1L define TwoLongExpMaxLong: 2L ^ maximum Long define TwoLongExpMinLong: 2L ^ minimum Long +define DecimalPowerNeedsNormalization: 1.23456789 ^ 2.0 +define NegativeFractionalPower: (-1.0) ^ 0.5 +define ZeroNegativePower: 0.0 ^ -1.0 */ module.exports['Power'] = { @@ -5408,7 +5411,7 @@ module.exports['Power'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "281", + "r" : "308", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -5988,6 +5991,211 @@ module.exports['Power'] = { "annotation" : [ ] } ] } + }, { + "localId" : "290", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "DecimalPowerNeedsNormalization", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "290", + "s" : [ { + "value" : [ "", "define ", "DecimalPowerNeedsNormalization", ": " ] + }, { + "r" : "291", + "s" : [ { + "r" : "292", + "value" : [ "1.23456789", " ^ ", "2.0" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Power", + "localId" : "291", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "294", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + }, { + "type" : "NamedTypeSpecifier", + "localId" : "295", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : [ { + "type" : "Literal", + "localId" : "292", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "1.23456789", + "annotation" : [ ] + }, { + "type" : "Literal", + "localId" : "293", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "2.0", + "annotation" : [ ] + } ] + } + }, { + "localId" : "298", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "NegativeFractionalPower", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "298", + "s" : [ { + "value" : [ "", "define ", "NegativeFractionalPower", ": " ] + }, { + "r" : "299", + "s" : [ { + "r" : "300", + "s" : [ { + "value" : [ "(" ] + }, { + "r" : "300", + "s" : [ { + "r" : "301", + "value" : [ "-", "1.0" ] + } ] + }, { + "value" : [ ")" ] + } ] + }, { + "r" : "303", + "value" : [ " ^ ", "0.5" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Power", + "localId" : "299", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "304", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + }, { + "type" : "NamedTypeSpecifier", + "localId" : "305", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : [ { + "type" : "Negate", + "localId" : "300", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "302", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "301", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "1.0", + "annotation" : [ ] + } + }, { + "type" : "Literal", + "localId" : "303", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "0.5", + "annotation" : [ ] + } ] + } + }, { + "localId" : "308", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "ZeroNegativePower", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "308", + "s" : [ { + "value" : [ "", "define ", "ZeroNegativePower", ": " ] + }, { + "r" : "309", + "s" : [ { + "r" : "310", + "value" : [ "0.0", " ^ " ] + }, { + "r" : "311", + "s" : [ { + "r" : "312", + "value" : [ "-", "1.0" ] + } ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Power", + "localId" : "309", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "314", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + }, { + "type" : "NamedTypeSpecifier", + "localId" : "315", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : [ { + "type" : "Literal", + "localId" : "310", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "0.0", + "annotation" : [ ] + }, { + "type" : "Negate", + "localId" : "311", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "313", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "312", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "1.0", + "annotation" : [ ] + } + } ] + } } ] } } @@ -8642,6 +8850,8 @@ define Up: Round(4.56) define Up_percent: Round(4.56,1) define Down: Round(4.49) define Down_percent: Round(4.43,1) +define NegativeHalf: Round(-0.5) +define NegativeOnePointFive: Round(-1.5) */ module.exports['Round'] = { @@ -8656,7 +8866,7 @@ module.exports['Round'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "244", + "r" : "267", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -8922,6 +9132,128 @@ module.exports['Round'] = { "annotation" : [ ] } } + }, { + "localId" : "256", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "NegativeHalf", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "256", + "s" : [ { + "value" : [ "", "define ", "NegativeHalf", ": " ] + }, { + "r" : "263", + "s" : [ { + "value" : [ "Round", "(" ] + }, { + "r" : "257", + "s" : [ { + "r" : "258", + "value" : [ "-", "0.5" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Round", + "localId" : "263", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "264", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Negate", + "localId" : "257", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "259", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "258", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "0.5", + "annotation" : [ ] + } + } + } + }, { + "localId" : "267", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "NegativeOnePointFive", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "267", + "s" : [ { + "value" : [ "", "define ", "NegativeOnePointFive", ": " ] + }, { + "r" : "274", + "s" : [ { + "value" : [ "Round", "(" ] + }, { + "r" : "268", + "s" : [ { + "r" : "269", + "value" : [ "-", "1.5" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Round", + "localId" : "274", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "275", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Negate", + "localId" : "268", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "270", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "269", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "1.5", + "annotation" : [ ] + } + } + } } ] } } diff --git a/test/elm/convert/convert-test.ts b/test/elm/convert/convert-test.ts index a5f7cc01d..92a83bad6 100644 --- a/test/elm/convert/convert-test.ts +++ b/test/elm/convert/convert-test.ts @@ -376,6 +376,22 @@ describe('ToDecimal', () => { // TODO: parseFloat is more forgiving than the CQL spec, so this does get converted should(await this.wrongFormat.exec(this.ctx)).be.null(); }); + + it('should reject exponent notation and malformed Decimal strings', async function () { + should(await this.exponentNotation.exec(this.ctx)).be.null(); + should(await this.exponentNotationUpper.exec(this.ctx)).be.null(); + should(await this.trailingDecimalPoint.exec(this.ctx)).be.null(); + should(await this.leadingDecimalPoint.exec(this.ctx)).be.null(); + }); + + it('should accept an integer-form Decimal string', async function () { + (await this.integerFormat.exec(this.ctx)).should.equalDecimal(Decimal.from(1)); + }); + + it('should format Decimals in fixed-point notation', async function () { + (await this.decimalToString.exec(this.ctx)).should.equal('1.0'); + (await this.smallDecimalToString.exec(this.ctx)).should.equal('0.00000001'); + }); }); describe('ToInteger', () => { @@ -860,6 +876,11 @@ describe('ConvertsToDecimal', () => { (await this.isFalse.exec(this.ctx)).should.equal(false); }); + it('should reject exponent notation and accept fixed-point Decimal notation', async function () { + (await this.exponentNotation.exec(this.ctx)).should.equal(false); + (await this.decimalFormat.exec(this.ctx)).should.equal(true); + }); + it('should return null for null input', async function () { should(await this.isNull.exec(this.ctx)).be.null(); }); diff --git a/test/elm/convert/data.cql b/test/elm/convert/data.cql index 35c249ba4..68c1742c6 100644 --- a/test/elm/convert/data.cql +++ b/test/elm/convert/data.cql @@ -81,6 +81,13 @@ define TooLargeDec: ToDecimal('444444444444444444444444444444') define TooSmallDec: ToDecimal('-444444444444444444444444444444') define NullDecimal: ToDecimal((null as String)) define WrongFormat: ToDecimal('+.1') +define ExponentNotation: ToDecimal('1e3') +define ExponentNotationUpper: ToDecimal('1E-8') +define TrailingDecimalPoint: ToDecimal('1.') +define LeadingDecimalPoint: ToDecimal('.1') +define IntegerFormat: ToDecimal('+1') +define DecimalToString: ToString(1.0) +define SmallDecimalToString: ToString(0.00000001) // @Test: ToInteger define NoSign: ToInteger('12345') @@ -206,6 +213,8 @@ define IsNull: ConvertsToDateTime(null as DateTime) define IsTrue: ConvertsToDecimal('0.1') define IsFalse: ConvertsToDecimal('foo') define IsNull: ConvertsToDecimal(null as Decimal) +define ExponentNotation: ConvertsToDecimal('1e3') +define DecimalFormat: ConvertsToDecimal('1.0') // @Test: ConvertsToInteger define IsTrue: ConvertsToInteger('101') diff --git a/test/elm/convert/data.js b/test/elm/convert/data.js index bc8abadd2..764975694 100644 --- a/test/elm/convert/data.js +++ b/test/elm/convert/data.js @@ -3854,6 +3854,13 @@ define TooLargeDec: ToDecimal('444444444444444444444444444444') define TooSmallDec: ToDecimal('-444444444444444444444444444444') define NullDecimal: ToDecimal((null as String)) define WrongFormat: ToDecimal('+.1') +define ExponentNotation: ToDecimal('1e3') +define ExponentNotationUpper: ToDecimal('1E-8') +define TrailingDecimalPoint: ToDecimal('1.') +define LeadingDecimalPoint: ToDecimal('.1') +define IntegerFormat: ToDecimal('+1') +define DecimalToString: ToString(1.0) +define SmallDecimalToString: ToString(0.00000001) */ module.exports['ToDecimal'] = { @@ -3868,7 +3875,7 @@ module.exports['ToDecimal'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "285", + "r" : "354", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -4350,6 +4357,330 @@ module.exports['ToDecimal'] = { "annotation" : [ ] } } + }, { + "localId" : "295", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "ExponentNotation", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "295", + "s" : [ { + "value" : [ "", "define ", "ExponentNotation", ": " ] + }, { + "r" : "301", + "s" : [ { + "value" : [ "ToDecimal", "(" ] + }, { + "r" : "296", + "s" : [ { + "value" : [ "'1e3'" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "ToDecimal", + "localId" : "301", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "302", + "name" : "{urn:hl7-org:elm-types:r1}String", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "296", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "1e3", + "annotation" : [ ] + } + } + }, { + "localId" : "305", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "ExponentNotationUpper", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "305", + "s" : [ { + "value" : [ "", "define ", "ExponentNotationUpper", ": " ] + }, { + "r" : "311", + "s" : [ { + "value" : [ "ToDecimal", "(" ] + }, { + "r" : "306", + "s" : [ { + "value" : [ "'1E-8'" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "ToDecimal", + "localId" : "311", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "312", + "name" : "{urn:hl7-org:elm-types:r1}String", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "306", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "1E-8", + "annotation" : [ ] + } + } + }, { + "localId" : "315", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "TrailingDecimalPoint", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "315", + "s" : [ { + "value" : [ "", "define ", "TrailingDecimalPoint", ": " ] + }, { + "r" : "321", + "s" : [ { + "value" : [ "ToDecimal", "(" ] + }, { + "r" : "316", + "s" : [ { + "value" : [ "'1.'" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "ToDecimal", + "localId" : "321", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "322", + "name" : "{urn:hl7-org:elm-types:r1}String", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "316", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "1.", + "annotation" : [ ] + } + } + }, { + "localId" : "325", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "LeadingDecimalPoint", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "325", + "s" : [ { + "value" : [ "", "define ", "LeadingDecimalPoint", ": " ] + }, { + "r" : "331", + "s" : [ { + "value" : [ "ToDecimal", "(" ] + }, { + "r" : "326", + "s" : [ { + "value" : [ "'.1'" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "ToDecimal", + "localId" : "331", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "332", + "name" : "{urn:hl7-org:elm-types:r1}String", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "326", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : ".1", + "annotation" : [ ] + } + } + }, { + "localId" : "335", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "name" : "IntegerFormat", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "335", + "s" : [ { + "value" : [ "", "define ", "IntegerFormat", ": " ] + }, { + "r" : "341", + "s" : [ { + "value" : [ "ToDecimal", "(" ] + }, { + "r" : "336", + "s" : [ { + "value" : [ "'+1'" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "ToDecimal", + "localId" : "341", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "342", + "name" : "{urn:hl7-org:elm-types:r1}String", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "336", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "+1", + "annotation" : [ ] + } + } + }, { + "localId" : "345", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "name" : "DecimalToString", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "345", + "s" : [ { + "value" : [ "", "define ", "DecimalToString", ": " ] + }, { + "r" : "350", + "s" : [ { + "r" : "346", + "value" : [ "ToString", "(", "1.0", ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "ToString", + "localId" : "350", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "351", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "346", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "1.0", + "annotation" : [ ] + } + } + }, { + "localId" : "354", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "name" : "SmallDecimalToString", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "354", + "s" : [ { + "value" : [ "", "define ", "SmallDecimalToString", ": " ] + }, { + "r" : "359", + "s" : [ { + "r" : "355", + "value" : [ "ToString", "(", "0.00000001", ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "ToString", + "localId" : "359", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "360", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "355", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "0.00000001", + "annotation" : [ ] + } + } } ] } } @@ -10503,6 +10834,8 @@ context Patient define IsTrue: ConvertsToDecimal('0.1') define IsFalse: ConvertsToDecimal('foo') define IsNull: ConvertsToDecimal(null as Decimal) +define ExponentNotation: ConvertsToDecimal('1e3') +define DecimalFormat: ConvertsToDecimal('1.0') */ module.exports['ConvertsToDecimal'] = { @@ -10517,7 +10850,7 @@ module.exports['ConvertsToDecimal'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "234", + "r" : "255", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -10752,6 +11085,102 @@ module.exports['ConvertsToDecimal'] = { } } } + }, { + "localId" : "245", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Boolean", + "name" : "ExponentNotation", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "245", + "s" : [ { + "value" : [ "", "define ", "ExponentNotation", ": " ] + }, { + "r" : "251", + "s" : [ { + "value" : [ "ConvertsToDecimal", "(" ] + }, { + "r" : "246", + "s" : [ { + "value" : [ "'1e3'" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "ConvertsToDecimal", + "localId" : "251", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Boolean", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "252", + "name" : "{urn:hl7-org:elm-types:r1}Any", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "246", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "1e3", + "annotation" : [ ] + } + } + }, { + "localId" : "255", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Boolean", + "name" : "DecimalFormat", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "255", + "s" : [ { + "value" : [ "", "define ", "DecimalFormat", ": " ] + }, { + "r" : "261", + "s" : [ { + "value" : [ "ConvertsToDecimal", "(" ] + }, { + "r" : "256", + "s" : [ { + "value" : [ "'1.0'" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "ConvertsToDecimal", + "localId" : "261", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Boolean", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "262", + "name" : "{urn:hl7-org:elm-types:r1}Any", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "256", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}String", + "valueType" : "{urn:hl7-org:elm-types:r1}String", + "value" : "1.0", + "annotation" : [ ] + } + } } ] } } diff --git a/test/util/immutableUtil-test.ts b/test/util/immutableUtil-test.ts index f7463ce5c..be3adfb50 100644 --- a/test/util/immutableUtil-test.ts +++ b/test/util/immutableUtil-test.ts @@ -37,6 +37,14 @@ describe('ImmutableUtil Tests', () => { immutableIs(iq2, iq3).should.be.false(); }); + it('should normalize fractional unit conversions without JavaScript-number rounding', () => { + const inches = new Quantity(1, '[in_i]'); + const centimeters = new Quantity('2.54', 'cm'); + + equals(inches, centimeters).should.be.true(); + immutableIs(toNormalizedKey(inches), toNormalizedKey(centimeters)).should.be.true(); + }); + it('should properly match ratios', () => { const r1 = new Ratio(new Quantity(1, 'km'), new Quantity(1, 'h')); const r2 = new Ratio(new Quantity(1000, 'm'), new Quantity(60, 'min')); diff --git a/test/util/math-test.ts b/test/util/math-test.ts index a6e6d3629..56305f115 100644 --- a/test/util/math-test.ts +++ b/test/util/math-test.ts @@ -1,7 +1,7 @@ import { Uncertainty } from '../../src/datatypes/uncertainty'; import { MAX_FLOAT_VALUE, MIN_FLOAT_VALUE } from '../../src/util/limits'; import { Decimal } from '../../src/datatypes/decimal'; -import { predecessor, successor } from '../../src/util/math'; +import { finalizeNumericResult, predecessor, successor } from '../../src/util/math'; describe('successor', () => { it('should preserve integers in an Uncertainty', () => { @@ -40,3 +40,22 @@ describe('predecessor', () => { result.should.eql(new Uncertainty(MIN_FLOAT_VALUE, Decimal.from(1.99999999))); }); }); + +describe('finalizeNumericResult', () => { + it('should normalize Decimal results to eight places using the implicit rounding mode', () => { + const result = finalizeNumericResult(Decimal.from('1.234567895')); + + result.should.equalDecimal(Decimal.from('1.23456790')); + }); + + it('should return a new normalized Uncertainty without modifying the input', () => { + const input = new Uncertainty(Decimal.from('1.234567895'), Decimal.from('2.345678995')); + const result = finalizeNumericResult(input); + + result.should.not.equal(input); + input.low.should.equalDecimal(Decimal.from('1.234567895')); + input.high.should.equalDecimal(Decimal.from('2.345678995')); + result.low.should.equalDecimal(Decimal.from('1.23456790')); + result.high.should.equalDecimal(Decimal.from('2.34567900')); + }); +}); From 65203efd71c633d088498f287f5aa5e19fa52f82 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Wed, 26 Aug 2026 16:36:48 -0400 Subject: [PATCH 13/19] one more round of cleanup --- src/datatypes/decimal.ts | 20 ++-- src/elm/arithmetic.ts | 18 ++- src/elm/literal.ts | 2 +- test/datatypes/decimal-test.ts | 15 +++ test/elm/arithmetic/arithmetic-test.ts | 12 ++ test/elm/arithmetic/data.cql | 3 + test/elm/arithmetic/data.js | 154 ++++++++++++++++++++++++- 7 files changed, 207 insertions(+), 17 deletions(-) diff --git a/src/datatypes/decimal.ts b/src/datatypes/decimal.ts index 5d0a99ec0..e72e6eeb9 100644 --- a/src/datatypes/decimal.ts +++ b/src/datatypes/decimal.ts @@ -1,24 +1,26 @@ import { Decimal as DecimalJS } from 'decimal.js'; -// Default precision is set to 30 significant figures. (Not decimal places) -// MAX_DECIMAL_VALUE = 99999999999999999999.99999999 is 28 significant figures, -// 30 is just a cleaner number. -DecimalJS.set({ precision: 30 }); +// Use a clone rather than DecimalJS.set because decimal.js configuration is otherwise global. +// This keeps our settings from changing the behavior of other decimal.js instances in +// the same process. +// Precision is significant figures (not decimal places); +// CQL's maximum Decimal value has 28 significant figures, 30 is just a cleaner number. +const CQLDecimalJS = DecimalJS.clone({ precision: 30 }); export type DecimalInput = Decimal | string | number | bigint; export type DecimalRoundingMode = DecimalJS.Rounding; -const MIN_PRECISION_VALUE = DecimalJS.pow(10, -8); +const MIN_PRECISION_VALUE = CQLDecimalJS.pow(10, -8); const CQL_IMPLICIT_SCALE = 8; -const CQL_IMPLICIT_ROUNDING = DecimalJS.ROUND_HALF_UP; +const CQL_IMPLICIT_ROUNDING = CQLDecimalJS.ROUND_HALF_UP; export class Decimal { private value: DecimalJS; private constructor(value: string | number | bigint | DecimalJS) { - this.value = new DecimalJS(value); + this.value = new CQLDecimalJS(value); if (!this.value.isFinite()) { throw new Error('Cannot create a decimal with a non-finite value'); } @@ -167,10 +169,10 @@ export class Decimal { // ROUND_HALF_CEIL "Rounds towards nearest neighbour. If equidistant, rounds towards Infinity" // rounds 0.5 -> 1.0, -0.5 -> 0.0 // https://mikemcl.github.io/decimal.js/#modes - return this.setScale(scale, DecimalJS.ROUND_HALF_CEIL); + return this.setScale(scale, CQLDecimalJS.ROUND_HALF_CEIL); } - setScale(scale: number, roundingMode: DecimalRoundingMode = DecimalJS.ROUND_DOWN) { + setScale(scale: number, roundingMode: DecimalRoundingMode = CQLDecimalJS.ROUND_DOWN) { if (!Number.isInteger(scale) || scale < 0) { throw new RangeError('Decimal scale must be a non-negative integer'); } diff --git a/src/elm/arithmetic.ts b/src/elm/arithmetic.ts index c0d7f796a..ffff1a73f 100644 --- a/src/elm/arithmetic.ts +++ b/src/elm/arithmetic.ts @@ -216,7 +216,8 @@ export class Ceiling extends Expression { return null; } - return arg.isDecimal ? arg.ceil() : Math.ceil(arg); + const ceiling = arg.isDecimal ? arg.ceil() : Math.ceil(arg); + return MathUtil.isValidInteger(ceiling) ? ceiling : null; } } @@ -231,7 +232,8 @@ export class Floor extends Expression { return null; } - return arg.isDecimal ? arg.floor() : Math.floor(arg); + const floor = arg.isDecimal ? arg.floor() : Math.floor(arg); + return MathUtil.isValidInteger(floor) ? floor : null; } } @@ -246,7 +248,15 @@ export class Truncate extends Expression { return null; } - return arg.isDecimal ? arg.truncate() : arg >= 0 ? Math.floor(arg) : Math.ceil(arg); + let truncated; + if (arg.isDecimal) { + truncated = arg.truncate(); + } else if (arg >= 0) { + truncated = Math.floor(arg); + } else { + truncated = Math.ceil(arg); + } + return MathUtil.isValidInteger(truncated) ? truncated : null; } } export class Abs extends Expression { @@ -349,7 +359,7 @@ export class Exp extends Expression { let power; try { - power = Decimal.from(arg).exp().normalized(); + power = Decimal.from(arg).exp(); } catch { return null; } diff --git a/src/elm/literal.ts b/src/elm/literal.ts index dea41feb3..68d0a448c 100644 --- a/src/elm/literal.ts +++ b/src/elm/literal.ts @@ -97,7 +97,7 @@ export class LongLiteral extends Literal { export class DecimalLiteral extends Literal { constructor(json: any) { super(json); - this.value = Decimal.from(this.value); + this.value = Decimal.from(this.value).normalized(); } // Define a simple getter to allow type-checking of this class without instanceof diff --git a/test/datatypes/decimal-test.ts b/test/datatypes/decimal-test.ts index 053584fd3..e2682a5ae 100644 --- a/test/datatypes/decimal-test.ts +++ b/test/datatypes/decimal-test.ts @@ -1,3 +1,4 @@ +import { Decimal as DecimalJS } from 'decimal.js'; import { Decimal } from '../../src/datatypes/decimal'; describe('Decimal', () => { @@ -49,4 +50,18 @@ describe('Decimal', () => { it('should not coerce a nonzero Decimal divisor through a JavaScript number', () => { (() => Decimal.from(1).divideBy('1e-1000')).should.not.throw(); }); + + it('should keep CQL Decimal precision independent from the base decimal.js constructor', () => { + const basePrecision = DecimalJS.precision; + try { + DecimalJS.set({ precision: 5 }); + + const oneThird = Decimal.from(1).divideBy(3); + oneThird.toString().should.equal('0.333333333333333333333333333333'); // we specify precision of 30 = significant figures + + oneThird.normalized().toString().should.equal('0.33333333'); + } finally { + DecimalJS.set({ precision: basePrecision }); + } + }); }); diff --git a/test/elm/arithmetic/arithmetic-test.ts b/test/elm/arithmetic/arithmetic-test.ts index 67c861761..092a36c7d 100644 --- a/test/elm/arithmetic/arithmetic-test.ts +++ b/test/elm/arithmetic/arithmetic-test.ts @@ -517,6 +517,10 @@ describe('Truncate', () => { // NOTE: Truncate returns an integer (not specified to return a Long) (await this.truncTenLong.exec(this.ctx)).should.equal(10); }); + + it('should return null when the result is outside the Integer range', async function () { + should(await this.truncateOverflow.exec(this.ctx)).be.null(); + }); }); describe('Floor', () => { @@ -530,6 +534,10 @@ describe('Floor', () => { // NOTE: Floor returns an Integer (not specified to return a Long) (await this.floorTenLong.exec(this.ctx)).should.equal(10); }); + + it('should return null when the result is outside the Integer range', async function () { + should(await this.floorUnderflow.exec(this.ctx)).be.null(); + }); }); describe('Ceiling', () => { @@ -543,6 +551,10 @@ describe('Ceiling', () => { // Note: Ceiling returns an Integer (not specified to return a Long) (await this.ceilTenLong.exec(this.ctx)).should.equal(10); }); + + it('should return null when the result is outside the Integer range', async function () { + should(await this.ceilingOverflow.exec(this.ctx)).be.null(); + }); }); describe('Ln', () => { diff --git a/test/elm/arithmetic/data.cql b/test/elm/arithmetic/data.cql index 8907b069a..7d5e49457 100644 --- a/test/elm/arithmetic/data.cql +++ b/test/elm/arithmetic/data.cql @@ -123,16 +123,19 @@ define ThreeModZeroDecimal: 3.0 mod 0.0 define Ceil: Ceiling(10.1) define Even: Ceiling(10) define CeilTenLong: Ceiling(10L) +define CeilingOverflow: Ceiling(2147483647.1) // @Test: Floor define flr: Floor(10.1) define Even: Floor(10) define FloorTenLong: Floor(10L) +define FloorUnderflow: Floor(-2147483648.1) // @Test: Truncate define Trunc: Truncate(10.1) define Even: Truncate(10) define TruncTenLong: Truncate(10L) +define TruncateOverflow: Truncate(2147483648.0) // @Test: Abs define Pos: Abs(10) diff --git a/test/elm/arithmetic/data.js b/test/elm/arithmetic/data.js index c87f24008..ccfe850d1 100644 --- a/test/elm/arithmetic/data.js +++ b/test/elm/arithmetic/data.js @@ -7700,6 +7700,7 @@ context Patient define Ceil: Ceiling(10.1) define Even: Ceiling(10) define CeilTenLong: Ceiling(10L) +define CeilingOverflow: Ceiling(2147483647.1) */ module.exports['Ceiling'] = { @@ -7714,7 +7715,7 @@ module.exports['Ceiling'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "238", + "r" : "253", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -7934,6 +7935,48 @@ module.exports['Ceiling'] = { } } } + }, { + "localId" : "253", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "name" : "CeilingOverflow", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "253", + "s" : [ { + "value" : [ "", "define ", "CeilingOverflow", ": " ] + }, { + "r" : "258", + "s" : [ { + "r" : "254", + "value" : [ "Ceiling", "(", "2147483647.1", ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Ceiling", + "localId" : "258", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "259", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "254", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "2147483647.1", + "annotation" : [ ] + } + } } ] } } @@ -7946,6 +7989,7 @@ context Patient define flr: Floor(10.1) define Even: Floor(10) define FloorTenLong: Floor(10L) +define FloorUnderflow: Floor(-2147483648.1) */ module.exports['Floor'] = { @@ -7960,7 +8004,7 @@ module.exports['Floor'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "238", + "r" : "253", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -8180,6 +8224,67 @@ module.exports['Floor'] = { } } } + }, { + "localId" : "253", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "name" : "FloorUnderflow", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "253", + "s" : [ { + "value" : [ "", "define ", "FloorUnderflow", ": " ] + }, { + "r" : "260", + "s" : [ { + "value" : [ "Floor", "(" ] + }, { + "r" : "254", + "s" : [ { + "r" : "255", + "value" : [ "-", "2147483648.1" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Floor", + "localId" : "260", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "261", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Negate", + "localId" : "254", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "256", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "255", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "2147483648.1", + "annotation" : [ ] + } + } + } } ] } } @@ -8192,6 +8297,7 @@ context Patient define Trunc: Truncate(10.1) define Even: Truncate(10) define TruncTenLong: Truncate(10L) +define TruncateOverflow: Truncate(2147483648.0) */ module.exports['Truncate'] = { @@ -8206,7 +8312,7 @@ module.exports['Truncate'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "238", + "r" : "253", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -8426,6 +8532,48 @@ module.exports['Truncate'] = { } } } + }, { + "localId" : "253", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "name" : "TruncateOverflow", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "253", + "s" : [ { + "value" : [ "", "define ", "TruncateOverflow", ": " ] + }, { + "r" : "258", + "s" : [ { + "r" : "254", + "value" : [ "Truncate", "(", "2147483648.0", ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Truncate", + "localId" : "258", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "259", + "name" : "{urn:hl7-org:elm-types:r1}Decimal", + "annotation" : [ ] + } ], + "operand" : { + "type" : "Literal", + "localId" : "254", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Decimal", + "valueType" : "{urn:hl7-org:elm-types:r1}Decimal", + "value" : "2147483648.0", + "annotation" : [ ] + } + } } ] } } From a0b4f1d804e9df093cc258ac3fa7a461cc5efed4 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 27 Aug 2026 08:40:15 -0400 Subject: [PATCH 14/19] Low-hanging fruit to get interval tests passing --- src/elm/interval.ts | 19 +- .../cql/CqlIntervalOperatorsTest.cql | 48 +- .../cql/CqlIntervalOperatorsTest.json | 2656 ++++++++++++++--- test/spec-tests/skip-list.txt | 25 +- 4 files changed, 2291 insertions(+), 457 deletions(-) diff --git a/src/elm/interval.ts b/src/elm/interval.ts index cea2cf1a7..c7f53a91d 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -453,10 +453,17 @@ export class Expand extends Expression { return null; } - // CQL 1.5 introduced an overload to allow singular intervals; make it a list so we can use the same logic for either overload - if (!Array.isArray(intervals)) { + const isSingleInterval = !Array.isArray(intervals); + // CQL 1.5 introduced an overload to allow singular intervals; make it a list so we can use the same logic for either overload. + if (isSingleInterval) { intervals = [intervals]; } + + // If the list of intervals is empty, the result is empty. + if (intervals.length === 0) { + return []; + } + const type = intervalListType(intervals); if (type === 'mismatch') { throw new Error('List of intervals contains mismatched types.'); @@ -508,6 +515,14 @@ export class Expand extends Expression { results.push(...(items || [])); } + // If the input argument is an interval, rather than a list of intervals, + // the result is a list of points, rather than a list of intervals. + // In this case, the calculation is performed the same way, + // but the starting point of each resulting interval is returned, rather than the interval. + if (isSingleInterval) { + return results.map(i => i.start()); + } + return results; } diff --git a/test/spec-tests/cql/CqlIntervalOperatorsTest.cql b/test/spec-tests/cql/CqlIntervalOperatorsTest.cql index 1f15520ab..eb465ed41 100644 --- a/test/spec-tests/cql/CqlIntervalOperatorsTest.cql +++ b/test/spec-tests/cql/CqlIntervalOperatorsTest.cql @@ -247,11 +247,9 @@ define "Expand": Tuple{ output: null }, "ExpandEmptyList": Tuple{ - skipped: 'Wrong answer (should be empty list)' - /* expression: expand { }, output: { } - */ }, + }, "ExpandListWithNull": Tuple{ skipped: 'Wrong answer (should be empty list due to removing nulls)' /* @@ -263,61 +261,49 @@ define "Expand": Tuple{ output: { Interval[@2018-01-01, @2018-01-01], Interval[@2018-01-02, @2018-01-02], Interval[@2018-01-03, @2018-01-03], Interval[@2018-01-04, @2018-01-04] } }, "ExpandPerDayIntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' - /* expression: expand Interval[@2018-01-01, @2018-01-04] per day, output: { @2018-01-01, @2018-01-02, @2018-01-03, @2018-01-04 } - */ }, + }, "ExpandPer2Days": Tuple{ expression: expand { Interval[@2018-01-01, @2018-01-04] } per 2 days, output: { Interval[@2018-01-01, @2018-01-02], Interval[@2018-01-03, @2018-01-04] } }, "ExpandPer2DaysIntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' - /* expression: expand Interval[@2018-01-01, @2018-01-04] per 2 days, output: { @2018-01-01, @2018-01-03 } - */ }, + }, "ExpandPerHour": Tuple{ expression: expand { Interval[@T10:00, @T12:30] } per hour, output: { Interval[@T10, @T10], Interval[@T11, @T11], Interval[@T12, @T12] } }, "ExpandPerHourIntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' - /* expression: expand Interval[@T10:00, @T12:30] per hour, output: { @T10, @T11, @T12 } - */ }, + }, "ExpandPerHourOpen": Tuple{ expression: expand { Interval[@T10:00, @T12:30) } per hour, output: { Interval[@T10, @T10], Interval[@T11, @T11], Interval[@T12, @T12] } }, "ExpandPerHourOpenIntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' - /* expression: expand Interval[@T10:00, @T12:30) per hour, output: { @T10, @T11, @T12 } - */ }, + }, "ExpandPer1": Tuple{ expression: expand { Interval[10.0, 12.5] } per 1, output: { Interval[10, 10], Interval[11, 11], Interval[12, 12] } }, "ExpandPer1IntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' - /* expression: expand Interval[10.0, 12.5] per 1, output: { 10, 11, 12 } - */ }, + }, "ExpandPer1Open": Tuple{ expression: expand { Interval[10.0, 12.5) } per 1, output: { Interval[10, 10], Interval[11, 11], Interval[12, 12] } }, "ExpandPer1OpenIntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' - /* expression: expand Interval[10.0, 12.5) per 1, output: { 10, 11, 12 } - */ }, + }, "ExpandPerMinute": Tuple{ expression: expand { Interval[@T10, @T10] } per minute, output: { } @@ -327,13 +313,13 @@ define "Expand": Tuple{ output: { } }, "ExpandPer0D1": Tuple{ - skipped: 'Wrong answer (interval elements\'s decimals do not match expected output)' + skipped: 'Wrong output: Clarification still needed but provided answer is incorrect. See https://jira.hl7.org/browse/FHIR-58705' /* expression: expand { Interval[10, 10] } per 0.1, output: { Interval[10.0, 10.0], Interval[10.1, 10.1], Interval[10.2, 10.2], Interval[10.3, 10.3], Interval[10.4, 10.4], Interval[10.5, 10.5], Interval[10.6, 10.6], Interval[10.7, 10.7], Interval[10.8, 10.8], Interval[10.9, 10.9] } */ }, "ExpandPer0D1IntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' + skipped: 'Wrong output: Clarification still needed but provided answer is incorrect. See https://jira.hl7.org/browse/FHIR-58705' /* expression: expand Interval[10, 10] per 0.1, output: { 10.0, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9 } @@ -343,41 +329,33 @@ define "Expand": Tuple{ output: { Interval[1, 1], Interval[2, 2], Interval[3, 3], Interval[4, 4], Interval[5, 5], Interval[6, 6], Interval[7, 7], Interval[8, 8], Interval[9, 9], Interval[10, 10] } }, "ExpandIntegerIntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' - /* expression: expand Interval[1, 10], output: { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } - */ }, + }, "ExpandIntervalOpen": Tuple{ expression: expand { Interval[1, 10) }, output: { Interval[1, 1], Interval[2, 2], Interval[3, 3], Interval[4, 4], Interval[5, 5], Interval[6, 6], Interval[7, 7], Interval[8, 8], Interval[9, 9] } }, "ExpandIntegerOpenIntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' - /* expression: expand Interval[1, 10), output: { 1, 2, 3, 4, 5, 6, 7, 8, 9 } - */ }, + }, "ExpandIntervalPer2": Tuple{ expression: expand { Interval[1, 10] } per 2, output: { Interval[1, 2], Interval[3, 4], Interval[5, 6], Interval[7, 8], Interval[9, 10] } }, "ExpandIntervalPer2IntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' - /* expression: expand Interval[1, 10] per 2, output: { 1, 3, 5, 7, 9 } - */ }, + }, "ExpandIntervalOpenPer2": Tuple{ expression: expand { Interval[1, 10) } per 2, output: { Interval[1, 2], Interval[3, 4], Interval[5, 6], Interval[7, 8] } }, "ExpandIntervalOpenPer2IntervalOverload": Tuple{ - skipped: 'Wrong answer (single interval overload should return list of points)' - /* expression: expand Interval[1, 10) per 2, output: { 1, 3, 5, 7 } - */ } + } } define "Contains": Tuple{ diff --git a/test/spec-tests/cql/CqlIntervalOperatorsTest.json b/test/spec-tests/cql/CqlIntervalOperatorsTest.json index b229a9370..8fc6eda5e 100644 --- a/test/spec-tests/cql/CqlIntervalOperatorsTest.json +++ b/test/spec-tests/cql/CqlIntervalOperatorsTest.json @@ -13763,12 +13763,33 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } } } ] @@ -13845,12 +13866,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } } } ] @@ -13908,12 +13946,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } } } ] @@ -13971,12 +14026,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } } } ] @@ -14034,12 +14106,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } } } ] @@ -14097,12 +14186,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] @@ -14160,12 +14266,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] @@ -14337,12 +14460,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] @@ -14395,25 +14535,6 @@ { "name": "ExpandIntegerOpenIntervalOverload", "annotation": [], - "elementType": { - "type": "TupleTypeSpecifier", - "annotation": [], - "element": [ - { - "name": "skipped", - "annotation": [], - "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] - } - } - ] - } - }, - { - "name": "ExpandIntervalPer2", - "annotation": [], "elementType": { "type": "TupleTypeSpecifier", "annotation": [], @@ -14425,13 +14546,9 @@ "type": "ListTypeSpecifier", "annotation": [], "elementType": { - "type": "IntervalTypeSpecifier", - "annotation": [], - "pointType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Integer", - "annotation": [] - } + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] } } }, @@ -14442,13 +14559,9 @@ "type": "ListTypeSpecifier", "annotation": [], "elementType": { - "type": "IntervalTypeSpecifier", - "annotation": [], - "pointType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Integer", - "annotation": [] - } + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] } } } @@ -14456,26 +14569,87 @@ } }, { - "name": "ExpandIntervalPer2IntervalOverload", - "annotation": [], - "elementType": { - "type": "TupleTypeSpecifier", - "annotation": [], - "element": [ - { - "name": "skipped", - "annotation": [], - "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] - } - } - ] - } - }, - { - "name": "ExpandIntervalOpenPer2", + "name": "ExpandIntervalPer2", + "annotation": [], + "elementType": { + "type": "TupleTypeSpecifier", + "annotation": [], + "element": [ + { + "name": "expression", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + } + } + ] + } + }, + { + "name": "ExpandIntervalPer2IntervalOverload", + "annotation": [], + "elementType": { + "type": "TupleTypeSpecifier", + "annotation": [], + "element": [ + { + "name": "expression", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + } + ] + } + }, + { + "name": "ExpandIntervalOpenPer2", "annotation": [], "elementType": { "type": "TupleTypeSpecifier", @@ -14526,12 +14700,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] @@ -14586,12 +14777,33 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } } } ] @@ -14668,12 +14880,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } } } ] @@ -14731,12 +14960,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } } } ] @@ -14794,12 +15040,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } } } ] @@ -14857,12 +15120,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } } } ] @@ -14920,12 +15200,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] @@ -14983,12 +15280,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] @@ -15160,12 +15474,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] @@ -15223,12 +15554,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] @@ -15286,12 +15634,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] @@ -15349,12 +15714,29 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] @@ -15461,25 +15843,130 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } } } ] }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (should be empty list)", - "annotation": [] + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + } + }, + "signature": [], + "operand": [ + { + "type": "Query", + "annotation": [], + "source": [ + { + "alias": "X", + "annotation": [], + "expression": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + }, + "element": [] + } + } + ], + "let": [], + "relationship": [], + "return": { + "distinct": false, + "annotation": [], + "expression": { + "type": "As", + "annotation": [], + "signature": [], + "operand": { + "type": "AliasRef", + "name": "X", + "annotation": [] + }, + "asTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + } + } + } + }, + { + "type": "Null", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + }, + "element": [] } } ] @@ -15955,40 +16442,6 @@ }, { "name": "ExpandPerDayIntervalOverload", - "value": { - "type": "Tuple", - "annotation": [], - "resultTypeSpecifier": { - "type": "TupleTypeSpecifier", - "annotation": [], - "element": [ - { - "name": "skipped", - "annotation": [], - "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] - } - } - ] - }, - "element": [ - { - "name": "skipped", - "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", - "annotation": [] - } - } - ] - } - }, - { - "name": "ExpandPer2Days", "value": { "type": "Tuple", "annotation": [], @@ -16003,13 +16456,9 @@ "type": "ListTypeSpecifier", "annotation": [], "elementType": { - "type": "IntervalTypeSpecifier", - "annotation": [], - "pointType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Date", - "annotation": [] - } + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] } } }, @@ -16020,13 +16469,9 @@ "type": "ListTypeSpecifier", "annotation": [], "elementType": { - "type": "IntervalTypeSpecifier", - "annotation": [], - "pointType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Date", - "annotation": [] - } + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] } } } @@ -16042,128 +16487,379 @@ "type": "ListTypeSpecifier", "annotation": [], "elementType": { - "type": "IntervalTypeSpecifier", - "annotation": [], - "pointType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Date", - "annotation": [] - } + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] } }, "signature": [], "operand": [ - { - "type": "List", - "annotation": [], - "resultTypeSpecifier": { - "type": "ListTypeSpecifier", - "annotation": [], - "elementType": { - "type": "IntervalTypeSpecifier", - "annotation": [], - "pointType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Date", - "annotation": [] - } - } - }, - "element": [ - { - "type": "Interval", - "lowClosed": true, - "highClosed": true, - "annotation": [], - "resultTypeSpecifier": { - "type": "IntervalTypeSpecifier", - "annotation": [], - "pointType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Date", - "annotation": [] - } - }, - "low": { - "type": "Date", - "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", - "annotation": [], - "signature": [], - "year": { - "type": "Literal", - "valueType": "{urn:hl7-org:elm-types:r1}Integer", - "value": "2018", - "annotation": [] - }, - "month": { - "type": "Literal", - "valueType": "{urn:hl7-org:elm-types:r1}Integer", - "value": "1", - "annotation": [] - }, - "day": { - "type": "Literal", - "valueType": "{urn:hl7-org:elm-types:r1}Integer", - "value": "1", - "annotation": [] - } - }, - "high": { - "type": "Date", - "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", - "annotation": [], - "signature": [], - "year": { - "type": "Literal", - "valueType": "{urn:hl7-org:elm-types:r1}Integer", - "value": "2018", - "annotation": [] - }, - "month": { - "type": "Literal", - "valueType": "{urn:hl7-org:elm-types:r1}Integer", - "value": "1", - "annotation": [] - }, - "day": { - "type": "Literal", - "valueType": "{urn:hl7-org:elm-types:r1}Integer", - "value": "4", - "annotation": [] - } - } - } - ] - }, - { - "type": "Quantity", - "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", - "value": 2, - "unit": "days", - "annotation": [] - } - ] - } - }, - { - "name": "output", - "value": { - "type": "List", - "annotation": [], - "resultTypeSpecifier": { - "type": "ListTypeSpecifier", - "annotation": [], - "elementType": { - "type": "IntervalTypeSpecifier", - "annotation": [], - "pointType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Date", - "annotation": [] - } - } - }, - "element": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": true, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + }, + "low": { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + } + }, + "high": { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "4", + "annotation": [] + } + } + }, + { + "type": "Quantity", + "value": 1, + "unit": "day", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + }, + "element": [ + { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + } + }, + { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2", + "annotation": [] + } + }, + { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "3", + "annotation": [] + } + }, + { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "4", + "annotation": [] + } + } + ] + } + } + ] + } + }, + { + "name": "ExpandPer2Days", + "value": { + "type": "Tuple", + "annotation": [], + "resultTypeSpecifier": { + "type": "TupleTypeSpecifier", + "annotation": [], + "element": [ + { + "name": "expression", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + } + } + } + ] + }, + "element": [ + { + "name": "expression", + "value": { + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + } + }, + "signature": [], + "operand": [ + { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + } + }, + "element": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": true, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + }, + "low": { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + } + }, + "high": { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "4", + "annotation": [] + } + } + } + ] + }, + { + "type": "Quantity", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", + "value": 2, + "unit": "days", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + } + }, + "element": [ { "type": "Interval", "lowClosed": true, @@ -16306,25 +17002,187 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } } } ] }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", - "annotation": [] + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + }, + "signature": [], + "operand": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": true, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + }, + "low": { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + } + }, + "high": { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "4", + "annotation": [] + } + } + }, + { + "type": "Quantity", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", + "value": 2, + "unit": "days", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [] + } + }, + "element": [ + { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + } + }, + { + "type": "Date", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Date", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2018", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "3", + "annotation": [] + } + } + ] } } ] @@ -16627,25 +17485,162 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } } } ] }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", - "annotation": [] + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + }, + "signature": [], + "operand": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": true, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + }, + "low": { + "type": "Time", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [], + "signature": [], + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + }, + "minute": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "0", + "annotation": [] + } + }, + "high": { + "type": "Time", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [], + "signature": [], + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "12", + "annotation": [] + }, + "minute": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "30", + "annotation": [] + } + } + }, + { + "type": "Quantity", + "value": 1, + "unit": "hour", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + }, + "element": [ + { + "type": "Time", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [], + "signature": [], + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + } + }, + { + "type": "Time", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [], + "signature": [], + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "11", + "annotation": [] + } + }, + { + "type": "Time", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [], + "signature": [], + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "12", + "annotation": [] + } + } + ] } } ] @@ -16948,25 +17943,162 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } } } ] }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", - "annotation": [] + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + }, + "signature": [], + "operand": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": false, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + }, + "low": { + "type": "Time", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [], + "signature": [], + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + }, + "minute": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "0", + "annotation": [] + } + }, + "high": { + "type": "Time", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [], + "signature": [], + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "12", + "annotation": [] + }, + "minute": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "30", + "annotation": [] + } + } + }, + { + "type": "Quantity", + "value": 1, + "unit": "hour", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [] + } + }, + "element": [ + { + "type": "Time", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [], + "signature": [], + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + } + }, + { + "type": "Time", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [], + "signature": [], + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "11", + "annotation": [] + } + }, + { + "type": "Time", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Time", + "annotation": [], + "signature": [], + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "12", + "annotation": [] + } + } + ] } } ] @@ -17217,25 +18349,125 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", - "annotation": [] + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + }, + "signature": [], + "operand": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": true, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + }, + "low": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Decimal", + "valueType": "{urn:hl7-org:elm-types:r1}Decimal", + "value": "10.0", + "annotation": [] + }, + "high": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Decimal", + "valueType": "{urn:hl7-org:elm-types:r1}Decimal", + "value": "12.5", + "annotation": [] + } + }, + { + "type": "Quantity", + "value": 1, + "unit": "1", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "element": [ + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "11", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "12", + "annotation": [] + } + ] } } ] @@ -17486,25 +18718,125 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", - "annotation": [] + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + }, + "signature": [], + "operand": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": false, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + }, + "low": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Decimal", + "valueType": "{urn:hl7-org:elm-types:r1}Decimal", + "value": "10.0", + "annotation": [] + }, + "high": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Decimal", + "valueType": "{urn:hl7-org:elm-types:r1}Decimal", + "value": "12.5", + "annotation": [] + } + }, + { + "type": "Quantity", + "value": 1, + "unit": "1", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "element": [ + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "11", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "12", + "annotation": [] + } + ] } } ] @@ -17808,7 +19140,7 @@ "type": "Literal", "resultTypeName": "{urn:hl7-org:elm-types:r1}String", "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (interval elements's decimals do not match expected output)", + "value": "Wrong output: Clarification still needed but provided answer is incorrect. See https://jira.hl7.org/browse/FHIR-58705", "annotation": [] } } @@ -17842,7 +19174,7 @@ "type": "Literal", "resultTypeName": "{urn:hl7-org:elm-types:r1}String", "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", + "value": "Wrong output: Clarification still needed but provided answer is incorrect. See https://jira.hl7.org/browse/FHIR-58705", "annotation": [] } } @@ -18296,25 +19628,173 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", - "annotation": [] + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "signature": [], + "operand": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": true, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "low": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "high": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + } + }, + { + "type": "Null", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "element": [ + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "3", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "4", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "5", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "6", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "7", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "8", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "9", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + } + ] } } ] @@ -18738,25 +20218,166 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", - "annotation": [] + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "signature": [], + "operand": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": false, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "low": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "high": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + } + }, + { + "type": "Null", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "element": [ + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "3", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "4", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "5", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "6", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "7", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "8", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "9", + "annotation": [] + } + ] } } ] @@ -19065,25 +20686,139 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", - "annotation": [] + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "signature": [], + "operand": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": true, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "low": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "high": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + } + }, + { + "type": "Quantity", + "value": 2, + "unit": "1", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "element": [ + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "3", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "5", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "7", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "9", + "annotation": [] + } + ] } } ] @@ -19363,25 +21098,132 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", - "annotation": [] + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } } } ] }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (single interval overload should return list of points)", - "annotation": [] + "type": "Expand", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "signature": [], + "operand": [ + { + "type": "Interval", + "lowClosed": true, + "highClosed": false, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "low": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "high": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "10", + "annotation": [] + } + }, + { + "type": "Quantity", + "value": 2, + "unit": "1", + "annotation": [] + } + ] + } + }, + { + "name": "output", + "value": { + "type": "List", + "annotation": [], + "resultTypeSpecifier": { + "type": "ListTypeSpecifier", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + "element": [ + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "3", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "5", + "annotation": [] + }, + { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "7", + "annotation": [] + } + ] } } ] @@ -53485,11 +55327,11 @@ "annotation": [], "resultTypeSpecifier": { "type": "IntervalTypeSpecifier", - "localId": "9912", + "localId": "10275", "annotation": [], "pointType": { "type": "NamedTypeSpecifier", - "localId": "9913", + "localId": "10276", "name": "{urn:hl7-org:elm-types:r1}Date", "annotation": [] } @@ -55355,11 +57197,11 @@ "annotation": [], "resultTypeSpecifier": { "type": "IntervalTypeSpecifier", - "localId": "10237", + "localId": "10600", "annotation": [], "pointType": { "type": "NamedTypeSpecifier", - "localId": "10238", + "localId": "10601", "name": "{urn:hl7-org:elm-types:r1}Date", "annotation": [] } @@ -81779,11 +83621,11 @@ "annotation": [], "resultTypeSpecifier": { "type": "IntervalTypeSpecifier", - "localId": "15175", + "localId": "15538", "annotation": [], "pointType": { "type": "NamedTypeSpecifier", - "localId": "15176", + "localId": "15539", "name": "{urn:hl7-org:elm-types:r1}Any", "annotation": [] } diff --git a/test/spec-tests/skip-list.txt b/test/spec-tests/skip-list.txt index 6e8072e7b..ee19feabc 100644 --- a/test/spec-tests/skip-list.txt +++ b/test/spec-tests/skip-list.txt @@ -9,6 +9,8 @@ CqlIntervalOperatorsTest.ProperContains.TimeProperContainsPrecisionFalse Wrong CqlIntervalOperatorsTest.ProperContains.TimeProperContainsFalse Wrong output: According to spec, a contained point is properly contained as long as the interval is not a unit interval CqlIntervalOperatorsTest.ProperIn.TimeProperInPrecisionFalse Wrong output: According to spec, a contained point is properly in as long as the interval is not a unit interval CqlIntervalOperatorsTest.ProperIn.TimeProperInFalse Wrong output: According to spec, a contained point is properly in as long as the interval is not a unit interval +CqlIntervalOperatorsTest.Expand.ExpandPer0D1 Wrong output: Clarification still needed but provided answer is incorrect. See https://jira.hl7.org/browse/FHIR-58705 +CqlIntervalOperatorsTest.Expand.ExpandPer0D1IntervalOverload Wrong output: Clarification still needed but provided answer is incorrect. See https://jira.hl7.org/browse/FHIR-58705 CqlListOperatorsTest.Equal.EqualNullNull Wrong output: According to spec, if either list contains a null, the result is null CqlListOperatorsTest.Sort.simpleSortAsc Wrong output: Queries return distinct lists by default; need to use "all" to retain duplicates CqlListOperatorsTest.Sort.simpleSortDesc Wrong output: Queries return distinct lists by default; need to use "all" to retain duplicates @@ -27,20 +29,17 @@ CqlIntervalOperatorsTest.PointFrom.TestPointFromNull Wrong output: In CqlComparisonOperatorsTest.Equal.DateTimeEqNull Wrong answer (true vs null - due to not evaluating DateTime(null) as null) CqlIntervalOperatorsTest.Collapse.TestCollapseNull Wrong answer (Interval(null, null) vs null) CqlIntervalOperatorsTest.Except.NullInterval Wrong answer (Interval(null, null) vs null) -CqlIntervalOperatorsTest.Expand.ExpandEmptyList Wrong answer (should be empty list) -CqlIntervalOperatorsTest.Expand.ExpandIntegerIntervalOverload Wrong answer (single interval overload should return list of points) -CqlIntervalOperatorsTest.Expand.ExpandIntegerOpenIntervalOverload Wrong answer (single interval overload should return list of points) -CqlIntervalOperatorsTest.Expand.ExpandIntervalOpenPer2IntervalOverload Wrong answer (single interval overload should return list of points) -CqlIntervalOperatorsTest.Expand.ExpandIntervalPer2IntervalOverload Wrong answer (single interval overload should return list of points) +# CqlIntervalOperatorsTest.Expand.ExpandIntegerIntervalOverload Wrong answer (single interval overload should return list of points) +# CqlIntervalOperatorsTest.Expand.ExpandIntegerOpenIntervalOverload Wrong answer (single interval overload should return list of points) +# CqlIntervalOperatorsTest.Expand.ExpandIntervalOpenPer2IntervalOverload Wrong answer (single interval overload should return list of points) +# CqlIntervalOperatorsTest.Expand.ExpandIntervalPer2IntervalOverload Wrong answer (single interval overload should return list of points) CqlIntervalOperatorsTest.Expand.ExpandListWithNull Wrong answer (should be empty list due to removing nulls) -CqlIntervalOperatorsTest.Expand.ExpandPerDayIntervalOverload Wrong answer (single interval overload should return list of points) -CqlIntervalOperatorsTest.Expand.ExpandPerHourIntervalOverload Wrong answer (single interval overload should return list of points) -CqlIntervalOperatorsTest.Expand.ExpandPerHourOpenIntervalOverload Wrong answer (single interval overload should return list of points) -CqlIntervalOperatorsTest.Expand.ExpandPer0D1 Wrong answer (interval elements's decimals do not match expected output) -CqlIntervalOperatorsTest.Expand.ExpandPer0D1IntervalOverload Wrong answer (single interval overload should return list of points) -CqlIntervalOperatorsTest.Expand.ExpandPer1IntervalOverload Wrong answer (single interval overload should return list of points) -CqlIntervalOperatorsTest.Expand.ExpandPer1OpenIntervalOverload Wrong answer (single interval overload should return list of points) -CqlIntervalOperatorsTest.Expand.ExpandPer2DaysIntervalOverload Wrong answer (single interval overload should return list of points) +# CqlIntervalOperatorsTest.Expand.ExpandPerDayIntervalOverload Wrong answer (single interval overload should return list of points) +# CqlIntervalOperatorsTest.Expand.ExpandPerHourIntervalOverload Wrong answer (single interval overload should return list of points) +# CqlIntervalOperatorsTest.Expand.ExpandPerHourOpenIntervalOverload Wrong answer (single interval overload should return list of points) +# CqlIntervalOperatorsTest.Expand.ExpandPer1IntervalOverload Wrong answer (single interval overload should return list of points) +# CqlIntervalOperatorsTest.Expand.ExpandPer1OpenIntervalOverload Wrong answer (single interval overload should return list of points) +# CqlIntervalOperatorsTest.Expand.ExpandPer2DaysIntervalOverload Wrong answer (single interval overload should return list of points) CqlIntervalOperatorsTest.Intersect.TestIntersectNull Wrong answer (Interval[5, 10] vs Interval[5, null)) CqlTypeOperatorsTest.Convert.StringToDateTime Wrong answer (different offsets) CqlTypeOperatorsTest.ToDateTime.ToDateTime1 Wrong answer (different offsets) From 2e9115da3fdacb3e738c3a4812265da96ffff23f Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 27 Aug 2026 09:49:13 -0400 Subject: [PATCH 15/19] just a couple more tests --- test/datatypes/datetime-test.ts | 4 + test/datatypes/decimal-test.ts | 5 + test/elm/aggregate/aggregate-test.ts | 9 + test/elm/aggregate/data.cql | 2 + test/elm/aggregate/data.js | 298 ++++++++++++++++++++++--- test/elm/arithmetic/arithmetic-test.ts | 4 + test/elm/arithmetic/data.cql | 1 + test/elm/arithmetic/data.js | 66 +++++- 8 files changed, 351 insertions(+), 38 deletions(-) diff --git a/test/datatypes/datetime-test.ts b/test/datatypes/datetime-test.ts index e771a8aca..cc3105cb3 100644 --- a/test/datatypes/datetime-test.ts +++ b/test/datatypes/datetime-test.ts @@ -219,6 +219,10 @@ describe('DateTime', () => { DateTime.fromJSDate(new Date(Date.UTC(1999, 1, 16, 13, 56, 24, 123)), +4.5).should.eql( DateTime.parse('1999-02-16T18:26:24.123+04:30') ); + DateTime.fromJSDate( + new Date(Date.UTC(1999, 1, 16, 13, 56, 24, 123)), + Decimal.from(-5) + ).should.eql(DateTime.parse('1999-02-16T08:56:24.123-05:00')); }); it('should construct from a Luxon DateTime', () => diff --git a/test/datatypes/decimal-test.ts b/test/datatypes/decimal-test.ts index e2682a5ae..171ca129d 100644 --- a/test/datatypes/decimal-test.ts +++ b/test/datatypes/decimal-test.ts @@ -42,6 +42,11 @@ describe('Decimal', () => { Decimal.from('8').log(2).should.equalDecimal(Decimal.from(3)); }); + it('should reject an invalid scale', () => { + (() => Decimal.from(1).setScale(-1)).should.throw(RangeError); + (() => Decimal.from(1).setScale(1.5)).should.throw(RangeError); + }); + it('should reject non-finite and divide-by-zero values', () => { (() => Decimal.from('not a number')).should.throw(); (() => Decimal.from(1).divideBy(0)).should.throw(); diff --git a/test/elm/aggregate/aggregate-test.ts b/test/elm/aggregate/aggregate-test.ts index 5f946e4cc..f9300bc0c 100644 --- a/test/elm/aggregate/aggregate-test.ts +++ b/test/elm/aggregate/aggregate-test.ts @@ -420,6 +420,15 @@ describe('Mode', () => { (await this.bi_modal.exec(this.ctx)).should.eql([2, 3]); }); + it('should preserve units for single and tied quantity modes', async function () { + validateQuantity(await this.quantitySingleMode.exec(this.ctx), 1, 'g'); + + const modes = await this.quantityBiModal.exec(this.ctx); + modes.should.have.length(2); + validateQuantity(modes[0], 1, 'g'); + validateQuantity(modes[1], 2, 'g'); + }); + it('should be null if some are numbers and some are quantities', async function () { should(await this.numbersAndQuantities.exec(this.ctx)).be.null(); }); diff --git a/test/elm/aggregate/data.cql b/test/elm/aggregate/data.cql index 61eeffe92..48c754ae4 100644 --- a/test/elm/aggregate/data.cql +++ b/test/elm/aggregate/data.cql @@ -112,6 +112,8 @@ define has_null: Mode({1,null,null,2,2}) define empty: Mode({}) define bi_modal: Mode({1,2,2,2,3,3,3,4,5}) +define QuantitySingleMode: Mode({1.0 'g', 1.0 'g', 2.0 'g'}) +define QuantityBiModal: Mode({1.0 'g', 1.0 'g', 2.0 'g', 2.0 'g'}) define NumbersAndQuantities: Mode({1 ,2 'ml',3 'ml',4 'ml',5 'ml',0 'ml'}) define IncompatibleUnitsNull: Mode({1 'mg/d', 0.002 '/d'}) diff --git a/test/elm/aggregate/data.js b/test/elm/aggregate/data.js index 5f063253c..f3ef86c20 100644 --- a/test/elm/aggregate/data.js +++ b/test/elm/aggregate/data.js @@ -11421,6 +11421,8 @@ define has_null: Mode({1,null,null,2,2}) define empty: Mode({}) define bi_modal: Mode({1,2,2,2,3,3,3,4,5}) +define QuantitySingleMode: Mode({1.0 'g', 1.0 'g', 2.0 'g'}) +define QuantityBiModal: Mode({1.0 'g', 1.0 'g', 2.0 'g', 2.0 'g'}) define NumbersAndQuantities: Mode({1 ,2 'ml',3 'ml',4 'ml',5 'ml',0 'ml'}) define IncompatibleUnitsNull: Mode({1 'mg/d', 0.002 '/d'}) */ @@ -11437,7 +11439,7 @@ module.exports['Mode'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "331", + "r" : "364", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -12026,7 +12028,7 @@ module.exports['Mode'] = { }, { "localId" : "309", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", - "name" : "NumbersAndQuantities", + "name" : "QuantitySingleMode", "context" : "Patient", "accessLevel" : "Public", "annotation" : [ { @@ -12035,46 +12037,268 @@ module.exports['Mode'] = { "s" : { "r" : "309", "s" : [ { - "value" : [ "", "define ", "NumbersAndQuantities", ": " ] + "value" : [ "", "define ", "QuantitySingleMode", ": " ] }, { - "r" : "326", + "r" : "320", "s" : [ { "value" : [ "Mode", "(" ] }, { "r" : "310", "s" : [ { + "value" : [ "{" ] + }, { "r" : "311", - "value" : [ "{", "1", " ," ] + "s" : [ { + "value" : [ "1.0 ", "'g'" ] + } ] + }, { + "value" : [ ", " ] }, { "r" : "312", + "s" : [ { + "value" : [ "1.0 ", "'g'" ] + } ] + }, { + "value" : [ ", " ] + }, { + "r" : "313", + "s" : [ { + "value" : [ "2.0 ", "'g'" ] + } ] + }, { + "value" : [ "}" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Mode", + "localId" : "320", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ], + "signature" : [ { + "type" : "ListTypeSpecifier", + "localId" : "321", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "322", + "name" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ] + } + } ], + "source" : { + "type" : "List", + "localId" : "310", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "ListTypeSpecifier", + "localId" : "314", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "315", + "name" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ] + } + }, + "element" : [ { + "type" : "Quantity", + "localId" : "311", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 1.0, + "unit" : "g", + "annotation" : [ ] + }, { + "type" : "Quantity", + "localId" : "312", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 1.0, + "unit" : "g", + "annotation" : [ ] + }, { + "type" : "Quantity", + "localId" : "313", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 2.0, + "unit" : "g", + "annotation" : [ ] + } ] + } + } + }, { + "localId" : "325", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "name" : "QuantityBiModal", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "325", + "s" : [ { + "value" : [ "", "define ", "QuantityBiModal", ": " ] + }, { + "r" : "337", + "s" : [ { + "value" : [ "Mode", "(" ] + }, { + "r" : "326", + "s" : [ { + "value" : [ "{" ] + }, { + "r" : "327", + "s" : [ { + "value" : [ "1.0 ", "'g'" ] + } ] + }, { + "value" : [ ", " ] + }, { + "r" : "328", + "s" : [ { + "value" : [ "1.0 ", "'g'" ] + } ] + }, { + "value" : [ ", " ] + }, { + "r" : "329", + "s" : [ { + "value" : [ "2.0 ", "'g'" ] + } ] + }, { + "value" : [ ", " ] + }, { + "r" : "330", + "s" : [ { + "value" : [ "2.0 ", "'g'" ] + } ] + }, { + "value" : [ "}" ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } + } ], + "expression" : { + "type" : "Mode", + "localId" : "337", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ], + "signature" : [ { + "type" : "ListTypeSpecifier", + "localId" : "338", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "339", + "name" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ] + } + } ], + "source" : { + "type" : "List", + "localId" : "326", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "ListTypeSpecifier", + "localId" : "331", + "annotation" : [ ], + "elementType" : { + "type" : "NamedTypeSpecifier", + "localId" : "332", + "name" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ] + } + }, + "element" : [ { + "type" : "Quantity", + "localId" : "327", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 1.0, + "unit" : "g", + "annotation" : [ ] + }, { + "type" : "Quantity", + "localId" : "328", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 1.0, + "unit" : "g", + "annotation" : [ ] + }, { + "type" : "Quantity", + "localId" : "329", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 2.0, + "unit" : "g", + "annotation" : [ ] + }, { + "type" : "Quantity", + "localId" : "330", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 2.0, + "unit" : "g", + "annotation" : [ ] + } ] + } + } + }, { + "localId" : "342", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "name" : "NumbersAndQuantities", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "342", + "s" : [ { + "value" : [ "", "define ", "NumbersAndQuantities", ": " ] + }, { + "r" : "359", + "s" : [ { + "value" : [ "Mode", "(" ] + }, { + "r" : "343", + "s" : [ { + "r" : "344", + "value" : [ "{", "1", " ," ] + }, { + "r" : "345", "s" : [ { "value" : [ "2 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "313", + "r" : "346", "s" : [ { "value" : [ "3 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "314", + "r" : "347", "s" : [ { "value" : [ "4 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "315", + "r" : "348", "s" : [ { "value" : [ "5 ", "'ml'" ] } ] }, { "value" : [ "," ] }, { - "r" : "316", + "r" : "349", "s" : [ { "value" : [ "0 ", "'ml'" ] } ] @@ -12089,48 +12313,48 @@ module.exports['Mode'] = { } ], "expression" : { "type" : "Mode", - "localId" : "326", + "localId" : "359", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "327", + "localId" : "360", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "328", + "localId" : "361", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "310", + "localId" : "343", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "320", + "localId" : "353", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "321", + "localId" : "354", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "ToQuantity", - "localId" : "318", + "localId" : "351", "annotation" : [ ], "signature" : [ { "type" : "NamedTypeSpecifier", - "localId" : "319", + "localId" : "352", "name" : "{urn:hl7-org:elm-types:r1}Integer", "annotation" : [ ] } ], "operand" : { "type" : "Literal", - "localId" : "311", + "localId" : "344", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", "valueType" : "{urn:hl7-org:elm-types:r1}Integer", "value" : "1", @@ -12138,35 +12362,35 @@ module.exports['Mode'] = { } }, { "type" : "Quantity", - "localId" : "312", + "localId" : "345", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 2, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "313", + "localId" : "346", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 3, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "314", + "localId" : "347", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 4, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "315", + "localId" : "348", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 5, "unit" : "ml", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "316", + "localId" : "349", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0, "unit" : "ml", @@ -12175,7 +12399,7 @@ module.exports['Mode'] = { } } }, { - "localId" : "331", + "localId" : "364", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "name" : "IncompatibleUnitsNull", "context" : "Patient", @@ -12184,26 +12408,26 @@ module.exports['Mode'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "331", + "r" : "364", "s" : [ { "value" : [ "", "define ", "IncompatibleUnitsNull", ": " ] }, { - "r" : "341", + "r" : "374", "s" : [ { "value" : [ "Mode", "(" ] }, { - "r" : "332", + "r" : "365", "s" : [ { "value" : [ "{" ] }, { - "r" : "333", + "r" : "366", "s" : [ { "value" : [ "1 ", "'mg/d'" ] } ] }, { "value" : [ ", " ] }, { - "r" : "334", + "r" : "367", "s" : [ { "value" : [ "0.002 ", "'/d'" ] } ] @@ -12218,45 +12442,45 @@ module.exports['Mode'] = { } ], "expression" : { "type" : "Mode", - "localId" : "341", + "localId" : "374", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ], "signature" : [ { "type" : "ListTypeSpecifier", - "localId" : "342", + "localId" : "375", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "343", + "localId" : "376", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } } ], "source" : { "type" : "List", - "localId" : "332", + "localId" : "365", "annotation" : [ ], "resultTypeSpecifier" : { "type" : "ListTypeSpecifier", - "localId" : "335", + "localId" : "368", "annotation" : [ ], "elementType" : { "type" : "NamedTypeSpecifier", - "localId" : "336", + "localId" : "369", "name" : "{urn:hl7-org:elm-types:r1}Quantity", "annotation" : [ ] } }, "element" : [ { "type" : "Quantity", - "localId" : "333", + "localId" : "366", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 1, "unit" : "mg/d", "annotation" : [ ] }, { "type" : "Quantity", - "localId" : "334", + "localId" : "367", "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", "value" : 0.002, "unit" : "/d", diff --git a/test/elm/arithmetic/arithmetic-test.ts b/test/elm/arithmetic/arithmetic-test.ts index 092a36c7d..f3475e0c1 100644 --- a/test/elm/arithmetic/arithmetic-test.ts +++ b/test/elm/arithmetic/arithmetic-test.ts @@ -501,6 +501,10 @@ describe('TruncatedDivide', () => { it('should be able to return just the long portion of a dividing a long by an integer', async function () { (await this.tenDivThreeReverseMixed.exec(this.ctx)).should.equal(3n); }); + + it('should truncate quantity division results', async function () { + validateQuantity(await this.quantityTruncatedDivide.exec(this.ctx), 5, '1'); + }); }); describe('Truncate', () => { diff --git a/test/elm/arithmetic/data.cql b/test/elm/arithmetic/data.cql index 7d5e49457..6812f7c04 100644 --- a/test/elm/arithmetic/data.cql +++ b/test/elm/arithmetic/data.cql @@ -108,6 +108,7 @@ define Even: 9 div 3 define TenDivThreeLong: 10L div 3L define TenDivThreeMixed: 10 div 3L define TenDivThreeReverseMixed: 10L div 3 +define QuantityTruncatedDivide: 10.5 'g' div 2.0 'g' // @Test: Modulo define Mod: 3 mod 2 diff --git a/test/elm/arithmetic/data.js b/test/elm/arithmetic/data.js index ccfe850d1..b94eb9583 100644 --- a/test/elm/arithmetic/data.js +++ b/test/elm/arithmetic/data.js @@ -6808,6 +6808,7 @@ define Even: 9 div 3 define TenDivThreeLong: 10L div 3L define TenDivThreeMixed: 10 div 3L define TenDivThreeReverseMixed: 10L div 3 +define QuantityTruncatedDivide: 10.5 'g' div 2.0 'g' */ module.exports['TruncatedDivide'] = { @@ -6822,7 +6823,7 @@ module.exports['TruncatedDivide'] = { "type" : "Annotation", "t" : [ ], "s" : { - "r" : "249", + "r" : "260", "s" : [ { "value" : [ "", "library TestSnippet version '1'" ] } ] @@ -7186,6 +7187,69 @@ module.exports['TruncatedDivide'] = { } } ] } + }, { + "localId" : "260", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "name" : "QuantityTruncatedDivide", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "260", + "s" : [ { + "value" : [ "", "define ", "QuantityTruncatedDivide", ": " ] + }, { + "r" : "261", + "s" : [ { + "r" : "262", + "s" : [ { + "value" : [ "10.5 ", "'g'" ] + } ] + }, { + "value" : [ " div " ] + }, { + "r" : "263", + "s" : [ { + "value" : [ "2.0 ", "'g'" ] + } ] + } ] + } ] + } + } ], + "expression" : { + "type" : "TruncatedDivide", + "localId" : "261", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "264", + "name" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ] + }, { + "type" : "NamedTypeSpecifier", + "localId" : "265", + "name" : "{urn:hl7-org:elm-types:r1}Quantity", + "annotation" : [ ] + } ], + "operand" : [ { + "type" : "Quantity", + "localId" : "262", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 10.5, + "unit" : "g", + "annotation" : [ ] + }, { + "type" : "Quantity", + "localId" : "263", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Quantity", + "value" : 2.0, + "unit" : "g", + "annotation" : [ ] + } ] + } } ] } } From 965111f584321c987f48ebe81f9e75bb016ffefb Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 27 Aug 2026 11:26:30 -0400 Subject: [PATCH 16/19] more cleanup on manual review --- src/elm/interval.ts | 6 +++--- test/elm/convert/convert-test.ts | 1 - test/util/math-test.ts | 16 ++++++++-------- test/util/units-test.ts | 7 ------- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/elm/interval.ts b/src/elm/interval.ts index c7f53a91d..a60f7f080 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -481,12 +481,12 @@ export class Expand extends Expression { if (['time', 'date', 'datetime'].includes(type)) { expandFunction = this.expandDTishInterval; defaultPer = (interval: any) => new Quantity(1, interval.low.getPrecision()); - } else if (['integer', 'long', 'decimal'].includes(type)) { - expandFunction = this.expandNumericInterval; - defaultPer = (_interval: any) => new Quantity(1, '1'); } else if (['quantity'].includes(type)) { expandFunction = this.expandQuantityInterval; defaultPer = (interval: any) => new Quantity(1, interval.low.unit); + } else if (['integer', 'long', 'decimal'].includes(type)) { + expandFunction = this.expandNumericInterval; + defaultPer = (_interval: any) => new Quantity(1, '1'); } else { throw new Error('Interval list type not yet supported.'); } diff --git a/test/elm/convert/convert-test.ts b/test/elm/convert/convert-test.ts index 92a83bad6..f8986052c 100644 --- a/test/elm/convert/convert-test.ts +++ b/test/elm/convert/convert-test.ts @@ -373,7 +373,6 @@ describe('ToDecimal', () => { }); it('should be null if wrong format (+.1)', async function () { - // TODO: parseFloat is more forgiving than the CQL spec, so this does get converted should(await this.wrongFormat.exec(this.ctx)).be.null(); }); diff --git a/test/util/math-test.ts b/test/util/math-test.ts index 56305f115..ac82ebe3e 100644 --- a/test/util/math-test.ts +++ b/test/util/math-test.ts @@ -5,39 +5,39 @@ import { finalizeNumericResult, predecessor, successor } from '../../src/util/ma describe('successor', () => { it('should preserve integers in an Uncertainty', () => { - const result = successor(new Uncertainty(1.0, 2.0)); + const result = successor(new Uncertainty(1, 2)); result.low.should.equal(2); result.high.should.equal(3); }); it('should preserve decimals in an Uncertainty', () => { const result = successor(new Uncertainty(Decimal.from(1.0), Decimal.from(2.0))); - result.low.should.equalDecimal(Decimal.from(1.00000001)); - result.high.should.equalDecimal(Decimal.from(2.00000001)); + result.low.should.equalDecimal(Decimal.from('1.00000001')); + result.high.should.equalDecimal(Decimal.from('2.00000001')); }); it('should leave the uncertainty high unchanged when it overflows', () => { const result = successor(new Uncertainty(Decimal.from(1), MAX_FLOAT_VALUE)); - result.should.eql(new Uncertainty(Decimal.from(1.00000001), MAX_FLOAT_VALUE)); + result.should.eql(new Uncertainty(Decimal.from('1.00000001'), MAX_FLOAT_VALUE)); }); }); describe('predecessor', () => { it('should preserve integers in an Uncertainty', () => { - const result = successor(new Uncertainty(1.0, 2.0)); + const result = successor(new Uncertainty(1, 2)); result.low.should.equal(2); result.high.should.equal(3); }); it('should preserve decimals in an Uncertainty', () => { const result = successor(new Uncertainty(Decimal.from(1.0), Decimal.from(2.0))); - result.low.should.equalDecimal(Decimal.from(1.00000001)); - result.high.should.equalDecimal(Decimal.from(2.00000001)); + result.low.should.equalDecimal(Decimal.from('1.00000001')); + result.high.should.equalDecimal(Decimal.from('2.00000001')); }); it('should leave the uncertainty low unchanged when it underflows', () => { const result = predecessor(new Uncertainty(MIN_FLOAT_VALUE, Decimal.from(2))); - result.should.eql(new Uncertainty(MIN_FLOAT_VALUE, Decimal.from(1.99999999))); + result.should.eql(new Uncertainty(MIN_FLOAT_VALUE, Decimal.from('1.99999999'))); }); }); diff --git a/test/util/units-test.ts b/test/util/units-test.ts index 3550a6f25..21c8771c9 100644 --- a/test/util/units-test.ts +++ b/test/util/units-test.ts @@ -135,13 +135,6 @@ describe('convertUnit', () => { result.should.equalDecimal(Decimal.from('0.00018939')); }); - // it('should not truncate precision to 8 decimals when adjustPrecision is false', () => { - // const result = convertUnit(Decimal.from(1), '[ft_i]', '[mi_i]', false); - // result.should.not.equalDecimal(Decimal.from("0.00018939")); - // result.toString().length.should.be.greaterThan(10); - // result.toString().should.startWith('0.000189393939393'); - // }); - it('should return undefined for incompatible units', () => { should(convertUnit(Decimal.from(18), '[in_i]', '[in_i]2')).be.undefined(); }); From 937b982d784a3e902908a7e6ca97bfe487e0c41c Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 27 Aug 2026 11:30:53 -0400 Subject: [PATCH 17/19] one more --- src/elm/interval.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elm/interval.ts b/src/elm/interval.ts index a60f7f080..d4288fb70 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -484,7 +484,7 @@ export class Expand extends Expression { } else if (['quantity'].includes(type)) { expandFunction = this.expandQuantityInterval; defaultPer = (interval: any) => new Quantity(1, interval.low.unit); - } else if (['integer', 'long', 'decimal'].includes(type)) { + } else if (['long', 'integer', 'decimal'].includes(type)) { expandFunction = this.expandNumericInterval; defaultPer = (_interval: any) => new Quantity(1, '1'); } else { From f26d5fb22679a6adbe3c1e5a1d30906ff0826d86 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 27 Aug 2026 11:36:15 -0400 Subject: [PATCH 18/19] forgot to save this file --- test/spec-tests/skip-list.txt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/spec-tests/skip-list.txt b/test/spec-tests/skip-list.txt index ee19feabc..642e4f220 100644 --- a/test/spec-tests/skip-list.txt +++ b/test/spec-tests/skip-list.txt @@ -29,17 +29,7 @@ CqlIntervalOperatorsTest.PointFrom.TestPointFromNull Wrong output: In CqlComparisonOperatorsTest.Equal.DateTimeEqNull Wrong answer (true vs null - due to not evaluating DateTime(null) as null) CqlIntervalOperatorsTest.Collapse.TestCollapseNull Wrong answer (Interval(null, null) vs null) CqlIntervalOperatorsTest.Except.NullInterval Wrong answer (Interval(null, null) vs null) -# CqlIntervalOperatorsTest.Expand.ExpandIntegerIntervalOverload Wrong answer (single interval overload should return list of points) -# CqlIntervalOperatorsTest.Expand.ExpandIntegerOpenIntervalOverload Wrong answer (single interval overload should return list of points) -# CqlIntervalOperatorsTest.Expand.ExpandIntervalOpenPer2IntervalOverload Wrong answer (single interval overload should return list of points) -# CqlIntervalOperatorsTest.Expand.ExpandIntervalPer2IntervalOverload Wrong answer (single interval overload should return list of points) CqlIntervalOperatorsTest.Expand.ExpandListWithNull Wrong answer (should be empty list due to removing nulls) -# CqlIntervalOperatorsTest.Expand.ExpandPerDayIntervalOverload Wrong answer (single interval overload should return list of points) -# CqlIntervalOperatorsTest.Expand.ExpandPerHourIntervalOverload Wrong answer (single interval overload should return list of points) -# CqlIntervalOperatorsTest.Expand.ExpandPerHourOpenIntervalOverload Wrong answer (single interval overload should return list of points) -# CqlIntervalOperatorsTest.Expand.ExpandPer1IntervalOverload Wrong answer (single interval overload should return list of points) -# CqlIntervalOperatorsTest.Expand.ExpandPer1OpenIntervalOverload Wrong answer (single interval overload should return list of points) -# CqlIntervalOperatorsTest.Expand.ExpandPer2DaysIntervalOverload Wrong answer (single interval overload should return list of points) CqlIntervalOperatorsTest.Intersect.TestIntersectNull Wrong answer (Interval[5, 10] vs Interval[5, null)) CqlTypeOperatorsTest.Convert.StringToDateTime Wrong answer (different offsets) CqlTypeOperatorsTest.ToDateTime.ToDateTime1 Wrong answer (different offsets) From f913c70dec69fa17636ec3641ce8419d835aae9b Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 27 Aug 2026 12:06:57 -0400 Subject: [PATCH 19/19] update some comments --- src/datatypes/decimal.ts | 4 ++++ test/elm/interval/interval-test.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/datatypes/decimal.ts b/src/datatypes/decimal.ts index e72e6eeb9..3a04017c5 100644 --- a/src/datatypes/decimal.ts +++ b/src/datatypes/decimal.ts @@ -107,10 +107,14 @@ export class Decimal { } successor() { + // TODO: successor should be based on current precision + // For Decimal, successor is equivalent to adding 1 * the precision of the argument. return new Decimal(this.value.add(MIN_PRECISION_VALUE)); } predecessor() { + // TODO: predecessor should be based on current precision + // For Decimal, predecessor is equivalent to subtracting 1 * the precision of the argument. return new Decimal(this.value.minus(MIN_PRECISION_VALUE)); } diff --git a/test/elm/interval/interval-test.ts b/test/elm/interval/interval-test.ts index 20bceedd0..77dc2c701 100644 --- a/test/elm/interval/interval-test.ts +++ b/test/elm/interval/interval-test.ts @@ -3605,8 +3605,8 @@ describe('IntegerIntervalExpand', () => { // https://jira.hl7.org/browse/FHIR-58705 and // https://chat.fhir.org/#narrow/channel/179220-cql/topic/Interval.20Expand.20example/with/619051021 // Note that as of this writing the produced result is { } (empty list) - // which I believe is the correct result. - // But an empty list doesn't clearly show the intent of the test. + // but I believe the correct answer is either { } or { [ Interval[10.0, 10.0 ] } + // depending on whether the size of the interval is based on the precision of the decimals (not currently supported) // define PerDecimalMorePrecise: expand { Interval[10, 10] } per 0.1 const a = await this.perDecimalMorePrecise.exec(this.ctx);