Skip to content

Commit b7ed8e6

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 a22307d commit b7ed8e6

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
@@ -1876,20 +1876,23 @@ class Interval {
18761876
// comparisons used in the operation are performed at the specified precision."
18771877
return logic_1.ThreeValuedLogic.and(cmp.greaterThanOrEquals(item, this.start(), precision), cmp.lessThanOrEquals(item, this.end(), precision));
18781878
}
1879+
// https://build.fhir.org/ig/HL7/cql/09-b-cqlreference.html#properly-includes
18791880
properContains(item, precision) {
1881+
// From contains: "If the second argument is null, the result is null."
18801882
if (item == null) {
18811883
return null;
18821884
}
18831885
else if (item.isInterval) {
18841886
throw new Error('Argument to contains must be a point');
18851887
}
1886-
return logic_1.ThreeValuedLogic.and(cmp.lessThan(this.start(), item, precision), cmp.greaterThan(this.end(), item, precision));
1888+
// "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."
1891+
return logic_1.ThreeValuedLogic.and(this.contains(item, precision), logic_1.ThreeValuedLogic.not(cmp.equals(this.start(), this.end())));
18871892
}
18881893
// https://build.fhir.org/ig/HL7/cql/09-b-cqlreference.html#properly-includes
18891894
properlyIncludes(other, precision) {
1890-
// TODO: "For the interval-point overload, this operator returns true if the interval contains
1891-
// (i.e. includes) the point, and the interval is not a unit interval containing only the
1892-
// point."
1895+
// "For the interval-interval overload, if either argument is null, the result is null."
18931896
if (other == null || !other.isInterval) {
18941897
throw new Error('Argument to properlyIncludes must be an interval');
18951898
}

src/datatypes/interval.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,23 +101,26 @@ export class Interval {
101101
);
102102
}
103103

104+
// https://build.fhir.org/ig/HL7/cql/09-b-cqlreference.html#properly-includes
104105
properContains(item: any, precision?: any) {
106+
// From contains: "If the second argument is null, the result is null."
105107
if (item == null) {
106108
return null;
107109
} else if (item.isInterval) {
108110
throw new Error('Argument to contains must be a point');
109111
}
112+
// "For the interval-point overload, this operator returns true if the interval contains
113+
// (i.e. includes) the point, and the interval is not a unit interval containing only the
114+
// point."
110115
return ThreeValuedLogic.and(
111-
cmp.lessThan(this.start(), item, precision),
112-
cmp.greaterThan(this.end(), item, precision)
116+
this.contains(item, precision),
117+
ThreeValuedLogic.not(cmp.equals(this.start(), this.end()))
113118
);
114119
}
115120

116121
// https://build.fhir.org/ig/HL7/cql/09-b-cqlreference.html#properly-includes
117122
properlyIncludes(other: any, precision?: any) {
118-
// TODO: "For the interval-point overload, this operator returns true if the interval contains
119-
// (i.e. includes) the point, and the interval is not a unit interval containing only the
120-
// point."
123+
// "For the interval-interval overload, if either argument is null, the result is null."
121124
if (other == null || !other.isInterval) {
122125
throw new Error('Argument to properlyIncludes must be an interval');
123126
}

test/datatypes/interval-test.ts

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

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

480-
let next = d.beg2012.full.successor();
481-
d.all2012.closed.properContains(next).should.be.true();
482-
d.all2012.open.properContains(next).should.be.false();
480+
const prev = d.beg2012.full.predecessor();
481+
d.all2012.closed.properContains(prev).should.be.false();
482+
d.all2012.open.properContains(prev).should.be.false();
483483

484-
next = next.successor();
484+
const next = d.beg2012.full.successor();
485485
d.all2012.closed.properContains(next).should.be.true();
486486
d.all2012.open.properContains(next).should.be.true();
487487
});
@@ -491,16 +491,16 @@ describe('DateTimeInterval', () => {
491491
});
492492

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

497-
let prev = d.end2012.full.predecessor();
498-
d.all2012.closed.properContains(prev).should.be.true();
499-
d.all2012.open.properContains(prev).should.be.false();
500-
501-
prev = prev.predecessor();
497+
const prev = d.end2012.full.predecessor();
502498
d.all2012.closed.properContains(prev).should.be.true();
503499
d.all2012.open.properContains(prev).should.be.true();
500+
501+
const next = d.end2012.full.successor();
502+
d.all2012.closed.properContains(next).should.be.false();
503+
d.all2012.open.properContains(next).should.be.false();
504504
});
505505

