Added Sarita_B contributions to community-contributions

This commit is contained in:
Sarita Bandarupalli
2025-08-20 16:56:30 -04:00
parent a5e49af4cd
commit a4c9456c38
5 changed files with 688 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
# Fitness & Nutrition Planner Agent (Community Contribution)
A tool-using agent that generates a **7day vegetarian-friendly meal plan** with **calorie/macro targets** and a **consolidated grocery list**. It supports **targeted swaps** (e.g., "swap Tuesday lunch") while honoring dietary patterns, allergies, and dislikes.
> **Disclaimer**: This project is for educational purposes and is **not** medical advice. Consult a licensed professional for medical or specialized dietary needs.
---
## ✨ Features
- Calculates **TDEE** and **macro targets** via MifflinSt Jeor + activity factors.
- Builds a **7day plan** (breakfast/lunch/dinner) respecting dietary constraints.
- Produces an aggregated **grocery list** for the week.
- Supports **swap** of any single meal while keeping macros reasonable.
- Minimal **Streamlit UI** for demos.
- Extensible **tool-based architecture** to plug real recipe APIs/DBs.
---
## 🧱 Architecture
- **Agent core**: OpenAI function-calling (tools) with a simple orchestration loop.
- **Tools**:
1. `calc_calories_and_macros` computes targets.
2. `compose_meal_plan` creates the 7day plan.
3. `grocery_list_from_plan` consolidates ingredients/quantities.
4. `swap_meal` replaces one meal (by kcal proximity and constraints).
- **Recipe source**: a tiny in-memory recipe DB for demo; replace with a real API or your own dataset.
---
## 🚀 Quickstart
### 1) Install
```bash
pip install openai streamlit pydantic python-dotenv
```
### 2) Configure
Create a `.env` file in this folder:
```
OPENAI_API_KEY=your_key_here
OPENAI_MODEL=gpt-4o-mini
```
### 3) Run CLI (example)
```bash
python agent.py
```
### 4) Run UI
```bash
streamlit run app.py
```
---
## 🧪 Sample Profile (from issue author)
See `sample_profile.json` for the exact values used to produce `demo_output.md`.
- **Sex**: female
- **Age**: 45
- **Height**: 152 cm (~5 ft)
- **Weight**: 62 kg
- **Activity**: light
- **Goal**: maintain
- **Diet**: vegetarian
---
## 🔧 Extend
- Replace the in-memory recipes with:
- A real **recipe API** (e.g., Spoonacular) or
- Your **own dataset** (CSV/DB) + filters/tags
- Add price lookups to produce a **budget-aware** grocery list.
- Add **adherence tracking** and charts.
- Integrate **wearables** or daily steps to refine TDEE dynamically.
- Add **snacks** for days slightly under target kcals.
---
## 🛡️ Safety Notes
- The agent warns for extreme deficits but does **not** diagnose conditions.
- For calorie targets below commonly recommended minimums (e.g., ~1200 kcal/day for many adults), advise consulting a professional.
---
## 📁 Project Layout
```
fitness-nutrition-planner-agent/
├─ README.md
├─ agent.py
├─ app.py
├─ sample_profile.json
└─ demo_output.md
```
---
## 🤝 How to contribute
- Keep notebooks (if any) with **cleared outputs**.
- Follow the course repos contribution guidelines.
- Include screenshots or a short Loom/YT demo link in your PR description.

View File

