From 07d38a5a305ba8647bbf7f268c7d0bd340115675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Wed, 11 Nov 2026 20:03:22 +0100 Subject: [PATCH] Upload therapy profiles as named pump settings schedules --- .../Extensions/StoredSettings.swift | 42 +++++++++++++++---- .../Extensions/StoredSettingsTests.swift | 20 +++++++++ .../TidepoolServiceTests.swift | 4 ++ 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 1ba9b58..7fc216b 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -16,16 +16,18 @@ import TidepoolKit - date Date .time - controllerTimeZone TimeInterval .timeZone, .timeZoneOffset - dosingEnabled Bool TPumpSettingsDatum.automatedDelivery - - glucoseTargetRangeSchedule GlucoseRangeSchedule? TPumpSettingsDatum.bloodGlucoseTargetSchedules["Default"] + - glucoseTargetRangeSchedule GlucoseRangeSchedule? TPumpSettingsDatum.bloodGlucoseTargetSchedules[activeScheduleName] - preMealTargetRange ClosedRange? TPumpSettingsDatum.bloodGlucoseTargetPreprandial - overridePresets [TemporaryScheduleOverridePreset]? TPumpSettingsDatum.overridePresets - maximumBasalRatePerHour Double? TPumpSettingsDatum.basal.rateMaximum.value - maximumBolus Double? TPumpSettingsDatum.bolus.amountMaximum.value - suspendThreshold GlucoseThreshold? TPumpSettingsDatum.bloodGlucoseSafetyLimit - defaultRapidActingModel StoredInsulinModel? TPumpSettingsDatum.insulinModel - - basalRateSchedule BasalRateSchedule? TPumpSettingsDatum.basalRateSchedules["Default"] - - insulinSensitivitySchedule InsulinSensitivitySchedule? TPumpSettingsDatum.insulinSensitivitySchedules["Default"] - - carbRatioSchedule CarbRatioSchedule? TPumpSettingsDatum.carbohydrateRatioSchedules["Default"] + - basalRateSchedule BasalRateSchedule? TPumpSettingsDatum.basalRateSchedules[activeScheduleName] + - insulinSensitivitySchedule InsulinSensitivitySchedule? TPumpSettingsDatum.insulinSensitivitySchedules[activeScheduleName] + - carbRatioSchedule CarbRatioSchedule? TPumpSettingsDatum.carbohydrateRatioSchedules[activeScheduleName] + - therapyProfiles [TherapyProfile] the four schedule dictionaries, keyed by profile name + - activeProfileName String? TPumpSettingsDatum.activeScheduleName ("Default" when nil) - notificationSettings NotificationSettings? TControllerSettingsDatum.notifications - controllerDevice ControllerDevice? TControllerSettingsDatum.device - pumpDevice HKDevice? TPumpSettingsDatum[.firmwareVersion, .hardwareVersion, .manufacturers, .model, .serialNumber, .softwareVersion] @@ -144,7 +146,21 @@ extension StoredSettings: IdentifiableDatum { private var datumCGMUnits: TCGMSettingsDatum.Units { .milligramsPerDeciliter } private var datumPumpActiveScheduleName: String? { - return Self.activeScheduleNameDefault + return TherapyProfiles.activeName(activeProfileName) + } + + /// The profiles other than the active one, whose schedules are the live ones. + private var datumInactiveProfiles: [TherapyProfile] { + let activeName = TherapyProfiles.activeName(activeProfileName) + return resolvedTherapyProfiles.filter { $0.name != activeName } + } + + private func datumSchedules(live: [Value], _ transform: (TherapyProfile) -> [Value]) -> [String: [Value]] { + var schedules = [TherapyProfiles.activeName(activeProfileName): live] + for profile in datumInactiveProfiles { + schedules[profile.name] = transform(profile) + } + return schedules } private var datumPumpBasal: TPumpSettingsDatum.Basal? { @@ -158,7 +174,9 @@ extension StoredSettings: IdentifiableDatum { guard let basalRateSchedule = basalRateSchedule else { return nil } - return [Self.activeScheduleNameDefault: basalRateSchedule.items.map { TPumpSettingsDatum.BasalRateStart(start: $0.startTime, rate: $0.value) }] + return datumSchedules(live: basalRateSchedule.items.map { TPumpSettingsDatum.BasalRateStart(start: $0.startTime, rate: $0.value) }) { profile in + profile.basalRateSchedule.items.map { TPumpSettingsDatum.BasalRateStart(start: $0.startTime, rate: $0.value) } + } } private var datumPumpBloodGlucoseSafetyLimit: Double? { @@ -180,7 +198,9 @@ extension StoredSettings: IdentifiableDatum { guard let glucoseTargetRangeSchedule = glucoseTargetRangeSchedule else { return nil } - return [Self.activeScheduleNameDefault: glucoseTargetRangeSchedule.items(for: .milligramsPerDeciliter).map { TPumpSettingsDatum.BloodGlucoseStartTarget(start: $0.startTime, low: $0.value.minValue, high: $0.value.maxValue) }] + return datumSchedules(live: glucoseTargetRangeSchedule.items(for: .milligramsPerDeciliter).map { TPumpSettingsDatum.BloodGlucoseStartTarget(start: $0.startTime, low: $0.value.minValue, high: $0.value.maxValue) }) { profile in + profile.glucoseTargetRangeSchedule.items(for: .milligramsPerDeciliter).map { TPumpSettingsDatum.BloodGlucoseStartTarget(start: $0.startTime, low: $0.value.minValue, high: $0.value.maxValue) } + } } private var datumPumpBolus: TPumpSettingsDatum.Bolus? { @@ -194,7 +214,9 @@ extension StoredSettings: IdentifiableDatum { guard let carbRatioSchedule = carbRatioSchedule else { return nil } - return [Self.activeScheduleNameDefault: carbRatioSchedule.items(for: .gram).map { TPumpSettingsDatum.CarbohydrateRatioStart(start: $0.startTime, amount: $0.value) }] + return datumSchedules(live: carbRatioSchedule.items(for: .gram).map { TPumpSettingsDatum.CarbohydrateRatioStart(start: $0.startTime, amount: $0.value) }) { profile in + profile.carbRatioSchedule.items(for: .gram).map { TPumpSettingsDatum.CarbohydrateRatioStart(start: $0.startTime, amount: $0.value) } + } } private var datumPumpDisplay: TPumpSettingsDatum.Display? { @@ -250,7 +272,9 @@ extension StoredSettings: IdentifiableDatum { guard let insulinSensitivitySchedule = insulinSensitivitySchedule else { return nil } - return [Self.activeScheduleNameDefault: insulinSensitivitySchedule.items(for: .milligramsPerDeciliter).map { return TPumpSettingsDatum.InsulinSensitivityStart(start: $0.startTime, amount: $0.value) }] + return datumSchedules(live: insulinSensitivitySchedule.items(for: .milligramsPerDeciliter).map { TPumpSettingsDatum.InsulinSensitivityStart(start: $0.startTime, amount: $0.value) }) { profile in + profile.insulinSensitivitySchedule.items(for: .milligramsPerDeciliter).map { TPumpSettingsDatum.InsulinSensitivityStart(start: $0.startTime, amount: $0.value) } + } } private var datumPumpManufacturers: [String]? { pumpDevice?.manufacturer.map { [$0] } } diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index 43e9b3a..5a90311 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -240,6 +240,26 @@ class StoredSettingsTests: XCTestCase { ) } + func testDatumPumpSettingsNamesSchedulesAfterProfiles() { + var settings = StoredSettings.test + let live = settings.liveTherapyProfile! + var sport = live + sport.name = "Sport" + settings.basalRateSchedule = BasalRateSchedule(dailyItems: [RepeatingScheduleValue(startTime: 0, value: 0.5)], timeZone: live.basalRateSchedule.timeZone) + sport.basalRateSchedule = settings.basalRateSchedule! + settings.therapyProfiles = [live, sport] + settings.activeProfileName = "Sport" + + let datum = settings.datumPumpSettings(for: "1234567890", hostIdentifier: "Loop", hostVersion: "1.2.3") + + XCTAssertEqual(datum.activeScheduleName, "Sport") + XCTAssertEqual(datum.basalRateSchedules?["Sport"]?.map(\.rate), [0.5]) + XCTAssertEqual(datum.basalRateSchedules?["Default"]?.count, live.basalRateSchedule.items.count) + XCTAssertEqual(Set(datum.carbohydrateRatioSchedules?.keys ?? [:].keys), ["Default", "Sport"]) + XCTAssertEqual(Set(datum.insulinSensitivitySchedules?.keys ?? [:].keys), ["Default", "Sport"]) + XCTAssertEqual(Set(datum.bloodGlucoseTargetSchedules?.keys ?? [:].keys), ["Default", "Sport"]) + } + private static let encoder: JSONEncoder = { let encoder = JSONEncoder.tidepool encoder.outputFormatting.insert(.prettyPrinted) diff --git a/TidepoolServiceKitTests/TidepoolServiceTests.swift b/TidepoolServiceKitTests/TidepoolServiceTests.swift index 0805df6..409540d 100644 --- a/TidepoolServiceKitTests/TidepoolServiceTests.swift +++ b/TidepoolServiceKitTests/TidepoolServiceTests.swift @@ -296,6 +296,10 @@ private class MockServiceDelegate: ServiceDelegate { func cancelRemoteOverride() async throws {} func deliverRemoteCarbs(amountInGrams: Double, absorptionTime: TimeInterval?, foodType: String?, startDate: Date?) async throws {} func deliverRemoteBolus(amountInUnits: Double, decisionId: UUID?) async throws {} + func switchRemoteTherapyProfile(name: String, expectedFingerprint: String) async throws {} + func saveRemoteTherapyProfile(_ profile: TherapyProfile, originalName: String?, expectedFingerprint: String?) async throws {} + func deleteRemoteTherapyProfile(name: String, expectedFingerprint: String) async throws {} + func saveRemoteScheduledProfileSwitches(_ entries: [ScheduledProfileSwitch], expectedFingerprint: String) async throws {} } fileprivate extension StoredSettings.ControllerDevice {