506506
it('should properly calculate dates after it', () => {
@@ -513,16 +513,16 @@ describe('DateTimeInterval', () => {
513513
const early = DateTime.parse('2000-01-01T00:00:00.0');
514514
const late = DateTime.parse('2999-01-01T00:00:00.0');
515515
const maxDate = MAX_DATETIME_VALUE;
516-
new Interval(null, date).properContains(minDate).should.be.false();
516+
new Interval(null, date).properContains(minDate).should.be.true();
517517
new Interval(null, date).properContains(early).should.be.true();
518518
new Interval(null, date).properContains(late).should.be.false();
519-
new Interval(null, date, false, true).properContains(date).should.be.false();
519+
should(new Interval(null, date, false, true).properContains(date)).be.null();
520520
should(new Interval(null, date, false, true).properContains(early)).be.null();
521521
new Interval(null, date, false, true).properContains(late).should.be.false();
522522
new Interval(date, null).properContains(late).should.be.true();
523523
new Interval(date, null).properContains(early).should.be.false();
524-
new Interval(date, null).properContains(maxDate).should.be.false();
525-
new Interval(date, null, true, false).properContains(date).should.be.false();
524+
new Interval(date, null).properContains(maxDate).should.be.true();
525+
should(new Interval(date, null, true, false).properContains(date)).be.null();
526526
should(new Interval(date, null, true, false).properContains(late)).be.null();
527527
new Interval(date, null, true, false).properContains(early).should.be.false();
528528
new Interval(null, null, true, true, ELM_DATETIME_TYPE).properContains(date).should.be.true();
@@ -539,9 +539,9 @@ describe('DateTimeInterval', () => {
539539
d.all2012.closed.properContains(d.aft2012.toMonth).should.be.false();
540540

541541
d.all2012.toMonth.properContains(d.bef2012.toMonth).should.be.false();
542-
d.all2012.toMonth.properContains(d.beg2012.toMonth).should.be.false();
542+
d.all2012.toMonth.properContains(d.beg2012.toMonth).should.be.true();
543543
d.all2012.toMonth.properContains(d.mid2012.toMonth).should.be.true();
544-
d.all2012.toMonth.properContains(d.end2012.toMonth).should.be.false();
544+
d.all2012.toMonth.properContains(d.end2012.toMonth).should.be.true();
545545
d.all2012.toMonth.properContains(d.aft2012.toMonth).should.be.false();
546546

547547
d.all2012.toMonth.properContains(d.bef2012.full).should.be.false();
@@ -2681,10 +2681,10 @@ describe('IntegerInterval', () => {
26812681
});
26822682

26832683
it('should properly calculate the left boundary integer', () => {
2684-
d.zeroToHundred.closed.properContains(0).should.be.false();
2684+
d.zeroToHundred.closed.properContains(0).should.be.true();
26852685
d.zeroToHundred.open.properContains(0).should.be.false();
26862686
d.zeroToHundred.closed.properContains(1).should.be.true();
2687-
d.zeroToHundred.open.properContains(1).should.be.false();
2687+
d.zeroToHundred.open.properContains(1).should.be.true();
26882688
d.zeroToHundred.closed.properContains(2).should.be.true();
26892689
d.zeroToHundred.open.properContains(2).should.be.true();
26902690
});
@@ -2694,10 +2694,10 @@ describe('IntegerInterval', () => {
26942694
});
26952695

26962696
it('should properly calculate the right boundary integer', () => {
2697-
d.zeroToHundred.closed.properContains(100).should.be.false();
2697+
d.zeroToHundred.closed.properContains(100).should.be.true();
26982698
d.zeroToHundred.open.properContains(100).should.be.false();
26992699
d.zeroToHundred.closed.properContains(99).should.be.true();
2700-
d.zeroToHundred.open.properContains(99).should.be.false();
2700+
d.zeroToHundred.open.properContains(99).should.be.true();
27012701
d.zeroToHundred.closed.properContains(98).should.be.true();
27022702
d.zeroToHundred.open.properContains(98).should.be.true();
27032703
});
@@ -2709,12 +2709,12 @@ describe('IntegerInterval', () => {
27092709
it('should properly handle null endpoints', () => {
27102710
new Interval(null, 0).properContains(-123456789).should.be.true();
27112711
new Interval(null, 0).properContains(1).should.be.false();
2712-
new Interval(null, 0, false, true).properContains(0).should.be.false();
2712+
should(new Interval(null, 0, false, true).properContains(0)).be.null();
27132713
should(new Interval(null, 0, false, true).properContains(-123456789)).be.null();
27142714
new Interval(null, 0, false, true).properContains(1).should.be.false();
27152715
new Interval(0, null).properContains(123456789).should.be.true();
27162716
new Interval(0, null).properContains(-1).should.be.false();
2717-
new Interval(0, null, true, false).properContains(0).should.be.false();
2717+
should(new Interval(0, null, true, false).properContains(0)).be.null();
27182718
should(new Interval(0, null, true, false).properContains(123456789)).be.null();
27192719
new Interval(0, null, true, false).properContains(-1).should.be.false();
27202720
new Interval(null, null, true, true, ELM_INTEGER_TYPE).properContains(0).should.be.true();
@@ -2724,31 +2724,31 @@ describe('IntegerInterval', () => {
27242724
it('should properly handle imprecision', () => {
27252725
d.zeroToHundred.closed.properContains(new Uncertainty(-20, -10)).should.be.false();
27262726
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(-20, 20)));
2727-
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(0, 100)));
2728-
d.zeroToHundred.closed.properContains(new Uncertainty(1, 99)).should.be.true();
2727+
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(0, 101)));
2728+
d.zeroToHundred.closed.properContains(new Uncertainty(0, 100)).should.be.true();
27292729
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(80, 120)));
27302730
d.zeroToHundred.closed.properContains(new Uncertainty(120, 140)).should.be.false();
27312731
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(-20, 120)));
27322732

27332733
const uIvl = new Interval(new Uncertainty(5, 10), new Uncertainty(15, 20));
27342734

27352735
uIvl.properContains(0).should.be.false();
2736-
uIvl.properContains(5).should.be.false();
2736+
should.not.exist(uIvl.properContains(5));
27372737
should.not.exist(uIvl.properContains(6));
2738-
should.not.exist(uIvl.properContains(10));
2738+
uIvl.properContains(10).should.be.true();
27392739
uIvl.properContains(12).should.be.true();
2740-
should.not.exist(uIvl.properContains(15));
2740+
uIvl.properContains(15).should.be.true();
27412741
should.not.exist(uIvl.properContains(16));
2742-
uIvl.properContains(20).should.be.false();
2742+
should.not.exist(uIvl.properContains(20));
27432743
uIvl.properContains(25).should.be.false();
27442744

27452745
uIvl.properContains(new Uncertainty(0, 4)).should.be.false();
2746-
uIvl.properContains(new Uncertainty(0, 5)).should.be.false();
2746+
should.not.exist(uIvl.properContains(new Uncertainty(0, 5)));
27472747
should.not.exist(uIvl.properContains(new Uncertainty(5, 10)));
2748-
should.not.exist(uIvl.properContains(new Uncertainty(10, 15)));
2748+
uIvl.properContains(new Uncertainty(10, 15)).should.be.true();
27492749
uIvl.properContains(new Uncertainty(11, 14)).should.be.true();
27502750
should.not.exist(uIvl.properContains(new Uncertainty(15, 20)));
2751-
uIvl.properContains(new Uncertainty(20, 25)).should.be.false();
2751+
should.not.exist(uIvl.properContains(new Uncertainty(20, 25)));
27522752
uIvl.properContains(new Uncertainty(25, 30)).should.be.false();
27532753
});
27542754

@@ -4646,10 +4646,10 @@ describe('LongInterval', () => {
46464646
});
46474647

46484648
it('should properly calculate the left boundary long', () => {
4649-
d.zeroToHundredLong.closed.properContains(0n).should.be.false();
4649+
d.zeroToHundredLong.closed.properContains(0n).should.be.true();
46504650
d.zeroToHundredLong.open.properContains(0n).should.be.false();
46514651
d.zeroToHundredLong.closed.properContains(1n).should.be.true();
4652-
d.zeroToHundredLong.open.properContains(1n).should.be.false();
4652+
d.zeroToHundredLong.open.properContains(1n).should.be.true();
46534653
d.zeroToHundredLong.closed.properContains(2n).should.be.true();
46544654
d.zeroToHundredLong.open.properContains(2n).should.be.true();
46554655
});
@@ -4659,10 +4659,10 @@ describe('LongInterval', () => {
46594659
});
46604660

46614661
it('should properly calculate the right boundary long', () => {
4662-
d.zeroToHundredLong.closed.properContains(100n).should.be.false();
4662+
d.zeroToHundredLong.closed.properContains(100n).should.be.true();
46634663
d.zeroToHundredLong.open.properContains(100n).should.be.false();
46644664
d.zeroToHundredLong.closed.properContains(99n).should.be.true();
4665-
d.zeroToHundredLong.open.properContains(99n).should.be.false();
4665+
d.zeroToHundredLong.open.properContains(99n).should.be.true();
46664666
d.zeroToHundredLong.closed.properContains(98n).should.be.true();
46674667
d.zeroToHundredLong.open.properContains(98n).should.be.true();
46684668
});
@@ -4674,12 +4674,12 @@ describe('LongInterval', () => {
46744674
it('should properly handle null endpoints', () => {
46754675
new Interval(null, 0n).properContains(-123456789n).should.be.true();
46764676
new Interval(null, 0n).properContains(1n).should.be.false();
4677-
new Interval(null, 0n, false, true).properContains(0n).should.be.false();
4677+
should(new Interval(null, 0n, false, true).properContains(0n)).be.null();
46784678
should(new Interval(null, 0n, false, true).properContains(-123456789n)).be.null();
46794679
new Interval(null, 0n, false, true).properContains(1n).should.be.false();
46804680
new Interval(0n, null).properContains(123456789n).should.be.true();
46814681
new Interval(0n, null).properContains(-1n).should.be.false();
4682-
new Interval(0n, null, true, false).properContains(0n).should.be.false();
4682+
should(new Interval(0n, null, true, false).properContains(0n)).be.null();
46834683
should(new Interval(0n, null, true, false).properContains(123456789n)).be.null();
46844684
new Interval(0n, null, true, false).properContains(-1n).should.be.false();
46854685
new Interval(null, null, true, true, ELM_LONG_TYPE).properContains(0n).should.be.true();
@@ -4689,31 +4689,31 @@ describe('LongInterval', () => {
46894689
it('should properly handle imprecision', () => {
46904690
d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, -10n)).should.be.false();
46914691
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, 20n)));
4692-
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 100n)));
4693-
d.zeroToHundredLong.closed.properContains(new Uncertainty(1n, 99n)).should.be.true();
4692+
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 101n)));
4693+
d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 100n)).should.be.true();
46944694
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(80n, 120n)));
46954695
d.zeroToHundredLong.closed.properContains(new Uncertainty(120n, 140n)).should.be.false();
46964696
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, 120n)));
46974697

