Skip to content

Commit e72ad94

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 c987d81 commit e72ad94

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
@@ -1882,20 +1882,23 @@ class Interval {
18821882
// comparisons used in the operation are performed at the specified precision."
18831883
return logic_1.ThreeValuedLogic.and(cmp.greaterThanOrEquals(item, this.start(), precision), cmp.lessThanOrEquals(item, this.end(), precision));
18841884
}
1885+
// https://build.fhir.org/ig/HL7/cql/09-b-cqlreference.html#properly-includes
18851886
properContains(item, precision) {
1887+
// From contains: "If the second argument is null, the result is null."
18861888
if (item == null) {
18871889
return null;
18881890
}
18891891
else if (item.isInterval) {
18901892
throw new Error('Argument to contains must be a point');
18911893
}
1892-
return logic_1.ThreeValuedLogic.and(cmp.lessThan(this.start(), item, precision), cmp.greaterThan(this.end(), item, precision));
1894+
// "For the interval-point overload, this operator returns true if the interval contains
1895+
// (i.e. includes) the point, and the interval is not a unit interval containing only the
1896+
// point."
1897+
return logic_1.ThreeValuedLogic.and(this.contains(item, precision), logic_1.ThreeValuedLogic.not(cmp.equals(this.start(), this.end())));
18931898
}
18941899
// https://build.fhir.org/ig/HL7/cql/09-b-cqlreference.html#properly-includes
18951900
properlyIncludes(other, precision) {
1896-
// TODO: "For the interval-point overload, this operator returns true if the interval contains
1897-
// (i.e. includes) the point, and the interval is not a unit interval containing only the
1898-
// point."
1901+
// "For the interval-interval overload, if either argument is null, the result is null."
18991902
if (other == null || !other.isInterval) {
19001903
throw new Error('Argument to properlyIncludes must be an interval');
19011904
}

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
@@ -485,14 +485,14 @@ describe('DateTimeInterval', () => {
485485
});
486486

487487
it('should properly calculate the left boundary date', () => {
488-
d.all2012.closed.properContains(d.beg2012.full).should.be.false();
488+
d.all2012.closed.properContains(d.beg2012.full).should.be.true();
489489
d.all2012.open.properContains(d.beg2012.full).should.be.false();
490490

491-
let next = d.beg2012.full.successor();
492-
d.all2012.closed.properContains(next).should.be.true();
493-
d.all2012.open.properContains(next).should.be.false();
491+
const prev = d.beg2012.full.predecessor();
492+
d.all2012.closed.properContains(prev).should.be.false();
493+
d.all2012.open.properContains(prev).should.be.false();
494494

495-
next = next.successor();
495+
const next = d.beg2012.full.successor();
496496
d.all2012.closed.properContains(next).should.be.true();
497497
d.all2012.open.properContains(next).should.be.true();
498498
});
@@ -502,16 +502,16 @@ describe('DateTimeInterval', () => {
502502
});
503503

504504
it('should properly calculate the right boundary date', () => {
505-
d.all2012.closed.properContains(d.end2012.full).should.be.false();
505+
d.all2012.closed.properContains(d.end2012.full).should.be.true();
506506
d.all2012.open.properContains(d.end2012.full).should.be.false();
507507

508-
let prev = d.end2012.full.predecessor();
509-
d.all2012.closed.properContains(prev).should.be.true();
510-
d.all2012.open.properContains(prev).should.be.false();
511-
512-
prev = prev.predecessor();
508+
const prev = d.end2012.full.predecessor();
513509
d.all2012.closed.properContains(prev).should.be.true();
514510
d.all2012.open.properContains(prev).should.be.true();
511+
512+
const next = d.end2012.full.successor();
513+
d.all2012.closed.properContains(next).should.be.false();
514+
d.all2012.open.properContains(next).should.be.false();
515515
});
516516

517517
it('should properly calculate dates after it', () => {
@@ -524,16 +524,16 @@ describe('DateTimeInterval', () => {
524524
const early = DateTime.parse('2000-01-01T00:00:00.0');
525525
const late = DateTime.parse('2999-01-01T00:00:00.0');
526526
const maxDate = MAX_DATETIME_VALUE;
527-
new Interval(null, date).properContains(minDate).should.be.false();
527+
new Interval(null, date).properContains(minDate).should.be.true();
528528
new Interval(null, date).properContains(early).should.be.true();
529529
new Interval(null, date).properContains(late).should.be.false();
530-
new Interval(null, date, false, true).properContains(date).should.be.false();
530+
should(new Interval(null, date, false, true).properContains(date)).be.null();
531531
should(new Interval(null, date, false, true).properContains(early)).be.null();
532532
new Interval(null, date, false, true).properContains(late).should.be.false();
533533
new Interval(date, null).properContains(late).should.be.true();
534534
new Interval(date, null).properContains(early).should.be.false();
535-
new Interval(date, null).properContains(maxDate).should.be.false();
536-
new Interval(date, null, true, false).properContains(date).should.be.false();
535+
new Interval(date, null).properContains(maxDate).should.be.true();
536+
should(new Interval(date, null, true, false).properContains(date)).be.null();
537537
should(new Interval(date, null, true, false).properContains(late)).be.null();
538538
new Interval(date, null, true, false).properContains(early).should.be.false();
539539
new Interval(null, null, true, true, ELM_DATETIME_TYPE).properContains(date).should.be.true();
@@ -550,9 +550,9 @@ describe('DateTimeInterval', () => {
550550
d.all2012.closed.properContains(d.aft2012.toMonth).should.be.false();
551551

552552
d.all2012.toMonth.properContains(d.bef2012.toMonth).should.be.false();
553-
d.all2012.toMonth.properContains(d.beg2012.toMonth).should.be.false();
553+
d.all2012.toMonth.properContains(d.beg2012.toMonth).should.be.true();
554554
d.all2012.toMonth.properContains(d.mid2012.toMonth).should.be.true();
555-
d.all2012.toMonth.properContains(d.end2012.toMonth).should.be.false();
555+
d.all2012.toMonth.properContains(d.end2012.toMonth).should.be.true();
556556
d.all2012.toMonth.properContains(d.aft2012.toMonth).should.be.false();
557557

558558
d.all2012.toMonth.properContains(d.bef2012.full).should.be.false();
@@ -2692,10 +2692,10 @@ describe('IntegerInterval', () => {
26922692
});
26932693

26942694
it('should properly calculate the left boundary integer', () => {
2695-
d.zeroToHundred.closed.properContains(0).should.be.false();
2695+
d.zeroToHundred.closed.properContains(0).should.be.true();
26962696
d.zeroToHundred.open.properContains(0).should.be.false();
26972697
d.zeroToHundred.closed.properContains(1).should.be.true();
2698-
d.zeroToHundred.open.properContains(1).should.be.false();
2698+
d.zeroToHundred.open.properContains(1).should.be.true();
26992699
d.zeroToHundred.closed.properContains(2).should.be.true();
27002700
d.zeroToHundred.open.properContains(2).should.be.true();
27012701
});
@@ -2705,10 +2705,10 @@ describe('IntegerInterval', () => {
27052705
});
27062706

27072707
it('should properly calculate the right boundary integer', () => {
2708-
d.zeroToHundred.closed.properContains(100).should.be.false();
2708+
d.zeroToHundred.closed.properContains(100).should.be.true();
27092709
d.zeroToHundred.open.properContains(100).should.be.false();
27102710
d.zeroToHundred.closed.properContains(99).should.be.true();
2711-
d.zeroToHundred.open.properContains(99).should.be.false();
2711+
d.zeroToHundred.open.properContains(99).should.be.true();
27122712
d.zeroToHundred.closed.properContains(98).should.be.true();
27132713
d.zeroToHundred.open.properContains(98).should.be.true();
27142714
});
@@ -2720,12 +2720,12 @@ describe('IntegerInterval', () => {
27202720
it('should properly handle null endpoints', () => {
27212721
new Interval(null, 0).properContains(-123456789).should.be.true();
27222722
new Interval(null, 0).properContains(1).should.be.false();
2723-
new Interval(null, 0, false, true).properContains(0).should.be.false();
2723+
should(new Interval(null, 0, false, true).properContains(0)).be.null();
27242724
should(new Interval(null, 0, false, true).properContains(-123456789)).be.null();
27252725
new Interval(null, 0, false, true).properContains(1).should.be.false();
27262726
new Interval(0, null).properContains(123456789).should.be.true();
27272727
new Interval(0, null).properContains(-1).should.be.false();
2728-
new Interval(0, null, true, false).properContains(0).should.be.false();
2728+
should(new Interval(0, null, true, false).properContains(0)).be.null();
27292729
should(new Interval(0, null, true, false).properContains(123456789)).be.null();
27302730
new Interval(0, null, true, false).properContains(-1).should.be.false();
27312731
new Interval(null, null, true, true, ELM_INTEGER_TYPE).properContains(0).should.be.true();
@@ -2735,31 +2735,31 @@ describe('IntegerInterval', () => {
27352735
it('should properly handle imprecision', () => {
27362736
d.zeroToHundred.closed.properContains(new Uncertainty(-20, -10)).should.be.false();
27372737
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(-20, 20)));
2738-
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(0, 100)));
2739-
d.zeroToHundred.closed.properContains(new Uncertainty(1, 99)).should.be.true();
2738+
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(0, 101)));
2739+
d.zeroToHundred.closed.properContains(new Uncertainty(0, 100)).should.be.true();
27402740
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(80, 120)));
27412741
d.zeroToHundred.closed.properContains(new Uncertainty(120, 140)).should.be.false();
27422742
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(-20, 120)));
27432743

27442744
const uIvl = new Interval(new Uncertainty(5, 10), new Uncertainty(15, 20));
27452745

27462746
uIvl.properContains(0).should.be.false();
2747-
uIvl.properContains(5).should.be.false();
2747+
should.not.exist(uIvl.properContains(5));
27482748
should.not.exist(uIvl.properContains(6));
2749-
should.not.exist(uIvl.properContains(10));
2749+
uIvl.properContains(10).should.be.true();
27502750
uIvl.properContains(12).should.be.true();
2751-
should.not.exist(uIvl.properContains(15));
2751+
uIvl.properContains(15).should.be.true();
27522752
should.not.exist(uIvl.properContains(16));
2753-
uIvl.properContains(20).should.be.false();
2753+
should.not.exist(uIvl.properContains(20));
27542754
uIvl.properContains(25).should.be.false();
27552755

27562756
uIvl.properContains(new Uncertainty(0, 4)).should.be.false();
2757-
uIvl.properContains(new Uncertainty(0, 5)).should.be.false();
2757+
should.not.exist(uIvl.properContains(new Uncertainty(0, 5)));
27582758
should.not.exist(uIvl.properContains(new Uncertainty(5, 10)));
2759-
should.not.exist(uIvl.properContains(new Uncertainty(10, 15)));
2759+
uIvl.properContains(new Uncertainty(10, 15)).should.be.true();
27602760
uIvl.properContains(new Uncertainty(11, 14)).should.be.true();
27612761
should.not.exist(uIvl.properContains(new Uncertainty(15, 20)));
2762-
uIvl.properContains(new Uncertainty(20, 25)).should.be.false();
2762+
should.not.exist(uIvl.properContains(new Uncertainty(20, 25)));
27632763
uIvl.properContains(new Uncertainty(25, 30)).should.be.false();
27642764
});
27652765

@@ -4657,10 +4657,10 @@ describe('LongInterval', () => {
46574657
});
46584658

46594659
it('should properly calculate the left boundary long', () => {
4660-
d.zeroToHundredLong.closed.properContains(0n).should.be.false();
4660+
d.zeroToHundredLong.closed.properContains(0n).should.be.true();
46614661
d.zeroToHundredLong.open.properContains(0n).should.be.false();
46624662
d.zeroToHundredLong.closed.properContains(1n).should.be.true();
4663-
d.zeroToHundredLong.open.properContains(1n).should.be.false();
4663+
d.zeroToHundredLong.open.properContains(1n).should.be.true();
46644664
d.zeroToHundredLong.closed.properContains(2n).should.be.true();
46654665
d.zeroToHundredLong.open.properContains(2n).should.be.true();
46664666
});
@@ -4670,10 +4670,10 @@ describe('LongInterval', () => {
46704670
});
46714671

46724672
it('should properly calculate the right boundary long', () => {
4673-
d.zeroToHundredLong.closed.properContains(100n).should.be.false();
4673+
d.zeroToHundredLong.closed.properContains(100n).should.be.true();
46744674
d.zeroToHundredLong.open.properContains(100n).should.be.false();
46754675
d.zeroToHundredLong.closed.properContains(99n).should.be.true();
4676-
d.zeroToHundredLong.open.properContains(99n).should.be.false();
4676+
d.zeroToHundredLong.open.properContains(99n).should.be.true();
46774677
d.zeroToHundredLong.closed.properContains(98n).should.be.true();
46784678
d.zeroToHundredLong.open.properContains(98n).should.be.true();
46794679
});
@@ -4685,12 +4685,12 @@ describe('LongInterval', () => {
46854685
it('should properly handle null endpoints', () => {
46864686
new Interval(null, 0n).properContains(-123456789n).should.be.true();
46874687
new Interval(null, 0n).properContains(1n).should.be.false();
4688-
new Interval(null, 0n, false, true).properContains(0n).should.be.false();
4688+
should(new Interval(null, 0n, false, true).properContains(0n)).be.null();
46894689
should(new Interval(null, 0n, false, true).properContains(-123456789n)).be.null();
46904690
new Interval(null, 0n, false, true).properContains(1n).should.be.false();
46914691
new Interval(0n, null).properContains(123456789n).should.be.true();
46924692
new Interval(0n, null).properContains(-1n).should.be.false();
4693-
new Interval(0n, null, true, false).properContains(0n).should.be.false();
4693+
should(new Interval(0n, null, true, false).properContains(0n)).be.null();
46944694
should(new Interval(0n, null, true, false).properContains(123456789n)).be.null();
46954695
new Interval(0n, null, true, false).properContains(-1n).should.be.false();
46964696
new Interval(null, null, true, true, ELM_LONG_TYPE).properContains(0n).should.be.true();
@@ -4700,31 +4700,31 @@ describe('LongInterval', () => {
47004700
it('should properly handle imprecision', () => {
47014701
d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, -10n)).should.be.false();
47024702
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, 20n)));
4703-
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 100n)));
4704-
d.zeroToHundredLong.closed.properContains(new Uncertainty(1n, 99n)).should.be.true();
4703+
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 101n)));
4704+
d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 100n)).should.be.true();
47054705
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(80n, 120n)));
47064706
d.zeroToHundredLong.closed.properContains(new Uncertainty(120n, 140n)).should.be.false();
47074707
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, 120n)));
47084708

