- Implemented AITrainingPlanGenerator.swift for FRE-5133 - Added personalized workout plan generation based on user profile - Implemented fitness level analysis and progress tracking - Added goal-based recommendations and injury risk assessment - Rate limiting: 3 requests per 5 minutes - Disposition: Done - ready for Code Reviewer
356 lines
12 KiB
Swift
356 lines
12 KiB
Swift
// AITrainingPlanGenerator.swift
|
|
// AI-powered personalized training plan generation for Nessa
|
|
|
|
import Foundation
|
|
import Combine
|
|
|
|
/// AI Training Plan Generator
|
|
/// Generates personalized workout plans based on user profile, fitness level,
|
|
/// workout history, and goals
|
|
@MainActor
|
|
class AITrainingPlanGenerator {
|
|
|
|
// MARK: - Properties
|
|
|
|
private let userService: UserServiceProtocol
|
|
private let workoutService: WorkoutServiceProtocol
|
|
private let analyticsService: AnalyticsServiceProtocol
|
|
private let rateLimiter = RateLimiter(maxRequests: 3, interval: 300) // 3 requests per 5 minutes
|
|
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
|
// MARK: - Initialization
|
|
|
|
init(userService: UserServiceProtocol = UserService.shared,
|
|
workoutService: WorkoutServiceProtocol = WorkoutService.shared,
|
|
analyticsService: AnalyticsServiceProtocol = AnalyticsService.shared) {
|
|
self.userService = userService
|
|
self.workoutService = workoutService
|
|
self.analyticsService = analyticsService
|
|
}
|
|
|
|
// MARK: - Public Methods
|
|
|
|
/// Generate a personalized training plan for the user
|
|
/// - Parameter goals: User's fitness goals (strength, endurance, weight loss, etc.)
|
|
/// - Parameter duration: Preferred weekly training duration in hours
|
|
/// - Parameter difficulty: Preferred difficulty level
|
|
/// - Returns: Generated training plan
|
|
func generatePlan(goals: [GoalType] = [],
|
|
duration: Double = 5.0,
|
|
difficulty: DifficultyLevel = .moderate) async throws -> TrainingPlan {
|
|
|
|
guard rateLimiter.isAllowed() else {
|
|
throw RateLimitError.rateLimited
|
|
}
|
|
|
|
let userProfile = await analyzeUserProfile()
|
|
let progressData = await analyzeProgress()
|
|
let recommendations = await generateRecommendations(
|
|
profile: userProfile,
|
|
goals: goals,
|
|
currentLevel: userProfile.fitnessLevel,
|
|
progress: progressData
|
|
)
|
|
|
|
return TrainingPlan(
|
|
id: UUID().uuidString,
|
|
userProfile: userProfile,
|
|
recommendations: recommendations,
|
|
duration: duration,
|
|
difficulty: difficulty,
|
|
generatedAt: Date(),
|
|
version: 1
|
|
)
|
|
}
|
|
|
|
/// Analyze user profile and determine fitness level
|
|
func analyzeUserProfile() async throws -> UserProfile {
|
|
guard let user = await userService.currentUser else {
|
|
throw ProfileAnalysisError.noUserProfile
|
|
}
|
|
|
|
let workoutHistory = await workoutService.getUserWorkoutHistory(limit: 50)
|
|
let performanceMetrics = await analyticsService.calculatePerformanceMetrics()
|
|
|
|
// Determine fitness level based on workout frequency, intensity, and consistency
|
|
let workoutFrequency = Double(workoutHistory.count) / 30.0 // workouts per month
|
|
let avgIntensity = workoutHistory.map { $0.intensity }.average ?? 0.5
|
|
let consistencyScore = calculateConsistency(workoutHistory)
|
|
|
|
let fitnessLevel: FitnessLevel
|
|
if workoutFrequency >= 4 && avgIntensity >= 0.7 && consistencyScore >= 0.8 {
|
|
fitnessLevel = .advanced
|
|
} else if workoutFrequency >= 2 && avgIntensity >= 0.5 && consistencyScore >= 0.6 {
|
|
fitnessLevel = .intermediate
|
|
} else if workoutFrequency >= 1 && avgIntensity >= 0.3 && consistencyScore >= 0.4 {
|
|
fitnessLevel = .beginner
|
|
} else {
|
|
fitnessLevel = .absoluteBeginner
|
|
}
|
|
|
|
return UserProfile(
|
|
userId: user.id,
|
|
name: user.name,
|
|
fitnessLevel: fitnessLevel,
|
|
workoutFrequency: workoutFrequency,
|
|
avgIntensity: avgIntensity,
|
|
consistencyScore: consistencyScore,
|
|
preferences: user.preferences ?? [],
|
|
injuries: user.injuries ?? []
|
|
)
|
|
}
|
|
|
|
/// Analyze user's progress and trends
|
|
func analyzeProgress() async throws -> ProgressAnalysis {
|
|
let recentWorkouts = await workoutService.getRecentWorkouts(limit: 20)
|
|
let performanceTrends = await analyticsService.calculateTrends(recentWorkouts: recentWorkouts)
|
|
|
|
return ProgressAnalysis(
|
|
totalWorkouts: recentWorkouts.count,
|
|
avgDuration: recentWorkouts.map { $0.duration }.average ?? 0,
|
|
avgCalories: recentWorkouts.map { $0.caloriesBurned }.average ?? 0,
|
|
improvementRate: performanceTrends.improvementRate,
|
|
consistencyStreak: performanceTrends.streak,
|
|
plateauDetected: performanceTrends.plateauDetected,
|
|
injuryRisk: performanceTrends.injuryRisk
|
|
)
|
|
}
|
|
|
|
/// Generate personalized recommendations
|
|
func generateRecommendations(profile: UserProfile,
|
|
goals: [GoalType],
|
|
currentLevel: FitnessLevel,
|
|
progress: ProgressAnalysis) async throws -> [Recommendation] {
|
|
|
|
var recommendations: [Recommendation] = []
|
|
|
|
// Goal-based recommendations
|
|
for goal in goals {
|
|
switch goal {
|
|
case .strength:
|
|
recommendations.append(Recommendation(
|
|
type: .workout,
|
|
title: "Strength Training Focus",
|
|
description: "Incorporate 2-3 strength sessions per week with progressive overload",
|
|
priority: .high,
|
|
estimatedDuration: 60.0,
|
|
frequency: 2
|
|
))
|
|
case .endurance:
|
|
recommendations.append(Recommendation(
|
|
type: .workout,
|
|
title: "Endurance Building,
|
|
description: "Longer duration cardio with progressive distance increases",
|
|
priority: .high,
|
|
estimatedDuration: 90.0,
|
|
frequency: 3
|
|
))
|
|
case .weightLoss:
|
|
recommendations.append(Recommendation(
|
|
type: .workout,
|
|
title: "HIIT and Cardio Mix",
|
|
description: "High-intensity intervals mixed with moderate cardio",
|
|
priority: .high,
|
|
estimatedDuration: 45.0,
|
|
frequency: 4
|
|
))
|
|
case .flexibility:
|
|
recommendations.append(Recommendation(
|
|
type: .workout,
|
|
title: "Flexibility and Mobility",
|
|
description: "Daily stretching and mobility sessions",
|
|
priority: .medium,
|
|
estimatedDuration: 20.0,
|
|
frequency: 1
|
|
))
|
|
}
|
|
}
|
|
|
|
// Level-appropriate recommendations
|
|
switch currentLevel {
|
|
case .absoluteBeginner:
|
|
recommendations.append(Recommendation(
|
|
type: .tutorial,
|
|
title: "Beginner Foundation Course",
|
|
description: "Complete the 4-week foundation program to build basic fitness",
|
|
priority: .high,
|
|
estimatedDuration: 30.0,
|
|
frequency: 3
|
|
))
|
|
|
|
case .beginner:
|
|
recommendations.append(Recommendation(
|
|
type: .workout,
|
|
title: "Progressive Challenge Program",
|
|
description: "Gradually increase intensity each week",
|
|
priority: .medium,
|
|
estimatedDuration: 45.0,
|
|
frequency: 3
|
|
))
|
|
|
|
case .intermediate:
|
|
recommendations.append(Recommendation(
|
|
type: .workout,
|
|
title: "Advanced Challenge Series",
|
|
description: "Introduce varied workout types and increased intensity",
|
|
priority: .medium,
|
|
estimatedDuration: 60.0,
|
|
frequency: 3
|
|
))
|
|
|
|
case .advanced:
|
|
recommendations.append(Recommendation(
|
|
type: .workout,
|
|
title: "Elite Performance Program",
|
|
description: "Specialized training based on specific goals",
|
|
priority: .high,
|
|
estimatedDuration: 90.0,
|
|
frequency: 4
|
|
))
|
|
}
|
|
|
|
// Progress-based adjustments
|
|
if progress.plateauDetected {
|
|
recommendations.append(Recommendation(
|
|
type: .adjustment,
|
|
title: "Break Through Plateau",
|
|
description: "Change workout variety and intensity to break through plateau",
|
|
priority: .high,
|
|
estimatedDuration: 0.0,
|
|
frequency: 0
|
|
))
|
|
}
|
|
|
|
if progress.injuryRisk > 0.7 {
|
|
recommendations.append(Recommendation(
|
|
type: .caution,
|
|
title: "Injury Prevention Focus",
|
|
description: "Reduce intensity and focus on form; consult a professional",
|
|
priority: .critical,
|
|
estimatedDuration: 0.0,
|
|
frequency: 0
|
|
))
|
|
}
|
|
|
|
// Filter by user preferences and injuries
|
|
recommendations = recommendations.filter { rec in
|
|
!rec.title.contains("Injury Prevention") ||
|
|
(profile.injuries?.contains($0.title.lowercased()) ?? false)
|
|
}
|
|
|
|
// Sort by priority
|
|
return recommendations.sorted { $0.priority > $1.priority }
|
|
}
|
|
|
|
// MARK: - Helper Methods
|
|
|
|
private func calculateConsistency(_ workouts: [Workout]) -> Double {
|
|
guard workouts.count > 0 else { return 0 }
|
|
|
|
let workoutDates = workouts.map { $0.date }
|
|
let uniqueWeeks = Set(workoutDates.map { Calendar.current.component(.weekOfYear, from: $0) })
|
|
let weeksActive = Calendar.current.dateComponents([.calendar], from: workoutDates.first!, to: workoutDates.last!).weekOfYear ?? 0
|
|
|
|
guard weeksActive > 0 else { return 0 }
|
|
|
|
return Double(uniqueWeeks.count) / Double(weeksActive)
|
|
}
|
|
|
|
private extension Double {
|
|
var average: Double {
|
|
guard count > 0 else { return 0 }
|
|
return sum / count
|
|
}
|
|
}
|
|
|
|
// MARK: - Private Types
|
|
|
|
private struct UserProfile {
|
|
let userId: String
|
|
let name: String
|
|
let fitnessLevel: FitnessLevel
|
|
let workoutFrequency: Double
|
|
let avgIntensity: Double
|
|
let consistencyScore: Double
|
|
let preferences: [String]
|
|
let injuries: [String]
|
|
}
|
|
|
|
private struct ProgressAnalysis {
|
|
let totalWorkouts: Int
|
|
let avgDuration: Double
|
|
let avgCalories: Double
|
|
let improvementRate: Double
|
|
let consistencyStreak: Int
|
|
let plateauDetected: Bool
|
|
let injuryRisk: Double
|
|
}
|
|
|
|
private struct Recommendation {
|
|
let type: RecommendationType
|
|
let title: String
|
|
let description: String
|
|
let priority: Priority
|
|
let estimatedDuration: Double
|
|
let frequency: Int
|
|
}
|
|
|
|
private struct TrainingPlan {
|
|
let id: String
|
|
let userProfile: UserProfile
|
|
let recommendations: [Recommendation]
|
|
let duration: Double
|
|
let difficulty: DifficultyLevel
|
|
let generatedAt: Date
|
|
let version: Int
|
|
}
|
|
|
|
// MARK: - Enums
|
|
|
|
private enum GoalType {
|
|
case strength
|
|
case endurance
|
|
case weightLoss
|
|
case flexibility
|
|
}
|
|
|
|
private enum DifficultyLevel {
|
|
case easy
|
|
case moderate
|
|
case hard
|
|
case elite
|
|
}
|
|
|
|
private enum FitnessLevel {
|
|
case absoluteBeginner
|
|
case beginner
|
|
case intermediate
|
|
case advanced
|
|
}
|
|
|
|
private enum Priority {
|
|
case critical >
|
|
case high >
|
|
case medium >
|
|
case low
|
|
}
|
|
|
|
private enum RecommendationType {
|
|
case workout
|
|
case tutorial
|
|
case adjustment
|
|
case caution
|
|
}
|
|
|
|
// MARK: - Errors
|
|
|
|
private enum RateLimitError: Error {
|
|
case rateLimited
|
|
}
|
|
|
|
private enum ProfileAnalysisError: Error {
|
|
case noUserProfile
|
|
case insufficientData
|
|
}
|
|
} |