Apple Fitness workout fixer + Strava uploader

Generate 200 sample workouts spanning ~12 months for testing

- Replace 4 hardcoded samples with 200 workouts across 10 activity types
- Add deleteAllWorkouts() to reset sample data cleanly
- Auto-insert samples on launch when fewer than 200 exist

+34 -49
+31 -47
WorkoutEditor/HealthKitManager.swift
··· 177 177 178 178 #if DEBUG 179 179 func insertSampleWorkouts() async throws { 180 - let samples: [(HKWorkoutActivityType, TimeInterval, TimeInterval)] = [ 181 - (.running, -3600 * 25, 60 * 60), // yesterday, 30 min active + 30 min forgot-to-stop 182 - (.cycling, -3600 * 50, 90 * 60), 183 - (.swimming, -3600 * 74, 30 * 60), 184 - (.hiking, -3600 * 100, 120 * 60), 180 + let activityTypes: [HKWorkoutActivityType] = [ 181 + .running, .cycling, .swimming, .hiking, .walking, 182 + .yoga, .functionalStrengthTraining, .coreTraining, 183 + .elliptical, .rowing, 184 + ] 185 + let durations: [TimeInterval] = [ 186 + 20 * 60, 30 * 60, 45 * 60, 60 * 60, 75 * 60, 90 * 60, 120 * 60, 185 187 ] 186 188 187 - for (type, startOffset, duration) in samples { 188 - let start = Date(timeIntervalSinceNow: startOffset) 189 + // Generate 200 workouts spread over the past ~12 months 190 + let now = Date() 191 + let calendar = Calendar.current 192 + for i in 0..<200 { 193 + let daysAgo = Int(Double(i) * 1.8) + Int.random(in: 0...1) 194 + let hour = [6, 7, 8, 12, 17, 18, 19][i % 7] 195 + guard let day = calendar.date(byAdding: .day, value: -daysAgo, to: now), 196 + let start = calendar.date(bySettingHour: hour, minute: Int.random(in: 0...59), second: 0, of: day) 197 + else { continue } 198 + 199 + let activityType = activityTypes[i % activityTypes.count] 200 + let duration = durations[i % durations.count] 189 201 let end = start.addingTimeInterval(duration) 190 202 191 203 let configuration = HKWorkoutConfiguration() 192 - configuration.activityType = type 204 + configuration.activityType = activityType 193 205 194 206 let builder = HKWorkoutBuilder(healthStore: healthStore, configuration: configuration, device: nil) 195 207 try await builder.beginCollection(at: start) 196 - 197 - // Generate realistic heart rate samples with a drop-off at the end 198 - let hrType = HKQuantityType(.heartRate) 199 - let bpmUnit = HKUnit.count().unitDivided(by: .minute()) 200 - let sampleInterval: TimeInterval = 5 // every 5 seconds 201 - let totalSamples = Int(duration / sampleInterval) 202 - var hrSamples: [HKQuantitySample] = [] 203 - 204 - for i in 0..<totalSamples { 205 - let elapsed = Double(i) / Double(totalSamples) 206 - let sampleDate = start.addingTimeInterval(Double(i) * sampleInterval) 207 - 208 - let bpm: Double 209 - if elapsed < 0.05 { 210 - // Ramp up 211 - bpm = 70 + (elapsed / 0.05) * 80 212 - } else if elapsed < 0.47 { 213 - // Sustained effort (~28 min of a 60 min workout, or ~70% of shorter ones) 214 - bpm = 145 + Double.random(in: -15...15) 215 - } else if elapsed < 0.5 { 216 - // Sharp drop-off 217 - let dropProgress = (elapsed - 0.47) / 0.03 218 - bpm = 145 - dropProgress * 80 219 - } else { 220 - // Resting / idle tail (forgot to stop — whole second half) 221 - bpm = 62 + Double.random(in: -5...8) 222 - } 223 - 224 - let quantity = HKQuantity(unit: bpmUnit, doubleValue: max(bpm, 50)) 225 - let sample = HKQuantitySample( 226 - type: hrType, 227 - quantity: quantity, 228 - start: sampleDate, 229 - end: sampleDate.addingTimeInterval(sampleInterval) 230 - ) 231 - hrSamples.append(sample) 232 - } 233 - 234 - try await builder.addSamples(hrSamples) 235 208 try await builder.endCollection(at: end) 236 209 try await builder.finishWorkout() 210 + } 211 + } 212 + 213 + func deleteAllWorkouts() async throws { 214 + let descriptor = HKSampleQueryDescriptor( 215 + predicates: [.workout()], 216 + sortDescriptors: [SortDescriptor(\HKWorkout.startDate, order: .reverse)] 217 + ) 218 + let all = try await descriptor.result(for: healthStore) 219 + for workout in all { 220 + try await healthStore.delete(workout) 237 221 } 238 222 } 239 223 #endif
+3 -2
WorkoutEditor/WorkoutEditorApp.swift
··· 15 15 await HealthKitManager.shared.loadWorkouts() 16 16 logger.notice("Loaded \(HealthKitManager.shared.loadedWorkouts.count) workouts") 17 17 #if DEBUG 18 - if HealthKitManager.shared.loadedWorkouts.isEmpty { 19 - logger.notice("No workouts found, inserting samples...") 18 + if HealthKitManager.shared.loadedWorkouts.count < 200 { 19 + logger.notice("Fewer than 200 workouts, resetting sample data...") 20 + try await HealthKitManager.shared.deleteAllWorkouts() 20 21 try await HealthKitManager.shared.insertSampleWorkouts() 21 22 await HealthKitManager.shared.loadWorkouts() 22 23 logger.notice("After insert: \(HealthKitManager.shared.loadedWorkouts.count) workouts")