Forráskód Böngészése

update MainChartView
added:
* boluses
* carbs
* predictions
* basal profile
* scroll

Arif Kobel 2 éve
szülő
commit
2d7299ed8e
1 módosított fájl, 511 hozzáadás és 195 törlés
  1. 511 195
      FreeAPS/Sources/Modules/Home/View/Chart/MainChartView2.swift

+ 511 - 195
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView2.swift

@@ -1,233 +1,315 @@
 import Charts
 import SwiftUI
 
+let screenSize: CGRect = UIScreen.main.bounds
+let calendar = Calendar.current
+
+private struct BasalProfile: Hashable {
+    let amount: Double
+    var isOverwritten: Bool
+    let startDate: Date
+    let endDate: Date?
+    init(amount: Double, isOverwritten: Bool, startDate: Date, endDate: Date? = nil) {
+        self.amount = amount
+        self.isOverwritten = isOverwritten
+        self.startDate = startDate
+        self.endDate = endDate
+    }
+}
+
+private struct Prediction: Hashable {
+    let amount: Int
+    let timestamp: Date
+    let type: PredictionType
+}
+
+private enum PredictionType: Hashable {
+    case iob
+    case cob
+    case zt
+    case uam
+}
+
 struct MainChartView2: View {
-    @Binding var tempBasals: [PumpHistoryEvent]
+    private enum Config {
+        static let bolusSize: CGFloat = 8
+        static let bolusScale: CGFloat = 2.5
+        static let carbsSize: CGFloat = 10
+        static let carbsScale: CGFloat = 0.3
+    }
+
     @Binding var glucose: [BloodGlucose]
-    @Binding var screenHours: Int16
+    @Binding var suggestion: Suggestion?
+    @Binding var tempBasals: [PumpHistoryEvent]
+    @Binding var boluses: [PumpHistoryEvent]
+    @Binding var suspensions: [PumpHistoryEvent]
+    @Binding var announcement: [Announcement]
+    @Binding var hours: Int
+    @Binding var maxBasal: Decimal
+    @Binding var autotunedBasalProfile: [BasalProfileEntry]
+    @Binding var basalProfile: [BasalProfileEntry]
+    @Binding var tempTargets: [TempTarget]
+    @Binding var carbs: [CarbsEntry]
+    @Binding var smooth: Bool
     @Binding var highGlucose: Decimal
     @Binding var lowGlucose: Decimal
-    @Binding var carbs: [CarbsEntry]
-    @Binding var basalProfile: [BasalProfileEntry]
+    @Binding var screenHours: Int16
+    @Binding var displayXgridLines: Bool
+    @Binding var displayYgridLines: Bool
+    @Binding var thresholdLines: Bool
+
+    @State private var BasalProfiles: [BasalProfile] = []
+    @State private var TempBasals: [PumpHistoryEvent] = []
+    @State private var startMarker = Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 - 86400))
+    @State private var endMarker = Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 + 10800))
+
+    private var bolusFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.minimumIntegerDigits = 0
+        formatter.maximumFractionDigits = 2
+        formatter.decimalSeparator = "."
+        return formatter
+    }
+
+    private var carbsFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        return formatter
+    }
 
     var body: some View {
         VStack(alignment: .center, spacing: 8, content: {
-            ZStack {
-                GlucoseChart(glucose: $glucose, screenHours: $screenHours, highGlucose: $highGlucose, lowGlucose: $lowGlucose)
-                VStack {
-                    Spacer()
-                        .frame(height: 280)
-                    CarbsChart(carbs: $carbs, screenHours: $screenHours)
+            ScrollViewReader { scroller in
+                ScrollView(.horizontal, showsIndicators: false) {
+                    VStack {
+                        MainChart()
+                        BasalChart()
+                            .padding(.bottom, 8)
+                    }.onChange(of: screenHours) { _ in
+                        scroller.scrollTo("MainChart", anchor: .trailing)
+                    }.onAppear {
+                        scroller.scrollTo("MainChart", anchor: .trailing)
+                        calculateTempBasals()
+                    }
                 }
             }
-//            GlucoseChart(glucose: $glucose, screenHours: $screenHours, highGlucose: $highGlucose, lowGlucose: $lowGlucose)
-//                .padding(.bottom, 20)
-//            CarbsChart(carbs: $carbs, screenHours: $screenHours)
-//                .padding(.bottom, 8)
-
-            BasalChart(tempBasals: $tempBasals, screenHours: $screenHours)
-                .padding(.bottom, 8)
-
-//            ZStack {
-//                BasalChart(tempBasals: $tempBasals, basalProfile: $basalProfile, screenHours: $screenHours)
-//                BasalProfileChart(basalProfile: $basalProfile)
-//            }
             Legend()
         })
     }
 }
 
-// MARK: GLUCOSE FOR CHART
-
-struct GlucoseChart: View {
-    @Binding var glucose: [BloodGlucose]
-    @Binding var screenHours: Int16
-    @Binding var highGlucose: Decimal
-    @Binding var lowGlucose: Decimal
+// MARK: Components
 
-    var body: some View {
+extension MainChartView2 {
+    private func MainChart() -> some View {
         VStack {
-            let filteredGlucose: [BloodGlucose] = filterGlucoseData(for: screenHours)
-
-            Chart(filteredGlucose) {
-                RuleMark(y: .value("High", highGlucose))
-                    .foregroundStyle(Color.loopYellow)
-                    .lineStyle(StrokeStyle(lineWidth: 1, dash: [5]))
-
-                RuleMark(y: .value("Low", lowGlucose))
-                    .foregroundStyle(Color.loopRed)
-                    .lineStyle(StrokeStyle(lineWidth: 1, dash: [5]))
-
-//                MARK: TO DO -> at the moment this rule mark is not visible because the chart is not scrollable
-
-                if let currentTime = getCurrentTime() {
-                    RuleMark(x: .value("Current Time", currentTime))
-                        .foregroundStyle(Color.gray)
-                        .lineStyle(StrokeStyle(lineWidth: 1, dash: [5]))
+            Chart {
+                if thresholdLines {
+                    RuleMark(y: .value("High", highGlucose)).foregroundStyle(Color.red)
+                        .lineStyle(.init(lineWidth: 1, dash: [2]))
+                    RuleMark(y: .value("Low", lowGlucose)).foregroundStyle(Color.red)
+                        .lineStyle(.init(lineWidth: 1, dash: [2]))
                 }
-
-                PointMark(
-                    x: .value("Time", $0.dateString),
-                    y: .value("Value", $0.value)
-                )
-                .foregroundStyle(
-                    $0.value > Double(highGlucose) ? Color.yellow.gradient :
-                        $0.value < Double(lowGlucose) ? Color.red.gradient : Color.green.gradient
+                RuleMark(
+                    x: .value(
+                        "",
+                        startMarker,
+                        unit: .second
+                    )
+                ).foregroundStyle(.clear)
+                RuleMark(
+                    x: .value(
+                        "",
+                        Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
+                        unit: .second
+                    )
+                ).lineStyle(.init(lineWidth: 1, dash: [2]))
+                RuleMark(
+                    x: .value(
+                        "",
+                        endMarker,
+                        unit: .second
+                    )
+                ).foregroundStyle(.clear)
+                ForEach(carbs) { carb in
+                    let glucose = timeToNearestGlucose(time: carb.createdAt.timeIntervalSince1970)
+                    let carbAmount = carb.carbs
+                    PointMark(
+                        x: .value("Time", carb.createdAt, unit: .second),
+                        y: .value("Value", glucose.sgv ?? 120)
+                    )
+                    .symbolSize((Config.carbsSize + CGFloat(carb.carbs) * Config.carbsScale) * 10)
+                    .foregroundStyle(Color.orange)
+                    .annotation(position: .top) {
+                        Text(bolusFormatter.string(from: carbAmount as NSNumber)!).font(.caption2)
+                    }
+                }
+                ForEach(boluses) { bolus in
+                    let glucose = timeToNearestGlucose(time: bolus.timestamp.timeIntervalSince1970)
+                    let bolusAmount = bolus.amount ?? 0
+                    PointMark(
+                        x: .value("Time", bolus.timestamp, unit: .second),
+                        y: .value("Value", glucose.sgv ?? 120)
+                    )
+                    .symbolSize((Config.bolusSize + CGFloat(bolus.amount ?? 0) * Config.bolusScale) * 10)
+                    .foregroundStyle(Color.insulin)
+                    .annotation(position: .bottom) {
+                        Text(bolusFormatter.string(from: bolusAmount as NSNumber)!).font(.caption2)
+                    }
+                }
+                ForEach(calculatePredictions(), id: \.self) { info in
+                    if info.type == .uam, info.timestamp.timeIntervalSince1970 < endMarker.timeIntervalSince1970 {
+                        LineMark(
+                            x: .value("Time", info.timestamp, unit: .second),
+                            y: .value("Value", info.amount),
+                            series: .value("uam", "uam")
+                        ).foregroundStyle(Color.uam).symbolSize(16)
+                    }
+                    if info.type == .cob {
+                        LineMark(
+                            x: .value("Time", info.timestamp, unit: .second),
+                            y: .value("Value", info.amount),
+                            series: .value("cob", "cob")
+                        ).foregroundStyle(Color.orange).symbolSize(16)
+                    }
+                    if info.type == .iob {
+                        LineMark(
+                            x: .value("Time", info.timestamp, unit: .second),
+                            y: .value("Value", info.amount),
+                            series: .value("iob", "iob")
+                        ).foregroundStyle(Color.insulin).symbolSize(16)
+                    }
+                    if info.type == .zt {
+                        LineMark(
+                            x: .value("Time", info.timestamp, unit: .second),
+                            y: .value("Value", info.amount),
+                            series: .value("zt", "zt")
+                        ).foregroundStyle(Color.zt).symbolSize(16)
+                    }
+                }
+                ForEach(glucose) {
+                    if $0.sgv != nil {
+                        PointMark(
+                            x: .value("Time", $0.dateString, unit: .second),
+                            y: .value("Value", $0.sgv!)
+                        ).foregroundStyle(Color.green).symbolSize(16)
+                        if smooth {
+                            LineMark(
+                                x: .value("Time", $0.dateString, unit: .second),
+                                y: .value("Value", $0.sgv!),
+                                series: .value("glucose", "glucose")
+                            ).foregroundStyle(Color.green)
+                        }
+                    }
+                }
+            }.id("MainChart")
+                .frame(
+                    width: max(0, screenSize.width - 20, fullWidth(viewWidth: screenSize.width)),
+                    height: min(screenSize.height, 150)
                 )
-                .symbolSize(12)
-            }
-            .frame(height: 250)
-            .chartXAxis(.hidden)
-        }
-    }
-
-    private func filterGlucoseData(for hours: Int16) -> [BloodGlucose] {
-        guard hours > 0 else {
-            return glucose
+                .chartYScale(domain: 0 ... 450)
+                .chartXAxis {
+                    AxisMarks(values: .stride(by: .hour, count: 2)) { _ in
+                        if displayXgridLines {
+                            AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+                        } else {
+                            AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+                        }
+                    }
+                }.chartYAxis {
+                    AxisMarks(position: .trailing, values: .stride(by: 100)) { value in
+                        if displayYgridLines {
+                            AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+                        } else {
+                            AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+                        }
+                        if let glucoseValue = value.as(Double.self), glucoseValue > 0 {
+                            AxisTick(length: 4, stroke: .init(lineWidth: 4))
+                                .foregroundStyle(Color.gray)
+                            AxisValueLabel()
+                        }
+                    }
+                }
         }
-
-        let currentDate = Date()
-        let startDate = Calendar.current.date(byAdding: .hour, value: -Int(hours), to: currentDate) ?? currentDate
-
-        return glucose.filter { $0.dateString >= startDate }
     }
 
-    private func getCurrentTime() -> String? {
-        let dateFormatter = DateFormatter()
-        dateFormatter.dateFormat = "HH:mm"
-        return dateFormatter.string(from: Date())
-    }
-}
-
-// MARK: BASAL FOR CHART
-
-struct BasalChart: View {
-    @Binding var tempBasals: [PumpHistoryEvent]
-    @Binding var screenHours: Int16
-
-    var body: some View {
+    func BasalChart() -> some View {
         VStack {
-            let filteredBasal: [PumpHistoryEvent] = filterBasalData(for: screenHours)
-
-            Chart(filteredBasal) {
-                BarMark(
-                    x: .value("Time", $0.timestamp),
-                    y: .value("Value", $0.rate ?? 0)
-                )
-                .foregroundStyle(Color.blue.gradient)
-                .cornerRadius(0)
+            Chart {
+                RuleMark(
+                    x: .value(
+                        "",
+                        startMarker,
+                        unit: .second
+                    )
+                ).foregroundStyle(.clear)
+                RuleMark(
+                    x: .value(
+                        "",
+                        Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
+                        unit: .second
+                    )
+                ).lineStyle(.init(lineWidth: 1, dash: [2]))
+                RuleMark(
+                    x: .value(
+                        "",
+                        endMarker,
+                        unit: .second
+                    )
+                ).foregroundStyle(.clear)
+                ForEach(calculateTempBasals()) {
+                    BarMark(
+                        x: .value("Time", $0.timestamp),
+                        y: .value("Rate", $0.rate ?? 0)
+                    )
+                }
+                ForEach(BasalProfiles, id: \.self) { profile in
+                    LineMark(
+                        x: .value("Start Date", profile.startDate),
+                        y: .value("Amount", profile.amount),
+                        series: .value("profile", "profile")
+                    ).lineStyle(.init(lineWidth: 2, dash: [2, 3]))
+                    LineMark(
+                        x: .value("End Date", profile.endDate ?? endMarker),
+                        y: .value("Amount", profile.amount),
+                        series: .value("profile", "profile")
+                    ).lineStyle(.init(lineWidth: 2, dash: [2, 3]))
+                }
             }
             .frame(height: 80)
+            .chartYScale(domain: 0 ... maxBasal)
             //            .rotationEffect(.degrees(180))
             //            .chartXAxis(.hidden)
-            .chartYAxis(.hidden)
-            .chartPlotStyle { plotArea in
-                plotArea.background(.blue.gradient.opacity(0.1))
-            }
-        }
-    }
-
-    private func filterBasalData(for hours: Int16) -> [PumpHistoryEvent] {
-        guard hours > 0 else {
-            return tempBasals
-        }
-
-        let currentDate = Date()
-        let startDate = Calendar.current.date(byAdding: .hour, value: -Int(hours), to: currentDate) ?? currentDate
-
-        return tempBasals.filter { $0.timestamp >= startDate }
-    }
-}
-
-struct BasalProfileChart: View {
-    @Binding var basalProfile: [BasalProfileEntry]
-    @Binding var screenHours: Int16
-
-    var body: some View {
-        let filteredBasalProfile: [BasalProfileEntry] = filterBasalProfileData(for: screenHours)
-
-        VStack {
-//            MARK: DOES NOT WORK
-
-//            filtering function seems not to work...displays nothing
-
-            Chart(filteredBasalProfile) {
-                LineMark(
-                    x: .value("start", $0.minutes),
-                    y: .value("rate", $0.rate)
-                ).foregroundStyle(Color.blue.gradient)
-            }
-        }
-    }
-
-    private func filterBasalProfileData(for hours: Int16) -> [BasalProfileEntry] {
-        guard hours > 0 else {
-            return basalProfile
-        }
-
-        let currentDate = Date()
-        let startDate = Calendar.current.date(byAdding: .hour, value: -Int(hours), to: currentDate) ?? currentDate
-
-        return basalProfile.filter { entry in
-            if let entryDate = dateFormatter.date(from: entry.start) {
-                return entryDate >= startDate
-            } else {
-                return false
+            .chartXAxis {
+                AxisMarks(values: .stride(by: .hour, count: screenHours == 24 ? 4 : 2)) { _ in
+                    if displayXgridLines {
+                        AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+                    } else {
+                        AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+                    }
+                    AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .narrow)), anchor: .top)
+                }
+            }.chartYAxis {
+                AxisMarks(position: .trailing, values: .stride(by: 1)) { _ in
+                    if displayYgridLines {
+                        AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+                    } else {
+                        AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+                    }
+                    AxisTick(length: 30, stroke: .init(lineWidth: 4))
+                        .foregroundStyle(Color.clear)
+                }
             }
-        }
-    }
-
-    private let dateFormatter: DateFormatter = {
-        let formatter = DateFormatter()
-        formatter.dateFormat = "HH:mm:ss"
-        return formatter
-    }()
-}
-
-// MARK: COB
-
-struct CarbsChart: View {
-    @Binding var carbs: [CarbsEntry]
-    @Binding var screenHours: Int16
-    static var triangle: BasicChartSymbolShape { .triangle }
 
-    var body: some View {
-        VStack {
-//            MARK: DOES NOT WORK PROPERLY
-//            scaling is not correct because of swift charts automatic scaling...
-
-            let filteredCarbs: [CarbsEntry] = filterCarbData(for: screenHours)
-            Chart(filteredCarbs) {
-                PointMark(
-                    x: .value("Time", $0.createdAt),
-                    y: .value("Value", 10)
-                )
-                .foregroundStyle(Color.loopYellow.gradient)
-                .symbolSize(50)
-                .symbol(CarbsChart.triangle)
+            .chartPlotStyle { plotArea in
+                plotArea.background(.blue.gradient.opacity(0.1))
             }
-            .frame(height: 80)
-            .chartYAxis(.hidden)
-            .chartXAxis(.hidden)
         }
     }
 
-    private func filterCarbData(for hours: Int16) -> [CarbsEntry] {
-        guard hours > 0 else {
-            return carbs
-        }
-
-        let currentDate = Date()
-        let startDate = Calendar.current.date(byAdding: .hour, value: -Int(hours), to: currentDate) ?? currentDate
-
-        return carbs.filter { $0.createdAt >= startDate }
-    }
-}
-
-// MARK: LEGEND PANEL FOR CHART
-
-struct Legend: View {
-    var body: some View {
+    private func Legend() -> some View {
         HStack {
             Image(systemName: "line.diagonal")
                 .rotationEffect(Angle(degrees: 45))
@@ -259,9 +341,243 @@ struct Legend: View {
                 .foregroundColor(.orange)
             Text("UAM")
                 .foregroundColor(.secondary)
+            if suggestion?.predictions?.cob?.last != nil {
+                Text("⇢ " + String(suggestion?.predictions?.cob?.last ?? 0))
+            } else if suggestion?.predictions?.uam?.last != nil {
+                Text("⇢ " + String(suggestion?.predictions?.uam?.last ?? 0))
+            }
         }
         .font(.caption2)
         .padding(.horizontal, 40)
         .padding(.vertical, 1)
+        .onChange(of: basalProfile) { _ in
+            calculcateBasals()
+        }
+        .onChange(of: tempBasals) { _ in
+            calculateTempBasals()
+        }
+    }
+}
+
+// MARK: Calculations
+
+extension MainChartView2 {
+    private func timeToNearestGlucose(time: TimeInterval) -> BloodGlucose {
+        var nextIndex = 0
+        if glucose.last?.dateString.timeIntervalSince1970 ?? Date().timeIntervalSince1970 < time {
+            return glucose.last ?? BloodGlucose(
+                date: 0,
+                dateString: Date(),
+                unfiltered: nil,
+                filtered: nil,
+                noise: nil,
+                type: nil
+            )
+        }
+        for (index, value) in glucose.enumerated() {
+            if value.dateString.timeIntervalSince1970 > time {
+                nextIndex = index
+                print("Break", value.dateString.timeIntervalSince1970, time)
+                break
+            }
+            print("Glucose", value.dateString.timeIntervalSince1970, time)
+        }
+        return glucose[nextIndex]
+    }
+
+    private func fullWidth(viewWidth: CGFloat) -> CGFloat {
+        viewWidth * CGFloat(hours) / CGFloat(min(max(screenHours, 2), 24))
+    }
+
+    private func calculatePredictions() -> [Prediction] {
+        var calculatedPredictions: [Prediction] = []
+        let uam = suggestion?.predictions?.uam ?? []
+        let iob = suggestion?.predictions?.iob ?? []
+        let cob = suggestion?.predictions?.cob ?? []
+        let zt = suggestion?.predictions?.zt ?? []
+        guard let deliveredAt = suggestion?.deliverAt else {
+            return []
+        }
+        uam.indices.forEach { index in
+            let predTime = Date(
+                timeIntervalSince1970: deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes
+                    .timeInterval
+            )
+            if predTime.timeIntervalSince1970 < endMarker.timeIntervalSince1970 {
+                calculatedPredictions.append(
+                    Prediction(amount: uam[index], timestamp: predTime, type: .uam)
+                )
+            }
+            print(
+                "Vergleich",
+                index,
+                predTime.timeIntervalSince1970,
+                endMarker.timeIntervalSince1970,
+                predTime.timeIntervalSince1970 < endMarker.timeIntervalSince1970
+            )
+        }
+        iob.indices.forEach { index in
+            let predTime = Date(
+                timeIntervalSince1970: deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes
+                    .timeInterval
+            )
+            if predTime.timeIntervalSince1970 < endMarker.timeIntervalSince1970 {
+                calculatedPredictions.append(
+                    Prediction(amount: iob[index], timestamp: predTime, type: .iob)
+                )
+            }
+        }
+        cob.indices.forEach { index in
+            let predTime = Date(
+                timeIntervalSince1970: deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes
+                    .timeInterval
+            )
+            if predTime.timeIntervalSince1970 < endMarker.timeIntervalSince1970 {
+                calculatedPredictions.append(
+                    Prediction(amount: cob[index], timestamp: predTime, type: .cob)
+                )
+            }
+        }
+        zt.indices.forEach { index in
+            let predTime = Date(
+                timeIntervalSince1970: deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes
+                    .timeInterval
+            )
+            if predTime.timeIntervalSince1970 < endMarker.timeIntervalSince1970 {
+                calculatedPredictions.append(
+                    Prediction(amount: zt[index], timestamp: predTime, type: .zt)
+                )
+            }
+        }
+
+        return calculatedPredictions
+    }
+
+    private func getLastUam() -> Int {
+        let uam = suggestion?.predictions?.uam ?? []
+        return uam.last ?? 0
+    }
+
+    private func calculateTempBasals() -> [PumpHistoryEvent] {
+        var basals = tempBasals
+        var returnTempBasalRates: [PumpHistoryEvent] = []
+        var finished: [Int: Bool] = [:]
+        basals.indices.forEach { i in
+            basals.indices.forEach { j in
+                if basals[i].timestamp == basals[j].timestamp, i != j, !(finished[i] ?? false), !(finished[j] ?? false) {
+                    let rate = basals[i].rate ?? basals[j].rate
+                    let durationMin = basals[i].durationMin ?? basals[j].durationMin
+                    finished[i] = true
+                    if rate != 0 || durationMin != 0 {
+                        returnTempBasalRates.append(
+                            PumpHistoryEvent(
+                                id: basals[i].id, type: FreeAPS.EventType.tempBasal,
+                                timestamp: basals[i].timestamp,
+                                durationMin: durationMin,
+                                rate: rate
+                            )
+                        )
+                    }
+                }
+            }
+        }
+        print("Temp Basals", returnTempBasalRates)
+        return returnTempBasalRates
+    }
+
+    private func findRegularBasalPoints(
+        timeBegin: TimeInterval,
+        timeEnd: TimeInterval,
+        autotuned: Bool
+    ) -> [BasalProfile] {
+        guard timeBegin < timeEnd else {
+            return []
+        }
+        let beginDate = Date(timeIntervalSince1970: timeBegin)
+        let calendar = Calendar.current
+        let startOfDay = calendar.startOfDay(for: beginDate)
+
+        let profile = autotuned ? autotunedBasalProfile : basalProfile
+
+        let basalNormalized = profile.map {
+            (
+                time: startOfDay.addingTimeInterval($0.minutes.minutes.timeInterval).timeIntervalSince1970,
+                rate: $0.rate
+            )
+        } + profile.map {
+            (
+                time: startOfDay.addingTimeInterval($0.minutes.minutes.timeInterval + 1.days.timeInterval)
+                    .timeIntervalSince1970,
+                rate: $0.rate
+            )
+        } + profile.map {
+            (
+                time: startOfDay.addingTimeInterval($0.minutes.minutes.timeInterval + 2.days.timeInterval)
+                    .timeIntervalSince1970,
+                rate: $0.rate
+            )
+        }
+
+        let basalTruncatedPoints = basalNormalized.windows(ofCount: 2)
+            .compactMap { window -> BasalProfile? in
+                let window = Array(window)
+                if window[0].time < timeBegin, window[1].time < timeBegin {
+                    return nil
+                }
+
+                if window[0].time < timeBegin, window[1].time >= timeBegin {
+                    let startDate = Date(timeIntervalSince1970: timeBegin)
+                    let rate = window[0].rate
+                    return BasalProfile(amount: Double(rate), isOverwritten: false, startDate: startDate)
+                }
+
+                if window[0].time >= timeBegin, window[0].time < timeEnd {
+                    let startDate = Date(timeIntervalSince1970: window[0].time)
+                    let rate = window[0].rate
+                    return BasalProfile(amount: Double(rate), isOverwritten: false, startDate: startDate)
+                }
+
+                return nil
+            }
+
+        return basalTruncatedPoints
+    }
+
+    private func calculcateBasals() {
+        let dayAgoTime = Date().addingTimeInterval(-1.days.timeInterval).timeIntervalSince1970
+        let firstTempTime = (tempBasals.first?.timestamp ?? Date()).timeIntervalSince1970
+
+        let regularPoints = findRegularBasalPoints(
+            timeBegin: dayAgoTime,
+            timeEnd: endMarker.timeIntervalSince1970,
+            autotuned: false
+        )
+
+        let autotunedBasalPoints = findRegularBasalPoints(
+            timeBegin: dayAgoTime,
+            timeEnd: endMarker.timeIntervalSince1970,
+            autotuned: true
+        )
+        var totalBasal = regularPoints + autotunedBasalPoints
+        totalBasal.sort {
+            $0.startDate.timeIntervalSince1970 < $1.startDate.timeIntervalSince1970
+        }
+        var basals: [BasalProfile] = []
+        totalBasal.indices.forEach { index in
+            basals.append(BasalProfile(
+                amount: totalBasal[index].amount,
+                isOverwritten: totalBasal[index].isOverwritten,
+                startDate: totalBasal[index].startDate,
+                endDate: totalBasal.count > index + 1 ? totalBasal[index + 1].startDate : endMarker
+            ))
+            print(
+                "Basal",
+                totalBasal[index].startDate,
+                totalBasal.count > index + 1 ? totalBasal[index + 1].startDate : endMarker,
+                totalBasal[index].amount,
+                totalBasal[index].isOverwritten
+            )
+        }
+        BasalProfiles = basals
     }
 }