smart tdee

This commit is contained in:
2026-06-16 04:38:23 +00:00
parent f2e98942ff
commit a3f01cd850
56 changed files with 2519 additions and 591 deletions
+28 -17
View File
@@ -336,7 +336,7 @@ TOOL_DEFINITIONS: list[dict[str, Any]] = [
"type": "function",
"function": {
"name": "set_fitness_profile",
"description": "Настроить фитнес-профиль и пересчитать цели ккал/БЖУ/воды.",
"description": "Настроить фитнес-профиль и пересчитать цели ккал/БЖУ/воды (TDEE = BMR + NEAT).",
"parameters": {
"type": "object",
"properties": {
@@ -344,13 +344,12 @@ TOOL_DEFINITIONS: list[dict[str, Any]] = [
"age": {"type": "integer"},
"height_cm": {"type": "number"},
"weight_kg": {"type": "number"},
"activity_level": {
"type": "string",
"description": "sedentary/light/moderate/active/very_active",
},
"goal": {"type": "string", "description": "lose/maintain/gain"},
"target_weight_kg": {"type": "number"},
"weekly_workouts": {"type": "integer"},
"neat_base_kcal": {
"type": "number",
"description": "NEAT-база 200–300 ккал, по умолчанию 200",
},
},
"required": [],
},
@@ -360,7 +359,7 @@ TOOL_DEFINITIONS: list[dict[str, Any]] = [
"type": "function",
"function": {
"name": "calc_fitness_targets",
"description": "Калькулятор BMR/TDEE/макросов без сохранения.",
"description": "Калькулятор BMR/TDEE/макросов без сохранения (rest-day: BMR + NEAT).",
"parameters": {
"type": "object",
"properties": {
@@ -368,8 +367,9 @@ TOOL_DEFINITIONS: list[dict[str, Any]] = [
"age": {"type": "integer"},
"height_cm": {"type": "number"},
"weight_kg": {"type": "number"},
"activity_level": {"type": "string"},
"goal": {"type": "string"},
"neat_base_kcal": {"type": "number"},
"steps": {"type": "integer", "description": "Шаги за день для расчёта TDEE"},
},
"required": ["weight_kg", "height_cm", "age"],
},
@@ -539,15 +539,19 @@ TOOL_DEFINITIONS: list[dict[str, Any]] = [
"function": {
"name": "get_weather",
"description": (
"ОБЯЗАТЕЛЬНО для вопросов о погоде, «что на улице», «будет ли дождь». "
"Текущая погода и прогноз по часам."
"ОБЯЗАТЕЛЬНО для вопросов о погоде, «что на улице», «будет ли дождь», «завтра», «на неделю». "
"Текущая погода, почасовой и дневной прогноз."
),
"parameters": {
"type": "object",
"properties": {
"hours_ahead": {
"type": "integer",
"description": "Сколько часов прогноза (по умолчанию 12)",
"description": "Сколько часов почасового прогноза (по умолчанию 12, до 168)",
},
"days_ahead": {
"type": "integer",
"description": "Сколько дней дневного прогноза (по умолчанию 7, до 16)",
},
},
"required": [],
@@ -917,14 +921,17 @@ async def execute_tool(
updates = {
k: arguments[k]
for k in (
"sex", "age", "height_cm", "weight_kg", "activity_level",
"goal", "target_weight_kg", "weekly_workouts",
"sex", "age", "height_cm", "weight_kg",
"goal", "target_weight_kg", "neat_base_kcal",
)
if k in arguments and arguments[k] is not None
}
result = fitness.set_profile(updates)
elif name == "calc_fitness_targets":
result = fitness.calc_targets(arguments)
from app.fitness.calculators import compute_daily_targets
steps = int(arguments.get("steps") or 0)
result = compute_daily_targets(arguments, steps_total=steps, workouts=[])
elif name == "calc_body_composition":
result = fitness.calc_body_composition(arguments)
elif name == "log_meal":
@@ -980,6 +987,8 @@ async def execute_tool(
active_calories=structured.get("active_calories"),
total_calories=structured.get("total_calories"),
steps=structured.get("steps"),
activity_type=structured.get("activity_type"),
met=structured.get("met"),
day=day,
days_ago=arguments.get("days_ago"),
)
@@ -1002,12 +1011,14 @@ async def execute_tool(
interval_hours=arguments.get("interval_hours"),
)
elif name == "get_weather":
hours = int(arguments.get("hours_ahead") or 12)
hours = max(1, min(int(arguments.get("hours_ahead") or 12), 168))
days = max(1, min(int(arguments.get("days_ahead") or 7), 16))
client = OpenMeteoClient()
weather = client.fetch_current_and_hourly(hours_ahead=hours)
weather = client.fetch_forecast(hours_ahead=hours, days_ahead=days)
result = {
"weather": weather,
"rain_summary": client.rain_summary(hours_ahead=hours) if weather.get("ok") else "",
"rain_summary": client.rain_summary(hours_ahead=hours, daily=weather.get("daily")) if weather.get("ok") else "",
"daily_summary": client.daily_summary(days_ahead=days) if weather.get("ok") else "",
}
elif name == "get_morning_briefing":
include_news = arguments.get("include_news", True)