# Дизайн: создание и корректировка программы тренировок с помощью LLM Дата составления: 17.10.2026 Привязка к проекту: «Феникс 6X» (см. «Техническое задание.md»). Архитектура: телефон + часы Garmin Fenix 6 + личный сервер (FastAPI + PostgreSQL), данные из Garmin Connect в .fit. --- ## 1. Главная идея: LLM — планировщик, код — источник истины Ключевое архитектурное решение: **LLM никогда не является хранилищем программы и не имеет прямого доступа к данным.** - Программа живёт в БД сервера как **версионированный JSON-документ** строгой схемы. - LLM только **предлагает** программу или изменение — в виде структурного JSON (structured output / function calling), не свободного текста. - Детерминированный **валидатор** проверяет предложение по схеме и правилам спортивной методологии. Прошёл — новая версия программы. Не прошёл — повторный запрос LLM с текстом ошибки (ограниченное число попыток), затем fallback на правила. Почему так: | Риск «LLM всем управляет» | Как снимается | |---|---| | Галлюцинации (несуществующие упражнения, веса 500 кг) | Жёсткая JSON-схема + справочник ID упражнений + валидатор | | Небезопасная прогрессия (резкий скачок нагрузки) | Детерминированные правила-ограничители (разд. 6) | | Непредсказуемость, «откатить нельзя» | Неизменяемые версии программы + журнал причин изменений | | Дорого и медленно при каждом чихе | Гибрид: микроправки после тренировки делает простой код, LLM вызывается редко (разд. 3) | | Зависимость от интернета/провайдера | Офлайн-fallback: чисто правила (разд. 7) | --- ## 2. Представление программы: версионированный JSON Формат хранения — свой JSON (внутренний формат из документа «Форматы_тренировок_и_рекомендации.md»). Иерархия: ``` Программа (Program, версия неизменяема) └─ Неделя (Week / мезоцикл: целевой объём, тонус) └─ Тренировка (Session: день, фокус — верх/низ/тяни-толкай) └─ Упражнение (Exercise: ссылка на ID из библиотеки) └─ Подход-план (SetPlan: вес, повторения, RPE-цель, отдых) ``` Пример фрагмента: ```json { "program_id": "prg_2026_10", "version": 3, "based_on_version": 2, "status": "active", // draft | active | archived "author": "llm", // llm | rules | user "created_at": "2026-10-17T18:00:00Z", "weeks": [ { "week": 1, "goal": "гипертрофия, объёмная", "sessions": [ { "day": "monday", "focus": "push", "exercises": [ { "exercise_id": "bench_press", // только ID из библиотеки! "sets": [ {"weight_kg": 80, "reps": 8, "target_rpe": 7, "rest_sec": 150} ] } ] } ] } ], "rationale": "Увеличил вес в жиме на 2.5 кг: 3 тренировки подряд все подходы закрыты при RPE<=7. Объём недельный не менял." } ``` Важные свойства: - **Упражнения — только по ID** из библиотеки упражнений приложения (иначе сломаются голосовой ввод, статистика и сопоставление с .fit). - **Каждая версия неизменяема.** Корректировка = создание новой версии + запись `program_changes` (что, почему, решение валидатора, принял ли пользователь). - Выполненные тренировки ссылаются на конкретную версию — история не «поплывёт» после правок. - Из этой же структуры при необходимости можно собрать экспорт (CSV/JSON/FIT workout). --- ## 3. Два контура и три частоты корректировки ### Контур A — планирование (редко, LLM) Когда: холодный старт, смена цели, начало нового мезоцикла (каждые 4–6 недель), явная просьба пользователя. Что делает LLM: строит программу целиком (или следующий мезоцикл) из профиля, цели, инвентаря, ограничений (травмы) и сводки истории. ### Контур B — корректировка (три частоты, гибрид «правила + LLM») | Частота | Кто решает | Что корректирует | Пример | |---|---|---|---| | После каждой тренировки | **Код (правила), без LLM** | Вес/повторы следующего занятия по прогрессии | «Все подходы закрыты при RPE≤7 → +2.5 кг»; «не дотянул 2 раза → −5%» | | Раз в неделю | **LLM** | Следующая неделя: объём, порядок, замена упражнений, лёгкая неделя | «Сон плохой, пульс покоя +5 → снизить объём на 15%» | | Раз в мезоцикл (4–6 нед.) | **LLM** | Перепланирование: делоад, новые упражнения, смена акцентов | «Плато в жиме 3 недели → сменить на жим гантелей, добавить паузы» | Смысл гибрида: 90% решений — тривиальная прогрессия, которую надёжнее и дешевле считает обычный код. LLM подключается там, где нужна интерпретация нетривиальных сигналов (усталость, плато, пропуск тренировок), — редко, обдуманно и с полным контекстом недели. --- ## 4. Конвейер данных: из .fit — в признаки для LLM LLM **не видит** сырые .fit и пульсотограммы. Сервер сам считает метрики и отдаёт компактную сводку. ``` Garmin Connect (.fit) → парсер (Session / Lap / Record / Set) [уже есть в ТЗ] → каноническое JSON-представление тренировки → MetricsEngine: детерминированный расчёт признаков → хранилище фактов (PostgreSQL) → сборка промпта для LLM (сводка недели/мезоцикла) ``` Метрики, которые считает MetricsEngine (детерминированно, без LLM): - **Выполнение плана:** доля закрытых подходов/тренировок (adherence). - **Тоннаж** по упражнению и по мышечным группам (план vs факт). - **Оценочный 1ПМ** (формулы Эпли/Бжицки) и его динамика по неделям. - **Фактический RPE** — из голосового ввода («Подход 120 на 8» → если план был 120×6 при RPE 8, фактически RPE ниже) и/или короткого опроса после тренировки. - **Кардионагрузка:** средний/пиковый пульс за подход, время восстановления пульса между подходами (из Lap/Record .fit — это как раз данные часов). - **Сигналы усталости:** падение рабочего веса при том же RPE, рост времени отдыха, падение числа повторений в последних подходах (кластеры «до отказа»). - **Субъективный фидбек** из мини-опроса в приложении после тренировки (1 вопрос: «Как прошла? 1–10» + опционально сон/самочувствие) — стоит дёшево и резко повышает качество корректировок. --- ## 5. Контракт с LLM: промпт и структурный вывод ### Состав запроса 1. **Системный промпт** — роль и методология (правила прогрессии, пределы изменений, приоритет безопасности), требование вернуть строго JSON по схеме. 2. **Профиль:** пол, возраст, вес, цель, стаж, инвентарь, травмы/ограничения. 3. **Текущая версия программы** (JSON). 4. **Сводка фактов** от MetricsEngine: последняя неделя (или мезоцикл) в сжатом виде — adherence, тоннаж по группам, динамика e1PM, пульс-метрики, субъективный фидбек. Сырую историю — не давать. 5. **Задача:** «предложи план на следующую неделю/мезоцикл» или «создай программу с нуля». ### Ответ Только JSON по схеме: новая версия (или дельта-изменения) + обязательное поле `rationale` («почему») — оно попадает в журнал изменений и показывается пользователю («Феникс повысил вес: ты закрыл все подходы»). Технически: API провайдера с structured output / function calling + JSON Schema; при неудаче валидации — повтор с текстом ошибки, максимум 2–3 попытки. ### Дополнительно (при желании, этап 2) Двухпроходная схема: первый вызов генерирует план, второй («ревьюер») проверяет его по чек-листу методологии и возвращает замечания. Дёшево и ловит логические ошибки, которые схема не видит (например, два тяжёлых дня на одну группу подряд). --- ## 6. Слой валидации и безопасности (детерминированный) После LLM предложение проходит обязательные проверки. Это обычный код, не LLM: **Схема:** все ID упражнений существуют в библиотеке; веса/повторы/отдых в допустимых диапазонах. **Методологические лимиты (пример, настраиваются):** - Рост рабочего веса ≤ +5% (или ≤ +2.5 кг на базовых) в неделю на упражнение. - Изменение недельного объёма ≤ ±20% (кроме запланированного делаода). - Не поднимать одновременно объём и интенсивность (одна переменная за раз). - Делоад — не реже чем каждые 6 недель (объём −40–50%). - Не более 2 «тяжёлых» (RPE≥8) тренировок на одну мышечную группу в неделю. - Замена упражнения — только в пределах той же двигательной группы (жим → жим, тяга → тяга); список групп — атрибут библиотеки упражнений. - Лимит общего числа подходов за тренировку (например, ≤ 25). **Человеко-машинный контур:** предложение показывается в приложении в виде диффа («Вес в приседе: 100 → 102.5, потому что…») с кнопками «Принять / Изменить / Отклонить». По мере доверия настраивается «бюджет автобонуса»: например, микроправки весов ±5% применяются сами, смена упражнений и делоады — только по подтверждению. --- ## 7. Офлайн-fallback и отказоустойчивость Требование автономности из ТЗ (разд. 5) распространяется и на планирование: - Если LLM недоступен (нет сети, лимит API, ошибки) — работает **контур правил**: прогрессия по формулам + делоад по счётчику недель. Программа продолжает корректироваться безопасно, просто без «умных» интерпретаций. - Правила-микроправки (после каждой тренировки) вообще не зависят от LLM — это базовый режим, LLM — надстройка. - Корректировки — фоновая задача (cron после синхронизации тренировки / вечером воскресенья), не в реальном времени, поэтому очереди и ретраи из ТЗ подходят без изменений. --- ## 8. Компоненты сервера и схема данных ``` ┌────────────┐ .fit ┌──────────────┐ факты ┌───────────────┐ │ Garmin │─────────▶│ FitParser │────────▶│ PostgreSQL │ │ Connect API│ │ (из ТЗ) │ │ факты, БД │ └────────────┘ └──────────────┘ └──────┬────────┘ │ сводка ┌───────────────┐ план/дельта │ ▲ │ LLM Planner │◀──────────────┘ │ │ (промпт+JSON) │ │ └──────┬────────┘ ┌──────┴────────┐ ▼ │ MetricsEngine │ ┌───────────────┐ │ (правила, │ │ Validator │──✗ ошибка→ретрай│ прогрессия) │ └──────┬────────┘ └───────────────┘ ▼ версия ┌───────────────┐ дифф «принять?» ┌──────────┐ │ ProgramStore │────────────────────▶│ Телефон │ │ (версии JSON) │◀────────────────────│ (UI) │ └───────────────┘ подтверждение └──────────┘ ``` Таблицы (минимум): - `programs` — id, версия, статус, author, created_at, JSON-тело. - `program_changes` — from/to версия, rationale, отчёт валидатора, решение пользователя (auto/accepted/rejected/edited). - `workout_facts` — факт тренировки из .fit + голосового ввода (уже следует из ТЗ). - `workout_metrics` — посчитанные признаки (тоннаж, e1PM, adherence, пульс-метрики). - `athlete_state` — профиль, цели, текущие веса, счётчики недель до делоада. - `llm_calls` — журнал: входная сводка (хэш), ответ, результат валидации, принят ли. Нужен для анализа качества и отладки промптов. --- ## 9. Полный жизненный цикл (пример недели) 1. **Пн–Сб.** Пользователь тренируется: часы пишут .fit, телефон голосом пишет подходы, всё синхронизируется на сервер. 2. **После каждой тренировки:** MetricsEngine обновляет факты → правила двигают веса на следующее занятие (+2.5 кг / удержание / −5%). Без LLM, мгновенно. 3. **После тренировки — опрос 1–10** в приложении (5 секунд). 4. **Воскресенье, cron:** сервер собирает сводку недели → LLM предлагает план следующей недели (или «без изменений») → валидатор проверяет → дифф в приложении → пользователь принимает (или авто в рамках бюджета доверия). 5. **Каждые 4–6 недель:** LLM строит следующий мезоцикл (делоад/новая волна) на сводке всего цикла. 6. Всё попадает в журнал → раз в месяц можно глазами оценить: растут ли e1PM, как часто LLM менял план и помогало ли это (сравнение трендов до/после изменений). --- ## 10. Риски и их обработка | Риск | Обработка | |---|---| | LLM «перетренирует» пользователя | Жёсткие лимиты валидатора + делоад-счётчик в правилах (код, не LLM) | | LLM занижает нагрузку, прогресс остановится | Метрика «плато» в MetricsEngine (нет роста e1PM N недель) триггерит обязательное предложение изменений | | Пользователь не принимает правки | Бюджет автобонуса + показывать rationale простым языком; правки сохраняются как «edited» и учитываются в следующих промптах | | Расходы на API | Вызовы редки (1 раз в неделю + мезоцикл), сводка компактная — копейки; микроправки бесплатны (правила) | | Некачественный голосовой ввод портит факты | Сопоставление голосовых данных с .fit по времени (уже в ТЗ п. 5.1) — расхождения помечаются и не идут в метрики как истина | | Субъективность RPE | Опрос после тренировки + расчёт по выполненным повторам; расхождения — сигнал, а не приговор | --- ## 11. Порядок внедрения (в развитие MVP из ТЗ) 1. **Шаг 1 (без LLM):** ProgramStore с версиями JSON + правила прогрессии после тренировки + MetricsEngine (тоннаж, adherence, e1PM). Система уже полезна. 2. **Шаг 2:** еженедельный вызов LLM (структурный вывод) + валидатор + дифф-подтверждение в приложении + журнал `llm_calls`. 3. **Шаг 3:** мезоцикловое планирование, двухпроходная схема «генератор+ревьюер», опрос RPE, автоматизация бюджета доверия. Такой порядок даёт работающую систему на каждом этапе и позволяет накапливать данные для LLM, а не проектировать его вслепую.