Skip to content

Commit 2cebda4

Browse files
committed
Implement CQL reference definition for proper contains
The logical definition and reference definition for proper contains/includes disagree. The reference definition seems more consistent to me, so I've implemented that while we ask the CQL community.
1 parent 64176ea commit 2cebda4

9 files changed

Lines changed: 1546 additions & 1518 deletions

File tree

examples/browser/cql4browsers.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,20 +1874,23 @@ class Interval {
18741874
// comparisons used in the operation are performed at the specified precision."
18751875
return logic_1.ThreeValuedLogic.and(cmp.greaterThanOrEquals(item, this.start(), precision), cmp.lessThanOrEquals(item, this.end(), precision));
18761876
}
1877+
// https://build.fhir.org/ig/HL7/cql/09-b-cqlreference.html#properly-includes
18771878
properContains(item, precision) {
1879+
// From contains: "If the second argument is null, the result is null."
18781880
if (item == null) {
18791881
return null;
18801882
}
18811883
else if (item.isInterval) {
18821884
throw new Error('Argument to contains must be a point');
18831885
}
1884-
return logic_1.ThreeValuedLogic.and(cmp.lessThan(this.start(), item, precision), cmp.greaterThan(this.end(), item, precision));
1886+
// "For the interval-point overload, this operator returns true if the interval contains
1887+
// (i.e. includes) the point, and the interval is not a unit interval containing only the
1888+
// point."
1889+
return logic_1.ThreeValuedLogic.and(this.contains(item, precision), logic_1.ThreeValuedLogic.not(cmp.equals(this.start(), this.end())));
18851890
}
18861891
// https://build.fhir.org/ig/HL7/cql/09-b-cqlreference.html#properly-includes
18871892
properlyIncludes(other, precision) {
1888-
// TODO: "For the interval-point overload, this operator returns true if the interval contains
1889-
// (i.e. includes) the point, and the interval is not a unit interval containing only the
1890-
// point."
1893+
// "For the interval-interval overload, if either argument is null, the result is null."
18911894
if (other == null || !other.isInterval) {
18921895
throw new Error('Argument to properlyIncludes must be an interval');
18931896
}

src/datatypes/interval.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,23 +93,26 @@ export class Interval {
9393
);
9494
}
9595

96+
// https://build.fhir.org/ig/HL7/cql/09-b-cqlreference.html#properly-includes
9697
properContains(item: any, precision?: any) {
98+
// From contains: "If the second argument is null, the result is null."
9799
if (item == null) {
98100
return null;
99101
} else if (item.isInterval) {
100102
throw new Error('Argument to contains must be a point');
101103
}
104+
// "For the interval-point overload, this operator returns true if the interval contains
105+
// (i.e. includes) the point, and the interval is not a unit interval containing only the
106+
// point."
102107
return ThreeValuedLogic.and(
103-
cmp.lessThan(this.start(), item, precision),
104-
cmp.greaterThan(this.end(), item, precision)
108+
this.contains(item, precision),
109+
ThreeValuedLogic.not(cmp.equals(this.start(), this.end()))
105110
);
106111
}
107112