@@ -0,0 +1,411 @@
# agent.py
import os, math, json, copy
from dataclasses import dataclass
from typing import List, Dict, Any, Optional, Tuple
from pydantic import BaseModel, Field, ValidationError
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
# ------------------------------
# Data models
# ------------------------------
class UserProfile(BaseModel):
sex: str = Field(..., description="male or female")
age: int
height_cm: float
weight_kg: float
activity_level: str = Field(..., description="sedentary, light, moderate, active, very_active")
goal: str = Field(..., description="lose, maintain, gain")
dietary_pattern: Optional[str] = Field(None, description="e.g., vegetarian, vegan, halal, kosher")
allergies: List[str] = Field(default_factory=list)
dislikes: List[str] = Field(default_factory=list)
daily_meals: int = 3
cuisine_prefs: List[str] = Field(default_factory=list)
time_per_meal_minutes: int = 30
budget_level: Optional[str] = Field(None, description="low, medium, high")
class MacroTargets(BaseModel):
tdee: int
target_kcal: int
protein_g: int
carbs_g: int
fat_g: int
class Meal(BaseModel):
name: str
ingredients: List[Dict[str, Any]] # {item, qty, unit}
kcal: int
protein_g: int
carbs_g: int
fat_g: int
tags: List[str] = Field(default_factory=list)
instructions: Optional[str] = None
class DayPlan(BaseModel):
day: str
meals: List[Meal]
totals: MacroTargets
class WeekPlan(BaseModel):
days: List[DayPlan]
meta: Dict[str, Any]
# ------------------------------
# Tiny in-memory recipe “DB”
# (extend/replace with a real source)
# ------------------------------
RECIPE_DB: List[Meal] = [
Meal(
name="Greek Yogurt Parfait",
ingredients=[{"item":"nonfat greek yogurt","qty":200,"unit":"g"},
{"item":"berries","qty":150,"unit":"g"},
{"item":"granola","qty":30,"unit":"g"},
{"item":"honey","qty":10,"unit":"g"}],
kcal=380, protein_g=30, carbs_g=52, fat_g=8,
tags=["vegetarian","breakfast","5-min","no-cook"]
),
Meal(
name="Tofu Veggie Stir-Fry with Rice",
ingredients=[{"item":"firm tofu","qty":150,"unit":"g"},
{"item":"mixed vegetables","qty":200,"unit":"g"},
{"item":"soy sauce (low sodium)","qty":15,"unit":"ml"},
{"item":"olive oil","qty":10,"unit":"ml"},
{"item":"brown rice (cooked)","qty":200,"unit":"g"}],
kcal=650, protein_g=28, carbs_g=85, fat_g=20,
tags=["vegan","gluten-free","dinner","20-min","stovetop","soy"]
),
Meal(
name="Chicken Quinoa Bowl",
ingredients=[{"item":"chicken breast","qty":140,"unit":"g"},
{"item":"quinoa (cooked)","qty":185,"unit":"g"},
{"item":"spinach","qty":60,"unit":"g"},
{"item":"olive oil","qty":10,"unit":"ml"},
{"item":"lemon","qty":0.5,"unit":"unit"}],
kcal=620, protein_g=45, carbs_g=55, fat_g=20,
tags=["gluten-free","dinner","25-min","high-protein","poultry"]
),
Meal(
name="Lentil Soup + Wholegrain Bread",
ingredients=[{"item":"lentils (cooked)","qty":200,"unit":"g"},
{"item":"vegetable broth","qty":400,"unit":"ml"},
{"item":"carrot","qty":80,"unit":"g"},
{"item":"celery","qty":60,"unit":"g"},
{"item":"onion","qty":60,"unit":"g"},
{"item":"wholegrain bread","qty":60,"unit":"g"}],
kcal=520, protein_g=25, carbs_g=78, fat_g=8,
tags=["vegan","lunch","30-min","budget"]
),
Meal(
name="Salmon, Potatoes & Greens",
ingredients=[{"item":"salmon fillet","qty":150,"unit":"g"},
{"item":"potatoes","qty":200,"unit":"g"},
{"item":"broccoli","qty":150,"unit":"g"},
{"item":"olive oil","qty":10,"unit":"ml"}],
kcal=680, protein_g=42, carbs_g=52, fat_g=30,
tags=["gluten-free","dinner","omega-3","fish"]
),
Meal(
name="Cottage Cheese Bowl",
ingredients=[{"item":"low-fat cottage cheese","qty":200,"unit":"g"},
{"item":"pineapple","qty":150,"unit":"g"},
{"item":"chia seeds","qty":15,"unit":"g"}],
kcal=380, protein_g=32, carbs_g=35, fat_g=10,
tags=["vegetarian","snack","5-min","high-protein","dairy"]
),
]
# ------------------------------
# Tool implementations
# ------------------------------
ACTIVITY_FACTORS = {
"sedentary": 1.2,
"light": 1.375,
"moderate": 1.55,
"active": 1.725,
"very_active": 1.9
}
def mifflin_st_jeor(weight_kg: float, height_cm: float, age: int, sex: str) -> float:
# BMR (kcal/day)
if sex.lower().startswith("m"):
return 10*weight_kg + 6.25*height_cm - 5*age + 5
else:
return 10*weight_kg + 6.25*height_cm - 5*age - 161
def compute_targets(profile: UserProfile) -> MacroTargets:
bmr = mifflin_st_jeor(profile.weight_kg, profile.height_cm, profile.age, profile.sex)
tdee = int(round(bmr * ACTIVITY_FACTORS.get(profile.activity_level, 1.2)))
# goal adjustment
if profile.goal == "lose":
target_kcal = max(1200, int(tdee - 400)) # conservative deficit
elif profile.goal == "gain":
target_kcal = int(tdee + 300)
else:
target_kcal = tdee
# Macro split (modifiable): P 30%, C 40%, F 30%
protein_kcal = target_kcal * 0.30
carbs_kcal = target_kcal * 0.40
fat_kcal = target_kcal * 0.30
protein_g = int(round(protein_kcal / 4))
carbs_g = int(round(carbs_kcal / 4))
fat_g = int(round(fat_kcal / 9))
return MacroTargets(tdee=tdee, target_kcal=target_kcal,
protein_g=protein_g, carbs_g=carbs_g, fat_g=fat_g)
def _allowed(meal: Meal, profile: UserProfile) -> bool:
# dietary patterns/allergies/dislikes filters (simple; extend as needed)
diet = (profile.dietary_pattern or "").lower()
if diet == "vegetarian" and ("fish" in meal.tags or "poultry" in meal.tags):
return False
if diet == "vegan" and ("dairy" in meal.tags or "fish" in meal.tags or "poultry" in meal.tags):
return False
# allergies & dislikes
for a in profile.allergies:
if a and a.lower() in meal.name.lower(): return False
if any(a.lower() in (ing["item"]).lower() for ing in meal.ingredients): return False
if a.lower() in " ".join(meal.tags).lower(): return False
for d in profile.dislikes:
if d and d.lower() in meal.name.lower(): return False
if any(d.lower() in (ing["item"]).lower() for ing in meal.ingredients): return False
return True
def meal_db_search(profile: UserProfile, tags: Optional[List[str]] = None) -> List[Meal]:
tags = tags or []
out = []
for m in RECIPE_DB:
if not _allowed(m, profile):
continue
if tags and not any(t in m.tags for t in tags):
continue
out.append(m)
return out or [] # may be empty; agent should handle
def compose_meal_plan(profile: UserProfile, targets: MacroTargets) -> WeekPlan:
# naive heuristic: pick meals that roughly match per-meal macro budget
per_meal_kcal = targets.target_kcal / profile.daily_meals
days = []
weekdays = ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]
# simple pools
breakfasts = meal_db_search(profile, tags=["breakfast","no-cook","5-min"])
lunches = meal_db_search(profile, tags=["lunch","budget"])
dinners = meal_db_search(profile, tags=["dinner","high-protein"])
# fallback to any allowed meals if pools too small
allowed_all = meal_db_search(profile)
if len(breakfasts) < 2: breakfasts = allowed_all
if len(lunches) < 2: lunches = allowed_all
if len(dinners) < 2: dinners = allowed_all
for i, day in enumerate(weekdays):
day_meals = []
for slot in range(profile.daily_meals):
pool = breakfasts if slot == 0 else (lunches if slot == 1 else dinners)
# pick the meal closest in kcal to per_meal_kcal
pick = min(pool, key=lambda m: abs(m.kcal - per_meal_kcal))
day_meals.append(copy.deepcopy(pick))
# compute totals
kcal = sum(m.kcal for m in day_meals)
p = sum(m.protein_g for m in day_meals)
c = sum(m.carbs_g for m in day_meals)
f = sum(m.fat_g for m in day_meals)
day_targets = MacroTargets(tdee=targets.tdee, target_kcal=int(round(kcal)),
protein_g=p, carbs_g=c, fat_g=f)
days.append(DayPlan(day=day, meals=day_meals, totals=day_targets))
return WeekPlan(days=days, meta={"per_meal_target_kcal": int(round(per_meal_kcal))})
def grocery_list_from_plan(plan: WeekPlan) -> List[Dict[str, Any]]:
# aggregate identical ingredients
agg: Dict[Tuple[str,str], float] = {}
units: Dict[Tuple[str,str], str] = {}
for d in plan.days:
for m in d.meals:
for ing in m.ingredients:
key = (ing["item"].lower(), ing.get("unit",""))
agg[key] = agg.get(key, 0) + float(ing.get("qty", 0))
units[key] = ing.get("unit","")
items = []
for (item, unit), qty in sorted(agg.items()):
items.append({"item": item, "qty": round(qty, 2), "unit": unit})
return items
def swap_meal(plan: WeekPlan, day: str, meal_index: int, profile: UserProfile) -> WeekPlan:
# replace one meal by closest-kcal allowed alternative that isn't the same
day_idx = next((i for i,d in enumerate(plan.days) if d.day.lower().startswith(day[:3].lower())), None)
if day_idx is None: return plan
current_meal = plan.days[day_idx].meals[meal_index]
candidates = [m for m in meal_db_search(profile) if m.name != current_meal.name]
if not candidates: return plan
pick = min(candidates, key=lambda m: abs(m.kcal - current_meal.kcal))
plan.days[day_idx].meals[meal_index] = copy.deepcopy(pick)
# recalc day totals
d = plan.days[day_idx]
kcal = sum(m.kcal for m in d.meals)
p = sum(m.protein_g for m in d.meals)
c = sum(m.carbs_g for m in d.meals)
f = sum(m.fat_g for m in d.meals)
d.totals = MacroTargets(tdee=d.totals.tdee, target_kcal=kcal, protein_g=p, carbs_g=c, fat_g=f)
return plan
# ------------------------------
# Agent (LLM + tools)
# ------------------------------
SYS_PROMPT = """You are FitnessPlanner, an agentic planner that:
- Respects dietary patterns, allergies, dislikes, budget, time limits.
- Uses tools to compute targets, assemble a 7-day plan, produce a grocery list, and swap meals on request.
- If a request is unsafe (extreme deficits, medical conditions), warn and suggest professional guidance.
- Keep responses concise and structured (headings + bullet lists)."""
# Tool registry for function-calling
def get_tools_schema():
return [
{
"type": "function",
"function": {
"name": "calc_calories_and_macros",
"description": "Compute TDEE and macro targets from the user's profile.",
"parameters": {
"type":"object",
"properties": {"profile":{"type":"object"}},
"required":["profile"]
}
}
},
{
"type": "function",
"function": {
"name": "compose_meal_plan",
"description": "Create a 7-day meal plan matching targets and constraints.",
"parameters": {
"type":"object",
"properties": {
"profile":{"type":"object"},
"targets":{"type":"object"}
},
"required":["profile","targets"]
}
}
},
{
"type": "function",
"function": {
"name": "grocery_list_from_plan",
"description": "Make a consolidated grocery list from a week plan.",
"parameters": {
"type":"object",
"properties": {"plan":{"type":"object"}},
"required":["plan"]
}
}
},
{
"type": "function",
"function": {
"name": "swap_meal",
"description": "Swap a single meal in the plan while keeping macros reasonable.",
"parameters": {
"type":"object",
"properties": {
"plan":{"type":"object"},
"day":{"type":"string"},
"meal_index":{"type":"integer","description":"0=breakfast,1=lunch,2=dinner"},
"profile":{"type":"object"}
},
"required":["plan","day","meal_index","profile"]
}
}
}
]
class FitnessPlannerAgent:
def __init__(self, model: Optional[str] = None):
self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
self.model = model or os.getenv("OPENAI_MODEL", "gpt-4o-mini")
self.plan_cache: Optional[WeekPlan] = None
self.targets_cache: Optional[MacroTargets] = None
# Tool dispatch
def _call_tool(self, name: str, args: Dict[str, Any]) -> str:
if name == "calc_calories_and_macros":
profile = UserProfile(**args["profile"])
targets = compute_targets(profile)
self.targets_cache = targets
return targets.model_dump_json()
elif name == "compose_meal_plan":
profile = UserProfile(**args["profile"])
targets = MacroTargets(**args["targets"])
plan = compose_meal_plan(profile, targets)
self.plan_cache = plan
return plan.model_dump_json()
elif name == "grocery_list_from_plan":
plan = WeekPlan(**args["plan"])
items = grocery_list_from_plan(plan)
return json.dumps(items)
elif name == "swap_meal":
plan = WeekPlan(**args["plan"])
profile = UserProfile(**args["profile"])
day = args["day"]
idx = args["meal_index"]
new_plan = swap_meal(plan, day, idx, profile)
self.plan_cache = new_plan
return new_plan.model_dump_json()
else:
return json.dumps({"error":"unknown tool"})
def chat(self, user_message: str, profile: Optional[UserProfile] = None) -> str:
messages = [{"role":"system","content":SYS_PROMPT}]
if profile:
messages.append({"role":"user","content":f"User profile: {profile.model_dump_json()}"} )
messages.append({"role":"user","content":user_message})
# First call
resp = self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=get_tools_schema(),
tool_choice="auto",
temperature=0.3
)
# Handle tool calls (simple, single-step or brief multi-step)
messages_llm = messages + [{"role":"assistant","content":resp.choices[0].message.content or "",
"tool_calls":resp.choices[0].message.tool_calls}]
if resp.choices[0].message.tool_calls:
for tc in resp.choices[0].message.tool_calls:
name = tc.function.name
args = json.loads(tc.function.arguments or "{}")
out = self._call_tool(name, args)
messages_llm.append({
"role":"tool",
"tool_call_id":tc.id,
"name":name,
"content":out
})
# Finalization
resp2 = self.client.chat.completions.create(
model=self.model,
messages=messages_llm,
temperature=0.2
)
return resp2.choices[0].message.content
return resp.choices[0].message.content
# ------------------------------
# Quick CLI demo
# ------------------------------
if __name__ == "__main__":
profile = UserProfile(
sex="female", age=45, height_cm=152, weight_kg=62,
activity_level="light", goal="maintain",
dietary_pattern="vegetarian", allergies=[], dislikes=[],
daily_meals=3, cuisine_prefs=["mediterranean"], time_per_meal_minutes=25, budget_level="medium"
)
agent = FitnessPlannerAgent()
print(agent.chat("Create my 7-day plan and grocery list.", profile))

