Skip to content

Commit

Permalink
Merge pull request #240 from dnzxy/fix-fpu-conversion
Browse files Browse the repository at this point in the history
Refactor and optimize `storeCarbs` function to limit carb equivalents of FPU conversion to 1.0 grams
  • Loading branch information
bjornoleh committed May 28, 2024
2 parents 5faf162 + e93ef86 commit 29eb46e
Showing 1 changed file with 116 additions and 71 deletions.
187 changes: 116 additions & 71 deletions FreeAPS/Sources/APS/Storage/CarbsStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,93 +27,53 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
injectServices(resolver)
}

/**
Processes and stores carbohydrate entries, including handling entries with fat and protein to calculate and distribute future carb equivalents.
- The function processes fat and protein units (FPUs) by creating carb equivalents for future use.
- Ensures each carb equivalent is at least 1.0 grams by adjusting the interval if necessary.
- Stores the actual carbohydrate entries.
- Saves the data to CoreData for statistical purposes.
- Notifies observers of the carbohydrate data update.
- Parameters:
- entries: An array of `CarbsEntry` objects representing the carbohydrate entries to be processed and stored.
*/
func storeCarbs(_ entries: [CarbsEntry]) {
processQueue.sync {
let file = OpenAPS.Monitor.carbHistory
var uniqEvents: [CarbsEntry] = []

let fat = entries.last?.fat ?? 0
let protein = entries.last?.protein ?? 0

if fat > 0 || protein > 0 {
// -------------------------- FPU--------------------------------------
let interval = settings.settings.minuteInterval // Interval betwwen carbs
let timeCap = settings.settings.timeCap // Max Duration
let adjustment = settings.settings.individualAdjustmentFactor
let delay = settings.settings.delay // Tme before first future carb entry
let kcal = protein * 4 + fat * 9
let carbEquivalents = (kcal / 10) * adjustment
let fpus = carbEquivalents / 10
// Duration in hours used for extended boluses with Warsaw Method. Here used for total duration of the computed carbquivalents instead, excluding the configurable delay.
var computedDuration = 0
switch fpus {
case ..<2:
computedDuration = 3
case 2 ..< 3:
computedDuration = 4
case 3 ..< 4:
computedDuration = 5
default:
computedDuration = timeCap
}
// Size of each created carb equivalent if 60 minutes interval
var equivalent: Decimal = carbEquivalents / Decimal(computedDuration)
// Adjust for interval setting other than 60 minutes
equivalent /= Decimal(60 / interval)
// Round to 1 fraction digit
// equivalent = Decimal(round(Double(equivalent * 10) / 10))
let roundedEquivalent: Double = round(Double(equivalent * 10)) / 10
equivalent = Decimal(roundedEquivalent)
// Number of equivalents
var numberOfEquivalents = carbEquivalents / equivalent
// Only use delay in first loop
var firstIndex = true
// New date for each carb equivalent
var useDate = entries.last?.createdAt ?? Date()
// Group and Identify all FPUs together
let fpuID = UUID().uuidString
// Create an array of all future carb equivalents.
var futureCarbArray = [CarbsEntry]()
while carbEquivalents > 0, numberOfEquivalents > 0 {
if firstIndex {
useDate = useDate.addingTimeInterval(delay.minutes.timeInterval)
firstIndex = false
} else { useDate = useDate.addingTimeInterval(interval.minutes.timeInterval) }

let eachCarbEntry = CarbsEntry(
id: UUID().uuidString, createdAt: useDate,
carbs: equivalent, fat: 0, protein: 0, note: nil,
enteredBy: CarbsEntry.manual, isFPU: true,
fpuID: fpuID
)
futureCarbArray.append(eachCarbEntry)
numberOfEquivalents -= 1
}
// Save the array
var entriesToStore: [CarbsEntry] = []

guard let lastEntry = entries.last else { return }

if let fat = lastEntry.fat, let protein = lastEntry.protein, fat > 0 || protein > 0 {
let (futureCarbArray, carbEquivalents) = processFPU(
entries: entries,
fat: fat,
protein: protein,
createdAt: lastEntry.createdAt
)
if carbEquivalents > 0 {
self.storage.transaction { storage in
storage.append(futureCarbArray, to: file, uniqBy: \.id)
uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
entriesToStore = storage.retrieve(file, as: [CarbsEntry].self)?
.filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
.sorted { $0.createdAt > $1.createdAt } ?? []
storage.save(Array(uniqEvents), as: file)
storage.save(Array(entriesToStore), as: file)
}
}
} // ------------------------- END OF TPU ----------------------------------------
// Store the actual (normal) carbs
if entries.last?.carbs ?? 0 > 0 {
uniqEvents = []
}

if lastEntry.carbs > 0 {
self.storage.transaction { storage in
storage.append(entries, to: file, uniqBy: \.createdAt)
uniqEvents = storage.retrieve(file, as: [CarbsEntry].self)?
entriesToStore = storage.retrieve(file, as: [CarbsEntry].self)?
.filter { $0.createdAt.addingTimeInterval(1.days.timeInterval) > Date() }
.sorted { $0.createdAt > $1.createdAt } ?? []
storage.save(Array(uniqEvents), as: file)
storage.save(Array(entriesToStore), as: file)
}
}

// MARK: Save to CoreData. TEST

var cbs: Decimal = 0
var carbDate = Date()
if entries.isNotEmpty {
Expand All @@ -131,11 +91,96 @@ final class BaseCarbsStorage: CarbsStorage, Injectable {
}
}
broadcaster.notify(CarbsObserver.self, on: processQueue) {
$0.carbsDidUpdate(uniqEvents)
$0.carbsDidUpdate(entriesToStore)
}
}
}

/**
Calculates the duration for processing FPUs (fat and protein units) based on the FPUs and the time cap.
- The function uses predefined rules to determine the duration based on the number of FPUs.
- Ensures that the duration does not exceed the time cap.
- Parameters:
- fpus: The number of FPUs calculated from fat and protein.
- timeCap: The maximum allowed duration.
- Returns: The computed duration in hours.
*/
private func calculateComputedDuration(fpus: Decimal, timeCap: Int) -> Int {
switch fpus {
case ..<2:
return 3
case 2 ..< 3:
return 4
case 3 ..< 4:
return 5
default:
return timeCap
}
}

/**
Processes fat and protein entries to generate future carb equivalents, ensuring each equivalent is at least 1.0 grams.
- The function calculates the equivalent carb dosage size and adjusts the interval to ensure each equivalent is at least 1.0 grams.
- Creates future carb entries based on the adjusted carb equivalent size and interval.
- Parameters:
- entries: An array of `CarbsEntry` objects representing the carbohydrate entries to be processed.
- fat: The amount of fat in the last entry.
- protein: The amount of protein in the last entry.
- createdAt: The creation date of the last entry.
- Returns: A tuple containing the array of future carb entries and the total carb equivalents.
*/
private func processFPU(entries _: [CarbsEntry], fat: Decimal, protein: Decimal, createdAt: Date) -> ([CarbsEntry], Decimal) {
let interval = settings.settings.minuteInterval
let timeCap = settings.settings.timeCap
let adjustment = settings.settings.individualAdjustmentFactor
let delay = settings.settings.delay

let kcal = protein * 4 + fat * 9
let carbEquivalents = (kcal / 10) * adjustment
let fpus = carbEquivalents / 10
var computedDuration = calculateComputedDuration(fpus: fpus, timeCap: timeCap)

var carbEquivalentSize: Decimal = carbEquivalents / Decimal(computedDuration)
carbEquivalentSize /= Decimal(60 / interval)

if carbEquivalentSize < 1.0 {
carbEquivalentSize = 1.0
computedDuration = Int(carbEquivalents / carbEquivalentSize)
}

let roundedEquivalent: Double = round(Double(carbEquivalentSize * 10)) / 10
carbEquivalentSize = Decimal(roundedEquivalent)
var numberOfEquivalents = carbEquivalents / carbEquivalentSize

var useDate = createdAt
let fpuID = UUID().uuidString
var futureCarbArray = [CarbsEntry]()
var firstIndex = true

while carbEquivalents > 0, numberOfEquivalents > 0 {
useDate = firstIndex ? useDate.addingTimeInterval(delay.minutes.timeInterval) : useDate
.addingTimeInterval(interval.minutes.timeInterval)
firstIndex = false

let eachCarbEntry = CarbsEntry(
id: UUID().uuidString, createdAt: useDate,
carbs: carbEquivalentSize, fat: 0, protein: 0, note: nil,
enteredBy: CarbsEntry.manual, isFPU: true,
fpuID: fpuID
)
futureCarbArray.append(eachCarbEntry)
numberOfEquivalents -= 1
}

return (futureCarbArray, carbEquivalents)
}

func syncDate() -> Date {
Date().addingTimeInterval(-1.days.timeInterval)
}
Expand Down

0 comments on commit 29eb46e

Please sign in to comment.