108113
// https://build.fhir.org/ig/HL7/cql/09-b-cqlreference.html#properly-includes
109114
properlyIncludes(other: any, precision?: any) {
110-
// TODO: "For the interval-point overload, this operator returns true if the interval contains
111-
// (i.e. includes) the point, and the interval is not a unit interval containing only the
112-
// point."
115+
// "For the interval-interval overload, if either argument is null, the result is null."
113116
if (other == null || !other.isInterval) {
114117
throw new Error('Argument to properlyIncludes must be an interval');
115118
}

test/datatypes/interval-test.ts

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -471,14 +471,14 @@ describe('DateTimeInterval', () => {
471471
});
472472

473473
it('should properly calculate the left boundary date', () => {
474-
d.all2012.closed.properContains(d.beg2012.full).should.be.false();
474+
d.all2012.closed.properContains(d.beg2012.full).should.be.true();
475475
d.all2012.open.properContains(d.beg2012.full).should.be.false();
476476

477-
let next = d.beg2012.full.successor();
478-
d.all2012.closed.properContains(next).should.be.true();
479-
d.all2012.open.properContains(next).should.be.false();
477+
const prev = d.beg2012.full.predecessor();
478+
d.all2012.closed.properContains(prev).should.be.false();
479+
d.all2012.open.properContains(prev).should.be.false();
480480

481-
next = next.successor();
481+
const next = d.beg2012.full.successor();
482482
d.all2012.closed.properContains(next).should.be.true();
483483
d.all2012.open.properContains(next).should.be.true();
484484
});
@@ -488,16 +488,16 @@ describe('DateTimeInterval', () => {
488488
});
489489

490490
it('should properly calculate the right boundary date', () => {
491-
d.all2012.closed.properContains(d.end2012.full).should.be.false();
491+
d.all2012.closed.properContains(d.end2012.full).should.be.true();
492492
d.all2012.open.properContains(d.end2012.full).should.be.false();
493493

494-
let prev = d.end2012.full.predecessor();
495-
d.all2012.closed.properContains(prev).should.be.true();
496-
d.all2012.open.properContains(prev).should.be.false();
497-
498-
prev = prev.predecessor();
494+
const prev = d.end2012.full.predecessor();
499495
d.all2012.closed.properContains(prev).should.be.true();
500496
d.all2012.open.properContains(prev).should.be.true();
497+
498+
const next = d.end2012.full.successor();
499+
d.all2012.closed.properContains(next).should.be.false();
500+
d.all2012.open.properContains(next).should.be.false();
501501
});
502502

503503
it('should properly calculate dates after it', () => {
@@ -510,16 +510,16 @@ describe('DateTimeInterval', () => {
510510
const early = DateTime.parse('2000-01-01T00:00:00.0');
511511
const late = DateTime.parse('2999-01-01T00:00:00.0');
512512
const maxDate = MAX_DATETIME_VALUE;
513-
new Interval(null, date).properContains(minDate).should.be.false();
513+
new Interval(null, date).properContains(minDate).should.be.true();
514514
new Interval(null, date).properContains(early).should.be.true();
515515
new Interval(null, date).properContains(late).should.be.false();
516-
new Interval(null, date, false, true).properContains(date).should.be.false();
516+
should(new Interval(null, date, false, true).properContains(date)).be.null();
517517
should(new Interval(null, date, false, true).properContains(early)).be.null();
518518
new Interval(null, date, false, true).properContains(late).should.be.false();
519519
new Interval(date, null).properContains(late).should.be.true();
520520
new Interval(date, null).properContains(early).should.be.false();
521-
new Interval(date, null).properContains(maxDate).should.be.false();
522-
new Interval(date, null, true, false).properContains(date).should.be.false();
521+
new Interval(date, null).properContains(maxDate).should.be.true();
522+
should(new Interval(date, null, true, false).properContains(date)).be.null();
523523
should(new Interval(date, null, true, false).properContains(late)).be.null();
524524
new Interval(date, null, true, false).properContains(early).should.be.false();
525525
new Interval(null, null, true, true, ELM_DATETIME_TYPE).properContains(date).should.be.true();
@@ -536,9 +536,9 @@ describe('DateTimeInterval', () => {
536536
d.all2012.closed.properContains(d.aft2012.toMonth).should.be.false();
537537

538538
d.all2012.toMonth.properContains(d.bef2012.toMonth).should.be.false();
539-
d.all2012.toMonth.properContains(d.beg2012.toMonth).should.be.false();
539+
d.all2012.toMonth.properContains(d.beg2012.toMonth).should.be.true();
540540
d.all2012.toMonth.properContains(d.mid2012.toMonth).should.be.true();
541-
d.all2012.toMonth.properContains(d.end2012.toMonth).should.be.false();
541+
d.all2012.toMonth.properContains(d.end2012.toMonth).should.be.true();
542542
d.all2012.toMonth.properContains(d.aft2012.toMonth).should.be.false();
543543

544544
d.all2012.toMonth.properContains(d.bef2012.full).should.be.false();
@@ -2678,10 +2678,10 @@ describe('IntegerInterval', () => {
26782678
});
26792679

26802680
it('should properly calculate the left boundary integer', () => {
2681-
d.zeroToHundred.closed.properContains(0).should.be.false();
2681+
d.zeroToHundred.closed.properContains(0).should.be.true();
26822682
d.zeroToHundred.open.properContains(0).should.be.false();
26832683
d.zeroToHundred.closed.properContains(1).should.be.true();
2684-
d.zeroToHundred.open.properContains(1).should.be.false();
2684+
d.zeroToHundred.open.properContains(1).should.be.true();
26852685
d.zeroToHundred.closed.properContains(2).should.be.true();
26862686
d.zeroToHundred.open.properContains(2).should.be.true();
26872687
});
@@ -2691,10 +2691,10 @@ describe('IntegerInterval', () => {
26912691
});
26922692

26932693
it('should properly calculate the right boundary integer', () => {
2694-
d.zeroToHundred.closed.properContains(100).should.be.false();
2694+
d.zeroToHundred.closed.properContains(100).should.be.true();
26952695
d.zeroToHundred.open.properContains(100).should.be.false();
26962696
d.zeroToHundred.closed.properContains(99).should.be.true();
2697-
d.zeroToHundred.open.properContains(99).should.be.false();
2697+
d.zeroToHundred.open.properContains(99).should.be.true();
26982698
d.zeroToHundred.closed.properContains(98).should.be.true();
26992699
d.zeroToHundred.open.properContains(98).should.be.true();
27002700
});
@@ -2706,12 +2706,12 @@ describe('IntegerInterval', () => {
27062706
it('should properly handle null endpoints', () => {
27072707
new Interval(null, 0).properContains(-123456789).should.be.true();
27082708
new Interval(null, 0).properContains(1).should.be.false();
2709-
new Interval(null, 0, false, true).properContains(0).should.be.false();
2709+
should(new Interval(null, 0, false, true).properContains(0)).be.null();
27102710
should(new Interval(null, 0, false, true).properContains(-123456789)).be.null();
27112711
new Interval(null, 0, false, true).properContains(1).should.be.false();
27122712
new Interval(0, null).properContains(123456789).should.be.true();
27132713
new Interval(0, null).properContains(-1).should.be.false();
2714-
new Interval(0, null, true, false).properContains(0).should.be.false();
2714+
should(new Interval(0, null, true, false).properContains(0)).be.null();
27152715
should(new Interval(0, null, true, false).properContains(123456789)).be.null();
27162716
new Interval(0, null, true, false).properContains(-1).should.be.false();
27172717
new Interval(null, null, true, true, ELM_INTEGER_TYPE).properContains(0).should.be.true();
@@ -2721,31 +2721,31 @@ describe('IntegerInterval', () => {
27212721
it('should properly handle imprecision', () => {
27222722
d.zeroToHundred.closed.properContains(new Uncertainty(-20, -10)).should.be.false();
27232723
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(-20, 20)));
2724-
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(0, 100)));
2725-
d.zeroToHundred.closed.properContains(new Uncertainty(1, 99)).should.be.true();
2724+
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(0, 101)));
2725+
d.zeroToHundred.closed.properContains(new Uncertainty(0, 100)).should.be.true();
27262726
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(80, 120)));
27272727
d.zeroToHundred.closed.properContains(new Uncertainty(120, 140)).should.be.false();
27282728
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(-20, 120)));
27292729

27302730
const uIvl = new Interval(new Uncertainty(5, 10), new Uncertainty(15, 20));
27312731

27322732
uIvl.properContains(0).should.be.false();
2733-
uIvl.properContains(5).should.be.false();
2733+
should.not.exist(uIvl.properContains(5));
27342734
should.not.exist(uIvl.properContains(6));
2735-
should.not.exist(uIvl.properContains(10));
2735+
uIvl.properContains(10).should.be.true();
27362736
uIvl.properContains(12).should.be.true();
2737-
should.not.exist(uIvl.properContains(15));
2737+
uIvl.properContains(15).should.be.true();
27382738
should.not.exist(uIvl.properContains(16));
2739-
uIvl.properContains(20).should.be.false();
2739+
should.not.exist(uIvl.properContains(20));
27402740
uIvl.properContains(25).should.be.false();
27412741

27422742
uIvl.properContains(new Uncertainty(0, 4)).should.be.false();
2743-
uIvl.properContains(new Uncertainty(0, 5)).should.be.false();
2743+
should.not.exist(uIvl.properContains(new Uncertainty(0, 5)));
27442744
should.not.exist(uIvl.properContains(new Uncertainty(5, 10)));
2745-
should.not.exist(uIvl.properContains(new Uncertainty(10, 15)));
2745+
uIvl.properContains(new Uncertainty(10, 15)).should.be.true();
27462746
uIvl.properContains(new Uncertainty(11, 14)).should.be.true();
27472747
should.not.exist(uIvl.properContains(new Uncertainty(15, 20)));
2748-
uIvl.properContains(new Uncertainty(20, 25)).should.be.false();
2748+
should.not.exist(uIvl.properContains(new Uncertainty(20, 25)));
27492749
uIvl.properContains(new Uncertainty(25, 30)).should.be.false();
27502750
});
27512751

@@ -4643,10 +4643,10 @@ describe('LongInterval', () => {
46434643
});
46444644

46454645
it('should properly calculate the left boundary long', () => {
4646-
d.zeroToHundredLong.closed.properContains(0n).should.be.false();
4646+
d.zeroToHundredLong.closed.properContains(0n).should.be.true();
46474647
d.zeroToHundredLong.open.properContains(0n).should.be.false();
46484648
d.zeroToHundredLong.closed.properContains(1n).should.be.true();
4649-
d.zeroToHundredLong.open.properContains(1n).should.be.false();
4649+
d.zeroToHundredLong.open.properContains(1n).should.be.true();
46504650
d.zeroToHundredLong.closed.properContains(2n).should.be.true();
46514651
d.zeroToHundredLong.open.properContains(2n).should.be.true();
46524652
});
@@ -4656,10 +4656,10 @@ describe('LongInterval', () => {
46564656
});
46574657

46584658
it('should properly calculate the right boundary long', () => {
4659-
d.zeroToHundredLong.closed.properContains(100n).should.be.false();
4659+
d.zeroToHundredLong.closed.properContains(100n).should.be.true();
46604660
d.zeroToHundredLong.open.properContains(100n).should.be.false();
46614661
d.zeroToHundredLong.closed.properContains(99n).should.be.true();
4662-
d.zeroToHundredLong.open.properContains(99n).should.be.false();
4662+
d.zeroToHundredLong.open.properContains(99n).should.be.true();
46634663
d.zeroToHundredLong.closed.properContains(98n).should.be.true();
46644664
d.zeroToHundredLong.open.properContains(98n).should.be.true();
46654665
});
@@ -4671,12 +4671,12 @@ describe('LongInterval', () => {
46714671
it('should properly handle null endpoints', () => {
46724672
new Interval(null, 0n).properContains(-123456789n).should.be.true();
46734673
new Interval(null, 0n).properContains(1n).should.be.false();
4674-
new Interval(null, 0n, false, true).properContains(0n).should.be.false();
4674+
should(new Interval(null, 0n, false, true).properContains(0n)).be.null();
46754675
should(new Interval(null, 0n, false, true).properContains(-123456789n)).be.null();
46764676
new Interval(null, 0n, false, true).properContains(1n).should.be.false();
46774677
new Interval(0n, null).properContains(123456789n).should.be.true();
46784678
new Interval(0n, null).properContains(-1n).should.be.false();
4679-
new Interval(0n, null, true, false).properContains(0n).should.be.false();
4679+
should(new Interval(0n, null, true, false).properContains(0n)).be.null();
46804680
should(new Interval(0n, null, true, false).properContains(123456789n)).be.null();
46814681
new Interval(0n, null, true, false).properContains(-1n).should.be.false();
46824682
new Interval(null, null, true, true, ELM_LONG_TYPE).properContains(0n).should.be.true();
@@ -4686,31 +4686,31 @@ describe('LongInterval', () => {
46864686
it('should properly handle imprecision', () => {
46874687
d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, -10n)).should.be.false();
46884688
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, 20n)));
4689-
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 100n)));
4690-
d.zeroToHundredLong.closed.properContains(new Uncertainty(1n, 99n)).should.be.true();
4689+
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 101n)));
4690+
d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 100n)).should.be.true();
46914691
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(80n, 120n)));
46924692
d.zeroToHundredLong.closed.properContains(new Uncertainty(120n, 140n)).should.be.false();
46934693
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, 120n)));
46944694