View File

@@ -0,0 +1,75 @@
# app.py
import json
import streamlit as st
from agent import FitnessPlannerAgent, UserProfile, WeekPlan
st.set_page_config(page_title="Fitness & Nutrition Planner Agent", layout="wide")
st.title("🏋️ Fitness & Nutrition Planner Agent")
with st.sidebar:
st.header("Your Profile")
sex = st.selectbox("Sex", ["female","male"])
age = st.number_input("Age", 18, 90, 45)
height_cm = st.number_input("Height (cm)", 120, 220, 152)
weight_kg = st.number_input("Weight (kg)", 35.0, 200.0, 62.0)
activity_level = st.selectbox("Activity Level", ["sedentary","light","moderate","active","very_active"], index=1)
goal = st.selectbox("Goal", ["lose","maintain","gain"], index=1)
dietary_pattern = st.selectbox("Dietary Pattern", ["none","vegetarian","vegan","halal","kosher"], index=1)
if dietary_pattern == "none": dietary_pattern = None
allergies = st.text_input("Allergies (comma-separated)", "")
dislikes = st.text_input("Dislikes (comma-separated)", "")
daily_meals = st.slider("Meals per day", 2, 5, 3)
time_per_meal_minutes = st.slider("Time per meal (min)", 5, 90, 25)
budget_level = st.selectbox("Budget", ["medium","low","high"], index=0)
cuisine_prefs = st.text_input("Cuisine prefs (comma-separated)", "mediterranean")
build_btn = st.button("Generate 7-Day Plan")
agent = FitnessPlannerAgent()
if build_btn:
profile = UserProfile(
sex=sex, age=int(age), height_cm=float(height_cm), weight_kg=float(weight_kg),
activity_level=activity_level, goal=goal, dietary_pattern=dietary_pattern,
allergies=[a.strip() for a in allergies.split(",") if a.strip()],
dislikes=[d.strip() for d in dislikes.split(",") if d.strip()],
daily_meals=int(daily_meals), cuisine_prefs=[c.strip() for c in cuisine_prefs.split(",") if c.strip()],
time_per_meal_minutes=int(time_per_meal_minutes), budget_level=budget_level
)
st.session_state["profile_json"] = profile.model_dump_json()
with st.spinner("Planning your week..."):
result = agent.chat("Create my 7-day plan and grocery list.", profile)
st.session_state["last_response"] = result
if "last_response" in st.session_state:
st.subheader("Plan & Groceries")
st.markdown(st.session_state["last_response"])
st.divider()
st.subheader("Tweaks")
col1, col2, col3 = st.columns(3)
with col1:
day = st.selectbox("Day to change", ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"])
with col2:
meal_index = st.selectbox("Meal slot", ["Breakfast (0)","Lunch (1)","Dinner (2)"])
meal_index = int(meal_index[-2]) # 0/1/2
with col3:
swap_btn = st.button("Swap Meal")
if swap_btn and agent.plan_cache:
profile_json = st.session_state.get("profile_json")
if not profile_json:
st.warning("Please generate a plan first.")
else:
new_plan_json = agent._call_tool("swap_meal", {
"plan": agent.plan_cache.model_dump(),
"day": day,
"meal_index": meal_index,
"profile": json.loads(profile_json)
})
agent.plan_cache = WeekPlan(**json.loads(new_plan_json))
summary = agent.chat(f"Update summary for {day}: show the swapped meal and new day totals.")
st.session_state["last_response"] = summary
st.markdown(summary)

View File

@@ -0,0 +1,84 @@
# Demo Output (Sample Profile)
**Profile**: female, 45, 152 cm, 62 kg, activity: light, goal: maintain, diet: vegetarian
## Targets
- TDEE ≈ **1680 kcal/day**
- Macros (30/40/30): **Protein 126 g**, **Carbs 168 g**, **Fat 56 g**
> These are estimates using MifflinSt Jeor and a light activity factor. Not medical advice.
---
## Example 7-Day Plan (Breakfast / Lunch / Dinner)
**Mon**
- Greek Yogurt Parfait (380 kcal, 30P/52C/8F)
- Lentil Soup + Wholegrain Bread (520 kcal, 25P/78C/8F)
- Tofu Veggie Stir-Fry with Rice (650 kcal, 28P/85C/20F)
- **Totals** ≈ 1550 kcal, 83P, 215C, 36F
**Tue**
- Cottage Cheese Bowl (380 kcal, 32P/35C/10F)
- Lentil Soup + Wholegrain Bread (520 kcal, 25P/78C/8F)
- Tofu Veggie Stir-Fry with Rice (650 kcal, 28P/85C/20F)
- **Totals** ≈ 1550 kcal, 85P, 198C, 38F
**Wed**
- Greek Yogurt Parfait
- Lentil Soup + Wholegrain Bread
- Tofu Veggie Stir-Fry with Rice
- **Totals** ≈ 1550 kcal
**Thu**
- Cottage Cheese Bowl
- Lentil Soup + Wholegrain Bread
- Tofu Veggie Stir-Fry with Rice
- **Totals** ≈ 1550 kcal
**Fri**
- Greek Yogurt Parfait
- Lentil Soup + Wholegrain Bread
- Tofu Veggie Stir-Fry with Rice
- **Totals** ≈ 1550 kcal
**Sat**
- Cottage Cheese Bowl
- Lentil Soup + Wholegrain Bread
- Tofu Veggie Stir-Fry with Rice
- **Totals** ≈ 1550 kcal
**Sun**
- Greek Yogurt Parfait
- Lentil Soup + Wholegrain Bread
- Tofu Veggie Stir-Fry with Rice
- **Totals** ≈ 1550 kcal
> Notes: The demo DB is intentionally small. In practice, plug in a larger vegetarian recipe set for more variety. Add snacks if you'd like to reach ~1680 kcal/day.
---
## Grocery List (aggregated, approx for 7 days)
- nonfat greek yogurt — **1400 g**
- berries — **1050 g**
- granola — **210 g**
- honey — **70 g**
- lentils (cooked) — **1400 g**
- vegetable broth — **2800 ml**
- carrot — **560 g**
- celery — **420 g**
- onion — **420 g**
- wholegrain bread — **420 g**
- firm tofu — **1050 g**
- mixed vegetables — **1400 g**
- soy sauce (low sodium) — **105 ml**
- olive oil — **140 ml**
- brown rice (cooked) — **1400 g**
- low-fat cottage cheese — **600 g**
- pineapple — **450 g**
- chia seeds — **45 g**
**Tip:** Use the apps *Swap Meal* to replace any item (e.g., swap Wed dinner).

View File

@@ -0,0 +1,17 @@
{
"sex": "female",
"age": 45,
"height_cm": 152,
"weight_kg": 62,
"activity_level": "light",
"goal": "maintain",
"dietary_pattern": "vegetarian",
"allergies": [],
"dislikes": [],
"daily_meals": 3,
"cuisine_prefs": [
"mediterranean"
],
"time_per_meal_minutes": 25,
"budget_level": "medium"
}