| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690 |
- import Foundation
- protocol OverrideHandler {
- func overrideProfileParameters(profile: Profile, override: Override?) throws -> Profile
- // TODO: handle mutation of profile parameters that the user can alter using Overrides
- /// This could also possibly be handled via an extension of our existing `ProfileGenerator` (?)
- }
- enum DeterminationGenerator {
- // override data can just be fetched from the DB
- // handling via overrideManager ?
- /// Top-level determination generator, callers should use this function
- static func generate(
- profile: Profile,
- preferences: Preferences,
- currentTemp: TempBasal,
- iobData: [IobResult],
- mealData: ComputedCarbs,
- autosensData: Autosens,
- reservoirData: Decimal,
- glucose: [BloodGlucose],
- microBolusAllowed: Bool,
- trioCustomOrefVariables: TrioCustomOrefVariables,
- currentTime: Date
- ) throws -> Determination? {
- let glucoseStatus = try Self.getGlucoseStatus(glucoseReadings: glucose)
- guard let glucoseStatus = glucoseStatus else { throw DeterminationError.missingInputs }
- return try determineBasal(
- profile: profile,
- preferences: preferences,
- currentTemp: currentTemp,
- iobData: iobData,
- mealData: mealData,
- autosensData: autosensData,
- reservoirData: reservoirData,
- glucoseStatus: glucoseStatus,
- microBolusAllowed: microBolusAllowed,
- trioCustomOrefVariables: trioCustomOrefVariables,
- currentTime: currentTime
- )
- }
- /// Internal function to implement the core determine basal logic. We have a separate function
- /// from `generate` so that we can pass GlucoseStatus values directly into the function
- /// for testing.
- static func determineBasal(
- profile: Profile,
- preferences: Preferences,
- currentTemp: TempBasal,
- iobData: [IobResult],
- mealData: ComputedCarbs,
- autosensData: Autosens,
- reservoirData _: Decimal,
- glucoseStatus: GlucoseStatus,
- microBolusAllowed: Bool,
- trioCustomOrefVariables: TrioCustomOrefVariables,
- currentTime: Date
- ) throws -> Determination? {
- var autosensData = autosensData
- try checkDeterminationInputs(
- glucoseStatus: glucoseStatus,
- currentTemp: currentTemp,
- iobData: iobData,
- profile: profile,
- trioCustomOrefVariables: trioCustomOrefVariables
- )
- let currentGlucose: Decimal = glucoseStatus.glucose
- if let errorDetermination = try handleTempBasalCases(
- glucoseStatus: glucoseStatus,
- profile: profile,
- currentTemp: currentTemp,
- currentTime: currentTime,
- trioCustomOrefVariables: trioCustomOrefVariables
- ) {
- return errorDetermination
- }
- // Safety check: current temp vs. last temp in iob
- guard let lastTempTarget = iobData.first?.lastTemp else {
- throw DeterminationError.missingIob
- }
- if !checkCurrentTempBasalRateSafety(
- currentTemp: currentTemp,
- lastTempTarget: lastTempTarget,
- currentTime: currentTime
- ) {
- let reason =
- "Safety check: currentTemp does not match lastTemp in IOB or lastTemp ended long ago; canceling temp basal."
- return Determination(
- id: UUID(),
- reason: reason,
- units: nil,
- insulinReq: nil,
- eventualBG: nil,
- sensitivityRatio: nil,
- rate: 0,
- duration: 0,
- iob: nil,
- cob: nil,
- predictions: nil,
- deliverAt: currentTime,
- carbsReq: nil,
- temp: .absolute,
- bg: nil,
- reservoir: nil,
- isf: nil,
- timestamp: nil,
- tdd: nil,
- current_target: nil,
- minDelta: nil,
- expectedDelta: nil,
- minGuardBG: nil,
- minPredBG: nil,
- threshold: nil,
- carbRatio: nil,
- received: false
- )
- }
- let dynamicIsfResult = DynamicISF.calculate(
- profile: profile,
- preferences: preferences,
- currentGlucose: currentGlucose,
- trioCustomOrefVariables: trioCustomOrefVariables
- )
- if let dynamicIsfResult = dynamicIsfResult {
- autosensData = Autosens(
- ratio: dynamicIsfResult.ratio,
- newisf: autosensData.newisf,
- deviationsUnsorted: autosensData.deviationsUnsorted,
- timestamp: autosensData.timestamp
- )
- }
- let (sensitivityRatio, updateAutosensRatio) = calculateSensitivityRatio(
- currentGlucose: currentGlucose,
- profile: profile,
- autosens: autosensData,
- targetGlucose: profile.profileTarget(trioCustomOrefVariables: trioCustomOrefVariables) ?? 120,
- temptargetSet: profile.temptargetSet ?? false,
- dynamicIsfResult: dynamicIsfResult
- )
- if updateAutosensRatio {
- autosensData = Autosens(
- ratio: sensitivityRatio,
- newisf: autosensData.newisf,
- deviationsUnsorted: autosensData.deviationsUnsorted,
- timestamp: autosensData.timestamp
- )
- }
- var basal = profile.currentBasal ?? profile.basalFor(time: currentTime)
- if dynamicIsfResult == nil {
- basal = computeAdjustedBasal(
- profile: profile,
- currentBasalRate: profile.currentBasal ?? profile.basalFor(time: currentTime),
- sensitivityRatio: sensitivityRatio,
- overrideFactor: trioCustomOrefVariables.overrideFactor()
- )
- } else if let dynamicIsfResult = dynamicIsfResult, profile.tddAdjBasal {
- basal = computeAdjustedBasal(
- profile: profile,
- currentBasalRate: profile.currentBasal ?? profile.basalFor(time: currentTime),
- sensitivityRatio: dynamicIsfResult.tddRatio,
- overrideFactor: trioCustomOrefVariables.overrideFactor()
- )
- }
- // this is the `sens` variable in JS, it's the adjusted sensitivity
- let adjustedSensitivity = computeAdjustedSensitivity(
- sensitivity: profile.sens ?? profile.sensitivityFor(time: currentTime),
- sensitivityRatio: sensitivityRatio,
- trioCustomOrefVariables: trioCustomOrefVariables
- )
- let (adjustedGlucoseTargets, threshold) = adjustGlucoseTargets(
- profile: profile,
- autosens: autosensData,
- trioCustomOrefVariables: trioCustomOrefVariables,
- temptargetSet: profile.temptargetSet ?? false,
- targetGlucose: profile.minBg ?? 100,
- minGlucose: profile.minBg ?? 70, // TODO: can we force unwrap?
- maxGlucose: profile.maxBg ?? 180,
- noise: 1
- )
- let glucoseImpactSeries = buildGlucoseImpactSeries(iobDataSeries: iobData, sensitivity: adjustedSensitivity)
- let glucoseImpactSeriesWithZeroTemp = buildGlucoseImpactSeries(
- iobDataSeries: iobData,
- sensitivity: adjustedSensitivity,
- withZeroTemp: true
- )
- guard let currentGlucoseImpact = glucoseImpactSeries.first?.jsRounded(scale: 2) else {
- throw DeterminationError.determinationError
- }
- let minDelta = min(glucoseStatus.delta, glucoseStatus.shortAvgDelta)
- let minAvgDelta = min(glucoseStatus.shortAvgDelta, glucoseStatus.longAvgDelta)
- let longAvgDelta = glucoseStatus.longAvgDelta
- let intervals: Decimal = 6 // 30 / 5
- var deviation = (intervals * (minDelta - currentGlucoseImpact)).rounded(toPlaces: 0)
- if deviation < 0 {
- deviation = (intervals * (minAvgDelta - currentGlucoseImpact)).rounded(toPlaces: 0)
- if deviation < 0 {
- deviation = (intervals * (longAvgDelta - currentGlucoseImpact)).rounded(toPlaces: 0)
- }
- }
- // Calculate what oref calls "naive eventual glucose"
- guard let currentIob = iobData.first?.iob else {
- throw DeterminationError.missingIob
- }
- let naiveEventualGlucose: Decimal
- if currentIob > 0 {
- naiveEventualGlucose = (currentGlucose - (currentIob * adjustedSensitivity)).rounded(toPlaces: 0)
- } else {
- naiveEventualGlucose =
- (
- currentGlucose -
- (
- currentIob *
- min(
- profile.profileSensitivity(at: currentTime, trioCustomOrefVaribales: trioCustomOrefVariables),
- adjustedSensitivity
- )
- )
- )
- .rounded(toPlaces: 0)
- }
- let eventualGlucose = naiveEventualGlucose + deviation
- // Safety: if we ever get an invalid Decimal (very rare with Decimal), handle
- guard eventualGlucose.isFinite else {
- throw DeterminationError.eventualGlucoseCalculationError(sensitivity: adjustedSensitivity, deviation: deviation)
- }
- let forecastResult = ForecastGenerator.generate(
- glucose: currentGlucose,
- glucoseStatus: glucoseStatus,
- currentGlucoseImpact: currentGlucoseImpact,
- glucoseImpactSeries: glucoseImpactSeries,
- glucoseImpactSeriesWithZeroTemp: glucoseImpactSeriesWithZeroTemp,
- iobData: iobData,
- mealData: mealData,
- profile: profile,
- preferences: preferences,
- trioCustomOrefVariables: trioCustomOrefVariables,
- dynamicIsfResult: dynamicIsfResult,
- targetGlucose: adjustedGlucoseTargets.targetGlucose,
- adjustedSensitivity: adjustedSensitivity,
- sensitivityRatio: sensitivityRatio,
- naiveEventualGlucose: naiveEventualGlucose,
- eventualGlucose: eventualGlucose,
- threshold: threshold,
- currentTime: currentTime
- )
- // used for pre dosing decision sanity later on
- let expectedDelta = calculateExpectedDelta(
- targetGlucose: adjustedGlucoseTargets.targetGlucose,
- eventualGlucose: eventualGlucose,
- glucoseImpact: currentGlucoseImpact
- )
- let dosingInputs = DosingEngine.prepareDosingInputs(
- profile: profile,
- mealData: mealData,
- forecast: forecastResult,
- naiveEventualGlucose: naiveEventualGlucose,
- threshold: threshold,
- glucoseImpact: currentGlucoseImpact,
- deviation: deviation,
- currentBasal: profile.currentBasal ?? profile.basalFor(time: currentTime),
- overrideFactor: trioCustomOrefVariables.overrideFactor(),
- adjustedSensitivity: adjustedSensitivity,
- isfReason: "", // Placeholder
- tddReason: "", // Placeholder
- targetLog: "" // Placeholder
- )
- let smbDecision = try DosingEngine.makeSMBDosingDecision(
- profile: profile,
- meal: mealData,
- currentGlucose: currentGlucose,
- adjustedTargetGlucose: adjustedGlucoseTargets.targetGlucose,
- minGuardGlucose: forecastResult.minGuardGlucose,
- threshold: threshold,
- glucoseStatus: glucoseStatus,
- trioCustomOrefVariables: trioCustomOrefVariables,
- clock: currentTime
- )
- let smbIsEnabled = smbDecision.isEnabled
- var reason = dosingInputs.reason
- if let smbReason = smbDecision.reason {
- reason += smbReason
- }
- var determination = Determination(
- id: UUID(),
- reason: reason,
- units: nil,
- insulinReq: 0,
- eventualBG: Int(forecastResult.eventualGlucose.jsRounded()),
- sensitivityRatio: sensitivityRatio, // this would only the AS-adjusted one for now
- rate: nil,
- duration: nil,
- iob: iobData.first?.iob,
- cob: mealData.mealCOB,
- predictions: Predictions(
- iob: forecastResult.iob.map { Int($0.jsRounded()) },
- zt: forecastResult.zt.map { Int($0.jsRounded()) },
- cob: forecastResult.cob?.map { Int($0.jsRounded()) },
- uam: forecastResult.uam?.map { Int($0.jsRounded()) }
- ),
- deliverAt: currentTime,
- carbsReq: dosingInputs.carbsRequired?.carbs,
- temp: nil,
- bg: currentGlucose,
- reservoir: nil,
- isf: nil,
- timestamp: currentTime,
- tdd: nil,
- current_target: nil,
- minDelta: nil,
- expectedDelta: expectedDelta,
- minGuardBG: smbDecision.minGuardGlucose ?? forecastResult.minGuardGlucose,
- minPredBG: forecastResult.minForecastedGlucose,
- threshold: threshold.jsRounded(),
- carbRatio: forecastResult.adjustedCarbRatio.jsRounded(scale: 1),
- received: false
- )
- // MARK: - Core dosing logic
- let (shouldSetTempBasalForLowGlucoseSuspend, lowGlucoseSuspendDetermination) = try DosingEngine.lowGlucoseSuspend(
- currentGlucose: currentGlucose,
- minGuardGlucose: forecastResult.minGuardGlucose,
- iob: currentIob,
- minDelta: minDelta,
- expectedDelta: expectedDelta,
- threshold: threshold,
- overrideFactor: trioCustomOrefVariables.overrideFactor(),
- profile: profile,
- adjustedSensitivity: adjustedSensitivity,
- targetGlucose: adjustedGlucoseTargets.targetGlucose,
- currentTemp: currentTemp,
- determination: determination
- )
- determination = lowGlucoseSuspendDetermination
- if shouldSetTempBasalForLowGlucoseSuspend {
- return determination
- }
- let (shouldSetTempBasalForSkipNeutralTemp, skipNeutralTempDetermination) = try DosingEngine.skipNeutralTempBasal(
- smbIsEnabled: smbIsEnabled,
- profile: profile,
- clock: currentTime,
- currentTemp: currentTemp,
- determination: determination
- )
- determination = skipNeutralTempDetermination
- if shouldSetTempBasalForSkipNeutralTemp {
- return determination
- }
- let (shouldSetTempBasalForLowEventualGlucose, lowEventualGlucoseDetermination) = try DosingEngine
- .handleLowEventualGlucose(
- eventualGlucose: forecastResult.eventualGlucose,
- minGlucose: adjustedGlucoseTargets.minGlucose,
- targetGlucose: adjustedGlucoseTargets.targetGlucose,
- minDelta: minDelta,
- expectedDelta: expectedDelta,
- carbsRequired: dosingInputs.rawCarbsRequired,
- naiveEventualGlucose: naiveEventualGlucose,
- glucoseStatus: glucoseStatus,
- currentTemp: currentTemp,
- basal: basal,
- profile: profile,
- determination: determination,
- adjustedSensitivity: adjustedSensitivity,
- overrideFactor: trioCustomOrefVariables.overrideFactor()
- )
- determination = lowEventualGlucoseDetermination
- if shouldSetTempBasalForLowEventualGlucose {
- return determination
- }
- let (
- shouldSetTempBasalForGlucoseFallingFasterThanExpected,
- glucoseFallingFasterThanExpectedDetermination
- ) = try DosingEngine.glucoseFallingFasterThanExpected(
- eventualGlucose: forecastResult.eventualGlucose,
- minGlucose: adjustedGlucoseTargets.minGlucose,
- minDelta: minDelta,
- expectedDelta: expectedDelta,
- glucoseStatus: glucoseStatus,
- currentTemp: currentTemp,
- basal: basal,
- smbIsEnabled: smbIsEnabled,
- profile: profile,
- determination: determination
- )
- determination = glucoseFallingFasterThanExpectedDetermination
- if shouldSetTempBasalForGlucoseFallingFasterThanExpected {
- return determination
- }
- let (
- shouldSetTempBasalEventualOrForecastGlucoseLessThanMax,
- eventualOrForecastGlucoseLessThanMaxDetermination
- ) = try DosingEngine.eventualOrForecastGlucoseLessThanMax(
- eventualGlucose: forecastResult.eventualGlucose,
- maxGlucose: adjustedGlucoseTargets.maxGlucose,
- minForecastGlucose: forecastResult.minForecastedGlucose,
- currentTemp: currentTemp,
- basal: basal,
- smbIsEnabled: smbIsEnabled,
- profile: profile,
- determination: determination
- )
- determination = eventualOrForecastGlucoseLessThanMaxDetermination
- if shouldSetTempBasalEventualOrForecastGlucoseLessThanMax {
- return determination
- }
- if forecastResult.eventualGlucose >= adjustedGlucoseTargets.maxGlucose {
- determination
- .reason +=
- "Eventual BG \(DosingEngine.convertGlucose(profile: profile, glucose: forecastResult.eventualGlucose)) >= \(DosingEngine.convertGlucose(profile: profile, glucose: adjustedGlucoseTargets.maxGlucose)), "
- }
- let (shouldSetTempBasalForIobGreaterThanMax, iobGreaterThanMaxDetermination) = try DosingEngine.iobGreaterThanMax(
- iob: currentIob,
- maxIob: profile.maxIob,
- currentTemp: currentTemp,
- basal: basal,
- profile: profile,
- determination: determination
- )
- determination = iobGreaterThanMaxDetermination
- if shouldSetTempBasalForIobGreaterThanMax {
- return determination
- }
- // MARK: - Aggressive dosing logic (SMB, High Temps)
- // Calculate Insulin Required
- let (insulinRequired, insulinReqDetermination) = DosingEngine.calculateInsulinRequired(
- minForecastGlucose: forecastResult.minForecastedGlucose,
- eventualGlucose: forecastResult.eventualGlucose,
- targetGlucose: adjustedGlucoseTargets.targetGlucose,
- adjustedSensitivity: adjustedSensitivity,
- maxIob: profile.maxIob,
- currentIob: currentIob,
- determination: determination
- )
- determination = insulinReqDetermination
- // SMB Delivery
- let (shouldSetTempBasalForSMB, smbDetermination) = try DosingEngine.determineSMBDelivery(
- insulinRequired: insulinRequired,
- microBolusAllowed: microBolusAllowed,
- smbIsEnabled: smbIsEnabled,
- currentGlucose: currentGlucose,
- threshold: threshold,
- profile: profile,
- mealData: mealData,
- iobData: iobData,
- currentTime: currentTime,
- targetGlucose: adjustedGlucoseTargets.targetGlucose,
- naiveEventualGlucose: naiveEventualGlucose,
- minIOBForecastedGlucose: forecastResult.minIOBForecastedGlucose,
- adjustedSensitivity: adjustedSensitivity,
- overrideFactor: trioCustomOrefVariables.overrideFactor(),
- adjustedCarbRatio: forecastResult.adjustedCarbRatio,
- basal: basal,
- determination: determination
- )
- determination = smbDetermination
- if shouldSetTempBasalForSMB {
- return determination
- }
- // High Temp Basal (Fallback)
- return try DosingEngine.determineHighTempBasal(
- insulinRequired: insulinRequired,
- basal: basal,
- profile: profile,
- currentTemp: currentTemp,
- determination: determination
- )
- }
- static func checkDeterminationInputs(
- glucoseStatus: GlucoseStatus?,
- currentTemp _: TempBasal?,
- iobData: [IobResult]?,
- profile: Profile?,
- trioCustomOrefVariables: TrioCustomOrefVariables
- ) throws {
- guard let glucoseStatus = glucoseStatus else {
- throw DeterminationError.missingGlucoseStatus
- }
- guard let profile = profile else {
- throw DeterminationError.missingProfile
- }
- guard profile.profileTarget(trioCustomOrefVariables: trioCustomOrefVariables) != nil else {
- throw DeterminationError.invalidProfileTarget
- }
- // we have to allow 38 values so that we can cancel high temps
- if glucoseStatus.glucose < 38 || glucoseStatus.glucose > 600 {
- throw DeterminationError.glucoseOutOfRange(glucose: glucoseStatus.glucose)
- }
- guard let _ = iobData else {
- throw DeterminationError.missingIob
- }
- }
- static func handleTempBasalCases(
- glucoseStatus: GlucoseStatus,
- profile: Profile,
- currentTemp: TempBasal?,
- currentTime: Date,
- trioCustomOrefVariables: TrioCustomOrefVariables
- ) throws -> Determination? {
- let glucose = glucoseStatus.glucose
- let noise = glucoseStatus.noise
- let bgTime = glucoseStatus.date
- let minAgo = Decimal(currentTime.timeIntervalSince(bgTime) / 60) // minutes
- let shortAvgDelta = glucoseStatus.shortAvgDelta
- let longAvgDelta = glucoseStatus.longAvgDelta
- let delta = glucoseStatus.delta
- let device = glucoseStatus.device
- // Always use profile-supplied basal
- guard let profileBasal = profile.currentBasal else {
- throw DeterminationError.missingCurrentBasal
- }
- let basal = profileBasal * trioCustomOrefVariables.overrideFactor()
- // Compose tick for log
- let tick: String = (delta > -0.5) ? "+\(delta.rounded(toPlaces: 0))" : "\(delta.rounded(toPlaces: 0))"
- let minDelta = min(delta, shortAvgDelta)
- let minAvgDelta = min(shortAvgDelta, longAvgDelta)
- let maxDelta = max(delta, shortAvgDelta, longAvgDelta)
- var reason = ""
- // === ERROR CONDITIONS ===
- // xDrip code 38 = sensor error; BG <= 10 = ???/calibrating; noise >= 3 = high noise
- if glucose <= 10 || glucose == 38 || noise >= 3 {
- reason = "CGM is calibrating, in ??? state, or noise is high"
- }
- // minAgo (BG age) > 12 or < -5 = old/future BG
- if minAgo > 12 || minAgo < -5 {
- reason =
- "If current system time \(currentTime) is correct, then BG data is too old. The last BG data was read \(minAgo) min ago at \(bgTime)"
- }
- // CGM data unchanged (flat)
- if shortAvgDelta == 0 && longAvgDelta == 0 {
- if glucoseStatus.lastCalIndex != nil, glucoseStatus.lastCalIndex! < 3 {
- reason = "CGM was just calibrated"
- } else {
- reason =
- "CGM data is unchanged (\(glucose)+\(delta)) for 5m w/ \(shortAvgDelta) mg/dL ~15m change & \(longAvgDelta) mg/dL ~45m change"
- }
- }
- let errorDetected =
- glucose <= 10 ||
- glucose == 38 ||
- noise >= 3 ||
- minAgo > 12 ||
- minAgo < -5
- // === IF ERROR, CANCEL/SHORTEN TEMPS ===
- guard errorDetected, let currentTemp = currentTemp else { return nil }
- if currentTemp.rate >= basal { // high temp is running
- // Replace high temp with neutral temp at scheduled basal rate for 30min
- let reasonWithAction = reason +
- ". Replacing high temp basal of \(currentTemp.rate)U/hr with neutral temp of \(basal)U/hr"
- return Determination(
- id: UUID(),
- reason: reasonWithAction,
- units: nil,
- insulinReq: nil,
- eventualBG: nil,
- sensitivityRatio: nil,
- rate: basal,
- duration: 30,
- iob: nil,
- cob: nil,
- predictions: nil,
- deliverAt: currentTime,
- carbsReq: nil,
- temp: .absolute,
- bg: nil,
- reservoir: nil,
- isf: profile.sens,
- timestamp: currentTime,
- tdd: nil,
- current_target: profile.targetBg,
- minDelta: minDelta,
- expectedDelta: nil,
- minGuardBG: nil,
- minPredBG: nil,
- threshold: nil,
- carbRatio: nil,
- received: false
- )
- } else if currentTemp.rate == 0, currentTemp.duration > 30 {
- // Shorten long zero temp to 30m
- let reasonWithAction = reason + ". Shortening \(currentTemp.duration)m long zero temp to 30m."
- return Determination(
- id: UUID(),
- reason: reasonWithAction,
- units: nil,
- insulinReq: nil,
- eventualBG: nil,
- sensitivityRatio: nil,
- rate: 0,
- duration: 30,
- iob: nil,
- cob: nil,
- predictions: nil,
- deliverAt: currentTime,
- carbsReq: nil,
- temp: .absolute,
- bg: nil,
- reservoir: nil,
- isf: profile.sens,
- timestamp: currentTime,
- tdd: nil,
- current_target: profile.targetBg,
- minDelta: minDelta,
- expectedDelta: nil,
- minGuardBG: nil,
- minPredBG: nil,
- threshold: nil,
- carbRatio: nil,
- received: false
- )
- } else {
- // Do nothing (temp already safe)
- let reasonWithAction = reason + ". Temp \(currentTemp.rate) <= current basal \(basal)U/hr; doing nothing."
- return Determination(
- id: UUID(),
- reason: reasonWithAction,
- units: nil,
- insulinReq: nil,
- eventualBG: nil,
- sensitivityRatio: nil,
- rate: nil,
- duration: nil,
- iob: nil,
- cob: nil,
- predictions: nil,
- deliverAt: currentTime,
- carbsReq: nil,
- temp: currentTemp.temp,
- bg: nil,
- reservoir: nil,
- isf: profile.sens,
- timestamp: currentTime,
- tdd: nil,
- current_target: profile.targetBg,
- minDelta: minDelta,
- expectedDelta: nil,
- minGuardBG: nil,
- minPredBG: nil,
- threshold: nil,
- carbRatio: nil,
- received: false
- )
- }
- }
- }
|