diff --git a/js/data/segmentutil.js b/js/data/segmentutil.js index c4d3cd3f..65e2f11d 100644 --- a/js/data/segmentutil.js +++ b/js/data/segmentutil.js @@ -21,23 +21,11 @@ var log = require('../lib/').bows('SegmentUtil'); var Timeline = require('./util/timeline.js'); var keysForEquality = ['type', 'deliveryType', 'value', 'percent', 'deviceId', 'scheduleName', 'source', 'link']; - function eventsSmooshable(lhs, rhs) { return _.isEqual(_.pick(lhs, keysForEquality), _.pick(rhs, keysForEquality)); } -function determinePriority(e) { - switch(e.deliveryType) { - case 'scheduled': - return 1; - case 'temp': - return 10; - case 'suspend': - return 100; - default: - return 0; - } -} +var eventPriority = ['temp', 'suspend']; function SegmentUtil(actual, undelivered) { this.actual = actual; @@ -49,197 +37,238 @@ SegmentUtil.prototype.getUndelivered = function(type) { return retVal == null ? [] : retVal; }; -module.exports = function(data){ - var maxTimestamp = '0000-01-01T00:00:00'; - var actuals = new Timeline(eventsSmooshable); - var undelivereds = { }; - var overlaps = []; - - function addToActuals(e) { - if (e.start === e.end) { - return []; +function insertSorted(array, startIndex, e, field) { + if (array.length === 0) { + array.push(e); + } else { + var index = startIndex; + + for (; index < array.length; ++index) { + if (array[index][field] > e[field]) { + array.splice(index, 0, e); + return; + } } - var theActual = _.extend({}, e, {vizType: 'actual'}); - return actuals.add(theActual); + if (array[array.length - 1][field] <= e[field]) { + array.push(e); + } else { + insertSorted(array, 0, e, field); + } } +} - function addToUndelivered(e) { - if (undelivereds[e.deliveryType] == null) { - undelivereds[e.deliveryType] = new Timeline(eventsSmooshable); - } +module.exports = function(data){ + var scheduledTimeline = new Timeline(); + var otherEvents = {}; - if (e.deliveryType === 'temp' && undelivereds.scheduled != null) { - // If we have an undelivered temp, then that temp most likely kicked out a scheduled before. That scheduled - // is going to still be associated with the temp that has now been kicked out, so we need to pull it out - // of the scheduled undelivereds and re-process it. - var scheduledArray = undelivereds.scheduled.add(e); - if (scheduledArray.length > 1) { - log('Should only get one scheduled out of the undelivereds.', scheduledArray); - scheduledArray.forEach(function(putBack){ undelivereds.scheduled.add(putBack); }); - } else if (scheduledArray.length === 1) { - var scheduledItem = scheduledArray[0]; - scheduledItem.link = e.link; - undelivereds.scheduled.add(scheduledItem); - } + for (var i = 0; i < data.length; ++i) { + var e = _.clone(data[i]); + if (e.type === 'basal-rate-segment') { + var deliveryType = e.deliveryType; + + if (deliveryType === 'scheduled') { + if (e.end == null) { + // TODO: Jana, this is the point that sets the end equal to the start when end is null. + // TODO: Please adjust the code to add the actual end timestamp of the stream instead of e.start. + // TODO: If you are not named Jana and you are viewing this after April 30, 2014. + // TODO: Please just delete this TODO comment + e.end = e.start; + } - while (undelivereds.scheduled.peek().deliveryType !== 'scheduled') { - undelivereds.scheduled.pop(); - } - } + var overlap = scheduledTimeline.add(e); + if (overlap.length > 0) { + // A scheduled overlapped a scheduled, throw away the overlap and the initial event that git inserted. It generally + // indicates multiple pumps in concurrent operation. - undelivereds[e.deliveryType].add(_.extend({}, e, {vizType: 'undelivered'})); - } + overlap.forEach(function(overlapped){ + // Allow scheduled overlaps for the same device (schedule changes) + if (overlapped.deviceId != null && overlapped.deviceId === e.deviceId) { + return; + } + + // Put the overlapped back in to chunk up the thing that did the overlapping + var inserted = scheduledTimeline.add(overlapped)[0]; + if (inserted.value === overlapped.value) { + return; + } + + // Next, pop the stack until we find what we just inserted, throw that way and push stuff back on + var collateralDamage = []; + var popped = scheduledTimeline.pop(); + while (popped != null && popped.start !== overlapped.start && popped.end !== overlapped.end) { + collateralDamage.push(popped); + popped = scheduledTimeline.pop(); + } - function addLinkFn(e) { - return function(event) { - event.link = e.id; - return event; - }; + collateralDamage.forEach(scheduledTimeline.add.bind(scheduledTimeline)); + }); + } + } else { + if (otherEvents[deliveryType] == null) { + otherEvents[deliveryType] = []; + } + insertSorted(otherEvents[deliveryType], otherEvents[deliveryType].length, e, 'start'); + } + } } - function addToActualsAndLink(e) { - var overflow = addToActuals(e); + var unsmooshed = scheduledTimeline.getArray(); + scheduledTimeline = null; // let go of the memory - var lastActual = actuals.peek(); - var addLink; - if (lastActual.start <= e.start && lastActual.end >= e.end) { - // The event was smooshed into the last actual, so use the last actual's id for linking - addLink = addLinkFn(lastActual); + var baseTimeline = []; + for (i = 0; i < unsmooshed.length; ++i) { + var next = unsmooshed[i]; + if (baseTimeline.length === 0) { + baseTimeline.push(next); } else { - addLink = addLinkFn(e); + var last = baseTimeline[baseTimeline.length - 1]; + + if (last.end === next.start && eventsSmooshable(last, next)) { + last.end = next.end; + } else { + baseTimeline.push(next); + } } + } - return overflow.map(addLink); + function addToBaseTimeline(index, e) { + if (e.value != null) { + baseTimeline.splice(index, 0, e); + } } - function processElement(e) { - if (e.type === 'basal-rate-segment') { - if (maxTimestamp > e.start) { - log('Unordered data', maxTimestamp, e); - throw new Error('Unordered data'); - } else { - maxTimestamp = e.start; - } + eventPriority.forEach(function(eventType){ + var otherArray = otherEvents[eventType]; + delete otherEvents[eventType]; - if (e.start != null && e.end == null) { - // TODO: Jana, this is the point that sets the end equal to the start when end is null. - // TODO: Please adjust the code to add the actual end timestamp of the stream instead of e.start. - // TODO: If you are not named Jana and you are viewing this after April 30, 2014. - // TODO: Please just delete this TODO comment - e.end = e.start; - } + if (otherArray == null) { + return; + } - var lastActual = actuals.peek(); - if (lastActual == null) { - addToActuals(e); - return; + var timelineIndex = 0; + for (var i = 0; i < otherArray.length; ++i) { + var e = otherArray[i]; + while (timelineIndex > 0 && baseTimeline[timelineIndex].start > e.start) { + --timelineIndex; } - var eventPriority = determinePriority(e); - var incumbentPriority = determinePriority(lastActual); + while (timelineIndex < baseTimeline.length && baseTimeline[timelineIndex].end <= e.start) { + ++timelineIndex; + } - if (eventPriority === incumbentPriority) { - if (lastActual.end <= e.start) { - // No overlap! - addToActualsAndLink(e).forEach(addToUndelivered); - return; + if (timelineIndex >= baseTimeline.length) { + // We're at the end of the baseTimeline, but we have more events to insert, so attach them + // as long as the delivery value isn't determined by a percentage + addToBaseTimeline(baseTimeline.length, e); + } else if (baseTimeline[timelineIndex].start > e.end) { + // The item is completely before this one. This means that there is a gap in the data, + // so just insert the item as long as the delivery value isn't determined by a percentage + addToBaseTimeline(timelineIndex, e); + } else { + // Split based on start if needed + var baseItem = baseTimeline[timelineIndex]; + var clone = null; + if (e.start > baseItem.start) { + // Current event starts after the item in the base timeline, + // so keep the first bit of the baseTimeline in tact + clone = _.cloneDeep(baseItem); + baseItem.end = e.start; + clone.start = e.start; + + ++timelineIndex; + addToBaseTimeline(timelineIndex, clone); + baseItem = clone; + } else if (e.start < baseItem.start) { + // Current event starts even before the item in the base timeline, this means there was a gap + // and we want to inject the portion from before the item in the base timeline + clone = _.cloneDeep(e); + + e.start = baseItem.start; + clone.end = baseItem.start; + + addToBaseTimeline(timelineIndex, clone); + ++timelineIndex; } - if (e.deliveryType === 'scheduled' && lastActual.deliveryType === 'scheduled') { - // scheduled overlapping a scheduled, this is known to happen when a patient used multiple - // pumps at the exact same time. Which is rare, to say the least. We want to just eliminate - // both data points and act like we know nothing when this happens - overlaps.push(e); - overlaps.push(actuals.pop()); - return; - } - } else if (eventPriority < incumbentPriority) { - // For example, a scheduled is potentially overlapping a temp, figure out what's going on. - if (lastActual.end <= e.start) { - // No overlap, yay! - addToActualsAndLink(e).forEach(addToUndelivered); - return; - } + // Split based on end if needed + if (e.end > baseItem.end) { + // The current event ends after the item in the base timeline, + // so keep the first bit of the current event and set aside the rest to be processed later + clone = _.cloneDeep(e); - // The scheduled is overlapped by the temp. In this case, what we actually want - // to do is chunk up the temp into invididual chunks to line up with the scheduled. - // We accomplish this by - // 1. Add the scheduled to the actuals timeline, this will return the temp matching our scheduled. - // 2. Adjust the returned temp's value if it specifies a percent. - // 3. Push it back in, this will return the scheduled that we originally put in. - // 4. Push the scheduled into the undelivereds - var arrayWithTemp = addToActuals(e); - if (arrayWithTemp.length !== 1) { - if (arrayWithTemp.length > 1) { - // This is a very special case indeed. If a patient uses 2 pumps at the same time, and - // they have a temp basal that overrides a long chunk of schedules, it is possible that - // one of those scheduleds overlaps another scheduled that was already overlapped by the - // temp. The proper thing to do in this case is to throw away these events, because we - // cannot actually know what is correct. - while (arrayWithTemp.length > 0) { - overlaps.push(arrayWithTemp.pop()); - overlaps.push(actuals.pop()); - } - return; - } else { - log('Should\'ve gotten just the chunked temp, didn\'t.', arrayWithTemp, e); - throw new Error('Should\'ve gotten just the chunked temp, didn\'t.'); - } - } + e.end = baseItem.end; + clone.start = baseItem.end; - var tempMatchingScheduled = arrayWithTemp[0]; - var tempPercent = tempMatchingScheduled.percent; + otherArray.splice(i+1, 0, clone); // Put clone back into the array + } else if (e.end < baseItem.end) { + // The current event ends before the item in the base timeline, + // so keep the last bit of the item in the base timeline in tact + clone = _.cloneDeep(baseItem); + baseItem.end = e.end; + clone.start = e.end; - var adjustments = { id : tempMatchingScheduled.id + '_' + e.id, link: e.id }; - if (tempPercent != null) { - adjustments.value = e.value * tempPercent; + addToBaseTimeline(timelineIndex + 1, clone); } - tempMatchingScheduled = _.assign({}, tempMatchingScheduled, adjustments); - var arrayWithOriginalScheduled = addToActuals(tempMatchingScheduled); - if (arrayWithOriginalScheduled.length !== 1) { - throw new Error('Should\'ve gotten just the original scheduled, didn\'t.', arrayWithOriginalScheduled); + // Push now-supressed base item onto its stack of "supressed" + var overlappingItem = _.clone(e); + overlappingItem.suppressed = baseItem.suppressed == null ? [] : baseItem.suppressed; + delete baseItem.suppressed; + overlappingItem.suppressed.unshift(baseItem); + + if (overlappingItem.percent != null) { + overlappingItem.value = overlappingItem.percent * baseItem.value; } - var theUndelivered = _.clone(arrayWithOriginalScheduled[0]); - theUndelivered.link = tempMatchingScheduled.id; - addToUndelivered(theUndelivered); - return; + // Replace split base item with current item + baseTimeline[timelineIndex] = overlappingItem; } + } + }); - var eventToAdd = e; - if (eventToAdd.percent != null) { - eventToAdd = _.assign({}, e, {value: e.percent * lastActual.value}); - } - var overflow = addToActualsAndLink(eventToAdd); - - while (overflow.length > 0) { - var event = overflow.pop(); - if (eventToAdd.id != null && eventToAdd.id === event.id) { - // If the timeline kicks back out an event with an equivalent id as we just put in, then there - // is another event in there that is overriding us. We want what we just put in to win, - // so put it back in. - delete event.link; - overflow = addToActualsAndLink(event).concat(overflow); - } else { - addToUndelivered(event); - } - } + if (Object.keys(otherEvents).length > 1) { + log('Unhandled basal-rate-segment objects of deliveryType:', Object.keys(otherEvents)); + } + + var actuals = new Array(baseTimeline.length); + var undelivered = {}; + + var newIdCounter = 0; + + function attachId(e) { + if (e.id != null) { + e.datumId = e.id; } + e.id = 'segment_' + newIdCounter++; + return e; + } + + for (i = 0; i < baseTimeline.length; ++i) { + attachId(baseTimeline[i]); + baseTimeline[i].vizType = 'actual'; } - data.forEach(processElement); + for (i = 0; i < baseTimeline.length; ++i) { + if (baseTimeline[i].suppressed != null) { + for (var j = 0; j < baseTimeline[i].suppressed.length; ++j) { + var theUn = baseTimeline[i].suppressed[j]; + attachId(theUn); + theUn.link = baseTimeline[i].id; + theUn.start = baseTimeline[i].start; + theUn.end = baseTimeline[i].end; + theUn.vizType = 'undelivered'; + + if (undelivered[theUn.deliveryType] == null) { + undelivered[theUn.deliveryType] = []; + } + undelivered[theUn.deliveryType].push(theUn); + } + } - log(overlaps.length, 'instances of scheduled overlapping a scheduled.'); - if (overlaps.length > 0) { - log('First example', overlaps[0], overlaps[1]); + delete baseTimeline[i].suppressed; + actuals[i] = baseTimeline[i]; } - var actual = actuals.getArray(); - var undelivered = {}; - Object.keys(undelivereds).forEach(function(key){ - undelivered[key] = undelivereds[key].getArray(); - }); - return new SegmentUtil(actual, undelivered); + return new SegmentUtil(actuals, undelivered); }; diff --git a/plugins/data/preprocess/index.js b/plugins/data/preprocess/index.js index b23822ec..e9c133d2 100644 --- a/plugins/data/preprocess/index.js +++ b/plugins/data/preprocess/index.js @@ -31,6 +31,13 @@ function notZero(e) { return e.value !== 0; } +function withTiming(name, fn) { + var now = Date.now(); + var retVal = fn.apply(null, Array.prototype.slice.call(arguments, 2)); + log(name + ' completed in ' + (Date.now() - now) + ' millis. '); + return retVal; +} + /** * This converts suspend start and end events into basal-rate-segments for the visualization * @@ -111,12 +118,11 @@ var Preprocess = { MMOL_TO_MGDL: 18, mungeBasals: function(data) { - var segments = new SegmentUtil(_.sortBy(_.where(data, {'type': 'basal-rate-segment'}), 'deviceTime')); + var segments = new SegmentUtil(data); data = _.reject(data, function(d) { return d.type === 'basal-rate-segment'; }); - data = data.concat(segments.actual.concat(segments.getUndelivered('scheduled'))); - return data; + return data.concat(segments.actual.concat(segments.getUndelivered('scheduled'))); }, editBoluses: function(data) { @@ -279,13 +285,13 @@ var Preprocess = { data = []; } - data = this.editBoluses(data); - data = this.filterData(data); - data = this.processDeviceMeta(data); - data = this.mungeBasals(data); - data = this.runWatson(data); - data = this.translateMmol(data); - data = this.sortBasalSchedules(data); + data = withTiming('editBoluses', this.editBoluses.bind(this), data); + data = withTiming('filterData', this.filterData.bind(this), data); + data = withTiming('processDeviceMeta', this.processDeviceMeta.bind(this), data); + data = withTiming('mungeBasals', this.mungeBasals.bind(this), data); + data = withTiming('runWatson', this.runWatson.bind(this), data); + data = withTiming('translateMmol', this.translateMmol.bind(this), data); + data = withTiming('sortBasalSchedules', this.sortBasalSchedules.bind(this), data); var tidelineData = this.checkRequired(new TidelineData(data)); diff --git a/test/basalutil_test.js b/test/basalutil_test.js index 2499c46d..45f563de 100644 --- a/test/basalutil_test.js +++ b/test/basalutil_test.js @@ -140,7 +140,7 @@ describe('basal utilities', function() { expect(format.fixFloatingPoint(st)).to.equal(format.fixFloatingPoint(t.total)); }); - it('should have an excluded of length 7 when span of 7 days of data removed', function() { + it('should have an excluded of length 8 when span of 7 days of data removed', function() { var first = _.find(data, {'type': 'basal-rate-segment'}); var midnight = 'T00:00:00.000Z'; var start = new Date(first.normalTime.slice(0,10) + midnight); @@ -157,7 +157,7 @@ describe('basal utilities', function() { gap.setUTCDate(gap.getUTCDate() + 1); } var t = basal.totalBasal(start.toISOString(), end.toISOString(), {'exclusionThreshold': 7}); - expect(t.excluded.length).to.equal(7); + expect(t.excluded.length).to.equal(8); }); it('should return total of NaN when a further day removed', function() { diff --git a/test/segmentutil_test.js b/test/segmentutil_test.js index 32a5ac9f..f72f6566 100644 --- a/test/segmentutil_test.js +++ b/test/segmentutil_test.js @@ -85,29 +85,6 @@ function testData (data) { expect(_.uniq(basal.actual)).to.be.eql(basal.actual); }); - it.skip('should have squashed contiguous identical segments', function() { - // This test is poorly defined. It assumes that if the start and the end are abutting, then their - // values must be different. This is incorrect as there are times where the value is the same and other - // things are different. This is 100% the case whenever there is a 0% temp basal that spans - // multiple scheduled basal rates. Either delete this test or fix it - basal.actual.forEach(function(segment, i, segments) { - if (i < (segments.length - 1)) { - var next = segments[i + 1]; - if ((segment.end === next.start) && (segment.deliveryType === next.deliveryType)) { - try { - expect(segment.value).to.not.eql(segments[i + 1].value); - } - catch(e) { - console.log('should have squashed contiguous identical segments'); - console.log(segment); - console.log(segments[i + 1]); - throw(e); - } - } - } - }); - }); - it('can have gaps, but should not have overlaps', function() { var actuals = _.sortBy(basal.actual, function(d) { return new Date(d.start).valueOf(); @@ -186,6 +163,34 @@ function testData (data) { } describe('segmentUtil.js', function(){ + it('Sets scheduled end == null to end = start', function(){ + var segs = segmentUtil( + [ + { + id: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + value: 0.6 + } + ]); + + expect(segs.undelivered).is.empty; + + expect(segs.actual).length(1); + expect(segs.actual[0]).is.deep.equal( + { + id: 'segment_0', + datumId: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-01', + value: 0.6, + vizType: 'actual' + }); + }); + it('Doesn\'t choke on starting temp basal', function(){ var segs = segmentUtil( [ @@ -194,7 +199,7 @@ describe('segmentUtil.js', function(){ deliveryType: 'temp', start: '2014-01-01', end: '2014-01-02', - percent: 0.6 + value: 0.6 } ]); @@ -203,15 +208,416 @@ describe('segmentUtil.js', function(){ expect(segs.actual).length(1); expect(segs.actual[0]).is.deep.equal( { + id: 'segment_0', type: 'basal-rate-segment', deliveryType: 'temp', start: '2014-01-01', end: '2014-01-02', - percent: 0.6, + value: 0.6, vizType: 'actual' }); }); + it('Throws away only overlapping portion of scheduled basals', function(){ + var segs = segmentUtil( + [ + { + id: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-02', + value: 0.6 + }, + { + id: '2', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-04', + value: 0.7 + }, + { + id: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-02', + end: '2014-01-03', + value: 0.8 + } + ] + ); + + expect(segs.undelivered).is.empty; + + expect(segs.actual).to.deep.equal( + [ + { + id: 'segment_0', + datumId: '2', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-03', + end: '2014-01-04', + value: 0.7, + vizType: 'actual' + } + ]); + }); + + it('Throws away only overlapping portion of scheduled basals2', function(){ + var segs = segmentUtil( + [ + { + id: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-04', + value: 0.7 + }, + { + id: '2', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-02', + value: 0.6 + }, + { + id: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-02', + end: '2014-01-03', + value: 0.8 + } + ] + ); + + expect(segs.undelivered).is.empty; + + expect(segs.actual).to.deep.equal( + [ + { + id: 'segment_0', + datumId: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-03', + end: '2014-01-04', + value: 0.7, + vizType: 'actual' + } + ]); + }); + + it('Throws away only overlapping portion of scheduled basals3', function(){ + var segs = segmentUtil( + [ + { + id: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-02', + value: 0.6 + }, + { + id: '2', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-02', + end: '2014-01-03', + value: 0.8 + }, + { + id: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-04', + value: 0.7 + } + ] + ); + + expect(segs.undelivered).is.empty; + + expect(segs.actual).to.deep.equal( + [ + { + id: 'segment_0', + datumId: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-03', + end: '2014-01-04', + value: 0.7, + vizType: 'actual' + } + ]); + }); + + it('Throws away only overlapping portion of scheduled basals4', function(){ + var segs = segmentUtil( + [ + { + id: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-02', + value: 0.6 + }, + { + id: '2', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-03', + end: '2014-01-04', + value: 0.8 + }, + { + id: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-04', + value: 0.7 + } + ] + ); + + expect(segs.undelivered).is.empty; + + expect(segs.actual).to.deep.equal( + [ + { + id: 'segment_0', + datumId: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-02', + end: '2014-01-03', + value: 0.7, + vizType: 'actual' + } + ]); + }); + + it('Allows overlapping schedules of the same rate', function(){ + var segs = segmentUtil( + [ + { + id: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-02', + value: 0.7 + }, + { + id: '2', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-03', + end: '2014-01-04', + value: 0.8 + }, + { + id: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-04', + value: 0.7 + } + ] + ); + + expect(segs.undelivered).is.empty; + + expect(segs.actual).to.deep.equal( + [ + { + id: 'segment_0', + datumId: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-03', + value: 0.7, + vizType: 'actual' + } + ]); + }); + + it('Allows overlapping schedules of the same rate2', function(){ + var segs = segmentUtil( + [ + { + id: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-02', + value: 0.7 + }, + { + id: '2', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-02', + end: '2014-01-04', + value: 0.8 + }, + { + id: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-04', + value: 0.7 + } + ] + ); + + expect(segs.undelivered).is.empty; + + expect(segs.actual).to.deep.equal( + [ + { + id: 'segment_0', + datumId: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-02', + value: 0.7, + vizType: 'actual' + } + ]); + }); + + it('Allows overlapping schedules of the same rate3', function(){ + var segs = segmentUtil( + [ + { + id: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-02', + value: 0.7 + }, + { + id: '2', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-02', + end: '2014-01-03', + value: 0.8 + }, + { + id: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-04', + value: 0.7 + } + ] + ); + + expect(segs.undelivered).is.empty; + + expect(segs.actual).to.deep.equal( + [ + { + id: 'segment_0', + datumId: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-02', + value: 0.7, + vizType: 'actual' + }, + { + id: 'segment_1', + datumId: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-03', + end: '2014-01-04', + value: 0.7, + vizType: 'actual' + } + ]); + }); + + it('Allows overlapping schedules of the same deviceId', function(){ + var segs = segmentUtil( + [ + { + id: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-04', + value: 1.7, + deviceId: 'billy' + }, + { + id: '2', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-02', + end: '2014-01-03', + value: 0.8 + }, + { + id: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-03', + end: '2014-01-04', + value: 0.7, + deviceId: 'billy' + } + ] + ); + + expect(segs.undelivered).is.empty; + + expect(segs.actual).to.deep.equal( + [ + { + id: 'segment_0', + datumId: '1', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-01', + end: '2014-01-02', + value: 1.7, + deviceId: 'billy', + vizType: 'actual' + }, + { + id: 'segment_1', + datumId: '3', + type: 'basal-rate-segment', + deliveryType: 'scheduled', + start: '2014-01-03', + end: '2014-01-04', + value: 0.7, + deviceId: 'billy', + vizType: 'actual' + } + ]); + }); + it('Smooshes delivered', function(){ var events = [ { @@ -232,8 +638,9 @@ describe('segmentUtil.js', function(){ expect(segs.actual).deep.equals( [ { - type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14', 'end': '2014-03-17', - 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 86400000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14', 'end': '2014-03-17', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 86400000, + id: 'segment_0', vizType: 'actual' } ] ); @@ -264,40 +671,48 @@ describe('segmentUtil.js', function(){ expect(segs.actual).deep.equals( [ { - type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14', 'end': '2014-03-14T12', - 'deliveryType': 'scheduled', 'value': 0.85, vizType: 'actual' + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14', 'end': '2014-03-14T12', + 'deliveryType': 'scheduled', 'value': 0.85, + id: 'segment_0', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '2', 'start': '2014-03-14T12', 'end': '2014-03-15', - 'deliveryType': 'temp', 'value': 0, percent: 0, vizType: 'actual' + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T12', 'end': '2014-03-15', + 'deliveryType': 'temp', 'value': 0, percent: 0, + id: 'segment_1', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '2_3', 'start': '2014-03-15', 'end': '2014-03-16', - 'deliveryType': 'temp', 'value': 0, percent: 0, vizType: 'actual', link: '3' + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-15', 'end': '2014-03-16', + 'deliveryType': 'temp', 'value': 0, percent: 0, + id: 'segment_2', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '2_5', 'start': '2014-03-16', 'end': '2014-03-16T12', - 'deliveryType': 'temp', 'value': 0, percent: 0, vizType: 'actual', link: '5' + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-16', 'end': '2014-03-16T12', + 'deliveryType': 'temp', 'value': 0, percent: 0, + id: 'segment_3', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '5', 'start': '2014-03-16T12', 'end': '2014-03-17', - 'deliveryType': 'scheduled', 'value': 0.85, vizType: 'actual' + type: 'basal-rate-segment', datumId: '5', 'start': '2014-03-16T12', 'end': '2014-03-17', + 'deliveryType': 'scheduled', 'value': 0.85, + id: 'segment_4', vizType: 'actual' } ] ); expect(segs.getUndelivered('scheduled')).deep.equals( [ { - type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T12', 'end': '2014-03-15', - 'deliveryType': 'scheduled', 'value': 0.85, link: '2', vizType: 'undelivered' + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T12', 'end': '2014-03-15', + 'deliveryType': 'scheduled', 'value': 0.85, + id: 'segment_5', link: 'segment_1', vizType: 'undelivered' }, { - type: 'basal-rate-segment', 'id': '3', 'start': '2014-03-15', 'end': '2014-03-16', - 'deliveryType': 'scheduled', 'value': 0.65, link: '2_3', vizType: 'undelivered' + type: 'basal-rate-segment', datumId: '3', 'start': '2014-03-15', 'end': '2014-03-16', + 'deliveryType': 'scheduled', 'value': 0.65, + id: 'segment_6', link: 'segment_2', vizType: 'undelivered' }, { - type: 'basal-rate-segment', 'id': '5', 'start': '2014-03-16', 'end': '2014-03-16T12', - 'deliveryType': 'scheduled', 'value': 0.85, link: '2_5', vizType: 'undelivered' + type: 'basal-rate-segment', datumId: '5', 'start': '2014-03-16', 'end': '2014-03-16T12', + 'deliveryType': 'scheduled', 'value': 0.85, + id: 'segment_7', link: 'segment_3', vizType: 'undelivered' } ] ); @@ -340,80 +755,142 @@ describe('segmentUtil.js', function(){ expect(segs.actual).deep.equals( [ { - type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T03:00:00', 'end': '2014-03-14T03:27:14', - 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T03:00:00', 'end': '2014-03-14T03:27:14', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, + id: 'segment_0', vizType: 'actual' + }, + { + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T03:27:14', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, + id: 'segment_1', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '2', 'start': '2014-03-14T03:27:14', 'end': '2014-03-14T04:00:00', - 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:02:09', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, + id: 'segment_2', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '2_3', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:02:09', - 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, vizType: 'actual', link: '3' + type: 'basal-rate-segment', datumId: '4', 'start': '2014-03-14T04:02:09', 'end': '2014-03-14T04:47:36', + 'deliveryType': 'temp', 'value': 0.45, 'duration': 7200000, + id: 'segment_3', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '4', 'start': '2014-03-14T04:02:09', 'end': '2014-03-14T04:47:36', - 'deliveryType': 'temp', 'value': 0.45, 'duration': 7200000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '5', 'start': '2014-03-14T04:47:36', 'end': '2014-03-14T05:27:14', + 'deliveryType': 'temp', 'value': 0.3, 'duration': 7200000, + id: 'segment_4', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '5', 'start': '2014-03-14T04:47:36', 'end': '2014-03-14T06:00:00', - 'deliveryType': 'temp', 'value': 0.3, 'duration': 7200000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '5', 'start': '2014-03-14T05:27:14', 'end': '2014-03-14T06:00:00', + 'deliveryType': 'temp', 'value': 0.3, 'duration': 7200000, + id: 'segment_5', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '5_6', 'start': '2014-03-14T06:00:00', 'end': '2014-03-14T06:11:46', - 'deliveryType': 'temp', 'value': 0.3, 'duration': 7200000, vizType: 'actual', link: '6' + type: 'basal-rate-segment', datumId: '5', 'start': '2014-03-14T06:00:00', 'end': '2014-03-14T06:02:09', + 'deliveryType': 'temp', 'value': 0.3, 'duration': 7200000, + id: 'segment_6', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '7', 'start': '2014-03-14T06:11:46', 'end': '2014-03-14T07:41:46', - 'deliveryType': 'temp', 'value': 0, 'duration': 5400000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '5', 'start': '2014-03-14T06:02:09', 'end': '2014-03-14T06:11:46', + 'deliveryType': 'temp', 'value': 0.3, 'duration': 7200000, + id: 'segment_7', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '6', 'start': '2014-03-14T07:41:46', 'end': '2014-03-14T09:00:00', - 'deliveryType': 'scheduled', 'value': 0.95, 'duration': 10800000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '7', 'start': '2014-03-14T06:11:46', 'end': '2014-03-14T06:47:36', + 'deliveryType': 'temp', 'value': 0, 'duration': 5400000, + id: 'segment_8', vizType: 'actual' + }, + { + type: 'basal-rate-segment', datumId: '7', 'start': '2014-03-14T06:47:36', 'end': '2014-03-14T07:41:46', + 'deliveryType': 'temp', 'value': 0, 'duration': 5400000, + id: 'segment_9', vizType: 'actual' + }, + { + type: 'basal-rate-segment', datumId: '6', 'start': '2014-03-14T07:41:46', 'end': '2014-03-14T09:00:00', + 'deliveryType': 'scheduled', 'value': 0.95, 'duration': 10800000, + id: 'segment_10', vizType: 'actual' } ] ); expect(segs.getUndelivered('temp')).deep.equals( [ { - type: 'basal-rate-segment', 'id': '2_3', 'start': '2014-03-14T04:02:09', 'end': '2014-03-14T04:47:36', - 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, vizType: 'undelivered', link: '4' + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T04:02:09', 'end': '2014-03-14T04:47:36', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, + id: 'segment_13', vizType: 'undelivered', link: 'segment_3' + }, + { + type: 'basal-rate-segment', datumId: '4', 'start': '2014-03-14T04:47:36', end: '2014-03-14T05:27:14', + 'deliveryType': 'temp', 'value': 0.45, 'duration': 7200000, + id: 'segment_15', vizType: 'undelivered', link: 'segment_4' }, { - type: 'basal-rate-segment', 'id': '4', 'start': '2014-03-14T04:47:36', 'end': '2014-03-14T06:02:09', - 'deliveryType': 'temp', 'value': 0.45, 'duration': 7200000, vizType: 'undelivered', link: '5' + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T04:47:36', end: '2014-03-14T05:27:14', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, + id: 'segment_16', vizType: 'undelivered', link: 'segment_4' }, { - type: 'basal-rate-segment', 'id': '5_6', 'start': '2014-03-14T06:11:46', 'end': '2014-03-14T06:47:36', - 'deliveryType': 'temp', 'value': 0.3, 'duration': 7200000, vizType: 'undelivered', link: '7' + type: 'basal-rate-segment', datumId: '4', 'start': '2014-03-14T05:27:14', 'end': '2014-03-14T06:00:00', + 'deliveryType': 'temp', 'value': 0.45, 'duration': 7200000, + id: 'segment_18', vizType: 'undelivered', link: 'segment_5' + }, + { + type: 'basal-rate-segment', datumId: '4', 'start': '2014-03-14T06:00:00', end: '2014-03-14T06:02:09', + 'deliveryType': 'temp', 'value': 0.45, 'duration': 7200000, + id: 'segment_20', vizType: 'undelivered', link: 'segment_6' + }, + { + type: 'basal-rate-segment', datumId: '5', 'start': '2014-03-14T06:11:46', 'end': '2014-03-14T06:47:36', + 'deliveryType': 'temp', 'value': 0.3, 'duration': 7200000, + id: 'segment_23', vizType: 'undelivered', link: 'segment_8' } ] ); expect(segs.getUndelivered('scheduled')).deep.equals( [ { - type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T03:27:14', 'end': '2014-03-14T04:00:00', - 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, vizType: 'undelivered', link: '2' + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T03:27:14', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, + id: 'segment_11', vizType: 'undelivered', link: 'segment_1' + }, + { + type: 'basal-rate-segment', datumId: '3', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:02:09', + 'deliveryType': 'scheduled', 'value': 0.9, 'duration': 3600000, + id: 'segment_12', vizType: 'undelivered', link: 'segment_2' + }, + { + type: 'basal-rate-segment', datumId: '3', 'start': '2014-03-14T04:02:09', 'end': '2014-03-14T04:47:36', + 'deliveryType': 'scheduled', 'value': 0.9, 'duration': 3600000, + id: 'segment_14', vizType: 'undelivered', link: 'segment_3' }, { - type: 'basal-rate-segment', 'id': '3', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:02:09', - 'deliveryType': 'scheduled', 'value': 0.9, 'duration': 3600000, vizType: 'undelivered', link: '2_3' + type: 'basal-rate-segment', datumId: '3', 'start': '2014-03-14T04:47:36', 'end': '2014-03-14T05:27:14', + 'deliveryType': 'scheduled', 'value': 0.9, 'duration': 3600000, + id: 'segment_17', vizType: 'undelivered', link: 'segment_4' }, { - type: 'basal-rate-segment', 'id': '3', 'start': '2014-03-14T04:02:09', 'end': '2014-03-14T04:47:36', - 'deliveryType': 'scheduled', 'value': 0.9, 'duration': 3600000, vizType: 'undelivered', link: '4' + type: 'basal-rate-segment', datumId: '3', 'start': '2014-03-14T05:27:14', end: '2014-03-14T06:00:00', + 'deliveryType': 'scheduled', 'value': 0.9, 'duration': 3600000, + id: 'segment_19', vizType: 'undelivered', link: 'segment_5' }, { - type: 'basal-rate-segment', 'id': '3', 'start': '2014-03-14T04:47:36', 'end': '2014-03-14T06:00:00', - 'deliveryType': 'scheduled', 'value': 0.9, 'duration': 3600000, vizType: 'undelivered', link: '5' + type: 'basal-rate-segment', datumId: '6', 'start': '2014-03-14T06:00:00', 'end': '2014-03-14T06:02:09', + 'deliveryType': 'scheduled', 'value': 0.95, 'duration': 10800000, + id: 'segment_21', vizType: 'undelivered', link: 'segment_6' }, { - type: 'basal-rate-segment', 'id': '6', 'start': '2014-03-14T06:00:00', 'end': '2014-03-14T06:11:46', - 'deliveryType': 'scheduled', 'value': 0.95, 'duration': 10800000, vizType: 'undelivered', link: '5_6' + type: 'basal-rate-segment', datumId: '6', 'start': '2014-03-14T06:02:09', 'end': '2014-03-14T06:11:46', + 'deliveryType': 'scheduled', 'value': 0.95, 'duration': 10800000, + id: 'segment_22', vizType: 'undelivered', link: 'segment_7' }, { - type: 'basal-rate-segment', 'id': '6', 'start': '2014-03-14T06:11:46', 'end': '2014-03-14T07:41:46', - 'deliveryType': 'scheduled', 'value': 0.95, 'duration': 10800000, vizType: 'undelivered', link: '7' + type: 'basal-rate-segment', datumId: '6', 'start': '2014-03-14T06:11:46', 'end': '2014-03-14T06:47:36', + 'deliveryType': 'scheduled', 'value': 0.95, 'duration': 10800000, + id: 'segment_24', vizType: 'undelivered', link: 'segment_8' + }, + { + type: 'basal-rate-segment', datumId: '6', 'start': '2014-03-14T06:47:36', 'end': '2014-03-14T07:41:46', + 'deliveryType': 'scheduled', 'value': 0.95, 'duration': 10800000, + id: 'segment_25', vizType: 'undelivered', link: 'segment_9' } ] ); @@ -440,50 +917,209 @@ describe('segmentUtil.js', function(){ expect(segs.actual).deep.equals( [ { - type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T03:00:00', 'end': '2014-03-14T03:27:14', - 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T03:00:00', 'end': '2014-03-14T03:27:14', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, + id: 'segment_0', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '2', 'start': '2014-03-14T03:27:14', 'end': '2014-03-14T04:00:00', - 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T03:27:14', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, + id: 'segment_1', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '3', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:30:00', - 'deliveryType': 'suspend', 'value': 0, vizType: 'actual' + type: 'basal-rate-segment', datumId: '3', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:30:00', + 'deliveryType': 'suspend', 'value': 0, + id: 'segment_2', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '2', 'start': '2014-03-14T04:30:00', 'end': '2014-03-14T05:27:14', - 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T04:30:00', 'end': '2014-03-14T05:27:14', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, + id: 'segment_3', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T05:27:14', 'end': '2014-03-14T06:00:00', - 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, vizType: 'actual' + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T05:27:14', 'end': '2014-03-14T06:00:00', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, + id: 'segment_4', vizType: 'actual' } ] ); expect(segs.getUndelivered('temp')).deep.equals( [ { - type: 'basal-rate-segment', 'id': '2', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:30:00', - 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, vizType: 'undelivered', link: '3' + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:30:00', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 7200000, + id: 'segment_6', vizType: 'undelivered', link: 'segment_2' + } + ] + ); + expect(segs.getUndelivered('scheduled')).deep.equals( + [ + { + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T03:27:14', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, + id: 'segment_5', vizType: 'undelivered', link: 'segment_1' + }, + { + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:30:00', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, + id: 'segment_7', vizType: 'undelivered', link: 'segment_2' + }, + { + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T04:30:00', 'end': '2014-03-14T05:27:14', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, + id: 'segment_8', vizType: 'undelivered', link: 'segment_3' } ] ); + }); + + it('Injects temps if there is a gap', function () { + var events = [ + { + type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T03:00:00', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000 + }, + { + type: 'basal-rate-segment', 'id': '2', 'start': '2014-03-14T04:27:14', 'end': '2014-03-14T05:27:14', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 3600000 + }, + { + type: 'basal-rate-segment', 'id': '3', 'start': '2014-03-14T06:00:00', 'end': '2014-03-14T07:00:00', + 'deliveryType': 'scheduled', value: 0 + } + ]; + + var segs = segmentUtil(events); + + expect(segs.actual).deep.equals( + [ + { + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T03:00:00', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, + id: 'segment_0', vizType: 'actual' + }, + { + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T04:27:14', 'end': '2014-03-14T05:27:14', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 3600000, + id: 'segment_1', vizType: 'actual' + }, + { + type: 'basal-rate-segment', datumId: '3', 'start': '2014-03-14T06:00:00', 'end': '2014-03-14T07:00:00', + 'deliveryType': 'scheduled', 'value': 0, + id: 'segment_2', vizType: 'actual' + } + ] + ); + expect(segs.getUndelivered('temp')).deep.equals([]); + expect(segs.getUndelivered('scheduled')).deep.equals([]); + }); + + it('Injects mega-overlaps when there is a gap', function () { + var events = [ + { + type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T03:00:00', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000 + }, + { + type: 'basal-rate-segment', 'id': '2', 'start': '2014-03-14T05:30:00', 'end': '2014-03-14T07:30:00', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 3600000 + }, + { + type: 'basal-rate-segment', 'id': '3', 'start': '2014-03-14T06:00:00', 'end': '2014-03-14T07:00:00', + 'deliveryType': 'scheduled', value: 0.1234 + } + ]; + + var segs = segmentUtil(events); + + expect(segs.actual).deep.equals( + [ + { + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T03:00:00', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'scheduled', 'value': 0.85, duration: 3600000, + id: 'segment_0', vizType: 'actual' + }, + { + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T05:30:00', 'end': '2014-03-14T06:00:00', + 'deliveryType': 'temp', 'value': 0.55, duration: 3600000, + id: 'segment_1', vizType: 'actual' + }, + { + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T06:00:00', 'end': '2014-03-14T07:00:00', + 'deliveryType': 'temp', 'value': 0.55, duration: 3600000, + id: 'segment_2', vizType: 'actual' + }, + { + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T07:00:00', 'end': '2014-03-14T07:30:00', + 'deliveryType': 'temp', 'value': 0.55, duration: 3600000, + id: 'segment_3', vizType: 'actual' + } + ] + ); + expect(segs.getUndelivered('temp')).deep.equals([]); expect(segs.getUndelivered('scheduled')).deep.equals( [ { - type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T03:27:14', 'end': '2014-03-14T04:00:00', - 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, vizType: 'undelivered', link: '2' + type: 'basal-rate-segment', datumId: '3', 'start': '2014-03-14T06:00:00', 'end': '2014-03-14T07:00:00', + 'deliveryType': 'scheduled', 'value': 0.1234, + id: 'segment_4', vizType: 'undelivered', link: 'segment_2' + } + ]); + }); + + it('Doesn\'t choke when an override lines up exactly with another override', function () { + var events = [ + { + type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T03:00:00', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'scheduled', 'value': 0.85 + }, + { + type: 'basal-rate-segment', 'id': '2', 'start': '2014-03-14T03:30:00', 'end': '2014-03-14T04:30:00', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 3600000 + }, + { + type: 'basal-rate-segment', 'id': '3', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:30:00', + 'deliveryType': 'temp', value: 0.1234, duration: 1800000 + } + ]; + + var segs = segmentUtil(events); + + expect(segs.actual).deep.equals( + [ + { + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T03:00:00', 'end': '2014-03-14T03:30:00', + 'deliveryType': 'scheduled', 'value': 0.85, + id: 'segment_0', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:30:00', - 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, vizType: 'undelivered', link: '3' + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T03:30:00', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'temp', 'value': 0.55, duration: 3600000, + id: 'segment_1', vizType: 'actual' }, { - type: 'basal-rate-segment', 'id': '1', 'start': '2014-03-14T04:30:00', 'end': '2014-03-14T05:27:14', - 'deliveryType': 'scheduled', 'value': 0.85, 'duration': 3600000, vizType: 'undelivered', link: '2' + type: 'basal-rate-segment', datumId: '3', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:30:00', + 'deliveryType': 'temp', 'value': 0.1234, duration: 1800000, + id: 'segment_2', vizType: 'actual' } ] ); + expect(segs.getUndelivered('temp')).deep.equals( + [ + { + type: 'basal-rate-segment', datumId: '2', 'start': '2014-03-14T04:00:00', 'end': '2014-03-14T04:30:00', + 'deliveryType': 'temp', 'value': 0.55, 'duration': 3600000, + id: 'segment_4', vizType: 'undelivered', link: 'segment_2' + } + ] + ); + expect(segs.getUndelivered('scheduled')).deep.equals( + [ + { + type: 'basal-rate-segment', datumId: '1', 'start': '2014-03-14T03:30:00', 'end': '2014-03-14T04:00:00', + 'deliveryType': 'scheduled', 'value': 0.85, + id: 'segment_3', vizType: 'undelivered', link: 'segment_1' + } + ]); }); });