jflo commited on
Commit
c55f421
Β·
verified Β·
1 Parent(s): e05899d

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +47 -36
main.py CHANGED
@@ -9,6 +9,7 @@ import asyncio
9
  from fastapi import FastAPI, HTTPException
10
  from fastapi.middleware.cors import CORSMiddleware
11
  from pydantic import BaseModel
 
12
  from models.model import (
13
  load_models,
14
  run_inference,
@@ -56,8 +57,6 @@ app.add_middleware(
56
 
57
  # ─────────────────────────────────────────────
58
  # REQUEST / RESPONSE SCHEMAS
59
- # Pydantic models define exactly what SwiftUI
60
- # sends and receives β€” easy to match in Swift
61
  # ─────────────────────────────────────────────
62
 
63
  class UserProfile(BaseModel):
@@ -66,8 +65,11 @@ class UserProfile(BaseModel):
66
 
67
 
68
  class PredictRequest(BaseModel):
69
- text: str # raw user post-workout summary text
70
- user_profile: UserProfile
 
 
 
71
 
72
 
73
  class LabelResult(BaseModel):
@@ -90,45 +92,51 @@ class MentalOutput(BaseModel):
90
 
91
 
92
  class PredictResponse(BaseModel):
93
- physical: PhysicalOutput # physical BERT output
94
- mental: MentalOutput # mental BERT output
95
- coach_response: str # Claude's workout summary + tips
96
- enriched_text: str # text sent to BERT (for debugging)
97
 
98
 
99
  # ─────────────────────────────────────────────
100
  # PROMPT BUILDER
101
  # ─────────────────────────────────────────────
102
 
103
- def build_claude_prompt(goal, modifiers, physical, mental, planned_workout):
104
- planned_section = f"Planned session: {planned_workout}" if planned_workout else "Planned session: none β€” user worked out independently"
 
 
 
 
105
 
106
  return f"""
107
  You are a personal fitness coach reviewing a completed training session with your athlete.
108
 
109
- User goal : {goal}
110
- Constraints : {', '.join(modifiers) if modifiers else 'none'}
 
 
111
  {planned_section}
112
 
113
  Physical state:
114
- - Pain : {physical['pain_label']['label']}
115
- - Completion : {physical['completion_label']['label']}
116
- - Fatigue : {physical['fatigue_label']['label']}
117
- - Recovery need: {physical['recovery_need_label']['label']}
118
 
119
  Mental state:
120
- - Performance : {mental['performance_label']['label']}
121
- - Satisfaction: {mental['satisfaction_label']['label']}
122
- - PR achieved : {mental['pr_achieved_label']['label']}
123
- - Motivation : {mental['motivation_label']['label']}
124
-
125
- REASONING RULES:
126
- 1. If no planned session was provided, skip the completion comparison entirely.
127
- Do not reference or imply there was a plan. Focus only on what the labels
128
  reveal about how the session went.
129
  2. PR handling: if pr_achieved is true, open with a genuine, specific celebration tied to the user's goal β€” not just "great job." Explain what this PR signals about their progress.
130
  3. Recovery routing:
131
- - recovery_need = rest_day β†’ tomorrow's advice must be strictly non-training: sleep, nutrition, or mobility only. Do not suggest any workout.
132
  - recovery_need = light_day β†’ suggest one low-intensity option only (walk, yoga, or easy swim).
133
  - recovery_need = normal or ready β†’ give a forward-looking tip about the next session.
134
  4. Pain flag: if pain label is moderate or high, add a one-line note recommending the user monitor that area and consider professional advice if it persists.
@@ -153,33 +161,36 @@ async def predict(request: PredictRequest):
153
  Main endpoint β€” called by SwiftUI after user submits post-workout summary.
154
 
155
  Flow:
156
- 1. Enriches text with goal
157
- 2. Runs physical BERT model
158
- 3. Runs mental BERT model
159
- 4. Builds Claude prompt from all labels
160
- 5. Calls Claude API
161
- 6. Returns classifications + coaching response to SwiftUI
162
  """
163
  try:
164
- # 1. Enrich text with goal
 
 
165
  enriched_text = f"Goal: {request.user_profile.primary_goal}. {request.text}"
166
 
167
- # 2. Run physical and mental model using asyncio
168
  physical_output, mental_output = await asyncio.gather(
169
  asyncio.to_thread(run_inference, ml_models["physical_model"], ml_models["tokenizer"], enriched_text, PHYSICAL_LABEL_COLS, PHYSICAL_DECODERS),
170
  asyncio.to_thread(run_inference, ml_models["mental_model"], ml_models["tokenizer"], enriched_text, MENTAL_LABEL_COLS, MENTAL_DECODERS)
171
  )
172
 
173
- # 3. Build Claude prompt
174
  prompt = build_claude_prompt(
175
  request.user_profile.primary_goal,
176
  request.user_profile.modifiers,
 
 
177
  physical_output,
178
- mental_output
 
179
  )
180
 
181
  # 4. Call Claude API
182
- # ANTHROPIC_API_KEY must be set as a HuggingFace Space secret
183
  claude_client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
184
  message = claude_client.messages.create(
185
  model="claude-sonnet-4-20250514",
 
9
  from fastapi import FastAPI, HTTPException
10
  from fastapi.middleware.cors import CORSMiddleware
11
  from pydantic import BaseModel
12
+ from typing import Optional
13
  from models.model import (
14
  load_models,
15
  run_inference,
 
57
 
58
  # ─────────────────────────────────────────────
59
  # REQUEST / RESPONSE SCHEMAS
 
 
60
  # ─────────────────────────────────────────────
61
 
62
  class UserProfile(BaseModel):
 
65
 
66
 
67
  class PredictRequest(BaseModel):
68
+ text: str # raw user post-workout text β€” passed to BERT only
69
+ duration_minutes: int # actual session duration
70
+ workout_type: str # e.g. "strength", "cardio", "mobility"
71
+ user_profile: UserProfile
72
+ planned_workout: Optional[str] = None # Claude output from pre-workout coach, if used
73
 
74
 
75
  class LabelResult(BaseModel):
 
92
 
93
 
94
  class PredictResponse(BaseModel):
95
+ physical: PhysicalOutput
96
+ mental: MentalOutput
97
+ coach_response: str # Claude's workout summary + tips
98
+ enriched_text: str # text sent to BERT (for debugging)
99
 
100
 
101
  # ─────────────────────────────────────────────
102
  # PROMPT BUILDER
103
  # ─────────────────────────────────────────────
104
 
105
+ def build_claude_prompt(goal, modifiers, duration_minutes, workout_type, physical, mental, planned_workout):
106
+ planned_section = (
107
+ f"Planned session: {planned_workout}"
108
+ if planned_workout
109
+ else "Planned session: none β€” user worked out independently"
110
+ )
111
 
112
  return f"""
113
  You are a personal fitness coach reviewing a completed training session with your athlete.
114
 
115
+ User goal : {goal}
116
+ Constraints : {', '.join(modifiers) if modifiers else 'none'}
117
+ Session duration: {duration_minutes} minutes
118
+ Workout type : {workout_type}
119
  {planned_section}
120
 
121
  Physical state:
122
+ - Pain : {physical['pain_label']['label']}
123
+ - Completion : {physical['completion_label']['label']}
124
+ - Fatigue : {physical['fatigue_label']['label']}
125
+ - Recovery need : {physical['recovery_need_label']['label']}
126
 
127
  Mental state:
128
+ - Performance : {mental['performance_label']['label']}
129
+ - Satisfaction : {mental['satisfaction_label']['label']}
130
+ - PR achieved : {mental['pr_achieved_label']['label']}
131
+ - Motivation : {mental['motivation_label']['label']}
132
+
133
+ REASONING RULES β€” apply before writing:
134
+ 1. If no planned session was provided, skip the completion comparison entirely.
135
+ Do not reference or imply there was a plan. Focus only on what the labels
136
  reveal about how the session went.
137
  2. PR handling: if pr_achieved is true, open with a genuine, specific celebration tied to the user's goal β€” not just "great job." Explain what this PR signals about their progress.
138
  3. Recovery routing:
139
+ - recovery_need = rest_day β†’ tomorrow's advice must be strictly non-training: sleep, nutrition, or mobility only. Do not suggest any workout.
140
  - recovery_need = light_day β†’ suggest one low-intensity option only (walk, yoga, or easy swim).
141
  - recovery_need = normal or ready β†’ give a forward-looking tip about the next session.
142
  4. Pain flag: if pain label is moderate or high, add a one-line note recommending the user monitor that area and consider professional advice if it persists.
 
161
  Main endpoint β€” called by SwiftUI after user submits post-workout summary.
162
 
163
  Flow:
164
+ 1. Enriches user text with goal (for BERT only)
165
+ 2. Runs physical and mental BERT models concurrently
166
+ 3. Builds Claude prompt from labels + session metadata + optional planned workout
167
+ 4. Calls Claude API
168
+ 5. Returns classifications + coaching response to SwiftUI
 
169
  """
170
  try:
171
+ # 1. Enrich text with goal for BERT β€” duration, workout_type, and
172
+ # planned_workout are kept separate so they don't pollute the BERT
173
+ # input distribution
174
  enriched_text = f"Goal: {request.user_profile.primary_goal}. {request.text}"
175
 
176
+ # 2. Run physical and mental models concurrently
177
  physical_output, mental_output = await asyncio.gather(
178
  asyncio.to_thread(run_inference, ml_models["physical_model"], ml_models["tokenizer"], enriched_text, PHYSICAL_LABEL_COLS, PHYSICAL_DECODERS),
179
  asyncio.to_thread(run_inference, ml_models["mental_model"], ml_models["tokenizer"], enriched_text, MENTAL_LABEL_COLS, MENTAL_DECODERS)
180
  )
181
 
182
+ # 3. Build Claude prompt with all available context
183
  prompt = build_claude_prompt(
184
  request.user_profile.primary_goal,
185
  request.user_profile.modifiers,
186
+ request.duration_minutes,
187
+ request.workout_type,
188
  physical_output,
189
+ mental_output,
190
+ request.planned_workout
191
  )
192
 
193
  # 4. Call Claude API
 
194
  claude_client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
195
  message = claude_client.messages.create(
196
  model="claude-sonnet-4-20250514",