from typing import Any from sqlalchemy.orm import Session from app.fitness.service import FitnessService def get_fitness_snapshot(db: Session, user_id: int) -> dict[str, Any]: return FitnessService(db, user_id).snapshot() def format_fitness_context(snapshot: dict[str, Any]) -> str: lines = ["[Фитнес — сводка на сегодня]"] profile = snapshot.get("profile") if not profile: lines.append("Профиль не настроен. set_fitness_profile для целей ккал/БЖУ/воды.") else: computed = profile.get("computed") or {} lines.append( f"Цели (база, без шагов/тренировок): {profile.get('calorie_target')} ккал, " f"Б {profile.get('protein_g')} / Ж {profile.get('fat_g')} / У {profile.get('carbs_g')} г, " f"вода {profile.get('water_l')} л" ) lines.append( f"BMR {computed.get('bmr', '?')} + NEAT {computed.get('neat_kcal', 200)} = " f"TDEE база {computed.get('tdee', '?')} ккал" ) if profile.get("goal"): lines.append( f"Цель: {profile.get('goal')}, вес {profile.get('weight_kg')} кг, " f"рост {profile.get('height_cm')} см" ) today = snapshot.get("today") or {} totals = today.get("totals") or {} targets = today.get("targets") or {} breakdown = today.get("tdee_breakdown") or {} expected = today.get("tdee_expected") or {} targets_expected = today.get("targets_expected") or {} steps_total = today.get("steps_total") or 0 water_l = totals.get("water_ml", 0) / 1000 water_target = targets.get("water_ml", 2500) / 1000 if breakdown: lines.append( f"TDEE факт: BMR {breakdown.get('bmr')} + NEAT {breakdown.get('neat_kcal')} + " f"шаги {breakdown.get('steps_kcal')} ({steps_total} шаг.) + " f"тренировки {breakdown.get('workout_kcal')} = {breakdown.get('tdee')} ккал → " f"цель {breakdown.get('calorie_target')} ккал" ) elif steps_total == 0: lines.append( "Шаги/тренировки не внесены — TDEE факт = BMR + NEAT. " "log_steps / log_workout для точной дневной цели." ) if expected: source = expected.get("source", "?") source_labels = { "weekly_avg": "среднее за неделю", "baseline": "baseline профиля", "defaults": "по activity_level", } source_label = source_labels.get(str(source), str(source)) days_data = expected.get("days_with_data", 0) lookback = expected.get("lookback_days", 7) extra = f", {days_data} дн. с данными за {lookback} дн." if source == "weekly_avg" else "" lines.append( f"TDEE план ({source_label}{extra}): BMR {expected.get('bmr')} + NEAT {expected.get('neat_kcal')} + " f"шаги {expected.get('steps_kcal')} (~{expected.get('steps', 0)} шаг.) + " f"тренировки {expected.get('workout_kcal')} = {expected.get('tdee')} ккал → " f"цель {expected.get('calorie_target')} ккал" ) lines.append("") if targets_expected and targets_expected.get("carbs_g") != targets.get("carbs_g"): lines.append( f"Съедено: {totals.get('calories', 0):.0f}/{targets.get('calories', 0):.0f} ккал " f"(план {targets_expected.get('calories', 0):.0f}) · " f"Б {totals.get('protein_g', 0):.0f}/{targets.get('protein_g', 0):.0f} · " f"Ж {totals.get('fat_g', 0):.0f}/{targets.get('fat_g', 0):.0f} · " f"У {totals.get('carbs_g', 0):.0f}/{targets.get('carbs_g', 0):.0f} " f"(план {targets_expected.get('carbs_g', 0):.0f}) г" ) else: lines.append( f"Съедено: {totals.get('calories', 0):.0f}/{targets.get('calories', 0):.0f} ккал · " f"Б {totals.get('protein_g', 0):.0f}/{targets.get('protein_g', 0):.0f} · " f"Ж {totals.get('fat_g', 0):.0f}/{targets.get('fat_g', 0):.0f} · " f"У {totals.get('carbs_g', 0):.0f}/{targets.get('carbs_g', 0):.0f} г" ) lines.append(f"Вода: {water_l:.1f}/{water_target:.1f} л") workouts = today.get("workouts") or [] if workouts: lines.append(f"Тренировок сегодня: {len(workouts)}") stats = snapshot.get("workout_stats") or {} if stats.get("count"): lines.append( f"Тренировки за {stats.get('days', 7)} дн.: {stats.get('count')} " f"(серия {stats.get('streak')} дн., {stats.get('active_kcal')} ккал активных)" ) latest = (snapshot.get("body_metrics") or [None])[0] if latest: lines.append("") lines.append("Антропометрия (последняя):") parts = [f"{latest.get('weight_kg')} кг"] if latest.get("body_fat_pct") is not None: method = latest.get("body_fat_method") or "?" parts.append(f"жир {latest.get('body_fat_pct')}% ({method})") if latest.get("neck_cm"): parts.append(f"шея {latest.get('neck_cm')}") if latest.get("waist_cm"): parts.append(f"талия {latest.get('waist_cm')}") if latest.get("hip_cm"): parts.append(f"бёдра {latest.get('hip_cm')}") if latest.get("whr"): parts.append(f"WHR {latest.get('whr')}") if latest.get("ffmi"): parts.append(f"FFMI {latest.get('ffmi')}") lines.append(" · ".join(parts)) lines.append("") lines.append( "Правила: log_meal, log_water, log_weight (обхваты → Navy), log_steps, log_workout (date/days_ago), " "calc_body_composition (расчёт без записи), get_fitness_summary (date/days_ago), get_fitness_history, " "set_fitness_profile, calc_fitness_targets, lookup_food, lookup_exercise. " "TDEE = BMR + NEAT (200 ккал) + шаги + тренировки. " "TDEE факт — по залогированной активности; TDEE план — среднее за неделю (или baseline) для утреннего бюджета углеводов. " "БЖУ: белок 2.2 г/кг (сушка) / 1.8 г/кг (поддержание/набор), жир 1.0 г/кг, угли — остаток от целевых ккал. " "Скриншоты Mi Fitness: vision уже извлекла данные в блок [Скриншот] с fitness_hints — используй их, не говори что не видишь картинку. " "Еда — оценка LLM (≈)." ) return chr(10).join(lines)