Skip to content

Commit f2bc8bd

Browse files
committed
trying to make unit test
1 parent 7cd4035 commit f2bc8bd

File tree

1 file changed

+236
-1
lines changed

1 file changed

+236
-1
lines changed

LoopKitTests/TemporaryScheduleOverrideTests.swift

Lines changed: 236 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,58 @@ import LoopAlgorithm
1212

1313
@testable import LoopKit
1414

15+
fileprivate struct SimpleInsulinDose: InsulinDose {
16+
var deliveryType: InsulinDeliveryType
17+
var startDate: Date
18+
var endDate: Date
19+
var volume: Double
20+
var insulinModel: InsulinModel
21+
}
22+
23+
fileprivate struct StoredDataAlgorithmInput: AlgorithmInput {
24+
typealias CarbType = StoredCarbEntry
25+
26+
typealias GlucoseType = StoredGlucoseSample
27+
28+
typealias InsulinDoseType = SimpleInsulinDose
29+
30+
var glucoseHistory: [StoredGlucoseSample]
31+
32+
var doses: [SimpleInsulinDose]
33+
34+
var carbEntries: [StoredCarbEntry]
35+
36+
var predictionStart: Date
37+
38+
var basal: [AbsoluteScheduleValue<Double>]
39+
40+
var sensitivity: [AbsoluteScheduleValue<LoopQuantity>]
41+
42+
var carbRatio: [AbsoluteScheduleValue<Double>]
43+
44+
var target: GlucoseRangeTimeline
45+
46+
var suspendThreshold: LoopQuantity?
47+
48+
var maxBolus: Double
49+
50+
var maxBasalRate: Double
51+
52+
var useIntegralRetrospectiveCorrection: Bool
53+
54+
var includePositiveVelocityAndRC: Bool
55+
56+
var carbAbsorptionModel: CarbAbsorptionModel
57+
58+
var recommendationInsulinModel: InsulinModel
59+
60+
var recommendationType: DoseRecommendationType
61+
62+
var automaticBolusApplicationFactor: Double?
63+
64+
let useMidAbsorptionISF: Bool = true
65+
}
66+
1567
extension TimeZone {
1668
static var fixtureTimeZone: TimeZone {
1769
return TimeZone(secondsFromGMT: 25200)! // -0700
@@ -366,7 +418,7 @@ class TemporaryScheduleOverrideTests: XCTestCase {
366418
XCTAssertEqual(expectedValues, values)
367419
}
368420

369-
func testTargetOverride() {
421+
func testTargetOverridePremeal() {
370422
let scheduledRange = LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 100)...LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 110)
371423
let overrideRange = LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 80)...LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 90)
372424

