Spaces:
Running
Running
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 filesExpose 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 +2 -2
- multi_llm_chatbot_backend/app/core/improved_orchestrator.py +1 -1
- multi_llm_chatbot_backend/app/tests/unit/test_nutrition_calculator.py +2 -0
- multi_llm_chatbot_backend/app/tests/unit/test_nutrition_calculator_tool.py +1 -0
- multi_llm_chatbot_backend/app/tools/nutrition_calculator.py +12 -0
- muscle_growth_config.yaml +1 -1
- personas/fitness_advisors/nutrition_strategist.yaml +1 -1
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 ≈
|
| 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
|