polscm32 aka Marvout 1 ano atrás
pai
commit
e8dde6e6a3

+ 12 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -327,6 +327,9 @@
 		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
 		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
+		BD4D738D2D15A4080052227B /* TDDStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4D738B2D15A4080052227B /* TDDStored+CoreDataClass.swift */; };
+		BD4D738E2D15A4080052227B /* TDDStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4D738C2D15A4080052227B /* TDDStored+CoreDataProperties.swift */; };
+		BD4D73A22D15A42A0052227B /* TDDStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4D73A12D15A4220052227B /* TDDStorage.swift */; };
 		BD4ED4FD2CF9D5E8000EDC9C /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4ED4FC2CF9D5E8000EDC9C /* AppState.swift */; };
 		BD6EB2D62C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */; };
 		BD793CB02CE7C61500D669AC /* OverrideRunStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */; };
@@ -1027,6 +1030,9 @@
 		BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleStyle.swift; sourceTree = "<group>"; };
 		BD3CC0712B0B89D50013189E /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
 		BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataObserver.swift; sourceTree = "<group>"; };
+		BD4D738B2D15A4080052227B /* TDDStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TDDStored+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; };
+		BD4D738C2D15A4080052227B /* TDDStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TDDStored+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
+		BD4D73A12D15A4220052227B /* TDDStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDDStorage.swift; sourceTree = "<group>"; };
 		BD4ED4FC2CF9D5E8000EDC9C /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
 		BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityWidgetConfiguration.swift; sourceTree = "<group>"; };
 		BD793CAF2CE7C60E00D669AC /* OverrideRunStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideRunStored+helper.swift"; sourceTree = "<group>"; };
@@ -2092,6 +2098,7 @@
 				CE82E02428E867BA00473A9C /* AlertStorage.swift */,
 				BDC2EA442C3043B000E5BBD0 /* OverrideStorage.swift */,
 				5864E8582C42CFAE00294306 /* DeterminationStorage.swift */,
+				BD4D73A12D15A4220052227B /* TDDStorage.swift */,
 			);
 			path = Storage;
 			sourceTree = "<group>";
