Kennedy Johnson Cursor commited on
Commit
7c3d69c
·
1 Parent(s): 96378bc

Fix protein midpoint to ~198 g for 230 lb at 1.9 g/kg.

Browse files

Expose protein_g_mid in the nutrition calculator and align docs, persona prompts, and tests with the corrected midpoint math.

Co-authored-by: Cursor <cursoragent@cursor.com>

docs/nutrition_tools.md CHANGED
@@ -13,10 +13,10 @@ Deterministic fitness/nutrition math for advisor responses. LLM personas receive
13
  ## Core formulas (local)
14
 
15
  - **Weight:** `kg = lbs × 0.45359237`
16
- - **Protein (hypertrophy):** `protein_g_min = round(weight_kg × 1.6)`, `protein_g_max = round(weight_kg × 2.2)`
17
  - **Meal split:** even distribution with remainder assigned to first meals
18
 
19
- Example: **230 lb** → **104.33 kg** → **167–230 g protein/day** (4 meals ≈ 42/42/42/41 g at the low end).
20
 
21
  ## How calculations reach advisors
22
 
 
13
  ## Core formulas (local)
14
 
15
  - **Weight:** `kg = lbs × 0.45359237`
16
+ - **Protein (hypertrophy):** `protein_g_min = round(weight_kg × 1.6)`, `protein_g_mid = round(weight_kg × 1.9)`, `protein_g_max = round(weight_kg × 2.2)`
17
  - **Meal split:** even distribution with remainder assigned to first meals
18
 
19
+ Example: **230 lb** → **104.33 kg** → **167–230 g protein/day**; midpoint at **1.9 g/kg** ≈ **~198 g/day** (4 meals ≈ 50/50/49/49 g at the midpoint).
20
 
21
  ## How calculations reach advisors
22
 
multi_llm_chatbot_backend/app/core/improved_orchestrator.py CHANGED
@@ -354,7 +354,7 @@ class ImprovedChatOrchestrator:
354
  "replies that supply requested data (body weight, schedule, equipment, "
355
  "experience level) are CLEAR ENOUGH when prior messages asked for that "
356
  "information or discussed a related topic (e.g. protein question then "
357
- "'230 lbs').\n\n"
358
  "A message NEEDS CLARIFICATION when it:\n"
359
  "- Expresses confusion or uncertainty without a concrete topic\n"
360
  "- Is a single generic request like 'help' or 'advice'\n"
 
354
  "replies that supply requested data (body weight, schedule, equipment, "
355
  "experience level) are CLEAR ENOUGH when prior messages asked for that "
356
  "information or discussed a related topic (e.g. protein question then "
357
+ "'230 lbs' → ~198 g/day at 1.9 g/kg midpoint).\n\n"
358
  "A message NEEDS CLARIFICATION when it:\n"
359
  "- Expresses confusion or uncertainty without a concrete topic\n"
360
  "- Is a single generic request like 'help' or 'advice'\n"
multi_llm_chatbot_backend/app/tests/unit/test_nutrition_calculator.py CHANGED
@@ -28,6 +28,7 @@ class TestProteinTarget(unittest.TestCase):
28
  protein = protein_target_g(weight_kg)
29
  self.assertEqual(weight_kg, 104.33)
30
  self.assertEqual(protein["protein_g_min"], 167)
 
31
  self.assertEqual(protein["protein_g_max"], 230)
32
 
33
 
@@ -72,6 +73,7 @@ class TestProteinAdvisoryContext(unittest.TestCase):
72
  self.assertIsNotNone(computed)
73
  self.assertEqual(computed["weight_kg"], 104.33)
74
  self.assertEqual(computed["protein_g_min"], 167)
 
75
  self.assertEqual(computed["protein_g_max"], 230)
76
  self.assertEqual(len(computed["meal_split_min_g"]), 4)
77
 
 
28
  protein = protein_target_g(weight_kg)
29
  self.assertEqual(weight_kg, 104.33)
30
  self.assertEqual(protein["protein_g_min"], 167)
31
+ self.assertEqual(protein["protein_g_mid"], 198)
32
  self.assertEqual(protein["protein_g_max"], 230)
33
 
34
 
 
73
  self.assertIsNotNone(computed)
74
  self.assertEqual(computed["weight_kg"], 104.33)
75
  self.assertEqual(computed["protein_g_min"], 167)
76
+ self.assertEqual(computed["protein_g_mid"], 198)
77
  self.assertEqual(computed["protein_g_max"], 230)
78
  self.assertEqual(len(computed["meal_split_min_g"]), 4)
79
 
multi_llm_chatbot_backend/app/tests/unit/test_nutrition_calculator_tool.py CHANGED
@@ -16,6 +16,7 @@ class TestNutritionCalculatorTool(unittest.TestCase):
16
  result = asyncio.run(execute(weight=230, unit="lb", meals=4))
