From 4d1d9f749c6d56fd1b5694cf5c4f7807ad335bd2 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 16 Jul 2026 17:40:50 -0400 Subject: [PATCH 1/8] Implement Interval PointFrom operator --- examples/browser/cql4browsers.js | 23 +- src/elm/interval.ts | 24 + test/elm/interval/data.cql | 10 + test/elm/interval/data.js | 855 ++++++++++++++++++ test/elm/interval/interval-test.ts | 38 + .../cql/CqlIntervalOperatorsTest.cql | 16 +- .../cql/CqlIntervalOperatorsTest.json | 342 ++++++- test/spec-tests/skip-list.txt | 1 - 8 files changed, 1250 insertions(+), 59 deletions(-) diff --git a/examples/browser/cql4browsers.js b/examples/browser/cql4browsers.js index 235dc3a55..6c00a421f 100644 --- a/examples/browser/cql4browsers.js +++ b/examples/browser/cql4browsers.js @@ -5085,7 +5085,7 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -exports.Collapse = exports.Expand = exports.Ends = exports.Starts = exports.End = exports.Start = exports.Size = exports.Width = exports.OverlapsBefore = exports.OverlapsAfter = exports.Overlaps = exports.MeetsBefore = exports.MeetsAfter = exports.Meets = exports.Interval = void 0; +exports.Collapse = exports.Expand = exports.Ends = exports.Starts = exports.End = exports.Start = exports.Size = exports.Width = exports.PointFrom = exports.OverlapsBefore = exports.OverlapsAfter = exports.Overlaps = exports.MeetsBefore = exports.MeetsAfter = exports.Meets = exports.Interval = void 0; exports.doContains = doContains; exports.doProperContains = doProperContains; exports.doIncludes = doIncludes; @@ -5101,6 +5101,7 @@ const math_1 = require("../util/math"); const units_1 = require("../util/units"); const dtivl = __importStar(require("../datatypes/interval")); const builder_1 = require("./builder"); +const comparison_1 = require("../util/comparison"); const elmTypes_1 = require("../util/elmTypes"); class Interval extends expression_1.Expression { constructor(json) { @@ -5263,6 +5264,24 @@ class OverlapsBefore extends expression_1.Expression { } } exports.OverlapsBefore = OverlapsBefore; +class PointFrom extends expression_1.Expression { + constructor(json) { + super(json); + } + async exec(ctx) { + const interval = await this.arg?.execute(ctx); + if (interval == null) { + return null; + } + const start = interval.start(); + const end = interval.end(); + if (start == end || (0, comparison_1.equals)(start, end)) { + return start; + } + throw new Error('PointFrom operator may only be used on an interval containing a single point.'); + } +} +exports.PointFrom = PointFrom; // Delegated to by overloaded#Union function doUnion(a, b) { return a.union(b); @@ -5848,7 +5867,7 @@ function truncateDecimal(decimal, decimalPlaces) { return parseFloat(decimal.toString().match(re)[0]); } -},{"../datatypes/interval":10,"../datatypes/quantity":12,"../util/elmTypes":55,"../util/math":57,"../util/units":58,"./builder":17,"./expression":23}],28:[function(require,module,exports){ +},{"../datatypes/interval":10,"../datatypes/quantity":12,"../util/comparison":53,"../util/elmTypes":55,"../util/math":57,"../util/units":58,"./builder":17,"./expression":23}],28:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Library = void 0; diff --git a/src/elm/interval.ts b/src/elm/interval.ts index 694d2fe94..509a0f5d8 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -5,6 +5,7 @@ import { convertUnit, compareUnits, convertToCQLDateUnit } from '../util/units'; import * as dtivl from '../datatypes/interval'; import { Context } from '../runtime/context'; import { build } from './builder'; +import { equals } from '../util/comparison'; import { ELM_NAMED_TYPE_SPECIFIER } from '../util/elmTypes'; export class Interval extends Expression { @@ -200,6 +201,29 @@ export class OverlapsBefore extends Expression { } } +export class PointFrom extends Expression { + constructor(json: any) { + super(json); + } + + async exec(ctx: Context) { + const interval = await this.arg?.execute(ctx); + if (interval == null) { + return null; + } + const start = interval.start(); + const end = interval.end(); + + if (start == end || equals(start, end)) { + return start; + } + + throw new Error( + 'PointFrom operator may only be used on an interval containing a single point.' + ); + } +} + // Delegated to by overloaded#Union export function doUnion(a: any, b: any) { return a.union(b); diff --git a/test/elm/interval/data.cql b/test/elm/interval/data.cql index f93cce9a6..ddcb47883 100644 --- a/test/elm/interval/data.cql +++ b/test/elm/interval/data.cql @@ -1008,6 +1008,16 @@ define OpenNotNull: end of Interval(DateTime(2012, 1, 1), DateTime(2013, 1, 1)) define OpenLongNotNull: end of Interval(1L, 3L) define OpenNull: end of Interval(DateTime(2013, 1, 1), null) +// @Test: PointFrom +define IntegerPoint: point from Interval[4, 4] +define IntegerOpenHighPoint: point from Interval[4, 5) +define LongPoint: point from Interval[4294967296L, 4294967296L] +define EmptyIntervalPoint: point from Interval(0, 0) +define DateTimePoint: point from Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 1)] +define NullPoint: point from (null as Interval) +define NonUnitPoint: point from Interval[1, 4] +define NullHighPoint: point from Interval[1, null] + // @Test: Starts define TestStartsNull: Interval[null, null] starts Interval[1, 10] define IntegerIntervalStartsTrue: Interval[4,10] starts Interval[4, 15] diff --git a/test/elm/interval/data.js b/test/elm/interval/data.js index c1949a706..03d4ca9bc 100644 --- a/test/elm/interval/data.js +++ b/test/elm/interval/data.js @@ -190032,6 +190032,861 @@ module.exports['End'] = { } } +/* PointFrom +library TestSnippet version '1' +using Simple version '1.0.0' +context Patient +define IntegerPoint: point from Interval[4, 4] +define IntegerOpenHighPoint: point from Interval[4, 5) +define LongPoint: point from Interval[4294967296L, 4294967296L] +define EmptyIntervalPoint: point from Interval(0, 0) +define DateTimePoint: point from Interval[DateTime(2012, 1, 1), DateTime(2012, 1, 1)] +define NullPoint: point from (null as Interval) +define NonUnitPoint: point from Interval[1, 4] +define NullHighPoint: point from Interval[1, null] +*/ + +module.exports['PointFrom'] = { + "library" : { + "localId" : "0", + "annotation" : [ { + "type" : "CqlToElmInfo", + "translatorVersion" : "4.2.0", + "translatorOptions" : "EnableDateRangeOptimization,EnableAnnotations,EnableResultTypes", + "signatureLevel" : "All" + }, { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "318", + "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", + "annotation" : [ ], + "signature" : [ ], + "operand" : { + "type" : "Retrieve", + "localId" : "208", + "dataType" : "{https://github.com/cqframework/cql-execution/simple}Patient", + "annotation" : [ ], + "include" : [ ], + "codeFilter" : [ ], + "dateFilter" : [ ], + "otherFilter" : [ ] + } + } + }, { + "localId" : "214", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "name" : "IntegerPoint", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "214", + "s" : [ { + "value" : [ "", "define ", "IntegerPoint", ": " ] + }, { + "r" : "215", + "s" : [ { + "value" : [ "point from " ] + }, { + "r" : "218", + "s" : [ { + "r" : "216", + "value" : [ "Interval[", "4", ", ", "4", "]" ] + } ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PointFrom", + "localId" : "215", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ], + "signature" : [ { + "type" : "IntervalTypeSpecifier", + "localId" : "221", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "222", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + } ], + "operand" : { + "type" : "Interval", + "localId" : "218", + "lowClosed" : true, + "highClosed" : true, + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "IntervalTypeSpecifier", + "localId" : "219", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "220", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + }, + "low" : { + "type" : "Literal", + "localId" : "216", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "4", + "annotation" : [ ] + }, + "high" : { + "type" : "Literal", + "localId" : "217", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "4", + "annotation" : [ ] + } + } + } + }, { + "localId" : "225", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "name" : "IntegerOpenHighPoint", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "225", + "s" : [ { + "value" : [ "", "define ", "IntegerOpenHighPoint", ": " ] + }, { + "r" : "226", + "s" : [ { + "value" : [ "point from " ] + }, { + "r" : "229", + "s" : [ { + "r" : "227", + "value" : [ "Interval[", "4", ", ", "5", ")" ] + } ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PointFrom", + "localId" : "226", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ], + "signature" : [ { + "type" : "IntervalTypeSpecifier", + "localId" : "232", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "233", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + } ], + "operand" : { + "type" : "Interval", + "localId" : "229", + "lowClosed" : true, + "highClosed" : false, + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "IntervalTypeSpecifier", + "localId" : "230", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "231", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + }, + "low" : { + "type" : "Literal", + "localId" : "227", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "4", + "annotation" : [ ] + }, + "high" : { + "type" : "Literal", + "localId" : "228", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "5", + "annotation" : [ ] + } + } + } + }, { + "localId" : "236", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Long", + "name" : "LongPoint", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "236", + "s" : [ { + "value" : [ "", "define ", "LongPoint", ": " ] + }, { + "r" : "237", + "s" : [ { + "value" : [ "point from " ] + }, { + "r" : "240", + "s" : [ { + "r" : "238", + "value" : [ "Interval[", "4294967296L", ", ", "4294967296L", "]" ] + } ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PointFrom", + "localId" : "237", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Long", + "annotation" : [ ], + "signature" : [ { + "type" : "IntervalTypeSpecifier", + "localId" : "243", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "244", + "name" : "{urn:hl7-org:elm-types:r1}Long", + "annotation" : [ ] + } + } ], + "operand" : { + "type" : "Interval", + "localId" : "240", + "lowClosed" : true, + "highClosed" : true, + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "IntervalTypeSpecifier", + "localId" : "241", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "242", + "name" : "{urn:hl7-org:elm-types:r1}Long", + "annotation" : [ ] + } + }, + "low" : { + "type" : "Literal", + "localId" : "238", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Long", + "valueType" : "{urn:hl7-org:elm-types:r1}Long", + "value" : "4294967296", + "annotation" : [ ] + }, + "high" : { + "type" : "Literal", + "localId" : "239", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Long", + "valueType" : "{urn:hl7-org:elm-types:r1}Long", + "value" : "4294967296", + "annotation" : [ ] + } + } + } + }, { + "localId" : "247", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "name" : "EmptyIntervalPoint", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "247", + "s" : [ { + "value" : [ "", "define ", "EmptyIntervalPoint", ": " ] + }, { + "r" : "248", + "s" : [ { + "value" : [ "point from " ] + }, { + "r" : "251", + "s" : [ { + "r" : "249", + "value" : [ "Interval(", "0", ", ", "0", ")" ] + } ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PointFrom", + "localId" : "248", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ], + "signature" : [ { + "type" : "IntervalTypeSpecifier", + "localId" : "254", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "255", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + } ], + "operand" : { + "type" : "Interval", + "localId" : "251", + "lowClosed" : false, + "highClosed" : false, + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "IntervalTypeSpecifier", + "localId" : "252", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "253", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + }, + "low" : { + "type" : "Literal", + "localId" : "249", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "0", + "annotation" : [ ] + }, + "high" : { + "type" : "Literal", + "localId" : "250", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "0", + "annotation" : [ ] + } + } + } + }, { + "localId" : "258", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}DateTime", + "name" : "DateTimePoint", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "258", + "s" : [ { + "value" : [ "", "define ", "DateTimePoint", ": " ] + }, { + "r" : "259", + "s" : [ { + "value" : [ "point from " ] + }, { + "r" : "284", + "s" : [ { + "value" : [ "Interval[" ] + }, { + "r" : "268", + "s" : [ { + "r" : "260", + "value" : [ "DateTime", "(", "2012", ", ", "1", ", ", "1", ")" ] + } ] + }, { + "value" : [ ", " ] + }, { + "r" : "280", + "s" : [ { + "r" : "272", + "value" : [ "DateTime", "(", "2012", ", ", "1", ", ", "1", ")" ] + } ] + }, { + "value" : [ "]" ] + } ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PointFrom", + "localId" : "259", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}DateTime", + "annotation" : [ ], + "signature" : [ { + "type" : "IntervalTypeSpecifier", + "localId" : "287", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "288", + "name" : "{urn:hl7-org:elm-types:r1}DateTime", + "annotation" : [ ] + } + } ], + "operand" : { + "type" : "Interval", + "localId" : "284", + "lowClosed" : true, + "highClosed" : true, + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "IntervalTypeSpecifier", + "localId" : "285", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "286", + "name" : "{urn:hl7-org:elm-types:r1}DateTime", + "annotation" : [ ] + } + }, + "low" : { + "type" : "DateTime", + "localId" : "268", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}DateTime", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "269", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + }, { + "type" : "NamedTypeSpecifier", + "localId" : "270", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + }, { + "type" : "NamedTypeSpecifier", + "localId" : "271", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } ], + "year" : { + "type" : "Literal", + "localId" : "260", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "2012", + "annotation" : [ ] + }, + "month" : { + "type" : "Literal", + "localId" : "261", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "1", + "annotation" : [ ] + }, + "day" : { + "type" : "Literal", + "localId" : "262", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "1", + "annotation" : [ ] + } + }, + "high" : { + "type" : "DateTime", + "localId" : "280", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}DateTime", + "annotation" : [ ], + "signature" : [ { + "type" : "NamedTypeSpecifier", + "localId" : "281", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + }, { + "type" : "NamedTypeSpecifier", + "localId" : "282", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + }, { + "type" : "NamedTypeSpecifier", + "localId" : "283", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } ], + "year" : { + "type" : "Literal", + "localId" : "272", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "2012", + "annotation" : [ ] + }, + "month" : { + "type" : "Literal", + "localId" : "273", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "1", + "annotation" : [ ] + }, + "day" : { + "type" : "Literal", + "localId" : "274", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "1", + "annotation" : [ ] + } + } + } + } + }, { + "localId" : "291", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "name" : "NullPoint", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "291", + "s" : [ { + "value" : [ "", "define ", "NullPoint", ": " ] + }, { + "r" : "292", + "s" : [ { + "value" : [ "point from " ] + }, { + "r" : "293", + "s" : [ { + "value" : [ "(" ] + }, { + "r" : "293", + "s" : [ { + "r" : "294", + "value" : [ "null", " as " ] + }, { + "r" : "295", + "s" : [ { + "value" : [ "Interval<" ] + }, { + "r" : "296", + "s" : [ { + "value" : [ "Integer" ] + } ] + }, { + "value" : [ ">" ] + } ] + } ] + }, { + "value" : [ ")" ] + } ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PointFrom", + "localId" : "292", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ], + "signature" : [ { + "type" : "IntervalTypeSpecifier", + "localId" : "303", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "304", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + } ], + "operand" : { + "type" : "As", + "localId" : "293", + "strict" : false, + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "IntervalTypeSpecifier", + "localId" : "301", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "302", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + }, + "signature" : [ ], + "operand" : { + "type" : "Null", + "localId" : "294", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", + "annotation" : [ ] + }, + "asTypeSpecifier" : { + "type" : "IntervalTypeSpecifier", + "localId" : "295", + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "IntervalTypeSpecifier", + "localId" : "297", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "298", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + }, + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "296", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + } + } + } + }, { + "localId" : "307", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "name" : "NonUnitPoint", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "307", + "s" : [ { + "value" : [ "", "define ", "NonUnitPoint", ": " ] + }, { + "r" : "308", + "s" : [ { + "value" : [ "point from " ] + }, { + "r" : "311", + "s" : [ { + "r" : "309", + "value" : [ "Interval[", "1", ", ", "4", "]" ] + } ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PointFrom", + "localId" : "308", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ], + "signature" : [ { + "type" : "IntervalTypeSpecifier", + "localId" : "314", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "315", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + } ], + "operand" : { + "type" : "Interval", + "localId" : "311", + "lowClosed" : true, + "highClosed" : true, + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "IntervalTypeSpecifier", + "localId" : "312", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "313", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + }, + "low" : { + "type" : "Literal", + "localId" : "309", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "1", + "annotation" : [ ] + }, + "high" : { + "type" : "Literal", + "localId" : "310", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "4", + "annotation" : [ ] + } + } + } + }, { + "localId" : "318", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "name" : "NullHighPoint", + "context" : "Patient", + "accessLevel" : "Public", + "annotation" : [ { + "type" : "Annotation", + "t" : [ ], + "s" : { + "r" : "318", + "s" : [ { + "value" : [ "", "define ", "NullHighPoint", ": " ] + }, { + "r" : "319", + "s" : [ { + "value" : [ "point from " ] + }, { + "r" : "322", + "s" : [ { + "r" : "320", + "value" : [ "Interval[", "1", ", ", "null", "]" ] + } ] + } ] + } ] + } + } ], + "expression" : { + "type" : "PointFrom", + "localId" : "319", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ], + "signature" : [ { + "type" : "IntervalTypeSpecifier", + "localId" : "326", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "327", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + } ], + "operand" : { + "type" : "Interval", + "localId" : "322", + "lowClosed" : true, + "highClosed" : true, + "annotation" : [ ], + "resultTypeSpecifier" : { + "type" : "IntervalTypeSpecifier", + "localId" : "324", + "annotation" : [ ], + "pointType" : { + "type" : "NamedTypeSpecifier", + "localId" : "325", + "name" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ] + } + }, + "low" : { + "type" : "Literal", + "localId" : "320", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Integer", + "valueType" : "{urn:hl7-org:elm-types:r1}Integer", + "value" : "1", + "annotation" : [ ] + }, + "high" : { + "type" : "As", + "localId" : "323", + "asType" : "{urn:hl7-org:elm-types:r1}Integer", + "annotation" : [ ], + "signature" : [ ], + "operand" : { + "type" : "Null", + "localId" : "321", + "resultTypeName" : "{urn:hl7-org:elm-types:r1}Any", + "annotation" : [ ] + } + } + } + } + } ] + } + } +} + /* Starts library TestSnippet version '1' using Simple version '1.0.0' diff --git a/test/elm/interval/interval-test.ts b/test/elm/interval/interval-test.ts index 07848d9cd..2cfa20214 100644 --- a/test/elm/interval/interval-test.ts +++ b/test/elm/interval/interval-test.ts @@ -1776,6 +1776,44 @@ describe('End', () => { }); }); +describe('PointFrom', () => { + beforeEach(function () { + setup(this, data); + }); + + it('should return the point from a unit interval', async function () { + (await this.integerPoint.exec(this.ctx)).should.equal(4); + }); + + it('should return the point from an interval with an open high bound', async function () { + (await this.integerOpenHighPoint.exec(this.ctx)).should.equal(4); + }); + + it('should return the point from a unit interval of type Long', async function () { + (await this.longPoint.exec(this.ctx)).should.equal(4294967296n); + }); + + it('should return the point from a unit interval of type DateTime', async function () { + (await this.dateTimePoint.exec(this.ctx)).should.eql(new DateTime(2012, 1, 1)); + }); + + it('should return null for a null interval', async function () { + should(await this.nullPoint.exec(this.ctx)).be.null(); + }); + + it('should throw for an interval with more than one point', function () { + return this.nonUnitPoint.exec(this.ctx).should.be.rejected(); + }); + + it('should throw for an interval with null high bound', function () { + return this.nullHighPoint.exec(this.ctx).should.be.rejected(); + }); + + it('should throw for an interval with undefined width', function () { + return this.emptyIntervalPoint.exec(this.ctx).should.be.rejected(); + }); +}); + describe('Starts', () => { beforeEach(function () { setup(this, data); diff --git a/test/spec-tests/cql/CqlIntervalOperatorsTest.cql b/test/spec-tests/cql/CqlIntervalOperatorsTest.cql index 9d88364c3..7bd967ba6 100644 --- a/test/spec-tests/cql/CqlIntervalOperatorsTest.cql +++ b/test/spec-tests/cql/CqlIntervalOperatorsTest.cql @@ -1403,29 +1403,21 @@ define "OverlapsAfter": Tuple{ define "PointFrom": Tuple{ "TestPointFromNull": Tuple{ - skipped: 'PointFrom not implemented' - /* expression: point from Interval[null, null], output: null - */ }, + }, "TestPointFromInteger": Tuple{ - skipped: 'PointFrom not implemented' - /* expression: point from Interval[1, 1], output: 1 - */ }, + }, "TestPointFromDecimal": Tuple{ - skipped: 'PointFrom not implemented' - /* expression: point from Interval[1.0, 1.0], output: 1.0 - */ }, + }, "TestPointFromQuantity": Tuple{ - skipped: 'PointFrom not implemented' - /* expression: point from Interval[1.0 'cm', 1.0 'cm'], output: 1.0'cm' - */ } + } } define "ProperContains": Tuple{ diff --git a/test/spec-tests/cql/CqlIntervalOperatorsTest.json b/test/spec-tests/cql/CqlIntervalOperatorsTest.json index 639e38a3e..72083c1d9 100644 --- a/test/spec-tests/cql/CqlIntervalOperatorsTest.json +++ b/test/spec-tests/cql/CqlIntervalOperatorsTest.json @@ -68012,11 +68012,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", "annotation": [] } } @@ -68031,11 +68040,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", "annotation": [] } } @@ -68050,11 +68068,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", "annotation": [] } } @@ -68069,11 +68096,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Quantity", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Quantity", "annotation": [] } } @@ -68097,11 +68133,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", "annotation": [] } } @@ -68116,11 +68161,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", "annotation": [] } } @@ -68135,11 +68189,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", "annotation": [] } } @@ -68154,11 +68217,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Quantity", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Quantity", "annotation": [] } } @@ -68178,11 +68250,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", "annotation": [] } } @@ -68190,12 +68271,44 @@ }, "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": "PointFrom not implemented", + "type": "PointFrom", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [], + "signature": [], + "operand": { + "type": "Interval", + "lowClosed": true, + "highClosed": true, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + }, + "low": { + "type": "Null", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + }, + "high": { + "type": "Null", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Any", + "annotation": [] + } + } + } + }, + { + "name": "output", + "value": { + "type": "Null", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Any", "annotation": [] } } @@ -68212,11 +68325,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Integer", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Integer", "annotation": [] } } @@ -68224,12 +68346,50 @@ }, "element": [ { - "name": "skipped", + "name": "expression", + "value": { + "type": "PointFrom", + "resultTypeName": "{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": "1", + "annotation": [] + } + } + } + }, + { + "name": "output", "value": { "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "PointFrom not implemented", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Integer", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", "annotation": [] } } @@ -68246,11 +68406,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Decimal", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Decimal", "annotation": [] } } @@ -68258,12 +68427,50 @@ }, "element": [ { - "name": "skipped", + "name": "expression", + "value": { + "type": "PointFrom", + "resultTypeName": "{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": "1.0", + "annotation": [] + }, + "high": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Decimal", + "valueType": "{urn:hl7-org:elm-types:r1}Decimal", + "value": "1.0", + "annotation": [] + } + } + } + }, + { + "name": "output", "value": { "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "PointFrom not implemented", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Decimal", + "valueType": "{urn:hl7-org:elm-types:r1}Decimal", + "value": "1.0", "annotation": [] } } @@ -68280,11 +68487,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}Quantity", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Quantity", "annotation": [] } } @@ -68292,12 +68508,50 @@ }, "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": "PointFrom not implemented", + "type": "PointFrom", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", + "annotation": [], + "signature": [], + "operand": { + "type": "Interval", + "lowClosed": true, + "highClosed": true, + "annotation": [], + "resultTypeSpecifier": { + "type": "IntervalTypeSpecifier", + "annotation": [], + "pointType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}Quantity", + "annotation": [] + } + }, + "low": { + "type": "Quantity", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", + "value": 1, + "unit": "cm", + "annotation": [] + }, + "high": { + "type": "Quantity", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", + "value": 1, + "unit": "cm", + "annotation": [] + } + } + } + }, + { + "name": "output", + "value": { + "type": "Quantity", + "resultTypeName": "{urn:hl7-org:elm-types:r1}Quantity", + "value": 1, + "unit": "cm", "annotation": [] } } @@ -81616,11 +81870,11 @@ "annotation": [], "resultTypeSpecifier": { "type": "IntervalTypeSpecifier", - "localId": "15143", + "localId": "15187", "annotation": [], "pointType": { "type": "NamedTypeSpecifier", - "localId": "15144", + "localId": "15188", "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 b11c6d708..947381938 100644 --- a/test/spec-tests/skip-list.txt +++ b/test/spec-tests/skip-list.txt @@ -52,7 +52,6 @@ ValueLiteralsAndSelectors.Decimal.DecimalNeg10Pow28ToZeroOneStepDecimalMinValue CqlArithmeticFunctionsTest.HighBoundary HighBoundary not implemented CqlArithmeticFunctionsTest.LowBoundary LowBoundary not implemented CqlArithmeticFunctionsTest.Precision.PrecisionDecimal Precision for Decimal not implemented -CqlIntervalOperatorsTest.PointFrom PointFrom not implemented CqlListOperatorsTest.Descendents Descendents not implemented # Unimplemented (New in CQL 1.5) From 16de73eba616aa3821a829c77a3f05d8a189f3d6 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 23 Jul 2026 10:06:29 -0400 Subject: [PATCH 2/8] Migrate implementation of interval pointFrom from elm layer to datatype --- src/datatypes/interval.ts | 11 +++++++++++ src/elm/interval.ts | 12 +----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/datatypes/interval.ts b/src/datatypes/interval.ts index dde392905..4d9aae0b5 100644 --- a/src/datatypes/interval.ts +++ b/src/datatypes/interval.ts @@ -647,6 +647,17 @@ export class Interval { } } + pointFrom() { + const start = this.start(); + if (cmp.equals(start, this.end())) { + return start; + } + + throw new Error( + 'PointFrom operator may only be used on an interval containing a single point.' + ); + } + toString() { const start = this.lowClosed ? '[' : '('; const end = this.highClosed ? ']' : ')'; diff --git a/src/elm/interval.ts b/src/elm/interval.ts index 509a0f5d8..e176d8884 100644 --- a/src/elm/interval.ts +++ b/src/elm/interval.ts @@ -5,7 +5,6 @@ import { convertUnit, compareUnits, convertToCQLDateUnit } from '../util/units'; import * as dtivl from '../datatypes/interval'; import { Context } from '../runtime/context'; import { build } from './builder'; -import { equals } from '../util/comparison'; import { ELM_NAMED_TYPE_SPECIFIER } from '../util/elmTypes'; export class Interval extends Expression { @@ -211,16 +210,7 @@ export class PointFrom extends Expression { if (interval == null) { return null; } - const start = interval.start(); - const end = interval.end(); - - if (start == end || equals(start, end)) { - return start; - } - - throw new Error( - 'PointFrom operator may only be used on an interval containing a single point.' - ); + return interval.pointFrom(); } } From 50afee82dd3e5337ce32c9f596c6a1055b93e8f7 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 23 Jul 2026 10:07:32 -0400 Subject: [PATCH 3/8] add extra tests --- test/datatypes/interval-data.ts | 4 ++ test/datatypes/interval-test.ts | 91 +++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/test/datatypes/interval-data.ts b/test/datatypes/interval-data.ts index 15bac1833..253ab9a0e 100644 --- a/test/datatypes/interval-data.ts +++ b/test/datatypes/interval-data.ts @@ -89,6 +89,10 @@ export default () => { DateTime.parse('2012-01-01T00:00:00.0'), DateTime.parse('2012-07-01T00:00:00.0') ); + data['unitIvl'] = new TestInterval( + DateTime.parse('2012-01-01T00:00:00.0'), + DateTime.parse('2012-01-01T00:00:00.0') + ); data['bef2012'] = TestDateTime.parse('2011-06-01T00:00:00.0'); data['beg2012'] = TestDateTime.parse('2012-01-01T00:00:00.0'); data['mid2012'] = TestDateTime.parse('2012-06-01T00:00:00.0'); diff --git a/test/datatypes/interval-test.ts b/test/datatypes/interval-test.ts index e73e395c1..49d9439c1 100644 --- a/test/datatypes/interval-test.ts +++ b/test/datatypes/interval-test.ts @@ -1933,6 +1933,33 @@ describe('DateTimeInterval', () => { x.toYear.meetsBefore(y.closed).should.be.false(); }); }); + + describe('pointFrom', () => { + it('should return the point value for a unit interval', () => { + const point = DateTime.parse('2012-01-01T00:00:00.0+00'); + const ivl = new Interval(point, point); + + ivl.pointFrom().should.eql(point); + }); + + it('should throw on pointFrom call if not a unit interval', () => { + const ivl = new Interval( + DateTime.parse('2012-01-01T00:00:00.0+00'), + DateTime.parse('2025-01-01T00:00:00.0+00') + ); + should(() => ivl.pointFrom()).throw(Error); + }); + + it('should throw for an interval with null high bound', function () { + const ivl = new Interval(DateTime.parse('2012-01-01T00:00:00.0+00'), null); + should(() => ivl.pointFrom()).throw(Error); + }); + + it('should throw for an interval with undefined width', function () { + const ivl = new Interval(0, 0, false, false); + should(() => ivl.pointFrom()).throw(Error); + }); + }); }); describe('IntegerInterval', () => { @@ -3713,6 +3740,43 @@ describe('IntegerInterval', () => { uIvl.meetsBefore(uIvl).should.be.false(); }); }); + + describe('pointFrom', () => { + it('should return the point value for a unit interval', () => { + const ivl = new Interval(0, 0); + ivl.pointFrom().should.eql(0); + }); + + it('should throw on pointFrom call if not a unit interval', () => { + const ivl = new Interval(0, 1); + should(() => ivl.pointFrom()).throw(Error); + }); + + it('should return the point value for a unit interval', () => { + const ivl = new Interval(12, 12); + ivl.pointFrom().should.eql(12); + }); + + it('should throw on pointFrom call if not a unit interval', () => { + const ivl = new Interval(8, 9); + should(() => ivl.pointFrom()).throw(Error); + }); + + it('should return the point from an interval with an open high bound', async function () { + const ivl = new Interval(100, 101, true, false); + ivl.pointFrom().should.eql(100); + }); + + it('should throw for an interval with null high bound', function () { + const ivl = new Interval(23, null); + should(() => ivl.pointFrom()).throw(Error); + }); + + it('should throw for an interval with undefined width', function () { + const ivl = new Interval(0, 0, false, false); + should(() => ivl.pointFrom()).throw(Error); + }); + }); }); describe('LongInterval', () => { @@ -5493,6 +5557,33 @@ describe('LongInterval', () => { uIvl.meetsBefore(uIvl).should.be.false(); }); }); + + describe('pointFrom', () => { + it('should return the point value for a unit interval', () => { + const ivl = new Interval(10n, 10n); + ivl.pointFrom().should.eql(10n); + }); + + it('should throw on pointFrom call if not a unit interval', () => { + const ivl = new Interval(0n, 1n); + should(() => ivl.pointFrom()).throw(Error); + }); + + it('should return the point from an interval with an open high bound', async function () { + const ivl = new Interval(5n, 6n, true, false); + ivl.pointFrom().should.eql(5n); + }); + + it('should throw for an interval with null high bound', function () { + const ivl = new Interval(0n, null); + should(() => ivl.pointFrom()).throw(Error); + }); + + it('should throw for an interval with undefined width', function () { + const ivl = new Interval(0n, 0n, false, false); + should(() => ivl.pointFrom()).throw(Error); + }); + }); }); describe('DecimalInterval', () => { From 6df77e13c4555763dd9c39bdbc5b5297581acc34 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 23 Jul 2026 10:08:12 -0400 Subject: [PATCH 4/8] Skip incorrect TestPointFromNull test --- .../cql/CqlIntervalOperatorsTest.cql | 4 +- .../cql/CqlIntervalOperatorsTest.json | 85 +++---------------- test/spec-tests/skip-list.txt | 1 + 3 files changed, 17 insertions(+), 73 deletions(-) diff --git a/test/spec-tests/cql/CqlIntervalOperatorsTest.cql b/test/spec-tests/cql/CqlIntervalOperatorsTest.cql index 7bd967ba6..17c7fe344 100644 --- a/test/spec-tests/cql/CqlIntervalOperatorsTest.cql +++ b/test/spec-tests/cql/CqlIntervalOperatorsTest.cql @@ -1403,9 +1403,11 @@ define "OverlapsAfter": Tuple{ define "PointFrom": Tuple{ "TestPointFromNull": Tuple{ + skipped: 'Wrong output: Interval[null, null] is not a unit interval, nor is it null' + /* expression: point from Interval[null, null], output: null - }, + */ }, "TestPointFromInteger": Tuple{ expression: point from Interval[1, 1], output: 1 diff --git a/test/spec-tests/cql/CqlIntervalOperatorsTest.json b/test/spec-tests/cql/CqlIntervalOperatorsTest.json index 72083c1d9..adf143535 100644 --- a/test/spec-tests/cql/CqlIntervalOperatorsTest.json +++ b/test/spec-tests/cql/CqlIntervalOperatorsTest.json @@ -68012,20 +68012,11 @@ "annotation": [], "element": [ { - "name": "expression", - "annotation": [], - "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Any", - "annotation": [] - } - }, - { - "name": "output", + "name": "skipped", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Any", + "name": "{urn:hl7-org:elm-types:r1}String", "annotation": [] } } @@ -68133,20 +68124,11 @@ "annotation": [], "element": [ { - "name": "expression", - "annotation": [], - "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Any", - "annotation": [] - } - }, - { - "name": "output", + "name": "skipped", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Any", + "name": "{urn:hl7-org:elm-types:r1}String", "annotation": [] } } @@ -68250,20 +68232,11 @@ "annotation": [], "element": [ { - "name": "expression", - "annotation": [], - "elementType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Any", - "annotation": [] - } - }, - { - "name": "output", + "name": "skipped", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Any", + "name": "{urn:hl7-org:elm-types:r1}String", "annotation": [] } } @@ -68271,44 +68244,12 @@ }, "element": [ { - "name": "expression", - "value": { - "type": "PointFrom", - "resultTypeName": "{urn:hl7-org:elm-types:r1}Any", - "annotation": [], - "signature": [], - "operand": { - "type": "Interval", - "lowClosed": true, - "highClosed": true, - "annotation": [], - "resultTypeSpecifier": { - "type": "IntervalTypeSpecifier", - "annotation": [], - "pointType": { - "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}Any", - "annotation": [] - } - }, - "low": { - "type": "Null", - "resultTypeName": "{urn:hl7-org:elm-types:r1}Any", - "annotation": [] - }, - "high": { - "type": "Null", - "resultTypeName": "{urn:hl7-org:elm-types:r1}Any", - "annotation": [] - } - } - } - }, - { - "name": "output", + "name": "skipped", "value": { - "type": "Null", - "resultTypeName": "{urn:hl7-org:elm-types:r1}Any", + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}String", + "valueType": "{urn:hl7-org:elm-types:r1}String", + "value": "Wrong output: Interval[null, null] is not a unit interval, nor is it null", "annotation": [] } } @@ -81870,11 +81811,11 @@ "annotation": [], "resultTypeSpecifier": { "type": "IntervalTypeSpecifier", - "localId": "15187", + "localId": "15176", "annotation": [], "pointType": { "type": "NamedTypeSpecifier", - "localId": "15188", + "localId": "15177", "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 947381938..239a64793 100644 --- a/test/spec-tests/skip-list.txt +++ b/test/spec-tests/skip-list.txt @@ -11,6 +11,7 @@ CqlIntervalOperatorsTest.In.TestInNullBoundaries Wrong output: Ac 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 +CqlIntervalOperatorsTest.PointFrom.TestPointFromNull Wrong output: Interval[null, null] is not a unit interval, nor is it null # Potentially Incorrect Expected Output "CqlStringOperatorsTest.toString tests.DateTimeToString2" Answer does not include timezone offset, but default offset depends on test environment From 5f505e91760a98ae26eaec11fe6b2ff106f90237 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 23 Jul 2026 10:08:36 -0400 Subject: [PATCH 5/8] Regenerate cql4browsers --- examples/browser/cql4browsers.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/browser/cql4browsers.js b/examples/browser/cql4browsers.js index 6c00a421f..62f903cd4 100644 --- a/examples/browser/cql4browsers.js +++ b/examples/browser/cql4browsers.js @@ -2412,6 +2412,13 @@ class Interval { return new Interval(this.low, this.high, lowClosed, highClosed); } } + pointFrom() { + const start = this.start(); + if (cmp.equals(start, this.end())) { + return start; + } + throw new Error('PointFrom operator may only be used on an interval containing a single point.'); + } toString() { const start = this.lowClosed ? '[' : '('; const end = this.highClosed ? ']' : ')'; @@ -5101,7 +5108,6 @@ const math_1 = require("../util/math"); const units_1 = require("../util/units"); const dtivl = __importStar(require("../datatypes/interval")); const builder_1 = require("./builder"); -const comparison_1 = require("../util/comparison"); const elmTypes_1 = require("../util/elmTypes"); class Interval extends expression_1.Expression { constructor(json) { @@ -5273,12 +5279,7 @@ class PointFrom extends expression_1.Expression { if (interval == null) { return null; } - const start = interval.start(); - const end = interval.end(); - if (start == end || (0, comparison_1.equals)(start, end)) { - return start; - } - throw new Error('PointFrom operator may only be used on an interval containing a single point.'); + return interval.pointFrom(); } } exports.PointFrom = PointFrom; @@ -5867,7 +5868,7 @@ function truncateDecimal(decimal, decimalPlaces) { return parseFloat(decimal.toString().match(re)[0]); } -},{"../datatypes/interval":10,"../datatypes/quantity":12,"../util/comparison":53,"../util/elmTypes":55,"../util/math":57,"../util/units":58,"./builder":17,"./expression":23}],28:[function(require,module,exports){ +},{"../datatypes/interval":10,"../datatypes/quantity":12,"../util/elmTypes":55,"../util/math":57,"../util/units":58,"./builder":17,"./expression":23}],28:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Library = void 0; From a2b8277ff6eb941d620a5040f5022e52cd6c516e Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 23 Jul 2026 10:19:53 -0400 Subject: [PATCH 6/8] a couple extra tests --- test/datatypes/interval-test.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/test/datatypes/interval-test.ts b/test/datatypes/interval-test.ts index 49d9439c1..d8df1db66 100644 --- a/test/datatypes/interval-test.ts +++ b/test/datatypes/interval-test.ts @@ -1950,13 +1950,22 @@ describe('DateTimeInterval', () => { should(() => ivl.pointFrom()).throw(Error); }); - it('should throw for an interval with null high bound', function () { - const ivl = new Interval(DateTime.parse('2012-01-01T00:00:00.0+00'), null); - should(() => ivl.pointFrom()).throw(Error); + it('should return the point value for a unit interval with open high bound', () => { + const point = DateTime.parse('2012-01-01T00:00:00.0+00'); + const ivl = new Interval(point, point.successor(), true, false); + + ivl.pointFrom().should.eql(point); }); - it('should throw for an interval with undefined width', function () { - const ivl = new Interval(0, 0, false, false); + it('should return the point value for a unit interval with limited precision', () => { + const point = DateTime.parse('2012-01-01'); + const ivl = new Interval(point, point); + + ivl.pointFrom().should.eql(point); + }); + + it('should throw for an interval with null high bound', function () { + const ivl = new Interval(DateTime.parse('2012-01-01T00:00:00.0+00'), null); should(() => ivl.pointFrom()).throw(Error); }); }); From 6025a3b24bddc048554eee32339bfaa696accff4 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Fri, 24 Jul 2026 15:00:22 -0400 Subject: [PATCH 7/8] Add a few more tests --- test/datatypes/interval-test.ts | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/test/datatypes/interval-test.ts b/test/datatypes/interval-test.ts index d8df1db66..72a5c363b 100644 --- a/test/datatypes/interval-test.ts +++ b/test/datatypes/interval-test.ts @@ -1950,6 +1950,13 @@ describe('DateTimeInterval', () => { should(() => ivl.pointFrom()).throw(Error); }); + it('should return the point value for a unit interval with open low bound', () => { + const point = DateTime.parse('2012-01-01T00:00:00.0+00'); + const ivl = new Interval(point.predecessor(), point, false, true); + + ivl.pointFrom().should.eql(point); + }); + it('should return the point value for a unit interval with open high bound', () => { const point = DateTime.parse('2012-01-01T00:00:00.0+00'); const ivl = new Interval(point, point.successor(), true, false); @@ -1957,6 +1964,13 @@ describe('DateTimeInterval', () => { ivl.pointFrom().should.eql(point); }); + it('should throw for a non-unit interval with closed high bound', () => { + const point = DateTime.parse('2012-01-01T00:00:00.0+00'); + const ivl = new Interval(point, point.successor(), true, true); + + should(() => ivl.pointFrom()).throw(Error); + }); + it('should return the point value for a unit interval with limited precision', () => { const point = DateTime.parse('2012-01-01'); const ivl = new Interval(point, point); @@ -1964,10 +1978,25 @@ describe('DateTimeInterval', () => { ivl.pointFrom().should.eql(point); }); - it('should throw for an interval with null high bound', function () { + it('should throw for an interval with closed null high bound', function () { const ivl = new Interval(DateTime.parse('2012-01-01T00:00:00.0+00'), null); should(() => ivl.pointFrom()).throw(Error); }); + + it('should throw for an interval with open null high bound', function () { + const ivl = new Interval(DateTime.parse('2012-01-01T00:00:00.0+00'), null, true, false); + should(() => ivl.pointFrom()).throw(Error); + }); + + it('should throw for an interval with closed null low bound', function () { + const ivl = new Interval(null, DateTime.parse('2012-01-01T00:00:00.0+00')); + should(() => ivl.pointFrom()).throw(Error); + }); + + it('should throw for an interval with open null low bound', function () { + const ivl = new Interval(null, DateTime.parse('2012-01-01T00:00:00.0+00'), false, true); + should(() => ivl.pointFrom()).throw(Error); + }); }); }); From e4ff9739750ac3093ba3b6a29a6fffbbbb1080e9 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Fri, 24 Jul 2026 15:13:04 -0400 Subject: [PATCH 8/8] Add even more tests --- test/datatypes/interval-test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/datatypes/interval-test.ts b/test/datatypes/interval-test.ts index 72a5c363b..2f8f59fbe 100644 --- a/test/datatypes/interval-test.ts +++ b/test/datatypes/interval-test.ts @@ -1971,6 +1971,18 @@ describe('DateTimeInterval', () => { should(() => ivl.pointFrom()).throw(Error); }); + it('should return the value for a unit interval with null (max) high bound', () => { + const ivl = new Interval(MAX_DATETIME_VALUE, null); + + ivl.pointFrom().should.eql(MAX_DATETIME_VALUE); + }); + + it('should return the value for a unit interval with null (min) low bound', () => { + const ivl = new Interval(null, MIN_DATETIME_VALUE); + + ivl.pointFrom().should.eql(MIN_DATETIME_VALUE); + }); + it('should return the point value for a unit interval with limited precision', () => { const point = DateTime.parse('2012-01-01'); const ivl = new Interval(point, point);