Sam King 9 месяцев назад
Родитель
Сommit
ae9d3c32bd

+ 4 - 0
Trio.xcodeproj/project.pbxproj

@@ -301,6 +301,7 @@
 		3B997DD32DC02AEF006B6BB2 /* glucose.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B997DD12DC02AEF006B6BB2 /* glucose.json */; };
 		3BA8D1B32DDB87150006191F /* DecimalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BA8D1B22DDB870F0006191F /* DecimalExtensions.swift */; };
 		3BAAE60C2DE7766C0049589B /* DynamicISFEnableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAAE60B2DE776630049589B /* DynamicISFEnableTests.swift */; };
+		3BAC929B2E55FF5300B853DA /* DetermineBasalEnableSmbTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAC929A2E55FF4F00B853DA /* DetermineBasalEnableSmbTests.swift */; };
 		3BAD36B22D7CDC1A00CC298D /* MainLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */; };
 		3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */; };
 		3BAE876E2E47F12900FCA8D2 /* DosingEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAE876D2E47F12900FCA8D2 /* DosingEngine.swift */; };
@@ -1225,6 +1226,7 @@
 		3B997DD12DC02AEF006B6BB2 /* glucose.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = glucose.json; sourceTree = "<group>"; };
 		3BA8D1B22DDB870F0006191F /* DecimalExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalExtensions.swift; sourceTree = "<group>"; };
 		3BAAE60B2DE776630049589B /* DynamicISFEnableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicISFEnableTests.swift; sourceTree = "<group>"; };
+		3BAC929A2E55FF4F00B853DA /* DetermineBasalEnableSmbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetermineBasalEnableSmbTests.swift; sourceTree = "<group>"; };
 		3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainLoadingView.swift; sourceTree = "<group>"; };
 		3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataInitializationCoordinator.swift; sourceTree = "<group>"; };
 		3BAE876D2E47F12900FCA8D2 /* DosingEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DosingEngine.swift; sourceTree = "<group>"; };
@@ -2933,6 +2935,7 @@
 				3B8B5D3B2DF523B800365ED3 /* AutosensJsonTests.swift */,
 				3BBC22622DF5B93900169236 /* AutosensTests.swift */,
 				DD30B9FF2E0745C400DA677C /* DetermineBasalDeltaCalculationTests.swift */,
+				3BAC929A2E55FF4F00B853DA /* DetermineBasalEnableSmbTests.swift */,
 				3BE9F0BA2E28C993001B14EB /* DetermineBasalJsonTests.swift */,
 				DD30B9FD2E0742E200DA677C /* DetermineBasalSMBEnablementTests.swift */,
 				3B2A3BC22E2B19F700658FB9 /* DynamicISFTests.swift */,
@@ -5227,6 +5230,7 @@
 				BD8FC0662D661A0000B95AED /* GlucoseStorageTests.swift in Sources */,
 				BD8FC05B2D6618AF00B95AED /* DeterminationStorageTests.swift in Sources */,
 				3BAAE60C2DE7766C0049589B /* DynamicISFEnableTests.swift in Sources */,
+				3BAC929B2E55FF5300B853DA /* DetermineBasalEnableSmbTests.swift in Sources */,
 				CE1F6DD92BADF4620064EB8D /* PluginManagerTests.swift in Sources */,
 				3B1C5C432D68E269004E9273 /* Extensions.swift in Sources */,
 				3BC26E552D7418830066ACD6 /* IobSuspendTests.swift in Sources */,

+ 442 - 0
TrioTests/OpenAPSSwiftTests/DetermineBasalEnableSmbTests.swift

