From 7b47453622cae1edd6412311e844858e8fd9f7bc Mon Sep 17 00:00:00 2001 From: Chris Almond Date: Wed, 3 Jun 2026 20:57:11 -0600 Subject: [PATCH] Show glucose predictions and allow bolus recommendations without pod connected. #2445 --- ...aManager+BolusEntryViewModelDelegate.swift | 4 + Loop/Localizable.xcstrings | 13 +++ Loop/Managers/LoopDataManager.swift | 83 +++++++++++++++---- .../PredictionTableViewController.swift | 43 ++++++++++ Loop/View Models/BolusEntryViewModel.swift | 23 ++++- .../ManualEntryDoseViewModel.swift | 11 ++- Loop/Views/BolusEntryView.swift | 7 +- .../ViewModels/BolusEntryViewModelTests.swift | 4 +- 8 files changed, 164 insertions(+), 24 deletions(-) diff --git a/Loop/Extensions/DeviceDataManager+BolusEntryViewModelDelegate.swift b/Loop/Extensions/DeviceDataManager+BolusEntryViewModelDelegate.swift index 25173f92d8..13ece69d49 100644 --- a/Loop/Extensions/DeviceDataManager+BolusEntryViewModelDelegate.swift +++ b/Loop/Extensions/DeviceDataManager+BolusEntryViewModelDelegate.swift @@ -75,6 +75,10 @@ extension DeviceDataManager: BolusEntryViewModelDelegate, ManualDoseViewModelDel return pumpManager != nil } + var shouldModelAsNoDelivery: Bool { + return pumpManager?.status.shouldModelAsNoDelivery ?? false + } + var preferredGlucoseUnit: HKUnit { return displayGlucosePreference.unit } diff --git a/Loop/Localizable.xcstrings b/Loop/Localizable.xcstrings index 44d94c62a4..4b89460064 100644 --- a/Loop/Localizable.xcstrings +++ b/Loop/Localizable.xcstrings @@ -6453,6 +6453,9 @@ } } }, + "Always active when no pump is connected" : { + "comment" : "Subtitle for suspend prediction input when no pump is connected" + }, "Amount Consumed" : { "comment" : "Label for carb quantity entry row on carb entry screen", "localizations" : { @@ -27636,6 +27639,12 @@ } } }, + "No Pump Connected" : { + "comment" : "Title for bolus screen notice when no pump is connected" + }, + "No pump is connected. Bolus delivery is unavailable." : { + "comment" : "Caption for bolus screen notice when no pump is connected" + }, "No Recent Glucose" : { "comment" : "The title of the cell indicating that there is no recent glucose", "localizations" : { @@ -41319,6 +41328,7 @@ }, "Your pump data is stale. %1$@ cannot recommend a bolus amount." : { "comment" : "Caption for bolus screen notice when pump data is missing or stale", + "extractionState" : "stale", "localizations" : { "da" : { "stringUnit" : { @@ -41400,6 +41410,9 @@ } } }, + "Your pump data is stale. Bolus delivery may be unavailable." : { + "comment" : "Caption for bolus screen notice when pump data is stale" + }, "Your pump is delivering a manual temporary basal rate." : { "comment" : "The description text for the looping enabled switch cell when closed loop is not allowed because the pump is delivering a manual temp basal.", "localizations" : { diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index c9aef285e8..ddff9c2a46 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -1235,7 +1235,8 @@ extension LoopDataManager { potentialCarbEntry: NewCarbEntry? = nil, replacingCarbEntry replacedCarbEntry: StoredCarbEntry? = nil, includingPendingInsulin: Bool = false, - includingPositiveVelocityAndRC: Bool = true + includingPositiveVelocityAndRC: Bool = true, + requireRecentPumpData: Bool = true ) throws -> [PredictedGlucoseValue] { dispatchPrecondition(condition: .onQueue(dataAccessQueue)) @@ -1254,8 +1255,10 @@ extension LoopDataManager { throw LoopError.invalidFutureGlucose(date: lastGlucoseDate) } - guard now().timeIntervalSince(pumpStatusDate) <= LoopCoreConstants.inputDataRecencyInterval else { - throw LoopError.pumpDataTooOld(date: pumpStatusDate) + if requireRecentPumpData { + guard now().timeIntervalSince(pumpStatusDate) <= LoopCoreConstants.inputDataRecencyInterval else { + throw LoopError.pumpDataTooOld(date: pumpStatusDate) + } } var momentum: [GlucoseEffect] = [] @@ -1487,8 +1490,16 @@ extension LoopDataManager { let pendingInsulin = try getPendingInsulin() let shouldIncludePendingInsulin = pendingInsulin > 0 - let prediction = try predictGlucose(using: .all, potentialBolus: nil, potentialCarbEntry: potentialCarbEntry, replacingCarbEntry: replacedCarbEntry, includingPendingInsulin: shouldIncludePendingInsulin, includingPositiveVelocityAndRC: considerPositiveVelocityAndRC) - return try recommendBolusValidatingDataRecency(forPrediction: prediction, consideringPotentialCarbEntry: potentialCarbEntry) + + var effectsToUse = PredictionInputEffect.all + var requireRecentPumpData = true // default to true to gate existing behavior on other pump types + if self.shouldModelAsNoDelivery { + // No active pod connected means no basal delivery — model it as suspension + effectsToUse.insert(.suspend) + requireRecentPumpData = false + } + let prediction = try predictGlucose(using: effectsToUse, potentialBolus: nil, potentialCarbEntry: potentialCarbEntry, replacingCarbEntry: replacedCarbEntry, includingPendingInsulin: shouldIncludePendingInsulin, includingPositiveVelocityAndRC: considerPositiveVelocityAndRC, requireRecentPumpData: requireRecentPumpData) + return try recommendBolusValidatingDataRecency(forPrediction: prediction, consideringPotentialCarbEntry: potentialCarbEntry, requireRecentPumpData: requireRecentPumpData) } /// - Throws: @@ -1498,7 +1509,8 @@ extension LoopDataManager { /// - LoopError.pumpDataTooOld /// - LoopError.configurationError fileprivate func recommendBolusValidatingDataRecency(forPrediction predictedGlucose: [Sample], - consideringPotentialCarbEntry potentialCarbEntry: NewCarbEntry?) throws -> ManualBolusRecommendation? { + consideringPotentialCarbEntry potentialCarbEntry: NewCarbEntry?, + requireRecentPumpData: Bool = true) throws -> ManualBolusRecommendation? { guard let glucose = glucoseStore.latestGlucose else { throw LoopError.missingDataError(.glucose) } @@ -1513,9 +1525,10 @@ extension LoopDataManager { guard lastGlucoseDate.timeIntervalSince(now()) <= LoopCoreConstants.inputDataRecencyInterval else { throw LoopError.invalidFutureGlucose(date: lastGlucoseDate) } - - guard now().timeIntervalSince(pumpStatusDate) <= LoopCoreConstants.inputDataRecencyInterval else { - throw LoopError.pumpDataTooOld(date: pumpStatusDate) + if requireRecentPumpData { + guard now().timeIntervalSince(pumpStatusDate) <= LoopCoreConstants.inputDataRecencyInterval else { + throw LoopError.pumpDataTooOld(date: pumpStatusDate) + } } guard glucoseMomentumEffect != nil else { @@ -1533,6 +1546,12 @@ extension LoopDataManager { return try recommendManualBolus(forPrediction: predictedGlucose, consideringPotentialCarbEntry: potentialCarbEntry) } + private var shouldModelAsNoDelivery: Bool { + // Model as no delivery if the delegate or status is not present. + // If both are present, use the pumpManagerStatus field directly + return delegate?.pumpManagerStatus?.shouldModelAsNoDelivery ?? true + } + /// - Throws: LoopError.configurationError private func recommendManualBolus(forPrediction predictedGlucose: [Sample], consideringPotentialCarbEntry potentialCarbEntry: NewCarbEntry?) throws -> ManualBolusRecommendation? { @@ -1719,7 +1738,8 @@ extension LoopDataManager { let pumpStatusDate = doseStore.lastAddedPumpData - if startDate.timeIntervalSince(pumpStatusDate) > LoopCoreConstants.inputDataRecencyInterval { + let pumpDataTooOld = startDate.timeIntervalSince(pumpStatusDate) > LoopCoreConstants.inputDataRecencyInterval + if pumpDataTooOld { errors.append(.pumpDataTooOld(date: pumpStatusDate)) } @@ -1773,18 +1793,34 @@ extension LoopDataManager { } dosingDecision.appendErrors(errors) - if let error = errors.first { + let errorsExcludingPumpDataTooOld = errors.filter { + if case .pumpDataTooOld = $0 { return false } + return true + } + if let error = errorsExcludingPumpDataTooOld.first { logger.error("%{public}@", String(describing: error)) return (dosingDecision, error) } var loopError: LoopError? do { - let predictedGlucose = try predictGlucose(using: settings.enabledEffects) + var effectsToUse = settings.enabledEffects + if self.shouldModelAsNoDelivery { + effectsToUse.insert(.suspend) // no pump = no basal delivery, model it as suspension + } + let predictedGlucose = try predictGlucose(using: effectsToUse, requireRecentPumpData: false) self.predictedGlucose = predictedGlucose - let predictedGlucoseIncludingPendingInsulin = try predictGlucose(using: settings.enabledEffects, includingPendingInsulin: true) + // Prediction is shown regardless of pump state, while automated dosing still requires fresh pump data (below via pumpDataTooOld) + let predictedGlucoseIncludingPendingInsulin = try predictGlucose(using: effectsToUse, includingPendingInsulin: true, requireRecentPumpData: false) self.predictedGlucoseIncludingPendingInsulin = predictedGlucoseIncludingPendingInsulin + if pumpDataTooOld { + self.logger.debug("Skipping automatic dose recommendation due to stale pump data.") + recommendedAutomaticDose = nil + dosingDecision.automaticDoseRecommendation = nil + return (dosingDecision, .pumpDataTooOld(date: pumpStatusDate)) + } + dosingDecision.predictedGlucose = predictedGlucose guard lastRequestedBolus == nil @@ -1944,6 +1980,17 @@ extension LoopDataManager { } } } + + +} + +extension PumpManagerStatus { + var shouldModelAsNoDelivery: Bool { + // Treat a non-active (faulted or setup incomplete) pod just like no pod + // OmniBLE reports no active pod as .active(.distantPast) + // See both OmniBLEPumpManager.basalDeliveryState(for:) and OmnipodPumpManager.basalDeliveryState(for:) + return basalDeliveryState == .active(.distantPast) + } } /// Describes a view into the loop state @@ -1985,9 +2032,10 @@ protocol LoopState { /// - Parameter replacedCarbEntry: An existing carb entry replaced by `potentialCarbEntry` /// - Parameter includingPendingInsulin: If `true`, the returned prediction will include the effects of scheduled but not yet delivered insulin /// - Parameter considerPositiveVelocityAndRC: Positive velocity and positive retrospective correction will not be used if this is false. + /// - Parameter requireRecentPumpData: Age of pump data will not be evaluated (and not throw LoopError.pumpDataTooOld) if this is `false`. Set to `false` for predicting insulin without a pump connected. /// - Returns: An timeline of predicted glucose values /// - Throws: LoopError.missingDataError if prediction cannot be computed - func predictGlucose(using inputs: PredictionInputEffect, potentialBolus: DoseEntry?, potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, includingPendingInsulin: Bool, considerPositiveVelocityAndRC: Bool) throws -> [PredictedGlucoseValue] + func predictGlucose(using inputs: PredictionInputEffect, potentialBolus: DoseEntry?, potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, includingPendingInsulin: Bool, considerPositiveVelocityAndRC: Bool, requireRecentPumpData: Bool) throws -> [PredictedGlucoseValue] /// Calculates a new prediction from a manual glucose entry in the context of a meal entry /// @@ -2035,7 +2083,8 @@ extension LoopState { /// - Returns: An timeline of predicted glucose values /// - Throws: LoopError.missingDataError if prediction cannot be computed func predictGlucose(using inputs: PredictionInputEffect, includingPendingInsulin: Bool = false) throws -> [GlucoseValue] { - try predictGlucose(using: inputs, potentialBolus: nil, potentialCarbEntry: nil, replacingCarbEntry: nil, includingPendingInsulin: includingPendingInsulin, considerPositiveVelocityAndRC: true) + // `requireRecentPumpData` set to false as this method is for visualization purposes + try predictGlucose(using: inputs, potentialBolus: nil, potentialCarbEntry: nil, replacingCarbEntry: nil, includingPendingInsulin: includingPendingInsulin, considerPositiveVelocityAndRC: true, requireRecentPumpData: false) } } @@ -2099,9 +2148,9 @@ extension LoopDataManager { return loopDataManager.retrospectiveCorrection.totalGlucoseCorrectionEffect } - func predictGlucose(using inputs: PredictionInputEffect, potentialBolus: DoseEntry?, potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, includingPendingInsulin: Bool, considerPositiveVelocityAndRC: Bool) throws -> [PredictedGlucoseValue] { + func predictGlucose(using inputs: PredictionInputEffect, potentialBolus: DoseEntry?, potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, includingPendingInsulin: Bool, considerPositiveVelocityAndRC: Bool, requireRecentPumpData: Bool) throws -> [PredictedGlucoseValue] { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) - return try loopDataManager.predictGlucose(using: inputs, potentialBolus: potentialBolus, potentialCarbEntry: potentialCarbEntry, replacingCarbEntry: replacedCarbEntry, includingPendingInsulin: includingPendingInsulin, includingPositiveVelocityAndRC: considerPositiveVelocityAndRC) + return try loopDataManager.predictGlucose(using: inputs, potentialBolus: potentialBolus, potentialCarbEntry: potentialCarbEntry, replacingCarbEntry: replacedCarbEntry, includingPendingInsulin: includingPendingInsulin, includingPositiveVelocityAndRC: considerPositiveVelocityAndRC, requireRecentPumpData: requireRecentPumpData) } func predictGlucoseFromManualGlucose( diff --git a/Loop/View Controllers/PredictionTableViewController.swift b/Loop/View Controllers/PredictionTableViewController.swift index a460e52aaf..a4932b502f 100644 --- a/Loop/View Controllers/PredictionTableViewController.swift +++ b/Loop/View Controllers/PredictionTableViewController.swift @@ -29,6 +29,10 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable tableView.rowHeight = UITableView.automaticDimension tableView.cellLayoutMarginsFollowReadableWidth = true + // No pump connected means no basal delivery — default prediction to suspension effect + if self.defaultPredictionShouldIncludeNoDelivery { + self.selectedInputs.insert(.suspend) + } glucoseChart.glucoseDisplayRange = LoopConstants.glucoseChartDefaultDisplayRangeWide let notificationCenter = NotificationCenter.default @@ -87,6 +91,11 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable } } + private var defaultPredictionShouldIncludeNoDelivery: Bool { + guard let pumpManager = self.deviceManager.pumpManager else { return true } + return pumpManager.status.shouldModelAsNoDelivery + } + let glucoseChart = PredictedGlucoseChart(yAxisStepSizeMGDLOverride: FeatureFlags.predictedGlucoseChartClampEnabled ? 40 : nil) override func createChartsManager() -> ChartsManager { @@ -148,6 +157,17 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable self.eventualGlucoseDescription = nil } + if self.defaultPredictionShouldIncludeNoDelivery { + var baseEffects = PredictionInputEffect(self.availableInputs) + baseEffects.insert(.suspend) + let selectionMatchesBase = self.selectedInputs == baseEffects + if selectionMatchesBase { + // We auto-apply the .suspend prediction in this condition, so we need to suppress the alternate predicted + // glucose line that would've shown if the user had clicked .supress, since it this situation is just a redundant line. + self.glucoseChart.setAlternatePredictedGlucoseValues([]) + } + } + if self.refreshContext.remove(.targets) != nil { self.glucoseChart.targetGlucoseSchedule = manager.settings.glucoseTargetRangeSchedule } @@ -233,6 +253,18 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable case .inputs: let cell = tableView.dequeueReusableCell(withIdentifier: PredictionInputEffectTableViewCell.className, for: indexPath) as! PredictionInputEffectTableViewCell self.tableView(tableView, updateTextFor: cell, at: indexPath) + let input = availableInputs[indexPath.row] + if input == .suspend && self.defaultPredictionShouldIncludeNoDelivery { + // When no pump connected, suspend effect is marked as active, so we show it as permanently selected and non-interactive + cell.contentView.alpha = 0.5 + cell.selectionStyle = .none + let checkmark = UIImageView(image: UIImage(systemName: "checkmark")) + checkmark.tintColor = .systemGray + cell.accessoryView = checkmark + } else { + cell.contentView.alpha = 1.0 + cell.selectionStyle = .default + } return cell } } @@ -297,6 +329,10 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable } + if input == .suspend && self.defaultPredictionShouldIncludeNoDelivery { + subtitleText = NSLocalizedString("Always active when no pump is connected", comment: "Subtitle for suspend prediction input when no pump is connected") + } + cell.subtitleLabel?.text = subtitleText } @@ -315,6 +351,13 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable guard Section(rawValue: indexPath.section) == .inputs else { return } let input = availableInputs[indexPath.row] + + // When no pump connected, suspend effect is permanently active — ignore taps + if input == .suspend && self.defaultPredictionShouldIncludeNoDelivery { + tableView.deselectRow(at: indexPath, animated: true) + return + } + let isSelected = selectedInputs.contains(input) if let cell = tableView.cellForRow(at: indexPath) { diff --git a/Loop/View Models/BolusEntryViewModel.swift b/Loop/View Models/BolusEntryViewModel.swift index a86f20e0cc..7a3a0bbf7c 100644 --- a/Loop/View Models/BolusEntryViewModel.swift +++ b/Loop/View Models/BolusEntryViewModel.swift @@ -45,6 +45,8 @@ protocol BolusEntryViewModelDelegate: AnyObject { var isPumpConfigured: Bool { get } + var shouldModelAsNoDelivery: Bool { get } + var pumpInsulinType: InsulinType? { get } var settings: LoopSettings { get } @@ -78,6 +80,7 @@ final class BolusEntryViewModel: ObservableObject { case staleGlucoseData case futureGlucoseData case stalePumpData + case noPumpConnected } var authenticationHandler: (String) async -> Bool = { message in @@ -580,13 +583,18 @@ final class BolusEntryViewModel: ObservableObject { considerPositiveVelocityAndRC: true ) } else { + var effectsToUse = PredictionInputEffect.all + if delegate?.shouldModelAsNoDelivery ?? false { + effectsToUse.insert(.suspend) // no pump = no basal delivery, model it as suspension + } predictedGlucoseValues = try state.predictGlucose( - using: .all, + using: effectsToUse, potentialBolus: enteredBolusDose, potentialCarbEntry: potentialCarbEntry, replacingCarbEntry: originalCarbEntry, includingPendingInsulin: true, - considerPositiveVelocityAndRC: true + considerPositiveVelocityAndRC: true, + requireRecentPumpData: false // this is for visualization only ) } } catch { @@ -657,7 +665,7 @@ final class BolusEntryViewModel: ObservableObject { let now = Date() var recommendation: ManualBolusRecommendation? let recommendedBolus: HKQuantity? - let notice: Notice? + var notice: Notice? do { recommendation = try computeBolusRecommendation(from: state) @@ -697,6 +705,15 @@ final class BolusEntryViewModel: ObservableObject { notice = nil } } + + // Surface appropriate pump warning even when recommendation succeeded + if notice == nil || notice == .predictedGlucoseInRange { + if delegate.shouldModelAsNoDelivery { + notice = .noPumpConnected + } else if isPumpDataStale { + notice = .stalePumpData + } + } DispatchQueue.main.async { let priorRecommendedBolus = self.recommendedBolus diff --git a/Loop/View Models/ManualEntryDoseViewModel.swift b/Loop/View Models/ManualEntryDoseViewModel.swift index 5fcd966c62..e40f7211a7 100644 --- a/Loop/View Models/ManualEntryDoseViewModel.swift +++ b/Loop/View Models/ManualEntryDoseViewModel.swift @@ -37,6 +37,8 @@ protocol ManualDoseViewModelDelegate: AnyObject { var isPumpConfigured: Bool { get } + var shouldModelAsNoDelivery: Bool { get } + var preferredGlucoseUnit: HKUnit { get } var pumpInsulinType: InsulinType? { get } @@ -261,13 +263,18 @@ final class ManualEntryDoseViewModel: ObservableObject { let predictedGlucoseValues: [PredictedGlucoseValue] do { + var effectsToUse = PredictionInputEffect.all + if delegate?.shouldModelAsNoDelivery ?? false { + effectsToUse.insert(.suspend) // no pump means no basal delivery. model it as suspension + } predictedGlucoseValues = try state.predictGlucose( - using: .all, + using: effectsToUse, potentialBolus: enteredBolusDose, potentialCarbEntry: nil, replacingCarbEntry: nil, includingPendingInsulin: true, - considerPositiveVelocityAndRC: true + considerPositiveVelocityAndRC: true, + requireRecentPumpData: false // visualization only — prediction chart does not require fresh pump data ) } catch { predictedGlucoseValues = [] diff --git a/Loop/Views/BolusEntryView.swift b/Loop/Views/BolusEntryView.swift index fecd2365c4..eb6c7b16a3 100644 --- a/Loop/Views/BolusEntryView.swift +++ b/Loop/Views/BolusEntryView.swift @@ -326,9 +326,14 @@ struct BolusEntryView: View { case .stalePumpData: return WarningView( title: Text("No Recent Pump Data", comment: "Title for bolus screen notice when pump data is missing or stale"), - caption: Text(String(format: NSLocalizedString("Your pump data is stale. %1$@ cannot recommend a bolus amount.", comment: "Caption for bolus screen notice when pump data is missing or stale"), appName)), + caption: Text(String(format: NSLocalizedString("Your pump data is stale. Bolus delivery may be unavailable.", comment: "Caption for bolus screen notice when pump data is stale"), appName)), severity: .critical ) + case .noPumpConnected: + return WarningView( + title: Text("No Pump Connected", comment: "Title for bolus screen notice when no pump is connected"), + caption: Text(NSLocalizedString("No pump is connected. Bolus delivery is unavailable.", comment: "Caption for bolus screen notice when no pump is connected")) + ) case .predictedGlucoseInRange, .glucoseBelowTarget: return WarningView( title: Text("No Bolus Recommended", comment: "Title for bolus screen notice when no bolus is recommended"), diff --git a/LoopTests/ViewModels/BolusEntryViewModelTests.swift b/LoopTests/ViewModels/BolusEntryViewModelTests.swift index 7f2c421ebf..955c742fa3 100644 --- a/LoopTests/ViewModels/BolusEntryViewModelTests.swift +++ b/LoopTests/ViewModels/BolusEntryViewModelTests.swift @@ -823,7 +823,7 @@ fileprivate class MockLoopState: LoopState { var totalRetrospectiveCorrection: HKQuantity? var predictGlucoseValueResult: [PredictedGlucoseValue] = [] - func predictGlucose(using inputs: PredictionInputEffect, potentialBolus: DoseEntry?, potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, includingPendingInsulin: Bool, considerPositiveVelocityAndRC: Bool) throws -> [PredictedGlucoseValue] { + func predictGlucose(using inputs: PredictionInputEffect, potentialBolus: DoseEntry?, potentialCarbEntry: NewCarbEntry?, replacingCarbEntry replacedCarbEntry: StoredCarbEntry?, includingPendingInsulin: Bool, considerPositiveVelocityAndRC: Bool, requireRecentPumpData: Bool) throws -> [PredictedGlucoseValue] { return predictGlucoseValueResult } @@ -948,6 +948,8 @@ fileprivate class MockBolusEntryViewModelDelegate: BolusEntryViewModelDelegate { var isPumpConfigured: Bool = true + var shouldModelAsNoDelivery: Bool = false + var preferredGlucoseUnit: HKUnit = .milligramsPerDeciliter var insulinModel: InsulinModel? = MockInsulinModel()