Added two AI-powered features using Claude API: meal recommendations and daily stretching routines.
Both personalize based on training load, recent activities, and upcoming workouts.
The Dining Service
Daily meal recommendations generated at 4am PT. Uses Claude to plan breakfast, lunch, dinner, and snacks based on:
- Current fitness metrics (CTL, ATL, form)
- Recent activities (last 7 days)
- Upcoming workouts (next 24 hours)
- Training intensity trends
The prompt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
prompt := fmt.Sprintf(`Generate personalized meal recommendations for today.
Current fitness metrics:
- CTL (fitness): %.1f
- ATL (fatigue): %.1f
- Form: %.1f
- Training load ramp rate: %.1f%%
Recent activities (last 7 days):
%s
Upcoming workouts (next 24 hours):
%s
Provide meal recommendations for:
- Breakfast
- Lunch
- Dinner
- Snacks
Focus on:
1. Recovery nutrition if fatigued (high ATL)
2. Fuel for upcoming hard workouts
3. Lighter meals on rest days
4. Practical, simple meals
Format as markdown with sections.`,
wellness.CTL,
wellness.ATL,
wellness.Form,
wellness.Ramp,
formatRecentActivities(recentActivities),
formatUpcomingWorkouts(upcomingWorkouts),
)
|
Context extraction:
The service fetches data from Intervals.icu:
1
2
3
4
5
6
7
8
9
10
11
12
|
func fetchFitnessContext() (AthleteWellness, []Workout, []Workout, error) {
// Get current wellness metrics
wellness, _ := fetchWellness()
// Recent activities (past 7 days)
recent, _ := fetchActivities(time.Now().AddDate(0, 0, -7), time.Now())
// Upcoming workouts (next 24 hours)
upcoming, _ := fetchWorkouts(time.Now(), time.Now().AddDate(0, 0, 1))
return wellness, recent, upcoming, nil
}
|
Example output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
# Meal Plan for October 12, 2025
Your form is +5.2 today (fitness outpacing fatigue). You have an easy 30-minute swim later and light gym work tomorrow.
## Breakfast
- Greek yogurt with granola and berries
- Two eggs (scrambled or over-easy)
- Toast with avocado
- Coffee
Focus on protein and moderate carbs. You're recovered, but no need to carb-load.
## Lunch
- Grilled chicken salad with mixed greens
- Quinoa or brown rice (1 cup)
- Olive oil and balsamic dressing
- Apple
Light but satisfying. Save bigger carbs for dinner if the swim feels harder than expected.
## Dinner
- Salmon (6oz) or chicken breast
- Roasted vegetables (broccoli, carrots, sweet potato)
- Small portion of pasta or rice
- Side salad
Protein-focused recovery meal. You're not depleted, but protein helps overnight repair.
## Snacks
- Protein shake (if needed post-workout)
- Mixed nuts (almonds, cashews)
- Banana with peanut butter
- Dark chocolate (because why not)
Light snacks. You don't need heavy fuel today - focus on protein and healthy fats.
|
Notice:
- Acknowledges form score (+5.2, feeling good)
- Adjusts meal size based on workout intensity (light swim = moderate meals)
- Specific portions and prep suggestions
- Practical foods
Daily generation:
CronJob triggers at 4am PT:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-dining-recommendations
spec:
schedule: "0 11 * * *" # 4am PT = 11am UTC
jobTemplate:
spec:
template:
spec:
containers:
- name: curl
image: curlimages/curl:latest
args:
- "curl -X POST http://dining/generate"
|
Results cached in memory and saved to S3 (dining/{date}.json). The service loads yesterday’s recommendation on startup so there’s always something to show.
Meal feedback:
Added thumbs up/down buttons for each meal section:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
http.HandleFunc("/feedback", handleFeedback)
func handleFeedback(w http.ResponseWriter, r *http.Request) {
meal := r.FormValue("meal") // breakfast, lunch, dinner, snacks
feedbackType := r.FormValue("type") // liked, modified, skipped, didnt_like
notes := r.FormValue("notes") // optional
feedback := MealFeedback{
Type: feedbackType,
Timestamp: time.Now().Format(time.RFC3339),
Notes: notes,
}
// Save to S3 under today's dining history
saveFeedback(meal, feedback)
}
|
The feedback doesn’t feed back into the next day’s recommendations yet. That’s next. For now it just tracks whether the suggestions were useful.
History page:
Browse past recommendations at /history. Shows last 30 days with navigation by date. Each day links to meal sections (#breakfast, #lunch, etc.).
The Stretching Service
Lives in the workouts service. Generates daily stretching routines at 4am PT based on:
- Recent activities (muscle groups worked)
- Upcoming workouts (pre-hab focus areas)
- Days since last rest day (recovery intensity)
- Activity types (run-specific vs. bike-specific stretches)
The prompt:
Stored in stretching_prompt.txt for easy editing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
Generate a personalized stretching routine for today.
Recent activities:
{activities}
Upcoming workouts:
{upcoming}
Training context:
{context}
Create a 10-15 minute routine focusing on:
1. Areas worked recently (soreness prevention)
2. Prep for upcoming workouts
3. General mobility maintenance
Format as markdown with:
- Warmup (light movement)
- Main stretches (hold times, reps)
- Focus areas (tight spots)
- Cooldown (relaxation)
Be specific: "Hold for 30 seconds, 2 sets per side" not "stretch hamstrings."
|
Variables replaced at runtime:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
func generateStretchingPlan() (string, error) {
// Load prompt template
template, _ := os.ReadFile("stretching_prompt.txt")
// Get context
recent, _ := fetchRecentActivities(7)
upcoming, _ := fetchUpcomingWorkouts(1)
context := buildTrainingContext(recent, upcoming)
// Replace variables
prompt := string(template)
prompt = strings.ReplaceAll(prompt, "{activities}", formatActivities(recent))
prompt = strings.ReplaceAll(prompt, "{upcoming}", formatWorkouts(upcoming))
prompt = strings.ReplaceAll(prompt, "{context}", context)
// Call Claude
response, _ := claudeClient.Messages.Create(context.Background(), &anthropic.MessageCreateParams{
Model: "claude-sonnet-4-20250514",
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(prompt)),
},
})
return response.Content[0].Text, nil
}
|
Example output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
# Daily Stretching - October 12, 2025
You ran 5 miles yesterday and have an easy swim later today. Focus on lower body recovery and shoulder mobility prep.
## Warmup (3 minutes)
- Arm circles: 10 forward, 10 backward
- Leg swings: 10 per leg (forward/back, side-to-side)
- Cat-cow spine stretch: 10 slow reps
## Main Stretches
### Lower Body (post-run recovery)
1. **Standing quad stretch**
- Hold wall for balance
- Pull heel to glutes
- Hold 30 seconds, 2 sets per leg
2. **Hamstring stretch**
- One leg elevated on chair/table
- Lean forward from hips (straight back)
- Hold 30 seconds, 2 sets per leg
3. **Calf stretch**
- Front foot elevated on step
- Keep heel down, lean forward
- Hold 30 seconds, 2 sets per leg
### Upper Body (swim prep)
4. **Shoulder circles**
- Arms extended at sides
- 10 circles forward, 10 backward
- 2 sets
5. **Doorway chest stretch**
- Arm at 90° on doorframe
- Step through, feel stretch across chest
- Hold 30 seconds per side
6. **Tricep overhead stretch**
- Reach behind head
- Pull elbow with other hand
- Hold 30 seconds per arm
## Focus Areas
- Hip flexors (tight from running)
- IT band (roll if available)
- Lower back (gentle twists)
## Cooldown (2 minutes)
- Child's pose: 60 seconds
- Lying spinal twist: 30 seconds per side
Total time: ~12 minutes
|
Notice:
- Acknowledges yesterday’s run and today’s swim
- Prioritizes lower body recovery (ran yesterday)
- Adds shoulder mobility for upcoming swim
- Specific hold times and rep counts
- Practical, no equipment needed (except chair)
Daily generation:
Same CronJob pattern as dining:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-stretching-recommendations
spec:
schedule: "0 11 * * *" # 4am PT
jobTemplate:
spec:
template:
spec:
containers:
- name: curl
args:
- "curl -X POST http://workouts/stretching/generate"
|
History:
Past recommendations saved to S3 (stretching/{date}.json). Available at /stretching/history.
Why Claude API
Inference vs. Templates:
Could’ve used rule-based logic:
- “If CTL > ATL by 5+, suggest light meals”
- “If hard workout tomorrow, recommend carb loading”
- “If ran yesterday, stretch quads and hamstrings”
But rules are rigid. Claude adapts:
Example 1: High fatigue + rest day
- Template would say: “Recovery day, eat light”
- Claude says: “You’re fatigued (ATL 85, CTL 75). Even though it’s a rest day, prioritize protein and quality carbs to support recovery. Don’t skimp on calories - your body is rebuilding.”
Example 2: Multiple hard workouts
- Template: “Hard workout tomorrow, carb load”
- Claude: “You have back-to-back hard sessions (tempo run tomorrow, HIIT Friday). Today’s meals should fuel both. Extra carbs at dinner, and prep overnight oats for tomorrow’s early breakfast.”
Example 3: New activity type
- Template: No rule for “kayaking”
- Claude: “You kayaked for 90 minutes yesterday. That’s unusual for you. Focus on upper back and shoulder stretches today - those muscles aren’t conditioned like your running legs.”
Context matters. Inference beats templates.
Cost
Dining service:
- 1 generation per day
- ~800 input tokens (prompt + context)
- ~500 output tokens (meal plan)
- Cost: ~$0.01 per day = $3.50/year
Stretching service:
- 1 generation per day
- ~600 input tokens
- ~400 output tokens
- Cost: ~$0.008 per day = $2.90/year
Total: $6.40/year for personalized daily meal and stretching plans.
Worth it.
What’s Next
Feedback integration:
Currently meal feedback is tracked but not used. Next iteration: feed yesterday’s feedback into today’s prompt:
1
2
3
4
5
6
7
8
9
10
11
|
prompt += fmt.Sprintf(`
Yesterday's feedback:
- Breakfast: %s (%s)
- Lunch: %s (%s)
- Dinner: %s (%s)
Adjust today's recommendations based on preferences.`,
yesterdayFeedback["breakfast"].Type,
yesterdayFeedback["breakfast"].Notes,
// ...
)
|
If you skip breakfast consistently, Claude will adjust. If you thumbs-down heavy dinners, it’ll lighten them.
Grocery list generation:
Endpoint that aggregates this week’s meal recommendations and outputs a shopping list. Group by category (produce, protein, pantry).
Stretching video links:
Parse the generated stretches and link to instructional videos (YouTube, etc.). Especially useful for uncommon stretches.
Progressive stretching:
Track which stretches you’ve done consistently and gradually increase difficulty. “You’ve been doing basic hamstring stretches for 2 weeks - ready for a deeper variation?”
Integration with Routine Service
Both dining and stretching show up on the routine service dashboard:
- Today’s meal plan (expandable sections)
- Today’s stretching routine (with checkboxes to mark completed)
- Links to history pages
Everything in one place. Morning routine: check dashboard, see workouts, meals, stretches.
🤖 Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com