CarbImpactParams.swift 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import Foundation
  2. struct CarbImpactParams {
  3. let cappedCarbImpact: Decimal
  4. let carbImpactDuration: Decimal
  5. let maxAbsorptionIntervals: Int
  6. let triangleIntervals: Int
  7. let remainingCarbImpactPeak: Decimal
  8. let remainingCarbAbsorptionTime: Decimal
  9. static func calculate(
  10. carbSensitivityFactor: Decimal,
  11. profile: Profile,
  12. mealData: ComputedCarbs,
  13. carbImpact: Decimal,
  14. sensitivityRatio: Decimal,
  15. currentTime: Date
  16. ) -> CarbImpactParams {
  17. let maxCarbAbsorptionRate: Decimal = 30 // g/h
  18. let maxCarbImpact = (maxCarbAbsorptionRate * carbSensitivityFactor * 5 / 60).jsRounded(scale: 1)
  19. let cappedCarbImpact = min(carbImpact, maxCarbImpact)
  20. let remainingCarbAbsorptionTime = ForecastGenerator.calculateRemainingCarbAbsorptionTime(
  21. sensitivityRatio: sensitivityRatio,
  22. maxMealAbsorptionTime: profile.maxMealAbsorptionTime,
  23. mealCOB: mealData.mealCOB,
  24. lastCarbTime: Date(timeIntervalSince1970: mealData.lastCarbTime / 1000),
  25. currentTime: currentTime
  26. )
  27. let carbImpactDuration: Decimal
  28. if carbImpact == 0 {
  29. carbImpactDuration = 0
  30. } else {
  31. // cid = Math.min(remainingCATime*60/5/2,Math.max(0, meal_data.mealCOB * csf / ci ));
  32. carbImpactDuration = min(
  33. remainingCarbAbsorptionTime * 60 / 5 / 2,
  34. max(0, mealData.mealCOB * carbSensitivityFactor / carbImpact)
  35. )
  36. }
  37. // Convert remainingCarbAbsorptionTime (hours) to intervals (each 5m):
  38. let dynamicAbsorptionIntervals = Int((remainingCarbAbsorptionTime * 60) / 5)
  39. // Number of 5-minute intervals over which we expect *all* carbs to absorb
  40. let maxAbsorptionIntervals = Int(profile.maxMealAbsorptionTime * Decimal(60) / 5)
  41. // Use smaller of both computed intervals, the dynamic and the max-clamped one as the actual # of decay triangle interval
  42. let triangleIntervals = min(dynamicAbsorptionIntervals, maxAbsorptionIntervals)
  43. // Total CI (mg/dL)
  44. let totalCarbImpact = max(0, cappedCarbImpact / 5 * 60 * remainingCarbAbsorptionTime / 2)
  45. // Total carbs absorbed from CI (g)
  46. let totalCarbsAbsorbed: Decimal = totalCarbImpact / carbSensitivityFactor
  47. // Remaining carbs cap/fraction logic
  48. let remainingCarbsCap = min(90, profile.remainingCarbsCap)
  49. let remainingCarbsFraction = min(1, profile.remainingCarbsFraction)
  50. let remainingCarbsIgnore = 1 - remainingCarbsFraction
  51. var remainingCarbs = max(0, mealData.mealCOB - totalCarbsAbsorbed - mealData.carbs * remainingCarbsIgnore)
  52. remainingCarbs = min(remainingCarbsCap, remainingCarbs)
  53. // /\ triangle for remaining carbs
  54. // Peak impact (mg/dL per 5m) of the *remaining* carbs
  55. let remainingCarbImpactPeak: Decimal
  56. if remainingCarbAbsorptionTime > 0 {
  57. remainingCarbImpactPeak = (remainingCarbs * carbSensitivityFactor * 5 / 60) / (remainingCarbAbsorptionTime / 2)
  58. } else {
  59. remainingCarbImpactPeak = 0
  60. }
  61. return CarbImpactParams(
  62. cappedCarbImpact: cappedCarbImpact,
  63. carbImpactDuration: carbImpactDuration,
  64. maxAbsorptionIntervals: maxAbsorptionIntervals,
  65. triangleIntervals: triangleIntervals,
  66. remainingCarbImpactPeak: remainingCarbImpactPeak,
  67. remainingCarbAbsorptionTime: remainingCarbAbsorptionTime
  68. )
  69. }
  70. }