@@ -444,6 +496,189 @@ class TemporaryScheduleOverrideTests: XCTestCase {
444496
values = applied.map { $0.value }
445497
XCTAssertEqual([scheduledRange], values)
446498
}
499+
500+
func testTargetOverrideWorkoutPrediction() {
501+
let startOfDay = Calendar.current.startOfDay(for: Date())
502+
let now = startOfDay.addingTimeInterval(.hours(12))
503+
let scheduledRange = LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 100)...LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 110)
504+
let overrideRange = LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 140)...LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 160)
505+
506+
let overrideDuration = TimeInterval(hours: 2)
507+
var overrides: [TemporaryScheduleOverride] = [
508+
.init(
509+
context: .legacyWorkout,
510+
settings: .init(targetRange: overrideRange),
511+
startDate: now,
512+
duration: .finite(overrideDuration),
513+
enactTrigger: .local,
514+
syncIdentifier: UUID()
515+
)
516+
]
517+
518+
// sensitivity with overrides
519+
let sensitivity1Value = LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 45)
520+
let sensitivity2Value = LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 55)
521+
let sensitivity1End = TimeInterval(hours: 9)
522+
let sensitivity2End = TimeInterval(hours: 24)
523+
let sensitivity: [AbsoluteScheduleValue<LoopQuantity>] = [
524+
AbsoluteScheduleValue(startDate: startOfDay, endDate: startOfDay.addingTimeInterval(sensitivity1End), value: sensitivity1Value),
525+
AbsoluteScheduleValue(startDate: startOfDay.addingTimeInterval(.hours(9)), endDate: startOfDay.addingTimeInterval(sensitivity2End), value: sensitivity2Value),
526+
]
527+
let sensitivityWithOverrides = overrides.applySensitivity(over: sensitivity)
528+
XCTAssertEqual(sensitivityWithOverrides.count, 4)
529+
XCTAssertEqual(sensitivityWithOverrides[0].startDate, startOfDay)
530+
XCTAssertEqual(sensitivityWithOverrides[0].endDate, startOfDay.addingTimeInterval(sensitivity1End))
531+
XCTAssertEqual(sensitivityWithOverrides[0].value, sensitivity1Value)
532+
XCTAssertEqual(sensitivityWithOverrides[1].startDate, startOfDay.addingTimeInterval(sensitivity1End))
533+
XCTAssertEqual(sensitivityWithOverrides[1].endDate, now)
534+
XCTAssertEqual(sensitivityWithOverrides[1].value, sensitivity2Value)
535+
XCTAssertEqual(sensitivityWithOverrides[2].startDate, now)
536+
XCTAssertEqual(sensitivityWithOverrides[2].endDate, now.addingTimeInterval(overrideDuration))
537+
XCTAssertEqual(sensitivityWithOverrides[2].value, sensitivity2Value)
538+
XCTAssertEqual(sensitivityWithOverrides[3].startDate, now.addingTimeInterval(overrideDuration))
539+
XCTAssertEqual(sensitivityWithOverrides[3].endDate, startOfDay.addingTimeInterval(sensitivity2End))
540+
XCTAssertEqual(sensitivityWithOverrides[3].value, sensitivity2Value)
541+
542+
// Basal with overrides
543+
let basal1Value = 1.0
544+
let basal2Value = 0.85
545+
let basal1End = TimeInterval(hours: 17)
546+
let basal2End = TimeInterval(hours: 24)
547+
let basal: [AbsoluteScheduleValue<Double>] = [
548+
AbsoluteScheduleValue(startDate: startOfDay, endDate: startOfDay.addingTimeInterval(basal1End), value: basal1Value),
549+
AbsoluteScheduleValue(startDate: startOfDay.addingTimeInterval(basal1End), endDate: startOfDay.addingTimeInterval(basal2End), value: basal2Value),
550+
]
551+
let basalWithOverrides = overrides.applyBasal(over: basal)
552+
XCTAssertEqual(basalWithOverrides.count, 4)
553+
XCTAssertEqual(basalWithOverrides[0].startDate, startOfDay)
554+
XCTAssertEqual(basalWithOverrides[0].endDate, now)
555+
XCTAssertEqual(basalWithOverrides[0].value, basal1Value)
556+
XCTAssertEqual(basalWithOverrides[1].startDate, now)
557+
XCTAssertEqual(basalWithOverrides[1].endDate, now.addingTimeInterval(overrideDuration))
558+
XCTAssertEqual(basalWithOverrides[1].value, basal1Value)
559+
XCTAssertEqual(basalWithOverrides[2].startDate, now.addingTimeInterval(overrideDuration))
560+
XCTAssertEqual(basalWithOverrides[2].endDate, startOfDay.addingTimeInterval(basal1End))
561+
XCTAssertEqual(basalWithOverrides[2].value, basal1Value)
562+
XCTAssertEqual(basalWithOverrides[3].startDate, startOfDay.addingTimeInterval(basal1End))
563+
XCTAssertEqual(basalWithOverrides[3].endDate, startOfDay.addingTimeInterval(basal2End))
564+
XCTAssertEqual(basalWithOverrides[3].value, basal2Value)
565+
566+
// carb ratio with overrides
567+
let carbRatio1Value = 10.0
568+
let carbRatio1End = TimeInterval(hours: 24)
569+
let carbRatio: [AbsoluteScheduleValue<Double>] = [
570+
AbsoluteScheduleValue(startDate: startOfDay, endDate: startOfDay.addingTimeInterval(carbRatio1End), value: carbRatio1Value),
571+
]
572+
let carbRatioWithOverrides = overrides.applyBasal(over: carbRatio)
573+
XCTAssertEqual(carbRatioWithOverrides.count, 3)
574+
XCTAssertEqual(carbRatioWithOverrides[0].startDate, startOfDay)
575+
XCTAssertEqual(carbRatioWithOverrides[0].endDate, now)
576+
XCTAssertEqual(carbRatioWithOverrides[0].value, carbRatio1Value)
577+
XCTAssertEqual(carbRatioWithOverrides[1].startDate, now)
578+
XCTAssertEqual(carbRatioWithOverrides[1].endDate, now.addingTimeInterval(overrideDuration))
579+
XCTAssertEqual(carbRatioWithOverrides[1].value, carbRatio1Value)
580+
XCTAssertEqual(carbRatioWithOverrides[2].startDate, now.addingTimeInterval(overrideDuration))
581+
XCTAssertEqual(carbRatioWithOverrides[2].endDate, startOfDay.addingTimeInterval(carbRatio1End))
582+
XCTAssertEqual(carbRatioWithOverrides[2].value, carbRatio1Value)
583+
584+
// target with overrides
585+
let targetDuration = TimeInterval.hours(8)
586+
let target = [
587+
AbsoluteScheduleValue(
588+
startDate: now.addingTimeInterval(-overrideDuration),
589+
endDate: now.addingTimeInterval(targetDuration),
590+
value: scheduledRange
591+
),
592+
]
593+
594+
var targetWithOverrides = overrides.applyTarget(over: target, at: now)
595+
var expetedRange = LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 100.0)...LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 110.0)
596+
XCTAssertEqual(targetWithOverrides.count, 2)
597+
XCTAssertEqual(targetWithOverrides.first?.value, expetedRange)
598+
XCTAssertEqual(targetWithOverrides.first?.startDate, now.addingTimeInterval(-overrideDuration))
599+
expetedRange = LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 140.0)...LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 160.0)
600+
XCTAssertEqual(targetWithOverrides.first?.endDate, now)
601+
XCTAssertEqual(targetWithOverrides.last?.value, expetedRange)
602+
XCTAssertEqual(targetWithOverrides.last?.startDate, now)
603+
XCTAssertEqual(targetWithOverrides.last?.endDate, now.addingTimeInterval(targetDuration))
604+
605+
// algorithm input
606+
let doses: [DoseEntry] = [
607+
DoseEntry(type: .tempBasal, startDate: now.addingTimeInterval(-overrideDuration), value: 0, unit: .unitsPerHour),
608+
DoseEntry(type: .tempBasal, startDate: now.addingTimeInterval(-(overrideDuration - .minutes(30)*1)), value: 1.15, unit: .unitsPerHour),
609+
DoseEntry(type: .basal, startDate: now.addingTimeInterval(-(overrideDuration - .minutes(30)*2)), value: 1, unit: .unitsPerHour),
610+
DoseEntry(type: .tempBasal, startDate: now.addingTimeInterval(-(overrideDuration - .minutes(30)*3)), value: 0.95, unit: .unitsPerHour),
611+
DoseEntry(type: .tempBasal, startDate: now.addingTimeInterval(-(overrideDuration - .minutes(30)*4)), value: 0.80, unit: .unitsPerHour),
612+
]
613+
let dosesWithModel = doses.map { SimpleInsulinDose(deliveryType: $0.type == .bolus ? .bolus : .basal, startDate: $0.startDate, endDate: $0.endDate, volume: $0.deliveredUnits ?? $0.programmedUnits, insulinModel: ExponentialInsulinModelPreset.rapidActingAdult.model) }
614+
615+
let correctionRange = target.closestPrior(to: now)?.value
616+
let carbEntry = StoredCarbEntry(startDate: now, quantity: LoopQuantity(unit: .gram, doubleValue: 10.0))
617+
618+
let glucoseValues: [Double] = [146, 143, 141, 137, 134, 131, 128, 124, 121, 117,
619+
114, 110, 107, 104, 101, 98, 95, 92, 90, 88,
620+
86, 84, 83, 82, 81, 80, 80, 80, 80, 81,
621+
81, 81, 82, 82, 83, 84, 85, 85, 87, 87,
622+
89, 89, 90, 91, 91, 92, 94, 94, 98]
623+
let timeIntervalStepSize = TimeInterval(minutes: 5)
624+
var glucoseHistory: [StoredGlucoseSample] = []
625+
var currentDate = Date().addingTimeInterval(-1*timeIntervalStepSize*Double(glucoseValues.count))
626+
for (index, glucoseValue) in glucoseValues.enumerated() {
627+
let uuid = UUID()
628+
currentDate = currentDate.addingTimeInterval(timeIntervalStepSize)
629+
var trendRate = 4.0/timeIntervalStepSize
630+
if index < glucoseValues.count - 1 {
631+
trendRate = (glucoseValues[index+1] - glucoseValues[index])/timeIntervalStepSize
632+
}
633+
glucoseHistory.append(
634+
StoredGlucoseSample(uuid: uuid,
635+
provenanceIdentifier: "org.tidepool.Loop",
636+
syncIdentifier: uuid.uuidString,
637+
syncVersion: 1,
638+
startDate: currentDate,
639+
quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: glucoseValue),
640+
trend: trendRate > 0 ? .upUp : .downDown,
641+
trendRate: LoopQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: trendRate)))
642+
}
643+
644+
let effectiveBolusApplicationFactor: Double? = LoopAlgorithm.defaultBolusPartialApplicationFactor
645+
646+
var input = StoredDataAlgorithmInput(
647+
glucoseHistory: glucoseHistory,
648+
doses: dosesWithModel,
649+
carbEntries: [carbEntry],
650+
predictionStart: now,
651+
basal: basalWithOverrides,
652+
sensitivity: sensitivityWithOverrides,
653+
carbRatio: carbRatioWithOverrides,
654+
target: targetWithOverrides,
655+
suspendThreshold: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 75),
656+
maxBolus: 10,
657+
maxBasalRate: 5,
658+
useIntegralRetrospectiveCorrection: false,
659+
includePositiveVelocityAndRC: true,
660+
carbAbsorptionModel: .linear,
661+
recommendationInsulinModel: ExponentialInsulinModel(actionDuration: 21600, peakActivityTime: 4500, delay: 600),
662+
recommendationType: .manualBolus,
663+
automaticBolusApplicationFactor: effectiveBolusApplicationFactor)
664+
665+
let prediction = LoopAlgorithm.generatePrediction(
666+
start: input.predictionStart,
667+
glucoseHistory: input.glucoseHistory,
668+
doses: input.doses,
669+
carbEntries: input.carbEntries,
670+
basal: input.basal,
671+
sensitivity: input.sensitivity,
672+
carbRatio: input.carbRatio,
673+
algorithmEffectsOptions: .all,
674+
useIntegralRetrospectiveCorrection: input.useIntegralRetrospectiveCorrection,
675+
includingPositiveVelocityAndRC: input.includePositiveVelocityAndRC,
676+
useMidAbsorptionISF: input.useMidAbsorptionISF,
677+
carbAbsorptionModel: input.carbAbsorptionModel.model)
678+
679+
XCTAssertTrue(prediction.glucose.last!.quantity > overrideRange.lowerBound)
680+
XCTAssertTrue(prediction.glucose.last!.quantity < overrideRange.upperBound)
681+
}
447682

448683
func testPreMealPreset() {
449684
let now = ISO8601DateFormatter().date(from: "2020-03-11T12:13:14-0700")!

0 commit comments

Comments
 (0)