46984698
const uIvl = new Interval(new Uncertainty(5n, 10n), new Uncertainty(15n, 20n));
46994699

47004700
uIvl.properContains(0n).should.be.false();
4701-
uIvl.properContains(5n).should.be.false();
4701+
should.not.exist(uIvl.properContains(5n));
47024702
should.not.exist(uIvl.properContains(6n));
4703-
should.not.exist(uIvl.properContains(10n));
4703+
uIvl.properContains(10n).should.be.true();
47044704
uIvl.properContains(12n).should.be.true();
4705-
should.not.exist(uIvl.properContains(15n));
4705+
uIvl.properContains(15n).should.be.true();
47064706
should.not.exist(uIvl.properContains(16n));
4707-
uIvl.properContains(20n).should.be.false();
4707+
should.not.exist(uIvl.properContains(20n));
47084708
uIvl.properContains(25n).should.be.false();
47094709

47104710
uIvl.properContains(new Uncertainty(0n, 4n)).should.be.false();
4711-
uIvl.properContains(new Uncertainty(0n, 5n)).should.be.false();
4711+
should.not.exist(uIvl.properContains(new Uncertainty(0n, 5n)));
47124712
should.not.exist(uIvl.properContains(new Uncertainty(5n, 10n)));
4713-
should.not.exist(uIvl.properContains(new Uncertainty(10n, 15n)));
4713+
uIvl.properContains(new Uncertainty(10n, 15n)).should.be.true();
47144714
uIvl.properContains(new Uncertainty(11n, 14n)).should.be.true();
47154715
should.not.exist(uIvl.properContains(new Uncertainty(15n, 20n)));
4716-
uIvl.properContains(new Uncertainty(20n, 25n)).should.be.false();
4716+
should.not.exist(uIvl.properContains(new Uncertainty(20n, 25n)));
47174717
uIvl.properContains(new Uncertainty(25n, 30n)).should.be.false();
47184718
});
47194719

0 commit comments

Comments
 (0)