lots of stuff, don't truly remember
This commit is contained in:
214
internal/ai/prompt.go
Normal file
214
internal/ai/prompt.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BuildSystemPrompt creates the system message for DeepSeek
|
||||
func BuildSystemPrompt() string {
|
||||
return `You are an expert cycling coach with deep knowledge of power-based training, periodization, and workout design.
|
||||
|
||||
Your task is to generate structured cycling workouts based on user data and goals. You must respond ONLY with valid JSON.
|
||||
|
||||
POWER ZONES (% of FTP):
|
||||
- Recovery: 30-55% FTP
|
||||
- Endurance/Zone 2: 56-75% FTP
|
||||
- Tempo: 76-87% FTP
|
||||
- Sweet Spot: 88-94% FTP
|
||||
- Threshold/FTP: 95-105% FTP
|
||||
- VO2max: 106-120% FTP
|
||||
- Anaerobic: 121-150% FTP
|
||||
- Neuromuscular: >150% FTP
|
||||
|
||||
WORKOUT STRUCTURE:
|
||||
- All workouts must have warmup, main intervals, and cooldown
|
||||
- Duration is in seconds
|
||||
- Power is expressed as decimal (0.65 = 65% FTP, 1.0 = 100% FTP)
|
||||
- Segment types: "warmup", "steadystate", "interval", "ramp", "cooldown", "rest", "freeride"
|
||||
- For steady state efforts, use "power" field
|
||||
- For variable efforts, use "power_low" and "power_high" fields
|
||||
|
||||
TSS CALCULATION:
|
||||
TSS = (duration_seconds × NP² / FTP²) / 36
|
||||
Approximate: duration_minutes × intensity_factor²
|
||||
|
||||
TRAINING PRINCIPLES:
|
||||
1. Progressive overload within user's current fitness
|
||||
2. Balance intensity and volume
|
||||
3. Include recovery when needed
|
||||
4. Respect weekly hour constraints
|
||||
5. Build on recent training load (CTL/ATL/TSB)
|
||||
6. Vary workouts to prevent monotony
|
||||
7. Include appropriate warmup and cooldown for all workouts
|
||||
|
||||
EVENT-BASED PERIODIZATION (when a target event is provided):
|
||||
- Structure the plan around the target event date using standard cycling periodization:
|
||||
- BUILD phase: Progressive volume/intensity increase (furthest from event)
|
||||
- PEAK phase: Highest intensity, race-specific intervals
|
||||
- TAPER phase: Reduce volume while maintaining intensity
|
||||
- RACE DAY: Generate a "Race" type workout on the event date with pre-race notes
|
||||
- RECOVERY: Easy rides after race day
|
||||
- A-priority race: 7-10 day taper, reduce volume 30-50%, maintain intensity
|
||||
- B-priority race: 3-5 day lighter taper
|
||||
- C-priority race: No taper, treat as training race
|
||||
- On race day, create a workout with type "Race" and include race distance/event details in notes
|
||||
- If multiple events exist, avoid scheduling hard workouts within 2 days of any event
|
||||
|
||||
NUTRITION GUIDANCE (when nutrition context is provided):
|
||||
- Include brief fueling notes in the "notes" field for each workout:
|
||||
- Pre-ride: what to eat 1-2 hours before (e.g., "Pre: oatmeal + banana, 400kcal")
|
||||
- During: fueling for rides >60min (e.g., "During: 60g carbs/hr, sports drink")
|
||||
- Post-ride: recovery nutrition within 30min (e.g., "Post: protein shake + rice, 500kcal")
|
||||
- For easy/recovery rides: lighter fueling notes
|
||||
- For hard/long rides: emphasize carb loading and during-ride nutrition
|
||||
- Keep fueling notes concise (one line each)
|
||||
|
||||
OUTPUT FORMAT (JSON only - BE CONCISE):
|
||||
{
|
||||
"workouts": [
|
||||
{
|
||||
"title": "Workout Name",
|
||||
"description": "Brief description",
|
||||
"type": "Endurance|Tempo|Threshold|VO2 Max|Recovery|Sprint",
|
||||
"scheduled_date": "YYYY-MM-DD",
|
||||
"duration": 3600,
|
||||
"segments": [
|
||||
{"type": "warmup", "duration": 600, "power_low": 0.40, "power_high": 0.65},
|
||||
{"type": "steadystate", "duration": 2400, "power": 0.65, "cadence": 85},
|
||||
{"type": "cooldown", "duration": 600, "power_low": 0.65, "power_high": 0.40}
|
||||
],
|
||||
"estimated_tss": 45.0,
|
||||
"notes": "Brief coaching note"
|
||||
}
|
||||
],
|
||||
"rationale": "Brief plan overview"
|
||||
}
|
||||
|
||||
IMPORTANT: Keep descriptions and notes VERY SHORT (max 10 words each). Response must be valid, complete JSON.`
|
||||
}
|
||||
|
||||
// BuildUserPrompt creates the user message with context
|
||||
func BuildUserPrompt(ctx UserContext, req GenerateRequest) string {
|
||||
contextJSON, _ := json.MarshalIndent(ctx, "", " ")
|
||||
|
||||
focusAreasStr := strings.Join(req.FocusAreas, ", ")
|
||||
|
||||
// Build event context section
|
||||
eventSection := ""
|
||||
if ctx.TargetEvent != nil {
|
||||
eventSection = fmt.Sprintf(`
|
||||
TARGET EVENT:
|
||||
- Name: %s
|
||||
- Date: %s (%d days away)
|
||||
- Type: %s
|
||||
- Distance: %.0f km
|
||||
- Priority: %s-race
|
||||
- IMPORTANT: Periodize the plan to peak for this event. Apply appropriate taper based on priority.
|
||||
On event day (%s), generate a "Race" type workout with event details in notes.
|
||||
`,
|
||||
ctx.TargetEvent.Name,
|
||||
ctx.TargetEvent.Date,
|
||||
ctx.TargetEvent.DaysAway,
|
||||
ctx.TargetEvent.EventType,
|
||||
ctx.TargetEvent.Distance,
|
||||
ctx.TargetEvent.Priority,
|
||||
ctx.TargetEvent.Date,
|
||||
)
|
||||
}
|
||||
|
||||
if len(ctx.UpcomingEvents) > 0 {
|
||||
eventSection += "\nUPCOMING EVENTS (avoid hard workouts within 2 days of these):\n"
|
||||
for _, ev := range ctx.UpcomingEvents {
|
||||
eventSection += fmt.Sprintf("- %s (%s, %s-race, %d days away)\n",
|
||||
ev.Name, ev.Date, ev.Priority, ev.DaysAway)
|
||||
}
|
||||
}
|
||||
|
||||
// Build nutrition context section
|
||||
nutritionSection := ""
|
||||
if ctx.Nutrition != nil {
|
||||
nutritionSection = fmt.Sprintf(`
|
||||
NUTRITION CONTEXT:
|
||||
- Goal: %s
|
||||
- Daily Calorie Target: %d kcal (before workout additions)
|
||||
- Macro Targets: Protein %dg, Carbs %dg, Fat %dg
|
||||
- Dietary Preference: %s
|
||||
- IMPORTANT: Include brief fueling notes (pre-ride, during, post-ride) in each workout's "notes" field. Adjust fueling intensity to match workout intensity.
|
||||
`,
|
||||
ctx.Nutrition.Goal,
|
||||
ctx.Nutrition.DailyCalories,
|
||||
ctx.Nutrition.ProteinG,
|
||||
ctx.Nutrition.CarbsG,
|
||||
ctx.Nutrition.FatG,
|
||||
ctx.Nutrition.DietaryPref,
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`Generate a %d-day training plan with the following requirements:
|
||||
|
||||
USER CONTEXT:
|
||||
%s
|
||||
|
||||
PLAN PARAMETERS:
|
||||
- Duration: %d days
|
||||
- Focus Areas: %s
|
||||
- Intensity Level: %s
|
||||
- Weekly Available Hours: %d
|
||||
- Start Date: %s
|
||||
- Include Rest Days: %v
|
||||
%s%s
|
||||
REQUIREMENTS:
|
||||
1. Generate workouts that fit within %d weekly hours
|
||||
2. Focus on: %s
|
||||
3. Overall intensity: %s
|
||||
4. Respect user's current fitness (FTP: %d, recent training load)
|
||||
5. Include variety and progressive adaptation
|
||||
6. Provide clear workout descriptions and coaching notes
|
||||
7. Ensure total duration of segments matches workout duration
|
||||
8. Use appropriate power zones for each workout type
|
||||
9. Include proper warmup and cooldown for every workout
|
||||
|
||||
IMPORTANT:
|
||||
- Return ONLY valid JSON in the exact format specified in the system prompt
|
||||
- Do not include any explanatory text before or after the JSON
|
||||
- Ensure all dates are sequential starting from %s
|
||||
- Calculate realistic TSS values for each workout
|
||||
- Segment durations must sum to workout duration (in seconds)
|
||||
- Power values should be between 0.30 and 2.50 (30%% to 250%% FTP)
|
||||
|
||||
Return ONLY valid JSON in the exact format specified in the system prompt.`,
|
||||
req.PlanDuration,
|
||||
string(contextJSON),
|
||||
req.PlanDuration,
|
||||
focusAreasStr,
|
||||
req.IntensityLevel,
|
||||
req.WeeklyHours,
|
||||
req.StartDate,
|
||||
req.IncludeRest,
|
||||
eventSection,
|
||||
nutritionSection,
|
||||
req.WeeklyHours,
|
||||
focusAreasStr,
|
||||
req.IntensityLevel,
|
||||
ctx.FTP,
|
||||
req.StartDate,
|
||||
)
|
||||
}
|
||||
|
||||
// extractJSON attempts to extract JSON from response that might be wrapped in markdown
|
||||
func extractJSON(response string) string {
|
||||
// Remove markdown code blocks if present
|
||||
if idx := strings.Index(response, "```json"); idx != -1 {
|
||||
response = response[idx+7:]
|
||||
} else if idx := strings.Index(response, "```"); idx != -1 {
|
||||
response = response[idx+3:]
|
||||
}
|
||||
|
||||
if idx := strings.Index(response, "```"); idx != -1 {
|
||||
response = response[:idx]
|
||||
}
|
||||
|
||||
return strings.TrimSpace(response)
|
||||
}
|
||||
Reference in New Issue
Block a user