@@ -2909,6 +2916,8 @@
 		DDE179112C9100FA003CDDB7 /* Classes+Properties */ = {
 			isa = PBXGroup;
 			children = (
+				BD4D738B2D15A4080052227B /* TDDStored+CoreDataClass.swift */,
+				BD4D738C2D15A4080052227B /* TDDStored+CoreDataProperties.swift */,
 				DDE179362C910127003CDDB7 /* BolusStored+CoreDataClass.swift */,
 				DDE179372C910127003CDDB7 /* BolusStored+CoreDataProperties.swift */,
 				DDE1793A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift */,
@@ -3864,6 +3873,8 @@
 				DDE179552C910127003CDDB7 /* LoopStatRecord+CoreDataProperties.swift in Sources */,
 				DDE179562C910127003CDDB7 /* BolusStored+CoreDataClass.swift in Sources */,
 				DDE179572C910127003CDDB7 /* BolusStored+CoreDataProperties.swift in Sources */,
+				BD4D738D2D15A4080052227B /* TDDStored+CoreDataClass.swift in Sources */,
+				BD4D738E2D15A4080052227B /* TDDStored+CoreDataProperties.swift in Sources */,
 				DDE179582C910127003CDDB7 /* ForecastValue+CoreDataClass.swift in Sources */,
 				DDE179592C910127003CDDB7 /* ForecastValue+CoreDataProperties.swift in Sources */,
 				DDE1795A2C910127003CDDB7 /* CarbEntryStored+CoreDataClass.swift in Sources */,
@@ -3882,6 +3893,7 @@
 				DDE179672C910127003CDDB7 /* OpenAPS_Battery+CoreDataProperties.swift in Sources */,
 				DDE179682C910127003CDDB7 /* TempBasalStored+CoreDataClass.swift in Sources */,
 				DDE179692C910127003CDDB7 /* TempBasalStored+CoreDataProperties.swift in Sources */,
+				BD4D73A22D15A42A0052227B /* TDDStorage.swift in Sources */,
 				DDE1796C2C910127003CDDB7 /* OverrideRunStored+CoreDataClass.swift in Sources */,
 				DDE1796D2C910127003CDDB7 /* OverrideRunStored+CoreDataProperties.swift in Sources */,
 				DDE1796E2C910127003CDDB7 /* OrefDetermination+CoreDataClass.swift in Sources */,

+ 36 - 0
FreeAPS/Sources/APS/APSManager.swift

@@ -69,6 +69,7 @@ final class BaseAPSManager: APSManager, Injectable {
     @Injected() private var deviceDataManager: DeviceDataManager!
     @Injected() private var nightscout: NightscoutManager!
     @Injected() private var settingsManager: SettingsManager!
+    @Injected() private var tddStorage: TDDStorage!
     @Injected() private var broadcaster: Broadcaster!
     @Persisted(key: "lastAutotuneDate") private var lastAutotuneDate = Date()
     @Persisted(key: "lastLoopStartDate") private var lastLoopStartDate: Date = .distantPast
@@ -351,9 +352,44 @@ final class BaseAPSManager: APSManager, Injectable {
         return false
     }
 
+    /// Calculates and stores the Total Daily Dose (TDD)
+    private func calculateAndStoreTDD() async {
+        // Get required data
+        let pumpHistory = await pumpHistoryStorage.getPumpHistory()
+        let basalProfile = await storage.retrieveAsync(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self) ?? []
+
+        // Calculate TDD
+        let tddResult = await tddStorage.calculateTDD(
+            pumpHistory: pumpHistory,
+            basalProfile: basalProfile
+        )
+
+        // TODO: Move this to storage as well
+        // Store TDD in Core Data
+        await privateContext.perform {
+            let tddStored = TDDStored(context: self.privateContext)
+            tddStored.id = UUID()
+            tddStored.date = Date()
+            tddStored.total = NSDecimalNumber(decimal: tddResult.total)
+            tddStored.bolus = NSDecimalNumber(decimal: tddResult.bolus)
+            tddStored.tempBasal = NSDecimalNumber(decimal: tddResult.tempBasal)
+            tddStored.scheduledBasal = NSDecimalNumber(decimal: tddResult.scheduledBasal)
+            tddStored.weightedAverage = tddResult.weightedAverage.map { NSDecimalNumber(decimal: $0) }
+
+            do {
+                guard self.privateContext.hasChanges else { return }
+                try self.privateContext.save()
+            } catch {
+                debug(.apsManager, "\(DebuggingIdentifiers.failed) Failed to save TDD: \(error.localizedDescription)")
+            }
+        }
+    }
+
     func determineBasal() async -> Bool {
         debug(.apsManager, "Start determine basal")
 
+        await calculateAndStoreTDD()
+
         // Fetch glucose asynchronously
         let glucose = await fetchGlucose(predicate: NSPredicate.predicateForOneHourAgo, fetchLimit: 6)
 

+ 38 - 0
FreeAPS/Sources/APS/Storage/PumpHistoryStorage.swift

@@ -11,6 +11,7 @@ protocol PumpHistoryObserver {
 
 protocol PumpHistoryStorage {
     var updatePublisher: AnyPublisher<Void, Never> { get }
+    func getPumpHistory() async -> [PumpHistoryEvent]
     func storePumpEvents(_ events: [NewPumpEvent])
     func storeExternalInsulinEvent(amount: Decimal, timestamp: Date) async
     func recent() -> [PumpHistoryEvent]
@@ -256,6 +257,43 @@ final class BasePumpHistoryStorage: PumpHistoryStorage, Injectable {
         }
     }
 
+    func getPumpHistory() async -> [PumpHistoryEvent] {
+        let results = await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: PumpEventStored.self,
+            onContext: context,
+            predicate: NSPredicate.pumpHistoryLast24h,
+            key: "timestamp",
+            ascending: false,
+            fetchLimit: 288
+        )
+
+        return await context.perform {
+            guard let fetchedPumpEvents = results as? [PumpEventStored] else { return [] }
+
+            return fetchedPumpEvents.map { event in
+                switch event.type {
+                case PumpEventStored.EventType.bolus.rawValue:
+                    return PumpHistoryEvent(
+                        id: event.id ?? UUID().uuidString,
+                        type: .bolus,
+                        timestamp: event.timestamp ?? Date(),
+                        amount: event.bolus?.amount as Decimal?
+                    )
+                case PumpEventStored.EventType.tempBasal.rawValue:
+                    return PumpHistoryEvent(
+                        id: event.id ?? UUID().uuidString,
+                        type: .tempBasal,
+                        timestamp: event.timestamp ?? Date(),
+                        amount: event.tempBasal?.rate as Decimal?,
+                        duration: Int(event.tempBasal?.duration ?? 0)
+                    )
+                default:
+                    return nil
+                }
+            }.compactMap { $0 }
+        }
+    }
+
     func recent() -> [PumpHistoryEvent] {
         storage.retrieve(OpenAPS.Monitor.pumpHistory, as: [PumpHistoryEvent].self)?.reversed() ?? []
     }

+ 265 - 0
FreeAPS/Sources/APS/Storage/TDDStorage.swift

@@ -0,0 +1,265 @@
+import Foundation
+import Swinject
+
+protocol TDDStorage {
+    func calculateTDD(pumpHistory: [PumpHistoryEvent], basalProfile: [BasalProfileEntry]) async -> TDDResult
+}
+
+/// Structure containing the results of TDD calculations
+struct TDDResult {
+    let total: Decimal
+    let bolus: Decimal
+    let tempBasal: Decimal
+    let scheduledBasal: Decimal
+    let weightedAverage: Decimal?
+    let hoursOfData: Double
+}
+
+/// Implementation of the TDD Calculator
+final class BaseTDDStorage: TDDStorage, Injectable {
+    init(resolver: Resolver) {
+        injectServices(resolver)
+    }
+
+    /// Main function to calculate TDD from pump history and basal profile
+    /// - Parameters:
+    ///   - pumpHistory: Array of pump history events
+    ///   - basalProfile: Array of basal profile entries
+    /// - Returns: TDDResult containing all calculated values
+    func calculateTDD(pumpHistory: [PumpHistoryEvent], basalProfile: [BasalProfileEntry]) async -> TDDResult {
+        debug(.apsManager, "Starting TDD calculation with \(pumpHistory.count) pump events")
+
+        var bolusInsulin: Decimal = 0
+        var tempInsulin: Decimal = 0
+        var scheduledBasalInsulin: Decimal = 0
+
+        let pumpData = calculatePumpDataHours(pumpHistory)
+        debug(.apsManager, "Hours of pump data available: \(pumpData)")
+
+        if pumpData < 23.9, pumpData > 21 {
+            let missingHours = 24 - pumpData
+            debug(.apsManager, "Filling \(missingHours) missing hours with scheduled basals")
+            if let lastEntry = pumpHistory.last {
+                let endDate = lastEntry.timestamp
+                let endDateAdjusted = endDate.addingTimeInterval(-missingHours * 3600)
+                scheduledBasalInsulin = calculateScheduledBasalInsulin(
+                    from: endDate,
+                    to: endDateAdjusted,
+                    basalProfile: basalProfile
+                )
+                debug(.apsManager, "Added scheduled basal insulin: \(scheduledBasalInsulin)U")
+            }
+        }
+
+        bolusInsulin = calculateBolusInsulin(pumpHistory)
+        debug(.apsManager, "Total bolus insulin: \(bolusInsulin)U")
+
+        tempInsulin = calculateTempBasalInsulin(pumpHistory)
+        debug(.apsManager, "Total temp basal insulin: \(tempInsulin)U")
+
+        let total = bolusInsulin + tempInsulin + scheduledBasalInsulin
+        let weightedAverage = calculateWeightedAverage()
+
+        debug(.apsManager, """
+        TDD Summary:
+        - Total: \(total)U
+        - Bolus: \(bolusInsulin)U (\((bolusInsulin / total * 100).rounded(toPlaces: 1))%)
+        - Temp Basal: \(tempInsulin)U (\((tempInsulin / total * 100).rounded(toPlaces: 1))%)
+        - Scheduled Basal: \(scheduledBasalInsulin)U (\((scheduledBasalInsulin / total * 100).rounded(toPlaces: 1))%)
+        - weightedAverage: \(weightedAverage ?? 0)
+        - Hours of Data: \(pumpData)
+        """)
+
+        return TDDResult(
+            total: total,
+            bolus: bolusInsulin,
+            tempBasal: tempInsulin,
+            scheduledBasal: scheduledBasalInsulin,
+            weightedAverage: weightedAverage,
+            hoursOfData: pumpData
+        )
+    }
+
+    /// Calculates the number of hours of available pump history data
+    /// - Parameter pumpHistory: Array of pump history events
+    /// - Returns: Number of hours of available data
+    private func calculatePumpDataHours(_ pumpHistory: [PumpHistoryEvent]) -> Double {
+        guard let firstEvent = pumpHistory.last, // we are fetching in a descending order
+              let lastEvent = pumpHistory.first
+        else {
+            return 0
+        }
+
+        let startDate = firstEvent.timestamp
+        var endDate = lastEvent.timestamp
+
+        // If last event is a temp basal, use current time
+        if lastEvent.type == .tempBasalDuration {
+            endDate = Date()
+        }
+
+        return Double(endDate.timeIntervalSince(startDate)) / 3600.0
+    }
+
+    /// Calculates total bolus insulin from pump history
+    /// - Parameter pumpHistory: Array of pump history events
+    /// - Returns: Total bolus insulin
+    private func calculateBolusInsulin(_ pumpHistory: [PumpHistoryEvent]) -> Decimal {
+        pumpHistory
+            .filter { $0.type == .bolus }
+            .reduce(Decimal(0)) { sum, event in
+                sum + (event.amount ?? 0)
+            }
+    }
+
+    /// Calculates insulin delivered via temporary basal rates
+    /// - Parameter pumpHistory: Array of pump history events
+    /// - Returns: Total temporary basal insulin
+    private func calculateTempBasalInsulin(_ pumpHistory: [PumpHistoryEvent]) -> Decimal {
+        var totalInsulin: Decimal = 0
+
+        for (index, event) in pumpHistory.enumerated() {
+            guard event.type == .tempBasal,
+                  let rate = event.amount,
+                  rate > 0,
+                  index > 0 else { continue }
+
+            let duration = Decimal(pumpHistory[index - 1].duration ?? 0) / 60 // Convert to hours
+            let insulin = accountForIncrements(rate * duration)
+            totalInsulin += insulin
+
+            debug(.apsManager, "Temp basal: \(rate)U/hr for \(duration)hr = \(insulin)U")
+        }
+
+        return totalInsulin
+    }
+
+    /// Calculates insulin delivered via scheduled basal rates
+    /// - Parameters:
+    ///   - from: Start date
+    ///   - to: End date
+    ///   - basalProfile: Array of basal profile entries
+    /// - Returns: Total scheduled basal insulin
+    private func calculateScheduledBasalInsulin(from: Date, to: Date, basalProfile: [BasalProfileEntry]) -> Decimal {
+        var totalInsulin: Decimal = 0
+        var currentDate = from
+
+        while currentDate < to {
+            let timeString = makeBaseString(from: currentDate)
+            guard let basalRate = findBasalRate(for: timeString, in: basalProfile) else { continue }
+
+            let nextScheduleTime = findNextScheduleTime(after: timeString, in: basalProfile)
+            let duration = calculateDuration(currentTime: timeString, nextScheduleTime: nextScheduleTime, endDate: to)
+
+            let insulin = accountForIncrements(basalRate * Decimal(duration))
+            totalInsulin += insulin
+
+            currentDate = currentDate.addingTimeInterval(duration * 3600)
+        }
+
+        return totalInsulin
+    }
+
+    /// Rounds insulin amounts according to pump increment constraints
+    /// - Parameter insulin: Raw insulin amount
+    /// - Returns: Rounded insulin amount
+    private func accountForIncrements(_ insulin: Decimal) -> Decimal {
+        let minimalDose: Decimal = 0.05 // For Omnipod, 0.1 for other pumps
+        let incrementsRaw = insulin / minimalDose
+
+        if incrementsRaw >= 1 {
+            // Convert to NSDecimalNumber to use its rounding capabilities
+            let nsIncrements = NSDecimalNumber(decimal: incrementsRaw)
+            let roundedIncrements = nsIncrements.rounding(
+                accordingToBehavior:
+                NSDecimalNumberHandler(
+                    roundingMode: .down,
+                    scale: 0,
+                    raiseOnExactness: false,
+                    raiseOnOverflow: false,
+                    raiseOnUnderflow: false,
+                    raiseOnDivideByZero: false
+                )
+            )
+            return (roundedIncrements.decimalValue * minimalDose).rounded(toPlaces: 3)
+        }
+        return 0
+    }
+
+    /// Formats a date to time string in "HH:mm:ss" format
+    /// - Parameter date: Date to format
+    /// - Returns: Formatted time string
+    private func makeBaseString(from date: Date) -> String {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "HH:mm:ss"
+        return formatter.string(from: date)
+    }
+
+    /// Finds the basal rate for a specific time in the profile
+    /// - Parameters:
+    ///   - timeString: Time string in "HH:mm:ss" format
+    ///   - profile: Array of basal profile entries
+    /// - Returns: Basal rate if found
+    private func findBasalRate(for timeString: String, in profile: [BasalProfileEntry]) -> Decimal? {
+        profile.first { $0.start == timeString }?.rate
+    }
+
+    /// Finds the next scheduled time in the basal profile
+    /// - Parameters:
+    ///   - time: Current time string
+    ///   - profile: Array of basal profile entries
+    /// - Returns: Next scheduled time
+    private func findNextScheduleTime(after time: String, in profile: [BasalProfileEntry]) -> String {
+        guard let currentIndex = profile.firstIndex(where: { $0.start == time }) else {
+            return profile[0].start
+        }
+
+        let nextIndex = (currentIndex + 1) % profile.count
+        return profile[nextIndex].start
+    }
+
+    /// Calculates duration between two schedule times
+    /// - Parameters:
+    ///   - currentTime: Current time string
+    ///   - nextScheduleTime: Next schedule time string
+    ///   - endDate: End date for calculations
+    /// - Returns: Duration in hours
+    private func calculateDuration(currentTime: String, nextScheduleTime: String, endDate _: Date) -> Double {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "HH:mm:ss"
+
+        guard let time1 = formatter.date(from: currentTime),
+              let time2 = formatter.date(from: nextScheduleTime)
+        else {
+            return 0
+        }
+
+        var difference = time2.timeIntervalSince(time1) / 3600
+        if difference < 0 {
+            difference += 24
+        }
+
+        return difference
+    }
+
+    /// Calculates weighted average of TDD from historical data
+    /// - Returns: Weighted average if available
+    private func calculateWeightedAverage() -> Decimal? {
+        // Implementation of weighted average calculation
+        // Would use historical TDD data from Core Data
+        nil
+    }
+}
+
+/// Extension for rounding Decimal numbers
+extension Decimal {
+    /// Rounds a decimal to specified number of places
+    /// - Parameter places: Number of decimal places
+    /// - Returns: Rounded decimal
+    func rounded(toPlaces places: Int) -> Decimal {
+        var value = self
+        var result = Decimal()
+        NSDecimalRound(&result, &value, places, .plain)
+        return result
+    }
+}

+ 1 - 0
FreeAPS/Sources/Assemblies/StorageAssembly.swift

@@ -10,6 +10,7 @@ final class StorageAssembly: Assembly {
         container.register(PumpHistoryStorage.self) { r in BasePumpHistoryStorage(resolver: r) }
         container.register(OverrideStorage.self) { r in BaseOverrideStorage(resolver: r) }
         container.register(DeterminationStorage.self) { r in BaseDeterminationStorage(resolver: r) }
+        container.register(TDDStorage.self) { r in BaseTDDStorage(resolver: r) }
         container.register(GlucoseStorage.self) { r in BaseGlucoseStorage(resolver: r) }
         container.register(TempTargetsStorage.self) { r in BaseTempTargetsStorage(resolver: r) }
         container.register(CarbsStorage.self) { r in BaseCarbsStorage(resolver: r) }

+ 10 - 1
Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23231" systemVersion="24C101" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
+<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="24C101" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
     <entity name="BolusStored" representedClassName="BolusStored" syncable="YES">
         <attribute name="amount" optional="YES" attributeType="Decimal" defaultValueString="0"/>
         <attribute name="isExternal" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
@@ -205,6 +205,15 @@
     <entity name="StatsData" representedClassName="StatsData" syncable="YES">
         <attribute name="lastrun" attributeType="Date" defaultDateTimeInterval="704497620" usesScalarValueType="NO"/>
     </entity>
+    <entity name="TDDStored" representedClassName="TDDStored" syncable="YES">
+        <attribute name="bolus" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
+        <attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
+        <attribute name="scheduledBasal" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
+        <attribute name="tempBasal" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
+        <attribute name="total" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
+        <attribute name="weightedAverage" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
+    </entity>
     <entity name="TempBasalStored" representedClassName="TempBasalStored" syncable="YES">
         <attribute name="duration" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
         <attribute name="rate" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>

+ 4 - 0
TDDStored+CoreDataClass.swift

@@ -0,0 +1,4 @@
+import CoreData
+import Foundation
+
+@objc(TDDStored) public class TDDStored: NSManagedObject {}

+ 18 - 0
TDDStored+CoreDataProperties.swift

@@ -0,0 +1,18 @@
+import CoreData
+import Foundation
+
+public extension TDDStored {
+    @nonobjc class func fetchRequest() -> NSFetchRequest<TDDStored> {
+        NSFetchRequest<TDDStored>(entityName: "TDDStored")
+    }
+
+    @NSManaged var id: UUID?
+    @NSManaged var date: Date?
+    @NSManaged var total: NSDecimalNumber?
+    @NSManaged var bolus: NSDecimalNumber?
+    @NSManaged var tempBasal: NSDecimalNumber?
+    @NSManaged var scheduledBasal: NSDecimalNumber?
+    @NSManaged var weightedAverage: NSDecimalNumber?
+}
+
+extension TDDStored: Identifiable {}