171 lines
5.6 KiB
Go
171 lines
5.6 KiB
Go
package ai
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"time"
|
|
|
|
"rideaware/internal/workout"
|
|
)
|
|
|
|
// GenerateRequest is the user's request for AI workout generation
|
|
type GenerateRequest struct {
|
|
PlanDuration int `json:"plan_duration"` // Number of days (1, 7, 14, 28)
|
|
FocusAreas []string `json:"focus_areas"` // ["endurance", "threshold", "vo2max", "recovery", "sprint", "sweet_spot"]
|
|
IntensityLevel string `json:"intensity_level"` // "easy", "moderate", "hard"
|
|
WeeklyHours int `json:"weekly_hours"` // Available training hours per week
|
|
StartDate string `json:"start_date"` // YYYY-MM-DD
|
|
IncludeRest bool `json:"include_rest"` // Whether to include rest days
|
|
TargetEventID *uint `json:"target_event_id"` // Optional target event to periodize for
|
|
}
|
|
|
|
// GenerateResponse contains AI-generated workouts
|
|
type GenerateResponse struct {
|
|
Workouts []AIWorkout `json:"workouts"`
|
|
Rationale string `json:"rationale"` // AI's explanation
|
|
TotalTSS float64 `json:"total_tss"`
|
|
RecommendationID uint `json:"recommendation_id"`
|
|
}
|
|
|
|
// AIWorkout represents a single AI-generated workout
|
|
type AIWorkout struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
Type string `json:"type"`
|
|
ScheduledDate string `json:"scheduled_date"`
|
|
Duration int `json:"duration"` // seconds
|
|
Segments []workout.WorkoutSegment `json:"segments"`
|
|
EstimatedTSS float64 `json:"estimated_tss"`
|
|
Notes string `json:"notes"`
|
|
}
|
|
|
|
// AIRecommendation database model
|
|
type AIRecommendation struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
UserID uint `gorm:"not null;index" json:"user_id"`
|
|
PromptContext JSONB `gorm:"type:jsonb" json:"prompt_context"`
|
|
AIResponse string `json:"ai_response"`
|
|
GeneratedWorkouts JSONB `gorm:"type:jsonb" json:"generated_workouts"`
|
|
Parameters JSONB `gorm:"type:jsonb" json:"parameters"`
|
|
Status string `gorm:"default:'generated'" json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
func (AIRecommendation) TableName() string {
|
|
return "ai_recommendations"
|
|
}
|
|
|
|
// JSONB type for PostgreSQL JSONB columns
|
|
type JSONB struct {
|
|
Data []byte
|
|
}
|
|
|
|
// Scan implements sql.Scanner interface
|
|
func (j *JSONB) Scan(value interface{}) error {
|
|
if value == nil {
|
|
j.Data = []byte("{}")
|
|
return nil
|
|
}
|
|
bytes, ok := value.([]byte)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
j.Data = bytes
|
|
return nil
|
|
}
|
|
|
|
// Value implements driver.Valuer interface
|
|
func (j JSONB) Value() (driver.Value, error) {
|
|
if len(j.Data) == 0 {
|
|
return []byte("{}"), nil
|
|
}
|
|
return j.Data, nil
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaler
|
|
func (j JSONB) MarshalJSON() ([]byte, error) {
|
|
if len(j.Data) == 0 {
|
|
return []byte("{}"), nil
|
|
}
|
|
return j.Data, nil
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler
|
|
func (j *JSONB) UnmarshalJSON(data []byte) error {
|
|
j.Data = data
|
|
return nil
|
|
}
|
|
|
|
// UserContext contains all user data sent to AI
|
|
type UserContext struct {
|
|
FTP int `json:"ftp"`
|
|
MaxHR int `json:"max_hr"`
|
|
RestingHR int `json:"resting_hr"`
|
|
Weight float64 `json:"weight"`
|
|
TrainingGoal string `json:"training_goal"`
|
|
WeeklyHours int `json:"weekly_hours"`
|
|
RecentWorkouts []RecentWorkoutSummary `json:"recent_workouts"`
|
|
TrainingLoad TrainingLoadSummary `json:"training_load"`
|
|
UpcomingEvents []UpcomingEventSummary `json:"upcoming_events,omitempty"`
|
|
TargetEvent *UpcomingEventSummary `json:"target_event,omitempty"`
|
|
Nutrition *NutritionContext `json:"nutrition,omitempty"`
|
|
}
|
|
|
|
// NutritionContext contains nutrition data for AI
|
|
type NutritionContext struct {
|
|
Goal string `json:"goal"`
|
|
DailyCalories int `json:"daily_calories"`
|
|
ProteinG int `json:"protein_g"`
|
|
CarbsG int `json:"carbs_g"`
|
|
FatG int `json:"fat_g"`
|
|
DietaryPref string `json:"dietary_preference"`
|
|
}
|
|
|
|
// UpcomingEventSummary summarizes an upcoming race/event for AI context
|
|
type UpcomingEventSummary struct {
|
|
Name string `json:"name"`
|
|
Date string `json:"date"`
|
|
EventType string `json:"event_type"`
|
|
Distance float64 `json:"distance"`
|
|
Priority string `json:"priority"`
|
|
DaysAway int `json:"days_away"`
|
|
}
|
|
|
|
// RecentWorkoutSummary summarizes a completed workout
|
|
type RecentWorkoutSummary struct {
|
|
Date string `json:"date"`
|
|
Type string `json:"type"`
|
|
Duration int `json:"duration"`
|
|
AvgPower int `json:"avg_power"`
|
|
TSS float64 `json:"tss"`
|
|
}
|
|
|
|
// TrainingLoadSummary contains CTL/ATL/TSB metrics
|
|
type TrainingLoadSummary struct {
|
|
CTL float64 `json:"ctl"` // Chronic Training Load (42-day EMA)
|
|
ATL float64 `json:"atl"` // Acute Training Load (7-day EMA)
|
|
TSB float64 `json:"tsb"` // Training Stress Balance (CTL - ATL)
|
|
}
|
|
|
|
// DeepSeek API structures
|
|
type DeepSeekRequest struct {
|
|
Model string `json:"model"`
|
|
Messages []DeepSeekMessage `json:"messages"`
|
|
Temperature float64 `json:"temperature"`
|
|
MaxTokens int `json:"max_tokens,omitempty"`
|
|
}
|
|
|
|
type DeepSeekMessage struct {
|
|
Role string `json:"role"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
type DeepSeekResponse struct {
|
|
Choices []struct {
|
|
Message DeepSeekMessage `json:"message"`
|
|
} `json:"choices"`
|
|
Error *struct {
|
|
Message string `json:"message"`
|
|
Type string `json:"type"`
|
|
} `json:"error"`
|
|
}
|