47094709
const uIvl = new Interval(new Uncertainty(5n, 10n), new Uncertainty(15n, 20n));
47104710

47114711
uIvl.properContains(0n).should.be.false();
4712-
uIvl.properContains(5n).should.be.false();
4712+
should.not.exist(uIvl.properContains(5n));
47134713
should.not.exist(uIvl.properContains(6n));
4714-
should.not.exist(uIvl.properContains(10n));
4714+
uIvl.properContains(10n).should.be.true();
47154715
uIvl.properContains(12n).should.be.true();
4716-
should.not.exist(uIvl.properContains(15n));
4716+
uIvl.properContains(15n).should.be.true();
47174717
should.not.exist(uIvl.properContains(16n));
4718-
uIvl.properContains(20n).should.be.false();
4718+
should.not.exist(uIvl.properContains(20n));
47194719
uIvl.properContains(25n).should.be.false();
47204720

47214721
uIvl.properContains(new Uncertainty(0n, 4n)).should.be.false();
4722-
uIvl.properContains(new Uncertainty(0n, 5n)).should.be.false();
4722+
should.not.exist(uIvl.properContains(new Uncertainty(0n, 5n)));
47234723
should.not.exist(uIvl.properContains(new Uncertainty(5n, 10n)));
4724-
should.not.exist(uIvl.properContains(new Uncertainty(10n, 15n)));
4724+
uIvl.properContains(new Uncertainty(10n, 15n)).should.be.true();
47254725
uIvl.properContains(new Uncertainty(11n, 14n)).should.be.true();
47264726
should.not.exist(uIvl.properContains(new Uncertainty(15n, 20n)));
4727-
uIvl.properContains(new Uncertainty(20n, 25n)).should.be.false();
4727+
should.not.exist(uIvl.properContains(new Uncertainty(20n, 25n)));
47284728
uIvl.properContains(new Uncertainty(25n, 30n)).should.be.false();
47294729
});
47304730

0 commit comments

Comments
 (0)