245 lines
22 KiB
Markdown
245 lines
22 KiB
Markdown
# Дизайн: создание и корректировка программы тренировок с помощью 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, а не проектировать его вслепую.
|