diff --git a/Sources/LoopAlgorithm/Experiments/GuardAndRunwayOutput.swift b/Sources/LoopAlgorithm/Experiments/GuardAndRunwayOutput.swift index 5afe682..2c80b00 100644 --- a/Sources/LoopAlgorithm/Experiments/GuardAndRunwayOutput.swift +++ b/Sources/LoopAlgorithm/Experiments/GuardAndRunwayOutput.swift @@ -13,6 +13,9 @@ public struct GuardAndRunwayOutput: Equatable { public var volatility: Double /// False when active carbs kept the volatility band off. public var bandApplied: Bool + /// The most the band lowered any point of the forecast, mg/dL; 0 when it was off. A forecast with no + /// points near 60 min, where the band is deepest, is lowered less. + public var bandDepth: Double /// How far the target for automatic dosing was lowered, mg/dL. Manual recommendations keep the target. public var runwayShift: Double /// True while the post-low cut scales down the rising side of retrospective correction. @@ -23,11 +26,6 @@ public struct GuardAndRunwayOutput: Equatable { /// The band's depth multiplier, from the volatility baseline. public var bandGain: Double { band.gain } - /// How far the band lowers the forecast at its deepest point (60 min), mg/dL; 0 when it was off. - public var bandDepth: Double { - bandApplied ? band.gain * volatility * pow(band.horizon.minutes / 5.0, band.exponent) : 0 - } - /// `glucose` lowered by the band as this run applied it; unchanged when the band was off. public func guarded(_ glucose: [PredictedGlucoseValue]) -> [PredictedGlucoseValue] { bandApplied ? LoopAlgorithm.volatilityBanded(glucose, band: band, volatility: volatility) : glucose diff --git a/Sources/LoopAlgorithm/LoopAlgorithm.swift b/Sources/LoopAlgorithm/LoopAlgorithm.swift index e5a3fa1..378f0e5 100644 --- a/Sources/LoopAlgorithm/LoopAlgorithm.swift +++ b/Sources/LoopAlgorithm/LoopAlgorithm.swift @@ -842,11 +842,16 @@ public struct LoopAlgorithm { } if let band = experimentParameters.volatilityBand, let volatility { let bandApplied = !(band.carbGate && (prediction.activeCarbs ?? 0) > 0) + var bandDepth = 0.0 if bandApplied { - prediction.glucose = volatilityBanded(prediction.glucose, band: band, volatility: volatility) + let banded = volatilityBanded(prediction.glucose, band: band, volatility: volatility) + bandDepth = zip(prediction.glucose, banded).map { + $0.quantity.doubleValue(for: .milligramsPerDeciliter) - $1.quantity.doubleValue(for: .milligramsPerDeciliter) + }.max() ?? 0 + prediction.glucose = banded } guardAndRunway = GuardAndRunwayOutput( - volatility: volatility, bandApplied: bandApplied, runwayShift: runwayShift, + volatility: volatility, bandApplied: bandApplied, bandDepth: bandDepth, runwayShift: runwayShift, postLowRiseCutActive: experimentParameters.postLowRiseCut?.isActive(glucoseHistory: input.glucoseHistory) ?? false, band: band) } diff --git a/Tests/LoopAlgorithmTests/AlgorithmExperimentsTests.swift b/Tests/LoopAlgorithmTests/AlgorithmExperimentsTests.swift index 5ea3fa9..45ab34b 100644 --- a/Tests/LoopAlgorithmTests/AlgorithmExperimentsTests.swift +++ b/Tests/LoopAlgorithmTests/AlgorithmExperimentsTests.swift @@ -248,8 +248,7 @@ final class AlgorithmExperimentsTests: XCTestCase { XCTAssertFalse(output.postLowRiseCutActive) XCTAssertEqual(output.guarded(standard.predictedGlucose), runway.predictedGlucose) let deepest = zip(plain, banded).map { $0 - $1 }.max()! - XCTAssertLessThanOrEqual(deepest, output.bandDepth + 1e-9) - XCTAssertGreaterThan(deepest, 0.9 * output.bandDepth, "forecast points fall near the 60 min horizon") + XCTAssertEqual(output.bandDepth, deepest, accuracy: 1e-9) if type == .tempBasal { XCTAssertGreaterThan(runway.recommendation!.automatic!.basalAdjustment.unitsPerHour, standard.recommendation!.automatic!.basalAdjustment.unitsPerHour)