added fitness

This commit is contained in:
2026-06-10 09:12:50 +03:00
parent 0b39692300
commit d0bdd1e95c
25 changed files with 2082 additions and 7 deletions
+223
View File
@@ -3,6 +3,10 @@ from typing import Any
from sqlalchemy.orm import Session
from app.fitness.service import FitnessService
from app.fitness.structuring import structure_meal, structure_workout
from app.integrations.openfoodfacts import OpenFoodFactsClient
from app.integrations.wger import WgerClient
from app.memory.service import MemoryService
from app.pomodoro.service import PomodoroService
from app.projects.service import ProjectService
@@ -268,6 +272,164 @@ TOOL_DEFINITIONS: list[dict[str, Any]] = [
},
},
},
{
"type": "function",
"function": {
"name": "get_fitness_summary",
"description": "Сводка фитнеса за сегодня: ккал, БЖУ, вода, тренировки.",
"parameters": {"type": "object", "properties": {}, "required": []},
},
},
{
"type": "function",
"function": {
"name": "set_fitness_profile",
"description": "Настроить фитнес-профиль и пересчитать цели ккал/БЖУ/воды.",
"parameters": {
"type": "object",
"properties": {
"sex": {"type": "string", "description": "male/female"},
"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"},
},
"required": [],
},
},
},
{
"type": "function",
"function": {
"name": "calc_fitness_targets",
"description": "Калькулятор BMR/TDEE/макросов без сохранения.",
"parameters": {
"type": "object",
"properties": {
"sex": {"type": "string"},
"age": {"type": "integer"},
"height_cm": {"type": "number"},
"weight_kg": {"type": "number"},
"activity_level": {"type": "string"},
"goal": {"type": "string"},
},
"required": ["weight_kg", "height_cm", "age"],
},
},
},
{
"type": "function",
"function": {
"name": "log_meal",
"description": "Записать приём пищи. LLM оценит ккал и БЖУ из текста.",
"parameters": {
"type": "object",
"properties": {
"text": {"type": "string", "description": "Что съел"},
"meal_type": {"type": "string"},
},
"required": ["text"],
},
},
},
{
"type": "function",
"function": {
"name": "log_water",
"description": "Записать воду в мл.",
"parameters": {
"type": "object",
"properties": {
"amount_ml": {"type": "integer"},
},
"required": ["amount_ml"],
},
},
},
{
"type": "function",
"function": {
"name": "log_weight",
"description": "Записать вес в кг.",
"parameters": {
"type": "object",
"properties": {
"weight_kg": {"type": "number"},
"body_fat_pct": {"type": "number"},
"notes": {"type": "string"},
},
"required": ["weight_kg"],
},
},
},
{
"type": "function",
"function": {
"name": "log_workout",
"description": "Записать тренировку из текста.",
"parameters": {
"type": "object",
"properties": {
"text": {"type": "string"},
},
"required": ["text"],
},
},
},
{
"type": "function",
"function": {
"name": "lookup_food",
"description": "Поиск продукта в Open Food Facts (ккал на 100г).",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"limit": {"type": "integer"},
},
"required": ["query"],
},
},
},
{
"type": "function",
"function": {
"name": "lookup_exercise",
"description": "Поиск упражнения в базе wger.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"limit": {"type": "integer"},
},
"required": ["query"],
},
},
},
{
"type": "function",
"function": {
"name": "set_fitness_reminder",
"description": "Вкл/выкл или настроить напоминание: water, meal, workout, weigh_in.",
"parameters": {
"type": "object",
"properties": {
"kind": {"type": "string"},
"enabled": {"type": "boolean"},
"hour": {"type": "integer"},
"minute": {"type": "integer"},
"interval_hours": {"type": "integer"},
},
"required": ["kind"],
},
},
},
{
"type": "function",
"function": {
@@ -299,6 +461,7 @@ async def execute_tool(
pomodoro = PomodoroService(db)
projects = ProjectService(db)
memory = MemoryService(db)
fitness = FitnessService(db)
try:
if name == "get_pomodoro_status":
@@ -370,6 +533,66 @@ async def execute_tool(
int(arguments["session_id"]),
arguments.get("summary", ""),
)
elif name == "get_fitness_summary":
result = fitness.get_daily_summary()
elif name == "set_fitness_profile":
updates = {
k: arguments[k]
for k in (
"sex", "age", "height_cm", "weight_kg", "activity_level",
"goal", "target_weight_kg", "weekly_workouts",
)
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)
elif name == "log_meal":
structured = await structure_meal(arguments.get("text", ""))
result = fitness.log_meal(
description=structured.get("description") or arguments.get("text", ""),
meal_type=arguments.get("meal_type") or structured.get("meal_type") or "snack",
calories=float(structured.get("calories") or 0),
protein_g=float(structured.get("protein_g") or 0),
fat_g=float(structured.get("fat_g") or 0),
carbs_g=float(structured.get("carbs_g") or 0),
source="llm",
estimated=True,
)
elif name == "log_water":
result = fitness.log_water(int(arguments.get("amount_ml", 250)))
elif name == "log_weight":
result = fitness.log_weight(
float(arguments["weight_kg"]),
body_fat_pct=arguments.get("body_fat_pct"),
notes=arguments.get("notes", ""),
)
elif name == "log_workout":
structured = await structure_workout(arguments.get("text", ""))
result = fitness.log_workout(
title=structured.get("title") or "Тренировка",
notes=structured.get("notes") or arguments.get("text", ""),
duration_min=structured.get("duration_min"),
exercises=structured.get("exercises"),
)
elif name == "lookup_food":
result = OpenFoodFactsClient().search(
arguments.get("query", ""),
limit=arguments.get("limit", 5),
)
elif name == "lookup_exercise":
result = WgerClient().search_exercises(
arguments.get("query", ""),
limit=arguments.get("limit", 8),
)
elif name == "set_fitness_reminder":
result = fitness.set_reminder(
arguments.get("kind", "water"),
enabled=arguments.get("enabled"),
hour=arguments.get("hour"),
minute=arguments.get("minute"),
interval_hours=arguments.get("interval_hours"),
)
else:
return json.dumps({"error": f"Unknown tool: {name}"}, ensure_ascii=False)