Files
rideaware-api/internal/ai/model.go
2026-05-17 20:39:47 -05:00

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"`
}