46954695
const uIvl = new Interval(new Uncertainty(5n, 10n), new Uncertainty(15n, 20n));
46964696

46974697
uIvl.properContains(0n).should.be.false();
4698-
uIvl.properContains(5n).should.be.false();
4698+
should.not.exist(uIvl.properContains(5n));
46994699
should.not.exist(uIvl.properContains(6n));
4700-
should.not.exist(uIvl.properContains(10n));
4700+
uIvl.properContains(10n).should.be.true();
47014701
uIvl.properContains(12n).should.be.true();
4702-
should.not.exist(uIvl.properContains(15n));
4702+
uIvl.properContains(15n).should.be.true();
47034703
should.not.exist(uIvl.properContains(16n));
4704-
uIvl.properContains(20n).should.be.false();
4704+
should.not.exist(uIvl.properContains(20n));
47054705
uIvl.properContains(25n).should.be.false();
47064706

47074707
uIvl.properContains(new Uncertainty(0n, 4n)).should.be.false();
4708-
uIvl.properContains(new Uncertainty(0n, 5n)).should.be.false();
4708+
should.not.exist(uIvl.properContains(new Uncertainty(0n, 5n)));
47094709
should.not.exist(uIvl.properContains(new Uncertainty(5n, 10n)));
4710-
should.not.exist(uIvl.properContains(new Uncertainty(10n, 15n)));
4710+
uIvl.properContains(new Uncertainty(10n, 15n)).should.be.true();
47114711
uIvl.properContains(new Uncertainty(11n, 14n)).should.be.true();
47124712
should.not.exist(uIvl.properContains(new Uncertainty(15n, 20n)));
4713-
uIvl.properContains(new Uncertainty(20n, 25n)).should.be.false();
4713+
should.not.exist(uIvl.properContains(new Uncertainty(20n, 25n)));
47144714
uIvl.properContains(new Uncertainty(25n, 30n)).should.be.false();
47154715
});
47164716

0 commit comments

Comments
 (0)