Skip to content

Commit 5cf3405

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 ab35042 commit 5cf3405

9 files changed

Lines changed: 1544 additions & 1512 deletions

File tree

examples/browser/cql4browsers.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1882,14 +1882,19 @@ 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) {

src/datatypes/interval.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,20 @@ 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

test/datatypes/interval-test.ts

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

535535
it('should properly calculate the left boundary date', () => {
536-
d.all2012.closed.properContains(d.beg2012.full).should.be.false();
536+
d.all2012.closed.properContains(d.beg2012.full).should.be.true();
537537
d.all2012.open.properContains(d.beg2012.full).should.be.false();
538538

539-
let next = d.beg2012.full.successor();
540-
d.all2012.closed.properContains(next).should.be.true();
541-
d.all2012.open.properContains(next).should.be.false();
539+
const prev = d.beg2012.full.predecessor();
540+
d.all2012.closed.properContains(prev).should.be.false();
541+
d.all2012.open.properContains(prev).should.be.false();
542542

543-
next = next.successor();
543+
const next = d.beg2012.full.successor();
544544
d.all2012.closed.properContains(next).should.be.true();
545545
d.all2012.open.properContains(next).should.be.true();
546546
});
@@ -550,16 +550,16 @@ describe('DateTimeInterval', () => {
550550
});
551551

552552
it('should properly calculate the right boundary date', () => {
553-
d.all2012.closed.properContains(d.end2012.full).should.be.false();
553+
d.all2012.closed.properContains(d.end2012.full).should.be.true();
554554
d.all2012.open.properContains(d.end2012.full).should.be.false();
555555

556-
let prev = d.end2012.full.predecessor();
557-
d.all2012.closed.properContains(prev).should.be.true();
558-
d.all2012.open.properContains(prev).should.be.false();
559-
560-
prev = prev.predecessor();
556+
const prev = d.end2012.full.predecessor();
561557
d.all2012.closed.properContains(prev).should.be.true();
562558
d.all2012.open.properContains(prev).should.be.true();
559+
560+
const next = d.end2012.full.successor();
561+
d.all2012.closed.properContains(next).should.be.false();
562+
d.all2012.open.properContains(next).should.be.false();
563563
});
564564

565565
it('should properly calculate dates after it', () => {
@@ -572,16 +572,16 @@ describe('DateTimeInterval', () => {
572572
const early = DateTime.parse('2000-01-01T00:00:00.0');
573573
const late = DateTime.parse('2999-01-01T00:00:00.0');
574574
const maxDate = MAX_DATETIME_VALUE;
575-
new Interval(null, date).properContains(minDate).should.be.false();
575+
new Interval(null, date).properContains(minDate).should.be.true();
576576
new Interval(null, date).properContains(early).should.be.true();
577577
new Interval(null, date).properContains(late).should.be.false();
578-
new Interval(null, date, false, true).properContains(date).should.be.false();
578+
should(new Interval(null, date, false, true).properContains(date)).be.null();
579579
should(new Interval(null, date, false, true).properContains(early)).be.null();
580580
new Interval(null, date, false, true).properContains(late).should.be.false();
581581
new Interval(date, null).properContains(late).should.be.true();
582582
new Interval(date, null).properContains(early).should.be.false();
583-
new Interval(date, null).properContains(maxDate).should.be.false();
584-
new Interval(date, null, true, false).properContains(date).should.be.false();
583+
new Interval(date, null).properContains(maxDate).should.be.true();
584+
should(new Interval(date, null, true, false).properContains(date)).be.null();
585585
should(new Interval(date, null, true, false).properContains(late)).be.null();
586586
new Interval(date, null, true, false).properContains(early).should.be.false();
587587
new Interval(null, null, true, true, ELM_DATETIME_TYPE).properContains(date).should.be.true();
@@ -598,9 +598,9 @@ describe('DateTimeInterval', () => {
598598
d.all2012.closed.properContains(d.aft2012.toMonth).should.be.false();
599599

600600
d.all2012.toMonth.properContains(d.bef2012.toMonth).should.be.false();
601-
d.all2012.toMonth.properContains(d.beg2012.toMonth).should.be.false();
601+
d.all2012.toMonth.properContains(d.beg2012.toMonth).should.be.true();
602602
d.all2012.toMonth.properContains(d.mid2012.toMonth).should.be.true();
603-
d.all2012.toMonth.properContains(d.end2012.toMonth).should.be.false();
603+
d.all2012.toMonth.properContains(d.end2012.toMonth).should.be.true();
604604
d.all2012.toMonth.properContains(d.aft2012.toMonth).should.be.false();
605605

606606
d.all2012.toMonth.properContains(d.bef2012.full).should.be.false();
@@ -2893,10 +2893,10 @@ describe('IntegerInterval', () => {
28932893
});
28942894

28952895
it('should properly calculate the left boundary integer', () => {
2896-
d.zeroToHundred.closed.properContains(0).should.be.false();
2896+
d.zeroToHundred.closed.properContains(0).should.be.true();
28972897
d.zeroToHundred.open.properContains(0).should.be.false();
28982898
d.zeroToHundred.closed.properContains(1).should.be.true();
2899-
d.zeroToHundred.open.properContains(1).should.be.false();
2899+
d.zeroToHundred.open.properContains(1).should.be.true();
29002900
d.zeroToHundred.closed.properContains(2).should.be.true();
29012901
d.zeroToHundred.open.properContains(2).should.be.true();
29022902
});
@@ -2906,10 +2906,10 @@ describe('IntegerInterval', () => {
29062906
});
29072907

29082908
it('should properly calculate the right boundary integer', () => {
2909-
d.zeroToHundred.closed.properContains(100).should.be.false();
2909+
d.zeroToHundred.closed.properContains(100).should.be.true();
29102910
d.zeroToHundred.open.properContains(100).should.be.false();
29112911
d.zeroToHundred.closed.properContains(99).should.be.true();
2912-
d.zeroToHundred.open.properContains(99).should.be.false();
2912+
d.zeroToHundred.open.properContains(99).should.be.true();
29132913
d.zeroToHundred.closed.properContains(98).should.be.true();
29142914
d.zeroToHundred.open.properContains(98).should.be.true();
29152915
});
@@ -2921,12 +2921,12 @@ describe('IntegerInterval', () => {
29212921
it('should properly handle null endpoints', () => {
29222922
new Interval(null, 0).properContains(-123456789).should.be.true();
29232923
new Interval(null, 0).properContains(1).should.be.false();
2924-
new Interval(null, 0, false, true).properContains(0).should.be.false();
2924+
should(new Interval(null, 0, false, true).properContains(0)).be.null();
29252925
should(new Interval(null, 0, false, true).properContains(-123456789)).be.null();
29262926
new Interval(null, 0, false, true).properContains(1).should.be.false();
29272927
new Interval(0, null).properContains(123456789).should.be.true();
29282928
new Interval(0, null).properContains(-1).should.be.false();
2929-
new Interval(0, null, true, false).properContains(0).should.be.false();
2929+
should(new Interval(0, null, true, false).properContains(0)).be.null();
29302930
should(new Interval(0, null, true, false).properContains(123456789)).be.null();
29312931
new Interval(0, null, true, false).properContains(-1).should.be.false();
29322932
new Interval(null, null, true, true, ELM_INTEGER_TYPE).properContains(0).should.be.true();
@@ -2936,31 +2936,31 @@ describe('IntegerInterval', () => {
29362936
it('should properly handle imprecision', () => {
29372937
d.zeroToHundred.closed.properContains(new Uncertainty(-20, -10)).should.be.false();
29382938
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(-20, 20)));
2939-
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(0, 100)));
2940-
d.zeroToHundred.closed.properContains(new Uncertainty(1, 99)).should.be.true();
2939+
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(0, 101)));
2940+
d.zeroToHundred.closed.properContains(new Uncertainty(0, 100)).should.be.true();
29412941
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(80, 120)));
29422942
d.zeroToHundred.closed.properContains(new Uncertainty(120, 140)).should.be.false();
29432943
should.not.exist(d.zeroToHundred.closed.properContains(new Uncertainty(-20, 120)));
29442944

29452945
const uIvl = new Interval(new Uncertainty(5, 10), new Uncertainty(15, 20));
29462946

29472947
uIvl.properContains(0).should.be.false();
2948-
uIvl.properContains(5).should.be.false();
2948+
should.not.exist(uIvl.properContains(5));
29492949
should.not.exist(uIvl.properContains(6));
2950-
should.not.exist(uIvl.properContains(10));
2950+
uIvl.properContains(10).should.be.true();
29512951
uIvl.properContains(12).should.be.true();
2952-
should.not.exist(uIvl.properContains(15));
2952+
uIvl.properContains(15).should.be.true();
29532953
should.not.exist(uIvl.properContains(16));
2954-
uIvl.properContains(20).should.be.false();
2954+
should.not.exist(uIvl.properContains(20));
29552955
uIvl.properContains(25).should.be.false();
29562956

29572957
uIvl.properContains(new Uncertainty(0, 4)).should.be.false();
2958-
uIvl.properContains(new Uncertainty(0, 5)).should.be.false();
2958+
should.not.exist(uIvl.properContains(new Uncertainty(0, 5)));
29592959
should.not.exist(uIvl.properContains(new Uncertainty(5, 10)));
2960-
should.not.exist(uIvl.properContains(new Uncertainty(10, 15)));
2960+
uIvl.properContains(new Uncertainty(10, 15)).should.be.true();
29612961
uIvl.properContains(new Uncertainty(11, 14)).should.be.true();
29622962
should.not.exist(uIvl.properContains(new Uncertainty(15, 20)));
2963-
uIvl.properContains(new Uncertainty(20, 25)).should.be.false();
2963+
should.not.exist(uIvl.properContains(new Uncertainty(20, 25)));
29642964
uIvl.properContains(new Uncertainty(25, 30)).should.be.false();
29652965
});
29662966

@@ -4971,10 +4971,10 @@ describe('LongInterval', () => {
49714971
});
49724972

49734973
it('should properly calculate the left boundary long', () => {
4974-
d.zeroToHundredLong.closed.properContains(0n).should.be.false();
4974+
d.zeroToHundredLong.closed.properContains(0n).should.be.true();
49754975
d.zeroToHundredLong.open.properContains(0n).should.be.false();
49764976
d.zeroToHundredLong.closed.properContains(1n).should.be.true();
4977-
d.zeroToHundredLong.open.properContains(1n).should.be.false();
4977+
d.zeroToHundredLong.open.properContains(1n).should.be.true();
49784978
d.zeroToHundredLong.closed.properContains(2n).should.be.true();
49794979
d.zeroToHundredLong.open.properContains(2n).should.be.true();
49804980
});
@@ -4984,10 +4984,10 @@ describe('LongInterval', () => {
49844984
});
49854985

49864986
it('should properly calculate the right boundary long', () => {
4987-
d.zeroToHundredLong.closed.properContains(100n).should.be.false();
4987+
d.zeroToHundredLong.closed.properContains(100n).should.be.true();
49884988
d.zeroToHundredLong.open.properContains(100n).should.be.false();
49894989
d.zeroToHundredLong.closed.properContains(99n).should.be.true();
4990-
d.zeroToHundredLong.open.properContains(99n).should.be.false();
4990+
d.zeroToHundredLong.open.properContains(99n).should.be.true();
49914991
d.zeroToHundredLong.closed.properContains(98n).should.be.true();
49924992
d.zeroToHundredLong.open.properContains(98n).should.be.true();
49934993
});
@@ -4999,12 +4999,12 @@ describe('LongInterval', () => {
49994999
it('should properly handle null endpoints', () => {
50005000
new Interval(null, 0n).properContains(-123456789n).should.be.true();
50015001
new Interval(null, 0n).properContains(1n).should.be.false();
5002-
new Interval(null, 0n, false, true).properContains(0n).should.be.false();
5002+
should(new Interval(null, 0n, false, true).properContains(0n)).be.null();
50035003
should(new Interval(null, 0n, false, true).properContains(-123456789n)).be.null();
50045004
new Interval(null, 0n, false, true).properContains(1n).should.be.false();
50055005
new Interval(0n, null).properContains(123456789n).should.be.true();
50065006
new Interval(0n, null).properContains(-1n).should.be.false();
5007-
new Interval(0n, null, true, false).properContains(0n).should.be.false();
5007+
should(new Interval(0n, null, true, false).properContains(0n)).be.null();
50085008
should(new Interval(0n, null, true, false).properContains(123456789n)).be.null();
50095009
new Interval(0n, null, true, false).properContains(-1n).should.be.false();
50105010
new Interval(null, null, true, true, ELM_LONG_TYPE).properContains(0n).should.be.true();
@@ -5014,31 +5014,31 @@ describe('LongInterval', () => {
50145014
it('should properly handle imprecision', () => {
50155015
d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, -10n)).should.be.false();
50165016
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, 20n)));
5017-
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 100n)));
5018-
d.zeroToHundredLong.closed.properContains(new Uncertainty(1n, 99n)).should.be.true();
5017+
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 101n)));
5018+
d.zeroToHundredLong.closed.properContains(new Uncertainty(0n, 100n)).should.be.true();
50195019
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(80n, 120n)));
50205020
d.zeroToHundredLong.closed.properContains(new Uncertainty(120n, 140n)).should.be.false();
50215021
should.not.exist(d.zeroToHundredLong.closed.properContains(new Uncertainty(-20n, 120n)));
50225022

