Просмотр исходного кода

Merge pull request #523 from marv-out/backdatingCarbs

Allow carb backdating in Treatments view
Mike Plante 1 год назад
Родитель
Сommit
1622653771

+ 19 - 0
Trio.xcodeproj/xcshareddata/xcschemes/Trio Tests.xcscheme

@@ -36,6 +36,16 @@
       debugDocumentVersioning = "YES"
       debugServiceExtension = "internal"
       allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "388E595725AD948C0019842D"
+            BuildableName = "Trio.app"
+            BlueprintName = "Trio"
+            ReferencedContainer = "container:Trio.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"
@@ -43,6 +53,15 @@
       savedToolIdentifier = ""
       useCustomWorkingDirectory = "NO"
       debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "388E595725AD948C0019842D"
+            BuildableName = "Trio.app"
+            BlueprintName = "Trio"
+            ReferencedContainer = "container:Trio.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
    </ProfileAction>
    <AnalyzeAction
       buildConfiguration = "Debug">

+ 12 - 3
Trio/Sources/APS/APSManager.swift

@@ -22,7 +22,11 @@ protocol APSManager {
     func enactTempBasal(rate: Double, duration: TimeInterval) async
     func determineBasal() async throws
     func determineBasalSync() async throws
-    func simulateDetermineBasal(simulatedCarbsAmount: Decimal, simulatedBolusAmount: Decimal) async -> Determination?
+    func simulateDetermineBasal(
+        simulatedCarbsAmount: Decimal,
+        simulatedBolusAmount: Decimal,
+        simulatedCarbsDate: Date?
+    ) async -> Determination?
     func roundBolus(amount: Decimal) -> Decimal
     var lastError: CurrentValueSubject<Error?, Never> { get }
     func cancelBolus(_ callback: ((Bool, String) -> Void)?) async
@@ -480,7 +484,11 @@ final class BaseAPSManager: APSManager, Injectable {
         _ = try await determineBasal()
     }
 
-    func simulateDetermineBasal(simulatedCarbsAmount: Decimal, simulatedBolusAmount: Decimal) async -> Determination? {
+    func simulateDetermineBasal(
+        simulatedCarbsAmount: Decimal,
+        simulatedBolusAmount: Decimal,
+        simulatedCarbsDate: Date? = nil
+    ) async -> Determination? {
         do {
             let temp = try await fetchCurrentTempBasal(date: Date.now)
             return try await openAPS.determineBasal(
@@ -488,11 +496,12 @@ final class BaseAPSManager: APSManager, Injectable {
                 clock: Date(),
                 simulatedCarbsAmount: simulatedCarbsAmount,
                 simulatedBolusAmount: simulatedBolusAmount,
+                simulatedCarbsDate: simulatedCarbsDate,
                 simulation: true
             )
         } catch {
             debugPrint(
-                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Error occurred in invokeDummyDetermineBasalSync: \(error)"
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Error occurred in simulateDetermineBasal: \(error)"
             )
             return nil
         }

+ 8 - 4
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -119,7 +119,7 @@ final class OpenAPS {
         }
     }
 
-    private func fetchAndProcessCarbs(additionalCarbs: Decimal? = nil) async throws -> String {
+    private func fetchAndProcessCarbs(additionalCarbs: Decimal? = nil, carbsDate: Date? = nil) async throws -> String {
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: CarbEntryStored.self,
             onContext: context,
@@ -136,13 +136,16 @@ final class OpenAPS {
             var jsonArray = self.jsonConverter.convertToJSON(carbResults)
 
             if let additionalCarbs = additionalCarbs {
+                let formattedDate = carbsDate.map { ISO8601DateFormatter().string(from: $0) } ?? ISO8601DateFormatter()
+                    .string(from: Date())
+
                 let additionalEntry = [
                     "carbs": Double(additionalCarbs),
-                    "actualDate": ISO8601DateFormatter().string(from: Date()),
+                    "actualDate": formattedDate,
                     "id": UUID().uuidString,
                     "note": NSNull(),
                     "protein": 0,
-                    "created_at": ISO8601DateFormatter().string(from: Date()),
+                    "created_at": formattedDate,
                     "isFPU": false,
                     "fat": 0,
                     "enteredBy": "Trio"
@@ -278,6 +281,7 @@ final class OpenAPS {
         clock: Date = Date(),
         simulatedCarbsAmount: Decimal? = nil,
         simulatedBolusAmount: Decimal? = nil,
+        simulatedCarbsDate: Date? = nil,
         simulation: Bool = false
     ) async throws -> Determination? {
         debug(.openAPS, "Start determineBasal")
@@ -287,7 +291,7 @@ final class OpenAPS {
 
         // Perform asynchronous calls in parallel
         async let pumpHistoryObjectIDs = fetchPumpHistoryObjectIDs() ?? []
-        async let carbs = fetchAndProcessCarbs(additionalCarbs: simulatedCarbsAmount ?? 0)
+        async let carbs = fetchAndProcessCarbs(additionalCarbs: simulatedCarbsAmount ?? 0, carbsDate: simulatedCarbsDate)
         async let glucose = fetchAndProcessGlucose()
         async let oref2 = oref2()
         async let profileAsync = loadFileFromStorageAsync(name: Settings.profile)

+ 3 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -43301,6 +43301,9 @@
         }
       }
     },
+    "Backdated carbs (%lld g) included in COB calculation" : {
+
+    },
     "Backfill Failed" : {
       "localizations" : {
         "bg" : {

+ 21 - 2
Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -89,6 +89,7 @@ extension Treatments {
         var note: String = ""
 
         var date = Date()
+        let defaultDate = Date()
 
         var carbsRequired: Decimal?
         var useFPUconversion: Bool = false
@@ -381,12 +382,26 @@ extension Treatments {
                 minPredBG
             }
 
+            // Use the cob value of the simulation if we have a simulated determination
+            var simulatedCOB: Int16?
+            if let simulatedCobValue = simulatedDetermination?.cob {
+                // Convert Decimal to Int16 and cap at maxCOB
+                let cobInt16 = Int16(truncating: NSDecimalNumber(decimal: simulatedCobValue))
+                let maxCobInt16 = Int16(truncating: NSDecimalNumber(decimal: maxCOB))
+                simulatedCOB = min(maxCobInt16, cobInt16)
+            }
+
+            // Check if this is a backdated entry by comparing with the default date
+            let isBackdated = date != defaultDate
+
             let result = await bolusCalculationManager.handleBolusCalculation(
                 carbs: carbs,
                 useFattyMealCorrection: useFattyMealCorrectionFactor,
                 useSuperBolus: useSuperBolus,
                 lastLoopDate: apsManager.lastLoopDate,
-                minPredBG: localMinPredBG
+                minPredBG: localMinPredBG,
+                simulatedCOB: simulatedCOB,
+                isBackdated: isBackdated
             )
 
             // Update state properties with calculation results on main thread
@@ -917,7 +932,11 @@ extension Treatments.StateModel {
         } else {
             simulatedDetermination = await Task { [self] in
                 debug(.bolusState, "calling simulateDetermineBasal to get forecast data")
-                return await apsManager.simulateDetermineBasal(simulatedCarbsAmount: carbs, simulatedBolusAmount: amount)
+                return await apsManager.simulateDetermineBasal(
+                    simulatedCarbsAmount: carbs,
+                    simulatedBolusAmount: amount,
+                    simulatedCarbsDate: date
+                )
             }.value
 
             // Update evBG and minPredBG from simulated determination

+ 10 - 2
Trio/Sources/Modules/Treatments/View/ForecastChart.swift

@@ -50,10 +50,18 @@ struct ForecastChart: View {
     }
 
     private var forecastChartLabels: some View {
-        HStack {
+        // Check if carbs are actually backdated (more than 15 minutes in the past)
+        // This ensures we only consider it backdated if the user has deliberately changed the date
+        let minutesThreshold = 15.0 // 15 minutes threshold
+        let isBackdated = state.date.timeIntervalSinceNow < -minutesThreshold * 60 && state.simulatedDetermination != nil
+
+        // When backdated, display no carbs as this label is only supposed to show current entered carbs
+        let displayedCarbs = isBackdated ? 0 : state.carbs
+
+        return HStack {
             HStack {
                 Image(systemName: "fork.knife")
-                Text("\(state.carbs.description) g")
+                Text("\(displayedCarbs.description) g")
             }
             .font(.footnote)
             .foregroundStyle(.orange)

+ 19 - 3
Trio/Sources/Modules/Treatments/View/PopupView.swift

@@ -312,7 +312,16 @@ struct PopupView: View {
     /// Don't allow total carbs to exceed Max IOB setting.
     /// Formula: (Current COB + New Carbs) / Carb Ratio = COB Correction Dose
     private var cobCardContent: some View {
-        let hasExceededMaxCOB: Bool = Decimal(state.cob) + state.carbs > state.maxCOB
+        // Check if carbs are actually backdated (more than 15 minutes in the past)
+        // This ensures we only consider it backdated if the user has deliberately changed the date
+        let minutesThreshold = 15.0 // 15 minutes threshold
+        let isBackdated = state.date.timeIntervalSinceNow < -minutesThreshold * 60 && state.simulatedDetermination != nil
+
+        // Determine COB and carbs to display based on backdating status
+        let displayedCOB = isBackdated ? (state.simulatedDetermination?.cob ?? Decimal(state.cob)) : Decimal(state.cob)
+        let displayedCarbs = isBackdated ? 0 : state.carbs
+
+        let hasExceededMaxCOB: Bool = displayedCOB + displayedCarbs > state.maxCOB
         return Group {
             Grid(alignment: .center) {
                 // Row 1: Column headers for the COB calculation
@@ -333,11 +342,11 @@ struct PopupView: View {
                 GridRow {
                     Text("(")
                         .operatorStyle()
-                    Text(Int(state.cob).description)
+                    Text(Int(displayedCOB).description)
                         .valueStyle()
                     Text("+")
                         .operatorStyle()
-                    Text(Int(state.carbs).description)
+                    Text(Int(displayedCarbs).description)
                         .valueStyle()
                     Text(")")
                         .operatorStyle()
@@ -378,6 +387,13 @@ struct PopupView: View {
             }
             .multilineTextAlignment(.center)
 
+            if isBackdated {
+                Text("Backdated carbs (\(Int(state.carbs)) g) included in COB calculation")
+                    .font(.caption)
+                    .foregroundStyle(.orange)
+                    .padding(.top, 4)
+            }
+
             // Additional grid only displayed when Max COB limit has been exceeded
             if hasExceededMaxCOB {
                 Grid(alignment: .center) {

+ 8 - 0
Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -224,6 +224,14 @@ extension Treatments {
                                         displayedComponents: [.hourAndMinute]
                                     ).controlSize(.mini)
                                         .labelsHidden()
+                                        .onChange(of: state.date) { _, _ in
+                                            // Trigger simulation when date changes to update forecasts for backdated carbs
+                                            Task {
+                                                // `updateForecasts()` does update the `simulatedDetermination` of type `Determination?` var on the main thread, so I can use this to pass its cob value into the bolus calc manager
+                                                await state.updateForecasts()
+                                                state.insulinCalculated = await state.calculateInsulin()
+                                            }
+                                        }
                                     Button {
                                         state.date = state.date.addingTimeInterval(15.minutes.timeInterval)
                                     }

+ 22 - 11
Trio/Sources/Services/BolusCalculator/BolusCalculationManager.swift

@@ -9,9 +9,10 @@ protocol BolusCalculationManager {
         useFattyMealCorrection: Bool,
         useSuperBolus: Bool,
         lastLoopDate: Date,
-        minPredBG: Decimal?
-    ) async
-        -> CalculationResult
+        minPredBG: Decimal?,
+        simulatedCOB: Int16?,
+        isBackdated: Bool
+    ) async -> CalculationResult
 }
 
 final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
@@ -289,7 +290,9 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
         useFattyMealCorrection: Bool,
         useSuperBolus: Bool,
         lastLoopDate: Date,
-        minPredBG: Decimal?
+        minPredBG: Decimal?,
+        simulatedCOB: Int16?,
+        isBackdated: Bool
     ) async throws -> CalculationInput {
         do {
             // Get settings
@@ -337,15 +340,20 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
                 )
             }
 
+            // If the entry is backdated (user explicitly changed the date), set carbs to 0
+            // This prevents double-counting of carbs (entered carbs + COB from backdated entry)
+            let effectiveCarbs = isBackdated ? 0 : carbs
+            let effectiveCob = isBackdated ? simulatedCOB : bolusVars.cob
+
             return CalculationInput(
-                carbs: carbs,
+                carbs: effectiveCarbs,
                 currentBG: glucoseVars.currentBG,
                 deltaBG: glucoseVars.deltaBG,
                 target: bolusVars.target,
                 isf: bolusVars.isf,
                 carbRatio: bolusVars.carbRatio,
                 iob: bolusVars.iob,
-                cob: bolusVars.cob,
+                cob: effectiveCob ?? bolusVars.cob,
                 useFattyMealCorrectionFactor: useFattyMealCorrection,
                 fattyMealFactor: settings.fattyMealFactor,
                 useSuperBolus: useSuperBolus,
@@ -384,6 +392,7 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
         debug(.default, "15min insulin: \(fifteenMinutesInsulin)")
 
         // determine whole COB for which we want to dose insulin for and then determine insulin for wholeCOB
+        // we need to take backdated carbs into account - so we are using a freshly created (simulated) COB if carbs are backdated, otherwise it will default to the mostRecentDeterminations' COB value (as before)
         let wholeCob = min(Decimal(input.cob) + input.carbs, input.maxCOB)
         let wholeCobInsulin = wholeCob / input.carbRatio
         debug(.default, "Whole COB: \(wholeCob), COB insulin: \(wholeCobInsulin)")
@@ -466,7 +475,6 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
             insulinCalculated: insulinCalculated,
             factoredInsulin: factoredInsulin,
             wholeCalc: wholeCalc,
-            correctionInsulin: targetDifferenceInsulin,
             iobInsulinReduction: iobInsulinReduction,
             superBolusInsulin: superBolusInsulin,
             targetDifference: targetDifference,
@@ -483,13 +491,16 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
     ///   - useFattyMealCorrection: Whether to apply fatty meal correction
     ///   - useSuperBolus: Whether to use super bolus calculation
     ///   - minPredBG: Minimum Predicted Glucose determined by Oref
+    ///   - simulatedCOB: Optional simulated COB from backdated entries (if available)
     /// - Returns: CalculationResult containing the calculated insulin dose and details
     func handleBolusCalculation(
         carbs: Decimal,
         useFattyMealCorrection: Bool,
         useSuperBolus: Bool,
         lastLoopDate: Date,
-        minPredBG: Decimal? = nil
+        minPredBG: Decimal? = nil,
+        simulatedCOB: Int16? = nil,
+        isBackdated: Bool = false
     ) async -> CalculationResult {
         do {
             let input = try await prepareCalculationInput(
@@ -497,7 +508,9 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
                 useFattyMealCorrection: useFattyMealCorrection,
                 useSuperBolus: useSuperBolus,
                 lastLoopDate: lastLoopDate,
-                minPredBG: minPredBG
+                minPredBG: minPredBG,
+                simulatedCOB: simulatedCOB,
+                isBackdated: isBackdated
             )
             let result = await calculateInsulin(input: input)
             return result
@@ -511,7 +524,6 @@ final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
                 insulinCalculated: 0,
                 factoredInsulin: 0,
                 wholeCalc: 0,
-                correctionInsulin: 0,
                 iobInsulinReduction: 0,
                 superBolusInsulin: 0,
                 targetDifference: 0,
@@ -552,7 +564,6 @@ struct CalculationResult: Sendable {
     let insulinCalculated: Decimal // Final calculated insulin amount which respects limits
     let factoredInsulin: Decimal // Total calculation after adjustments
     let wholeCalc: Decimal // Total calculation before adjustments
-    let correctionInsulin: Decimal // Insulin for BG correction
     let iobInsulinReduction: Decimal // IOB reduction amount
     let superBolusInsulin: Decimal // Additional insulin for super bolus
     let targetDifference: Decimal // Difference from target BG

+ 3 - 1
Trio/Sources/Services/WatchManager/AppleWatchManager.swift

@@ -622,7 +622,9 @@ final class BaseWatchManager: NSObject, WCSessionDelegate, Injectable, WatchMana
                         useFattyMealCorrection: false,
                         useSuperBolus: false,
                         lastLoopDate: apsManager.lastLoopDate,
-                        minPredBG: minPredBG
+                        minPredBG: minPredBG,
+                        simulatedCOB: nil,
+                        isBackdated: false // we cannot backdate carbs via watch
                     )
 
                     // Send recommendation back to watch

+ 175 - 10
TrioTests/BolusCalculatorTests/BolusCalculatorTests.swift

@@ -88,10 +88,10 @@ import Testing
         // wholeCalc = round(wholeCobInsulin + correctionInsulin + fifteenMinutesInsulin - iobInsulinReduction, 3) = 11.125U
         // insulinCalculated = round(wholeCalc × fraction, 3) = 8.9U
 
-        // Calculate expected values with proper rounding using roundBolus method from the apsManager
-        let wholeCobInsulin = apsManager.roundBolus(amount: Decimal(100) / Decimal(10)) // 10U
-        let targetDifferenceInsulin = apsManager.roundBolus(amount: Decimal(80) / Decimal(40)) // 2U
-        let fifteenMinutesInsulin = apsManager.roundBolus(amount: Decimal(5) / Decimal(40)) // 0.125U
+        // Calculate expected values
+        let wholeCobInsulin = Decimal(100) / Decimal(10) // 10U
+        let targetDifferenceInsulin = Decimal(80) / Decimal(40) // 2U
+        let fifteenMinutesInsulin = Decimal(5) / Decimal(40)
         let wholeCalc = wholeCobInsulin + targetDifferenceInsulin + fifteenMinutesInsulin - Decimal(1) // 11.125U
         let expectedInsulinCalculated = apsManager.roundBolus(amount: wholeCalc * fraction) // 8.9U
 
@@ -104,7 +104,6 @@ import Testing
             Components from CalculationResult:
             - insulinCalculated: \(result.insulinCalculated)U (expected: \(expectedInsulinCalculated)U)
             - wholeCalc: \(result.wholeCalc)U (expected: \(wholeCalc)U)
-            - correctionInsulin: \(result.correctionInsulin)U (expected: \(targetDifferenceInsulin)U)
             - iobInsulinReduction: \(result.iobInsulinReduction)U (expected: 1U)
             - superBolusInsulin: \(result.superBolusInsulin)U (expected: 0U)
             - targetDifference: \(result.targetDifference) mg/dL (expected: 80 mg/dL)
@@ -121,10 +120,6 @@ import Testing
             "Final calculated insulin amount should be \(expectedInsulinCalculated)U"
         )
         #expect(result.wholeCalc == wholeCalc, "Total calculation before fraction should be \(wholeCalc)U")
-        #expect(
-            result.correctionInsulin == targetDifferenceInsulin,
-            "Insulin for BG correction should be \(targetDifferenceInsulin)U"
-        )
         #expect(result.iobInsulinReduction == -1.0, "Absolute IOB reduction amount should be 1U, hence -1U")
         #expect(result.superBolusInsulin == 0, "Additional insulin for super bolus should be 0U")
         #expect(result.targetDifference == 80, "Difference from target BG should be 80 mg/dL")
@@ -491,7 +486,9 @@ import Testing
             useFattyMealCorrection: false,
             useSuperBolus: false,
             lastLoopDate: Date(),
-            minPredBG: nil
+            minPredBG: nil,
+            simulatedCOB: nil,
+            isBackdated: false
         )
 
         // Then
@@ -664,6 +661,174 @@ import Testing
             fileStorage.save(originalISFValues, as: OpenAPS.Settings.insulinSensitivities)
         }
     }
+
+    @Test("Calculate insulin with backdated carbs") func testHandleBolusCalculationFunction() async throws {
+        // STEP 1: Setup test scenario
+        let currentDate = Date()
+        let backdatedCarbsDate = currentDate.addingTimeInterval(-120 * 60) // 2 hours ago
+        let carbs: Decimal = 30 // 30g of carbs, backdated 2 hour
+        let cob: Int16 = 50
+
+        // Get the COB value for the backdated carbs
+        // Use the actual APS Manager to calculate simulated COB for more realistic test
+        let determination = await apsManager.simulateDetermineBasal(
+            simulatedCarbsAmount: carbs,
+            simulatedBolusAmount: 0,
+            simulatedCarbsDate: backdatedCarbsDate
+        )
+        let simulatedCOB = determination?.cob ?? Decimal(cob)
+
+        // STEP 2: Calculate results for normal and backdated carbs
+        let resultBackdated = await calculator.handleBolusCalculation(
+            carbs: carbs,
+            useFattyMealCorrection: false,
+            useSuperBolus: false,
+            lastLoopDate: Date.now.addingTimeInterval(-5.minutes.timeInterval),
+            minPredBG: 80,
+            simulatedCOB: Int16(truncating: NSDecimalNumber(decimal: simulatedCOB)),
+            isBackdated: true
+        )
+
+        let resultNormalEntry = await calculator.handleBolusCalculation(
+            carbs: carbs,
+            useFattyMealCorrection: false,
+            useSuperBolus: false,
+            lastLoopDate: Date.now.addingTimeInterval(-5.minutes.timeInterval),
+            minPredBG: 80,
+            simulatedCOB: Int16(truncating: NSDecimalNumber(decimal: simulatedCOB)),
+            isBackdated: false
+        )
+
+        // STEP 3: Compare
+        // The backdated scenario should recommend less insulin than the current time scenario
+        #expect(
+            resultBackdated.insulinCalculated < resultNormalEntry.insulinCalculated,
+            """
+            Backdated carbs should result in lower insulin recommendation
+            Current time: \(resultNormalEntry.insulinCalculated)U
+            Backdated: \(resultBackdated.insulinCalculated)U
+            Difference: \(resultNormalEntry.insulinCalculated - resultBackdated.insulinCalculated)U
+            """
+        )
+    }
+
+    @Test("Calculate insulin with backdated carbs") func testBackdatedCarbsCalculation() async throws {
+        // STEP 1: Setup test scenario
+        let currentDate = Date()
+        let backdatedCarbsDate = currentDate.addingTimeInterval(-60 * 60) // 1 hour ago
+
+        let currentBG: Decimal = 140
+        let target: Decimal = 100
+        let isf: Decimal = 40
+        let carbRatio: Decimal = 10
+        let iob: Decimal = 0.5
+        let cob: Int16 = 10 // Existing COB before adding backdated carbs
+        let carbs: Decimal = 30 // 30g of carbs, backdated 1 hour
+
+        // Get the COB value for the backdated carbs
+        // Use the actual APS Manager to calculate simulated COB for more realistic test
+        let determination = await apsManager.simulateDetermineBasal(
+            simulatedCarbsAmount: carbs,
+            simulatedBolusAmount: 0,
+            simulatedCarbsDate: backdatedCarbsDate
+        )
+
+        // Fallback to existing COB if determination is nil
+        let simulatedCOB = determination?.cob ?? Decimal(cob)
+
+        // For comparison - same scenario but with current time carbs
+        let currentTimeInput = CalculationInput(
+            carbs: carbs, // the newly entered carbs (30g)
+            currentBG: currentBG,
+            deltaBG: 0,
+            target: target,
+            isf: isf,
+            carbRatio: carbRatio,
+            iob: iob,
+            cob: cob, // the existing cob (10g)
+            useFattyMealCorrectionFactor: false,
+            fattyMealFactor: 0.8,
+            useSuperBolus: false,
+            sweetMealFactor: 1,
+            basal: 1.0,
+            fraction: 1.0,
+            maxBolus: 10,
+            maxIOB: 15,
+            maxCOB: 120,
+            minPredBG: 80,
+            lastLoopDate: currentDate
+        )
+
+        // Backdated scenario uses the same input but simulates date in the past
+        let backdatedInput = CalculationInput(
+            carbs: 0, // as the carbs are backdated we need to set the (newly entered) carbs to 0
+            currentBG: currentBG,
+            deltaBG: 0,
+            target: target,
+            isf: isf,
+            carbRatio: carbRatio,
+            iob: iob,
+            cob: Int16(truncating: NSDecimalNumber(decimal: simulatedCOB)), // current COB we got from the simulated Determination
+            useFattyMealCorrectionFactor: false,
+            fattyMealFactor: 0.8,
+            useSuperBolus: false,
+            sweetMealFactor: 1,
+            basal: 1.0,
+            fraction: 1.0,
+            maxBolus: 10,
+            maxIOB: 15,
+            maxCOB: 120,
+            minPredBG: 80,
+            lastLoopDate: currentDate
+        )
+
+        // STEP 2: Calculate insulin for both scenarios
+        let currentTimeResult = await calculator.calculateInsulin(input: currentTimeInput)
+        let backdatedResult = await calculator.calculateInsulin(input: backdatedInput)
+
+        // STEP 3: Verify results
+
+        // In the current time scenario, we expect COB to be old COB + current carbs
+        let expectedCurrentTimeCOB = Decimal(cob) + carbs
+        #expect(
+            currentTimeResult.wholeCob == expectedCurrentTimeCOB,
+            "Current time scenario should have \(expectedCurrentTimeCOB)g COB (\(cob)g existing + \(carbs)g new)"
+        )
+
+        // For backdated scenario, COB should be less than the current time scenario
+        // because some carbs have already been absorbed
+        #expect(
+            backdatedResult.wholeCob < currentTimeResult.wholeCob,
+            """
+            Backdated scenario should have less COB than current time scenario
+            Backdated: \(backdatedResult.wholeCob)g
+            Current time: \(currentTimeResult.wholeCob)g
+            Difference: \(currentTimeResult.wholeCob - backdatedResult.wholeCob)g
+            """
+        )
+
+        // The wholeCobInsulin should reflect the difference in COB
+        #expect(
+            backdatedResult.wholeCobInsulin < currentTimeResult.wholeCobInsulin,
+            """
+            Backdated scenario should require less insulin for carbs due to partial absorption
+            Backdated insulin: \(backdatedResult.wholeCobInsulin)U
+            Current time insulin: \(currentTimeResult.wholeCobInsulin)U
+            Difference: \(currentTimeResult.wholeCobInsulin - backdatedResult.wholeCobInsulin)U
+            """
+        )
+
+        // The backdated scenario should recommend less insulin than the current time scenario
+        #expect(
+            backdatedResult.insulinCalculated < currentTimeResult.insulinCalculated,
+            """
+            Backdated carbs should result in lower insulin recommendation
+            Current time: \(currentTimeResult.insulinCalculated)U
+            Backdated: \(backdatedResult.insulinCalculated)U
+            Difference: \(currentTimeResult.insulinCalculated - backdatedResult.insulinCalculated)U
+            """
+        )
+    }
 }
 
 // Copied over from BolusCalculationManager as they are not included in the protocol definition (and I don´t want them to be included)