17
  self.assertEqual(result["weight_kg"], 104.33)
18
  self.assertEqual(result["protein_g_min"], 167)
 
19
  self.assertEqual(result["protein_g_max"], 230)
20
  self.assertEqual(sum(result["meal_split_min_g"]), 167)
21
 
 
16
  result = asyncio.run(execute(weight=230, unit="lb", meals=4))
17
  self.assertEqual(result["weight_kg"], 104.33)
18
  self.assertEqual(result["protein_g_min"], 167)
19
+ self.assertEqual(result["protein_g_mid"], 198)
20
  self.assertEqual(result["protein_g_max"], 230)
21
  self.assertEqual(sum(result["meal_split_min_g"]), 167)
22
 
multi_llm_chatbot_backend/app/tools/nutrition_calculator.py CHANGED
@@ -14,6 +14,8 @@ from typing import Any, Dict, List, Optional, Tuple
14
  _LB_TO_KG = 0.45359237
15
  _KG_TO_LB = 1 / _LB_TO_KG
16
 
 
 
17
  _PROTEIN_TOPIC_KEYWORDS = (
18
  "protein",
19
  "macro",
@@ -51,10 +53,12 @@ def protein_target_g(
51
  weight_kg: float,
52
  g_per_kg_low: float = 1.6,
53
  g_per_kg_high: float = 2.2,
 
54
  ) -> Dict[str, int]:
55
  """Return rounded daily protein range in grams for hypertrophy."""
56
  return {
57
  "protein_g_min": round(weight_kg * g_per_kg_low),
 
58
  "protein_g_max": round(weight_kg * g_per_kg_high),
59
  }
60
 
@@ -136,6 +140,7 @@ def compute_protein_advisory_context(
136
  weight_kg = weight_info["weight_kg"]
137
  protein = protein_target_g(weight_kg, g_per_kg_low, g_per_kg_high)
138
  split_min = meal_split(protein["protein_g_min"], meals=meals)
 
139
  split_max = meal_split(protein["protein_g_max"], meals=meals)
140
 
141
  return {
@@ -147,9 +152,12 @@ def compute_protein_advisory_context(
147
  },
148
  "weight_kg": weight_kg,
149
  "protein_g_min": protein["protein_g_min"],
 
150
  "protein_g_max": protein["protein_g_max"],
151
  "g_per_kg_range": [g_per_kg_low, g_per_kg_high],
 
152
  "meal_split_min_g": split_min["per_meal_g"],
 
153
  "meal_split_max_g": split_max["per_meal_g"],
154
  "meals": meals,
155
  "formula": "protein_g = weight_kg × g_per_kg (Mifflin-independent; hypertrophy range 1.6–2.2 g/kg)",
@@ -215,6 +223,7 @@ async def execute(
215
  weight_kg = bodyweight_to_kg(float(weight), unit)
216
  protein = protein_target_g(weight_kg)
217
  split_min = meal_split(protein["protein_g_min"], meals=max(1, int(meals)))
 
218
  split_max = meal_split(protein["protein_g_max"], meals=max(1, int(meals)))
219
  except (TypeError, ValueError) as exc:
220
  return {"error": str(exc)}
@@ -222,9 +231,12 @@ async def execute(
222
  return {
223
  "weight_kg": weight_kg,
224
  "protein_g_min": protein["protein_g_min"],
 
225
  "protein_g_max": protein["protein_g_max"],
226
  "g_per_kg_range": [1.6, 2.2],
 
227
  "meal_split_min_g": split_min["per_meal_g"],
 
228
  "meal_split_max_g": split_max["per_meal_g"],
229
  "meals": max(1, int(meals)),
230
  }
 
14
  _LB_TO_KG = 0.45359237
15
  _KG_TO_LB = 1 / _LB_TO_KG
16
 
17
+ _G_PER_KG_MID = 1.9 # midpoint of 1.6–2.2 g/kg hypertrophy range
18
+
19
  _PROTEIN_TOPIC_KEYWORDS = (
20
  "protein",
21
  "macro",
 
53
  weight_kg: float,
54
  g_per_kg_low: float = 1.6,
55
  g_per_kg_high: float = 2.2,
56
+ g_per_kg_mid: float = _G_PER_KG_MID,
57
  ) -> Dict[str, int]:
58
  """Return rounded daily protein range in grams for hypertrophy."""
59
  return {
60
  "protein_g_min": round(weight_kg * g_per_kg_low),
61
+ "protein_g_mid": round(weight_kg * g_per_kg_mid),
62
  "protein_g_max": round(weight_kg * g_per_kg_high),
63
  }
64
 
 
140
  weight_kg = weight_info["weight_kg"]
141
  protein = protein_target_g(weight_kg, g_per_kg_low, g_per_kg_high)
142
  split_min = meal_split(protein["protein_g_min"], meals=meals)
143
+ split_mid = meal_split(protein["protein_g_mid"], meals=meals)
144
  split_max = meal_split(protein["protein_g_max"], meals=meals)
145
 
146
  return {
 
152
  },
153
  "weight_kg": weight_kg,
154
  "protein_g_min": protein["protein_g_min"],
155
+ "protein_g_mid": protein["protein_g_mid"],
156
  "protein_g_max": protein["protein_g_max"],
157
  "g_per_kg_range": [g_per_kg_low, g_per_kg_high],
158
+ "g_per_kg_mid": _G_PER_KG_MID,
159
  "meal_split_min_g": split_min["per_meal_g"],
160
+ "meal_split_mid_g": split_mid["per_meal_g"],
161
  "meal_split_max_g": split_max["per_meal_g"],
162
  "meals": meals,
163
  "formula": "protein_g = weight_kg × g_per_kg (Mifflin-independent; hypertrophy range 1.6–2.2 g/kg)",
 
223
  weight_kg = bodyweight_to_kg(float(weight), unit)
224
  protein = protein_target_g(weight_kg)
225
  split_min = meal_split(protein["protein_g_min"], meals=max(1, int(meals)))
226
+ split_mid = meal_split(protein["protein_g_mid"], meals=max(1, int(meals)))
227
  split_max = meal_split(protein["protein_g_max"], meals=max(1, int(meals)))
228
  except (TypeError, ValueError) as exc:
229
  return {"error": str(exc)}
 
231
  return {
232
  "weight_kg": weight_kg,
233
  "protein_g_min": protein["protein_g_min"],
234
+ "protein_g_mid": protein["protein_g_mid"],
235
  "protein_g_max": protein["protein_g_max"],
236
  "g_per_kg_range": [1.6, 2.2],
237
+ "g_per_kg_mid": _G_PER_KG_MID,
238
  "meal_split_min_g": split_min["per_meal_g"],
239
+ "meal_split_mid_g": split_mid["per_meal_g"],
240
  "meal_split_max_g": split_max["per_meal_g"],
241
  "meals": max(1, int(meals)),
242
  }
muscle_growth_config.yaml CHANGED
@@ -123,7 +123,7 @@ personas:
123
  - Read the full chat history before responding.
124
  - Never re-ask for information the user already provided in earlier messages.
125
  - When the user supplies follow-up data (body weight, schedule, equipment, experience), apply it immediately to the question they asked earlier in the thread.
126
- - Perform unit conversions and arithmetic when you have enough inputs (e.g. lbs→kg, protein g/day at 1.6–2.2 g/kg).
127
  - Default training and nutrition goal to muscle gain / hypertrophy unless the user specifies otherwise.
128
  personas_dir: "personas/fitness_advisors"
129
 
 
123
  - Read the full chat history before responding.
124
  - Never re-ask for information the user already provided in earlier messages.
125
  - When the user supplies follow-up data (body weight, schedule, equipment, experience), apply it immediately to the question they asked earlier in the thread.
126
+ - Perform unit conversions and arithmetic when you have enough inputs (e.g. lbs→kg, protein g/day at 1.6–2.2 g/kg; 230 lb ≈ ~198 g/day at 1.9 g/kg midpoint).
127
  - Default training and nutrition goal to muscle gain / hypertrophy unless the user specifies otherwise.
128
  personas_dir: "personas/fitness_advisors"
129
 
personas/fitness_advisors/nutrition_strategist.yaml CHANGED
@@ -23,6 +23,6 @@ persona_prompt: |
23
  - If the user asked about protein and then provides body weight (e.g. "230 lbs", "I'm 180"), calculate immediately:
24
  - Convert lbs to kg: divide by 2.205 (230 lbs ≈ 104.3 kg)
25
  - Hypertrophy protein target: 1.6–2.2 g/kg/day
26
- - Show the math briefly and give a rounded daily gram target (e.g. 167–230 g/day)
27
  - Default goal to muscle gain / hypertrophy when not specified
28
  - Only ask for missing inputs that are still unknown after reading the thread
 
23
  - If the user asked about protein and then provides body weight (e.g. "230 lbs", "I'm 180"), calculate immediately:
24
  - Convert lbs to kg: divide by 2.205 (230 lbs ≈ 104.3 kg)
25
  - Hypertrophy protein target: 1.6–2.2 g/kg/day
26
+ - Show the math briefly and give a rounded daily gram target (e.g. 167–230 g/day; ~198 g/day at 1.9 g/kg midpoint)
27
  - Default goal to muscle gain / hypertrophy when not specified
28
  - Only ask for missing inputs that are still unknown after reading the thread