@@ -0,0 +1,442 @@
+import Foundation
+import Testing
+@testable import Trio
+
+@Suite("DosingEngine: shouldEnableSmb Tests") struct DetermineBasalEnableSmbTests {
+    /// Helper to create a default set of inputs.
+    /// Each test can then modify the specific properties relevant to its case.
+    private func createDefaultInputs() -> (
+        profile: Profile,
+        meal: ComputedCarbs,
+        currentGlucose: Decimal,
+        adjustedTargetGlucose: Decimal,
+        adjustedSensitivity: Decimal,
+        minGuardGlucose: Decimal,
+        eventualGlucose: Decimal,
+        threshold: Decimal,
+        glucoseStatus: GlucoseStatus,
+        trioCustomOrefVariables: TrioCustomOrefVariables,
+        clock: Date
+    ) {
+        var profile = Profile()
+        // Ensure default is false so we can test enabling conditions.
+        profile.enableSMBAlways = false
+        profile.temptargetSet = false
+
+        let meal = ComputedCarbs(
+            carbs: 0,
+            mealCOB: 0,
+            currentDeviation: 0,
+            maxDeviation: 0,
+            minDeviation: 0,
+            slopeFromMaxDeviation: 0,
+            slopeFromMinDeviation: 0,
+            allDeviations: [],
+            lastCarbTime: Date().timeIntervalSince1970
+        )
+
+        let glucoseStatus = GlucoseStatus(
+            delta: 0,
+            glucose: 120,
+            noise: 0,
+            shortAvgDelta: 0,
+            longAvgDelta: 0,
+            date: Date(),
+            lastCalIndex: nil,
+            device: "test"
+        )
+
+        let trioCustomOrefVariables = TrioCustomOrefVariables(
+            average_total_data: 0,
+            weightedAverage: 0,
+            currentTDD: 0,
+            past2hoursAverage: 0,
+            date: Date(),
+            overridePercentage: 100,
+            useOverride: false,
+            duration: 0,
+            unlimited: false,
+            overrideTarget: 0,
+            smbIsOff: false,
+            advancedSettings: false,
+            isfAndCr: false,
+            isf: false,
+            cr: false,
+            smbIsScheduledOff: false,
+            start: 0,
+            end: 0,
+            smbMinutes: 0,
+            uamMinutes: 0,
+            shouldProtectDueToHIGH: false
+        )
+
+        return (
+            profile: profile,
+            meal: meal,
+            currentGlucose: 120,
+            adjustedTargetGlucose: 100,
+            adjustedSensitivity: 50,
+            minGuardGlucose: 110,
+            eventualGlucose: 115,
+            threshold: 70,
+            glucoseStatus: glucoseStatus,
+            trioCustomOrefVariables: trioCustomOrefVariables,
+            clock: Date()
+        )
+    }
+
+    // MARK: - Disabling Conditions
+
+    @Test("Should return false by default with no enabling preferences")  func defaultIsFalse() throws {
+        let inputs = createDefaultInputs()
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+    }
+
+    @Test("Should disable SMB when smbIsOff is true")  func disableWhenSmbIsOff() throws {
+        var inputs = createDefaultInputs()
+        inputs.trioCustomOrefVariables.smbIsOff = true
+        inputs.profile.enableSMBAlways = true // Ensure smbIsOff takes precedence
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+    }
+
+    @Test("Should disable SMB when shouldProtectDueToHIGH is true")  func disableWhenProtectDueToHigh() throws {
+        var inputs = createDefaultInputs()
+        inputs.trioCustomOrefVariables.shouldProtectDueToHIGH = true
+        inputs.profile.enableSMBAlways = true // Ensure protection takes precedence
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+    }
+
+    @Test("Should disable SMB with high temp target when not allowed")  func disableWithHighTempTarget() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.allowSMBWithHighTemptarget = false
+        inputs.profile.temptargetSet = true
+        inputs.adjustedTargetGlucose = 120
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+    }
+
+    @Test("Should disable SMB when minGuardGlucose is below threshold")  func disableWhenMinGuardBelowThreshold() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true // Enable SMB initially to test the safety override
+        inputs.minGuardGlucose = 65
+        inputs.threshold = 70
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+        #expect(decision.manualBolusError == 1)
+        #expect(decision.minGuardGlucose == 65)
+        #expect(decision.insulinForManualBolus != nil)
+    }
+
+    @Test("Should disable SMB when maxDelta is too high")  func disableWhenMaxDeltaTooHigh() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true // Enable SMB initially
+        inputs.profile.maxDeltaBgThreshold = 0.2
+        inputs.currentGlucose = 100
+        // Set maxDelta to be > 20% of currentGlucose
+        inputs.glucoseStatus = GlucoseStatus(
+            delta: 21,
+            glucose: 100,
+            noise: 0,
+            shortAvgDelta: 5,
+            longAvgDelta: 5,
+            date: Date(),
+            lastCalIndex: nil,
+            device: "test"
+        )
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+        #expect(decision.reason != nil)
+    }
+
+    // MARK: - Enabling Conditions
+
+    @Test("Should enable SMB when enableSMBAlways is true")  func enableWhenAlwaysOn() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == true)
+    }
+
+    @Test("Should enable SMB with COB")  func enableWithCob() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBWithCOB = true
+        inputs.meal = ComputedCarbs(
+            carbs: 20,
+            mealCOB: 10,
+            currentDeviation: 0,
+            maxDeviation: 0,
+            minDeviation: 0,
+            slopeFromMaxDeviation: 0,
+            slopeFromMinDeviation: 0,
+            allDeviations: [],
+            lastCarbTime: Date().timeIntervalSince1970
+        )
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == true)
+    }
+
+    @Test("Should enable SMB after carbs")  func enableAfterCarbs() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAfterCarbs = true
+        inputs.meal = ComputedCarbs(
+            carbs: 20,
+            mealCOB: 0,
+            currentDeviation: 0,
+            maxDeviation: 0,
+            minDeviation: 0,
+            slopeFromMaxDeviation: 0,
+            slopeFromMinDeviation: 0,
+            allDeviations: [],
+            lastCarbTime: Date().timeIntervalSince1970
+        )
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == true)
+    }
+
+    @Test("Should enable SMB with low temp target")  func enableWithLowTempTarget() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBWithTemptarget = true
+        inputs.profile.temptargetSet = true
+        inputs.adjustedTargetGlucose = 90
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == true)
+    }
+
+    @Test("Should enable SMB for high BG")  func enableWithHighBg() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBHighBg = true
+        inputs.profile.enableSMBHighBgTarget = 140
+        inputs.currentGlucose = 145
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == true)
+    }
+
+    // MARK: - Scheduled Off Tests
+
+    @Test("Scheduled Off (Normal): should disable SMB inside the window")  func scheduledOffNormal_Inside() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true // Ensure schedule is the only reason for failure
+        inputs.trioCustomOrefVariables.smbIsScheduledOff = true
+        inputs.trioCustomOrefVariables.start = 9 // 9 AM
+        inputs.trioCustomOrefVariables.end = 17 // 5 PM
+        inputs.clock = Calendar.current.date(bySettingHour: 14, minute: 0, second: 0, of: Date())!
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+    }
+
+    @Test("Scheduled Off (Normal): should NOT disable SMB outside the window")  func scheduledOffNormal_Outside() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true
+        inputs.trioCustomOrefVariables.smbIsScheduledOff = true
+        inputs.trioCustomOrefVariables.start = 9 // 9 AM
+        inputs.trioCustomOrefVariables.end = 17 // 5 PM
+        inputs.clock = Calendar.current.date(bySettingHour: 18, minute: 0, second: 0, of: Date())!
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == true)
+    }
+
+    @Test(
+        "Scheduled Off (Wrapping): should disable SMB inside the window (after midnight)"
+    )  func scheduledOffWrapping_InsideAfterMidnight() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true
+        inputs.trioCustomOrefVariables.smbIsScheduledOff = true
+        inputs.trioCustomOrefVariables.start = 22 // 10 PM
+        inputs.trioCustomOrefVariables.end = 6 // 6 AM
+        inputs.clock = Calendar.current.date(bySettingHour: 2, minute: 0, second: 0, of: Date())!
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+    }
+
+    @Test(
+        "Scheduled Off (Wrapping): should disable SMB inside the window (before midnight)"
+    )  func scheduledOffWrapping_InsideBeforeMidnight() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true
+        inputs.trioCustomOrefVariables.smbIsScheduledOff = true
+        inputs.trioCustomOrefVariables.start = 22 // 10 PM
+        inputs.trioCustomOrefVariables.end = 6 // 6 AM
+        inputs.clock = Calendar.current.date(bySettingHour: 23, minute: 0, second: 0, of: Date())!
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+    }
+
+    @Test("Scheduled Off (Wrapping): should NOT disable SMB outside the window")  func scheduledOffWrapping_Outside() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true
+        inputs.trioCustomOrefVariables.smbIsScheduledOff = true
+        inputs.trioCustomOrefVariables.start = 22 // 10 PM
+        inputs.trioCustomOrefVariables.end = 6 // 6 AM
+        inputs.clock = Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: Date())!
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == true)
+    }
+
+    @Test("Scheduled Off (All Day): should disable SMB")  func scheduledOffAllDay() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true
+        inputs.trioCustomOrefVariables.smbIsScheduledOff = true
+        inputs.trioCustomOrefVariables.start = 0
+        inputs.trioCustomOrefVariables.end = 0
+        inputs.clock = Calendar.current.date(bySettingHour: 15, minute: 0, second: 0, of: Date())!
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+    }
+
+    @Test("Scheduled Off (Single Hour): should disable SMB inside the window")  func scheduledOffSingleHour_Inside() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true
+        inputs.trioCustomOrefVariables.smbIsScheduledOff = true
+        inputs.trioCustomOrefVariables.start = 11 // 11 AM
+        inputs.trioCustomOrefVariables.end = 11 // 11 AM
+        inputs.clock = Calendar.current.date(bySettingHour: 11, minute: 30, second: 0, of: Date())!
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == false)
+    }
+
+    @Test("Scheduled Off (Single Hour): should NOT disable SMB outside the window")  func scheduledOffSingleHour_Outside() throws {
+        var inputs = createDefaultInputs()
+        inputs.profile.enableSMBAlways = true
+        inputs.trioCustomOrefVariables.smbIsScheduledOff = true
+        inputs.trioCustomOrefVariables.start = 11 // 11 AM
+        inputs.trioCustomOrefVariables.end = 11 // 11 AM
+        inputs.clock = Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: Date())!
+
+        let decision = try DosingEngine.shouldEnableSmb(
+            profile: inputs.profile, meal: inputs.meal, currentGlucose: inputs.currentGlucose,
+            adjustedTargetGlucose: inputs.adjustedTargetGlucose, adjustedSensitivity: inputs.adjustedSensitivity,
+            minGuardGlucose: inputs.minGuardGlucose, eventualGlucose: inputs.eventualGlucose,
+            threshold: inputs.threshold, glucoseStatus: inputs.glucoseStatus,
+            trioCustomOrefVariables: inputs.trioCustomOrefVariables, clock: inputs.clock
+        )
+        #expect(decision.isEnabled == true)
+    }
+}