50235023
const uIvl = new Interval(new Uncertainty(5n, 10n), new Uncertainty(15n, 20n));
50245024

50255025
uIvl.properContains(0n).should.be.false();
5026-
uIvl.properContains(5n).should.be.false();
5026+
should.not.exist(uIvl.properContains(5n));
50275027
should.not.exist(uIvl.properContains(6n));
5028-
should.not.exist(uIvl.properContains(10n));
5028+
uIvl.properContains(10n).should.be.true();
50295029
uIvl.properContains(12n).should.be.true();
5030-
should.not.exist(uIvl.properContains(15n));
5030+
uIvl.properContains(15n).should.be.true();
50315031
should.not.exist(uIvl.properContains(16n));
5032-
uIvl.properContains(20n).should.be.false();
5032+
should.not.exist(uIvl.properContains(20n));
50335033
uIvl.properContains(25n).should.be.false();
50345034

50355035
uIvl.properContains(new Uncertainty(0n, 4n)).should.be.false();
5036-
uIvl.properContains(new Uncertainty(0n, 5n)).should.be.false();
5036+
should.not.exist(uIvl.properContains(new Uncertainty(0n, 5n)));
50375037
should.not.exist(uIvl.properContains(new Uncertainty(5n, 10n)));
5038-
should.not.exist(uIvl.properContains(new Uncertainty(10n, 15n)));
5038+
uIvl.properContains(new Uncertainty(10n, 15n)).should.be.true();
50395039
uIvl.properContains(new Uncertainty(11n, 14n)).should.be.true();
50405040
should.not.exist(uIvl.properContains(new Uncertainty(15n, 20n)));
5041-
uIvl.properContains(new Uncertainty(20n, 25n)).should.be.false();
5041+
should.not.exist(uIvl.properContains(new Uncertainty(20n, 25n)));
50425042
uIvl.properContains(new Uncertainty(25n, 30n)).should.be.false();
50435043
});
50445044

0 commit comments

Comments
 (0)