604 lines
55 KiB
Markdown
604 lines
55 KiB
Markdown
# Функциональные требования: Феникс 6X v1
|
||
|
||
> Шаг 3 процесса разработки (`std1\_process\process.md`).
|
||
> Источник: [vision.md](vision.md) (утверждён 2026-10-17, v1.5), разделы
|
||
> 1.3 (БЦ), 1.4 (КУ), 2.1–2.4; решения опросников 4.1–4.7; ТЗ
|
||
> (`..\..\Техническое задание.md`), дизайн LLM-планирования (разд. 2–3,
|
||
> 6–8, 11 — шаг 1); изменение vision 1.1 (2026-10-17) — несколько
|
||
> атлетов на сервере, разные модели часов.
|
||
> Версия 1.1 — **УТВЕРЖДЕНО** (2026-10-17; изменения vision 1.1, 1.2
|
||
> учтены; утверждение — 2026-10-17). Изменение 1 (2026-10-17, указание
|
||
> пользователя): интегрирован документ
|
||
> `Метрики_тренировок_и_самоконтроля.md` — добавлена Ф-13, правила
|
||
> Ф-12 расширены (vision 1.3, БЦ-6, КУ-7).
|
||
> Версия 1.2 — черновик (2026-10-17): errata ревью
|
||
> (`0-mng\review-2026-10-17.md`, З-9–З-11) + интеграция документа
|
||
> `Перерывы_подводные_камни_и_рекомендации.md` — Ф-14 (изменение
|
||
> vision 1.4).
|
||
> Версия 1.3 — черновик (2026-10-17): волна 2 ревью по решениям
|
||
> владельца — Р1-А (пульс покоя/самочувствие в v1), Р2-Б (UUID как PK
|
||
> клиентских сущностей), Р3-А (Safety-пороги на телефоне); З-7 (RPE
|
||
> 1–10), З-8 (команда «РПЭ N»); интеграция UI/UX-рекомендаций
|
||
> (`3-tech\ui-ux-recommendations.md`). Версии 1.2, 1.3 —
|
||
> **УТВЕРЖДЕНЫ** (2026-10-17).
|
||
> Версия 1.4 — **УТВЕРЖДЕНО** (2026-10-17): ревью №2
|
||
> (`0-mng\review-2026-10-17-2.md`) — контур break (З-17), якорь Fenix 6X
|
||
> (З-18, изменение vision 1.5), ОВ-10 закрыт (З-19, решение владельца),
|
||
> `Safety.HrStrengthMax` (З-20), грамматика «…с половиной» (З-26, решение
|
||
> владельца О-2-Б), `Progression.IsoStepKg` (З-27, решение владельца
|
||
> О-3-А), режим `--mode migrate` (З-25). Правки ревью №3
|
||
> (`0-mng\review-2026-10-17-3.md`): Ф-13.7 — источник не программа
|
||
> (З-30), Ф-8.3 — импорт пакета программы (З-31, О-4-Б), вопрос 12.
|
||
|
||
## 1. Назначение и границы
|
||
|
||
Версия 1.0 реализует полный цикл «программа → тренировка без рук →
|
||
архив → план/факт» (Ф-1–Ф-11, правила прогрессии Ф-12):
|
||
|
||
- **В границах:** голосовой офлайн-учёт подходов на Android-телефоне;
|
||
поле данных на Fenix 6X в профиле «Силовая» (пульс, зоны, Lap);
|
||
офлайн-учёт с автосинхронизацией телефон ↔ сервер; забор .fit
|
||
из Garmin Connect неофициальным клиентом и парсинг на сервере;
|
||
REST API со статическим ключом; веб-история с CSV; программы
|
||
(версионированный JSON) с контролем «план/факт» и правилами
|
||
прогрессии (без LLM); поддержка нескольких атлетов на сервере —
|
||
у каждого свои программы, профиль и данные (изменение vision 1.1);
|
||
поддержка разных моделей часов Garmin (мультитаргет-сборка одного
|
||
поля данных); самоконтроль и допуск к тренировке: дневник телеметрии,
|
||
предстартовые пороги АД/пульса, RPE/боль по сетам, правила
|
||
координации 1–6 (Ф-13, изменение 1).
|
||
- **Вне границ v1:** LLM-планирование — еженедельный контур (v2,
|
||
опросник 4.6), мезоциклы и «генератор+ревьюер» (v2.x); wake-word
|
||
«Феникс, записывай» (v2, опросник 4.5); iOS (v2, опросник 4.2);
|
||
экспорт FIT из приложения и доставка программ на часы (v2);
|
||
трековые виды спорта — бег/вело (v3+, опросник 4.7); полная
|
||
автоматическая смена программы без подтверждения (vision 2.4);
|
||
саморегистрация атлетов и публичный доступ (vision 2.4, изменение
|
||
1.1); контрольные тесты (Руфье, Штанге—Генчи, тест ходьбы) и полная
|
||
утренняя телеметрия с правилами 7–10 (v2/v3 — документ метрик, п. 5).
|
||
|
||
## 2. Состав системы, точки входа и режимы
|
||
|
||
Система — три компонента. Каждый работает автономно от остальных
|
||
(vision 2.2, «независимость частей»). Клиентская часть одноатлетная
|
||
(изменение vision 1.1): один телефон + одни часы принадлежат одному
|
||
атлету; множество атлетов поддерживается на сервере — каждый своим
|
||
API-ключом.
|
||
|
||
### 2.1. Часы — поле данных «Феникс» (Monkey C, Connect IQ)
|
||
|
||
Точка входа: добавление поля данных в штатный профиль «Силовая»
|
||
(решение — опросник 4.1). Отдельных режимов нет: поле живёт в течение
|
||
активности. Кнопки не перехватываются (Lap и Start/Stop обрабатывает
|
||
Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных переносимый: сборки под
|
||
разные модели Garmin — конфигурациями одного проекта (разметка под
|
||
размеры/разрешения экранов); перечень моделей — открытый вопрос 8.
|
||
Якорное устройство — Fenix 6X (уточнение владельца 2026-10-17:
|
||
в ТЗ указан Fenix 6/260×260 — описка исходника; изменение vision 1.5).
|
||
|
||
### 2.2. Телефон — приложение «Феникс» (Android, Kotlin)
|
||
|
||
Точка входа: значок приложения. Режимы — экраны: «Главная», «Активная
|
||
тренировка», «Библиотека», «История», «Программа», «Профиль»,
|
||
«Настройки». Работает офлайн целиком; сеть нужна только для
|
||
синхронизации (Ф-7).
|
||
|
||
### 2.3. Сервер — «Феникс.Server» (C# / .NET 10, PostgreSQL)
|
||
|
||
Единая точка входа: CLI `--mode serve|import|fetch`.
|
||
|
||
| Параметр | Обязательный | Умолчание | Семантика |
|
||
|---|---|---|---|
|
||
| `--mode serve` | нет | этот режим | REST API + веб-интерфейс |
|
||
| `--mode import` | нет | выкл | разовый импорт .fit/TCX/GPX из папки импорта и выход |
|
||
| `--mode migrate` | нет | выкл | применение миграций из `2-db\migrations\` и выход (журнал — `src.migration`); коды возврата — как у import |
|
||
| `--mode fetch` | нет | выкл | разовый забор .fit из Garmin Connect и выход (расписание — внешний планировщик, открытый вопрос 4) |
|
||
| `--config` | нет | `appsettings.json` рядом с исполняемым файлом | путь к файлу настроек |
|
||
|
||
**Коды возврата (`--mode import|fetch`):** `0` — успех (в т.ч. «новых
|
||
файлов нет»); `1` — авария конфигурации; `2` — частичный сбой (часть
|
||
файлов не обработана, детали в логе).
|
||
|
||
## 3. Настройки (конфигурация)
|
||
|
||
🛡 Все пути, адреса и секреты настраиваемые — не зашиты в код.
|
||
|
||
**Телефон (`appsettings`/Preferences):**
|
||
|
||
| Параметр | Семантика | Умолчание |
|
||
|---|---|---|
|
||
| `Server.BaseUrl` | адрес сервера для синхронизации | — (обязательный) |
|
||
| `Server.ApiKey` | статический API-ключ | — (обязательный) |
|
||
| `Sync.WifiOnly` | синхронизация только по Wi-Fi: `1`/`0` | `0` |
|
||
| `Hr.MaxBpm` | максимальная ЧСС для расчёта зон; пусто — расчёт по формуле (открытый вопрос 1) | пусто |
|
||
| `Voice.Engine` | офлайн-движок распознавания (фиксируется шагом 5, открытый вопрос 2) | шаг 5 |
|
||
| `Voice.Language` | язык команд | `ru` |
|
||
| `Rest.DefaultSec` | отдых по умолчанию, если не задан командой | `120` |
|
||
| `Safety.BpCancel` | порог отмены тренировки по АД (сист./диаст.) | `150/90` |
|
||
| `Safety.BpCardio` | порог «только кардио» по АД | `140/90` |
|
||
| `Safety.RestingHrSitting` | порог пульса сидя для переноса тренировки | `90` |
|
||
| `Safety.HrStrengthMax` | порог пульса в силовой части (предупреждение) | `120` |
|
||
| `Safety.LightWeekHrRise` | рост пульса покоя (2 дня) для лёгкой недели | `+8` |
|
||
|
||
**Сервер (`appsettings.json`):**
|
||
|
||
| Параметр | Семантика | Умолчание |
|
||
|---|---|---|
|
||
| `Api.Key` | статический API-ключ по умолчанию (владелец) | — (обязательный) |
|
||
| `Api.Athletes` | список атлетов: `{ name, apiKey }` — ключ идентифицирует атлета (изменение vision 1.1; управление — через веб-консоль, Ф-9.1) | — (минимум владелец) |
|
||
| `Admin.Login / Admin.PasswordHash` | учётные данные владельца для входа в веб-консоль (пароль — только хэш, изменение vision 1.2) | — (обязательные) |
|
||
| `Api.Port` | порт HTTPS | `8443` |
|
||
| `Db.ConnectionString` | подключение к PostgreSQL | — (обязательный) |
|
||
| `Garmin.Login / Garmin.Password` | учётная запись Garmin Connect (решение — опросник 4.3) | — (обязателен для `fetch`) |
|
||
| `Garmin.DownloadDir` | папка хранения скачанных .fit | `data\fit` |
|
||
| `Import.Dir` | папка импорта fallback (ручной экспорт, опросник 4.3) | `data\import` |
|
||
| `Import.ArchiveDir` | папка обработанных файлов импорта | `data\import_done` |
|
||
| `Progression.BaseStepKg` | шаг прогрессии веса, кг | `2.5` |
|
||
| `Progression.IsoStepKg` | шаг прогрессии изоляции, кг (умолчание 1,0; валидация 0,25–5 — коридор метрик «+0,5–1 кг»; решение владельца О-3-А) | `1.0` |
|
||
| `Progression.BackoffPercent` | снижение при недовыполнении, % | `5` |
|
||
| `Deload.MaxWeeks` | недель между делоадами | `6` |
|
||
| `Safety.BpCancel` | порог отмены тренировки по АД (сист./диаст.) | `150/90` |
|
||
| `Safety.BpCardio` | порог «только кардио» по АД | `140/90` |
|
||
| `Safety.RestingHrSitting` | порог пульса сидя для переноса тренировки | `90` |
|
||
| `Safety.HrStrengthMax` | порог пульса в силовой части (для `red_flags`) | `120` |
|
||
| `Safety.LightWeekHrRise` | рост пульса покоя (2 дня) для лёгкой недели | `+8` |
|
||
| `Log.Path / Log.Level` | логи | `logs\`, `Info` |
|
||
|
||
🛡 Пароль Garmin и API-ключ — только в настройках, не в коде; логи их
|
||
не содержат (З-2, З-6).
|
||
|
||
## 4. Функциональные требования
|
||
|
||
### Ф-1. Голосовой учёт подходов (телефон)
|
||
|
||
1. Активация — кнопкой микрофона на экране активной тренировки
|
||
(решение — опросник 4.5); повторное нажатие выключает прослушивание.
|
||
Пока экран активной тренировки открыт, распознавание работает
|
||
офлайн, без сети.
|
||
2. Грамматика команд (голос → семантика):
|
||
- «Подход **W** на **R**» → подход: вес `W` кг × `R` повторений
|
||
(вес — целые кг либо «…с половиной» → +0,5 кг, шаг 0,5;
|
||
`W` — 0–500, `R` — 1–100; решение владельца О-2-Б);
|
||
- «Вес **W**» → рабочий вес `W` кг для следующих подходов;
|
||
- «Отдых **S** (секунд)» → таймер отдыха `S` секунд (5–600);
|
||
- «РПЭ **N**» → RPE последнего подхода, N 1–10 (Ф-13.3, З-8).
|
||
3. Команда вне грамматики или с числом вне диапазона — отклоняется:
|
||
короткий сигнал ошибки, состояние не меняется, в лог пишется
|
||
распознанный текст.
|
||
4. Принятая команда: сохраняется в текущую тренировку, проигрывается
|
||
короткий «щелчок» и голосовое подтверждение («Принято, подход
|
||
120 на 8» — TTS, офлайн).
|
||
5. 🛡 Каждая команда фиксируется в журнале команд: исходный
|
||
распознанный текст, семантика, время (секунды от начала тренировки)
|
||
— журнал нужен для сопоставления с .fit (Ф-7.6) и не редактируется.
|
||
6. Правка записанного подхода — не более 2 касаний (тап по подходу →
|
||
правка веса/повторов), КУ-1.
|
||
|
||
### Ф-2. Поле данных на часах (Monkey C)
|
||
|
||
1. Метрики на экране: текущий пульс (шрифт 40–48 pt), средний пульс
|
||
текущего подхода (от начала подхода до текущего момента), время
|
||
текущего подхода (под нагрузкой).
|
||
2. Зона пульса — цвет ободка/фона: синий < 50 %, зелёный 50–70 %,
|
||
жёлтый 70–85 %, красный > 85 % от `Hr.MaxBpm`; границы — из настроек
|
||
поля данных (Connect IQ settings; автоматическая передача с телефона
|
||
— открытый вопрос 3).
|
||
3. Границы подхода: поле считает подход открытым от штатного события
|
||
нового подхода (Strength Set) до события Lap; метрики подхода
|
||
обнуляются на границе.
|
||
4. Вибрация: однократная лёгкая — конец таймера отдыха, переданного
|
||
с телефона (если связь есть); двойная — выход пульса за красную
|
||
зону. При отсутствии связи с телефоном вибраций отдыха нет —
|
||
часы не блокируются (не зависимость от телефона).
|
||
5. Кнопка Lap — штатная: фиксирует конец подхода/круга в записи
|
||
активности (требование — метки Lap попадают в .fit, КУ-2).
|
||
6. Кнопка Start/Stop — штатная пауза/стоп активности; поле не требует
|
||
подтверждений и не блокирует штатное сохранение тренировки.
|
||
7. 🛡 Поле не пишет в сеть и не хранит историю: только отображение;
|
||
запись ведёт сама платформа Garmin.
|
||
8. Разметка экрана — на ресурсы макета по семействам разрешений;
|
||
функциональное поведение (Ф-2.1–Ф-2.7) одинаково на всех целевых
|
||
моделях; функциональность, отсутствующая на модели (например,
|
||
вибрация), молча пропускается — поле не падает (изменение vision
|
||
1.1).
|
||
|
||
### Ф-3. Экран активной тренировки (телефон)
|
||
|
||
1. Статусная строка: связь с часами (зелёный/красный/серый — серый =
|
||
связь недоступна, приложение не блокируется), заряд часов (если
|
||
передаётся), статус сети (онлайн/офлайн).
|
||
2. Пульс: крупное текущее значение + линия за последний подход;
|
||
источник — канал телефон ↔ часы (открытый вопрос 3). Если связи
|
||
нет — прочерк; запись голосом работает (Ф-1.1).
|
||
3. Таймер отдыха: обратный отсчёт, фон — красный (идёт отдых),
|
||
жёлтый (последние 10 с), зелёный (время начинать); старт — командой
|
||
«Отдых S» или кнопкой (умолчание `Rest.DefaultSec`).
|
||
4. Блок упражнения: текущее упражнение (из активной программы, Ф-11.2)
|
||
и текущий рабочий вес.
|
||
5. Кнопки нижней панели: микрофон (Ф-1.1), «Сбросить» (отмена текущей
|
||
тренировки с подтверждением — тренировка помечается `cancelled`
|
||
и сохраняется в историю).
|
||
6. На экране одновременно ≤ 4 активных элемента, тёмная тема,
|
||
крупные зоны касания (vision 2.2); UI/UX-стандарт
|
||
(`3-tech\ui-ux-recommendations.md`): шрифт ≥ 16 pt, контраст WCAG AA,
|
||
цели касания ≥ 44 pt, действия одной руки — в нижней трети экрана;
|
||
числовой ввод — пикеры с шагом 2,5 кг (не клавиатура), значение по
|
||
умолчанию — последний использованный вес; цветовые сигналы
|
||
дублируются текстом/иконкой (не только цветом).
|
||
|
||
### Ф-4. Библиотека упражнений (телефон, синхронизируется на сервер)
|
||
|
||
1. Сущность «Упражнение»: название (уникальное), двигательная группа
|
||
(жим/тяга/ноги/… — из фиксированного справочника, нужна для правил
|
||
замены и тоннажа), признак «базовое/изоляция».
|
||
2. Операции: добавить, переименовать, удалить. 🛡 Удаление упражнения,
|
||
на которое ссылаются программа или тренировки, — только логическое
|
||
(`is_active=0`): история остаётся читаемой.
|
||
3. 🛡 Идентификаторы упражнений (`exercise_id`) неизменяемы: программы
|
||
и факты ссылаются по ID (дизайн LLM-планирования, разд. 2).
|
||
4. 🛡 Библиотека принадлежит атлету (изоляция, изменение vision 1.1):
|
||
упражнения другого атлета недоступны; общий справочник на всех —
|
||
кандидат v2 (открытый вопрос 11).
|
||
|
||
### Ф-5. История тренировок (телефон)
|
||
|
||
1. Список завершённых тренировок: дата/время, длительность, средний
|
||
пульс, число подходов, названия упражнений.
|
||
2. Карточка тренировки: список подходов (упражнение, вес × повторения),
|
||
пометки источника («голос», «часы», «правка»), средний пульс
|
||
по подходам.
|
||
3. Офлайн список строится из локальной БД (без сети).
|
||
|
||
### Ф-6. Профиль и зоны пульса (телефон)
|
||
|
||
1. Поля профиля: пол, возраст, вес (ввод однократно, редактируется);
|
||
хранение — локально, синхронизируется на сервер (`athlete_state`).
|
||
2. Зоны: 50/70/85 % от `Hr.MaxBpm` (п. 3); расчёт по умолчанию —
|
||
формула максимальной ЧСС (открытый вопрос 1).
|
||
3. Зоны используются телефоном (график, статусы) и передаются на часы
|
||
в объёме, доступном каналу (открытый вопрос 3).
|
||
|
||
### Ф-7. Офлайн-учёт и синхронизация (телефон ↔ сервер)
|
||
|
||
1. 🛡 Все данные сначала пишутся в локальную БД телефона; сеть не стоит
|
||
на пути записи (аналог КУ-3).
|
||
2. Очередь `sync_pending`: каждая новая/изменённая сущность получает
|
||
`client_guid` (UUID v4, генерирует телефон) и попадает в очередь;
|
||
порядок — по времени создания; отправка — пакетами ≤ 50 записей.
|
||
🛡 Атлета определяет API-ключ: все записи пакета пишутся на сервере
|
||
от его имени; телефон не передаёт идентификатор атлета в данных.
|
||
🛡 Каждая клиентская сущность идентифицируется UUID — первичным
|
||
ключом и на телефоне, и на сервере (Р2-Б); UUID генерирует телефон
|
||
(строки «только .fit» — сервер); ссылки дочерних записей — по тем же
|
||
UUID, отдельный маппинг серверных id не нужен.
|
||
3. Отправка: `POST /api/v1/sync` (пакет записей + `client_guid` каждой);
|
||
успех записи на сервере подтверждается ответом (принятые GUID);
|
||
подтверждённые удаляются из очереди. Неудача сети — тихий баннер
|
||
«Синхронизация временно недоступна», повтор по восстановлению сети
|
||
(и по `Sync.WifiOnly`).
|
||
4. 🛡 Идемпотентность: сервер считает запись по `client_guid` — повтор
|
||
пакета не создаёт дублей (КУ-3); ответ идемпотентен.
|
||
5. После первой успешной синхронизации — тихий баннер «Данные успешно
|
||
синхронизированы».
|
||
6. Восстановление связи с часами: телефон запрашивает пропущенный
|
||
период пульса (в объёме возможностей канала — открытый вопрос 3)
|
||
и сопоставляет его с локально записанными подходами по времени
|
||
(границы подхода из журнала команд Ф-1.5); пульс, не попавший
|
||
в окно подхода, к подходам не относится.
|
||
7. Конфликтов правок нет: один пользователь, единственный источник
|
||
правки — телефон; сервер не меняет факт-записи (🛡).
|
||
|
||
### Ф-8. Сервер: забор и парсинг .fit, REST API
|
||
|
||
1. `--mode fetch` / расписание: неофициальный клиент Garmin Connect под
|
||
учётной записью владельца (решение — опросник 4.3) скачивает .fit
|
||
новых силовых тренировок в `Garmin.DownloadDir` (список уже
|
||
скачанных — по `fit_file_id` в БД; повторный запуск не качает
|
||
дубли — 🛡).
|
||
2. Парсинг .fit (Garmin FIT SDK): извлекаются `Session` (тип
|
||
`strength_training`, время), `Lap` (границы кругов/подходов),
|
||
`Record` (дискретный пульс), `Set` (повторения/вес, если есть);
|
||
результат — канонический JSON тренировки в `workout_facts`
|
||
(дизайн LLM-планирования, разд. 4).
|
||
3. Импорт (`--mode import`): файлы из `Import.Dir` — .fit, .tcx,
|
||
.gpx (по документу форматов) и пакет программы
|
||
`program-import.json` (формат — `3-tech\program-import-format.md`,
|
||
закрытый вопрос 12): пакет маппится на `src.program`/
|
||
`src.program_version.body` по `external_id`; повторный импорт
|
||
пакета с тем же `external_id` — новая версия `program_version`.
|
||
Файлы и пакеты парсятся тем же конвейером; обработанные
|
||
перемещаются в `Import.ArchiveDir`; необработанные остаются
|
||
с записью в логе (код возврата 2). Секция `profile` пакета
|
||
в v1 не переносится (Safety-порогами управляет только конфиг
|
||
`Safety.*`).
|
||
4. Сопоставление факт ↔ голосовой учёт: тренировка .fit сопоставляется
|
||
с тренировкой телефона по времени начала (± 15 минут) —
|
||
сопоставлённая пара связывается; несопоставленные остаются
|
||
раздельными (сопоставление не удаляет и не переписывает данные
|
||
— 🛡).
|
||
5. REST API (HTTPS, заголовок `X-Api-Key` = ключ атлета; без ключа или
|
||
с чужим ключом — `401`): `POST /api/v1/sync` (Ф-7.3);
|
||
`GET /api/v1/workouts` (список с пагинацией); `GET /api/v1/workouts/{id}`
|
||
(карточка); `GET /api/v1/programs/active` (активная версия программы).
|
||
🛡 Каждой запрос видит данные только своего атлета (изоляция,
|
||
изменение vision 1.1).
|
||
6. 🛡 API пишет факт-данные только из sync и импорта; ручного ввода
|
||
на сервере нет.
|
||
|
||
### Ф-9. Веб-интерфейс сервера — административная консоль владельца
|
||
|
||
🛡 Доступ в веб — только для владельца, вход по логину и паролю
|
||
(`Admin.Login` / `Admin.PasswordHash` из настроек; открытый вопрос 6
|
||
закрыт); атлеты работают только через телефон (изменение vision 1.2).
|
||
Разделы консоли:
|
||
|
||
1. **Атлеты** (управление, открытый вопрос 9 закрыт этим решением):
|
||
список, добавление атлета (имя + генерация API-ключа), смена ключа,
|
||
блокировка (заблокированный ключ → `401` на API).
|
||
2. **Тренировки**: таблица по всем атлетам с фильтром по атлету и дате
|
||
(дата, длительность, средний пульс, подходы, упражнений), сортировка,
|
||
пагинация; карточка тренировки: подходы, отчёт план/факт (Ф-11.4);
|
||
**ход тренировки** — таймлайн из .fit (границы подходов по Lap,
|
||
дискретный пульс по Record): начало/конец и длительность каждого
|
||
подхода, средний и пиковый пульс подхода, отдых после подхода,
|
||
пульсограмма за всю тренировку; выгрузка CSV текущей выборки (КУ-4).
|
||
3. **Программы**: выбор атлета → список версий (статусы, автор, даты);
|
||
создание и правка версий `draft`; смена активной версии (Ф-10.3);
|
||
просмотр предложений правил (Ф-12.3) — применение предложения
|
||
из консоли помечает версию `author=admin`.
|
||
4. **Метрики и динамика**: по атлету и периоду — adherence, тоннаж
|
||
по группам, динамика 1ПМ и рабочего веса по упражнениям (графики
|
||
по неделям из `workout_metrics`); у каждой тренировки карточки —
|
||
место в тренде (лучше/хуже предыдущей).
|
||
5. **Планы тренировок** (план ↔ факт): расписание активной версии
|
||
программы атлета по неделям и дням; по каждой запланированной
|
||
тренировке — планируемые результаты (упражнения, подходы, вес ×
|
||
повторения, целевой объём) против фактических (выполнена/пропущена,
|
||
подходы, веса, тоннаж, adherence); сводка выполнения плана за неделю
|
||
и мезоцикл (доля запланированных/выполненных/пропущенных).
|
||
|
||
### Ф-10. Программы тренировок (сервер + телефон)
|
||
|
||
1. Программа — версионированный JSON-документ (схема — дизайн
|
||
LLM-планирования, разд. 2): недели → тренировки (день, фокус) →
|
||
упражнения (`exercise_id` из библиотеки) → подходы-план (вес,
|
||
повторения, `target_rpe`, отдых). Хранение — таблица `programs`
|
||
(PostgreSQL); каждая версия неизменяема (🛡). 🛡 Программы принадлежат
|
||
атлету — у каждого свои (изменение vision 1.1).
|
||
2. В v1 создание/правка версии — на телефоне (редактор программы
|
||
атлета, `author=user`) или в административной консоли (владелец,
|
||
`author=admin`, изменение vision 1.2); отправка на сервер — при
|
||
синхронизации телефона либо сразу из консоли.
|
||
3. Ровно одна версия в статусе `active` (🛡); смена активной — явным
|
||
действием пользователя; выполненные тренировки ссылаются на версию,
|
||
которая была активна на их начало (история не «поплывёт»).
|
||
4. Статусы версии: `draft` → `active` → `archived`; переходы —
|
||
действиями пользователя.
|
||
|
||
### Ф-11. Контроль исполнения программы (план/факт)
|
||
|
||
1. На экране активной тренировки — текущая позиция активной программы:
|
||
упражнение, план подходов (вес × повторения), отметка выполненных.
|
||
2. Подход фиксируется голосом (Ф-1) и помечается: «по плану» (вес
|
||
и повторения = план ± допустимое отклонение: вес ± 2,5 кг,
|
||
повторения ± 0) или «отклонение» (всё остальное).
|
||
3. По завершении тренировки — отчёт план/факт: по каждому упражнению
|
||
план (подходы × вес × повторения) против факта, доля закрытых
|
||
подходов (adherence) (КУ-5).
|
||
4. Отчёт доступен на телефоне и в веб-интерфейсе (Ф-9.2).
|
||
|
||
### Ф-12. Правила прогрессии (без LLM, v1)
|
||
|
||
1. После каждой синхронизированной тренировки MetricsEngine (сервер)
|
||
считает по данным этого атлета: adherence (доля закрытых подходов),
|
||
тоннаж по упражнению и группе, оценочный 1ПМ (формула — открытый
|
||
вопрос 7) — в `workout_metrics` (дизайн LLM-планирования, разд. 4).
|
||
🛡 Метрики и правила считаются по каждому атлету независимо
|
||
(изоляция, изменение vision 1.1).
|
||
2. Микроправка весов на следующее занятие (правила, детерминированно):
|
||
- все запланированные подходы закрыты «по плану» при RPE ≤ 7
|
||
(2 тренировки подряд) → вес + `Progression.BaseStepKg` (базовые),
|
||
+ `Progression.IsoStepKg` (изоляция, З-27/О-3-А);
|
||
- RPE ≥ 9 в 2+ сетах → вес − `Progression.BackoffPercent` %;
|
||
- повторения достигли верхней границы диапазона (15–20) → вес ↑,
|
||
повторения к 12;
|
||
- ≥ 2 подходов не дотянуты по повторениям → вес −
|
||
`Progression.BackoffPercent` %;
|
||
- иначе (частичное выполнение) — вес без изменения.
|
||
Источник порогов — документ метрик («Метрики_тренировок_и_
|
||
самоконтроля.md», правила 3–5, МакРоберт).
|
||
3. Правила применяются только к `active` версии; результат —
|
||
предложение следующей версии программы со статусом `draft`
|
||
(`author=rules`), показывается атлету в приложении диффом;
|
||
применение — подтверждением атлета на телефоне или владельцем
|
||
в консоли (Ф-9.3, изменение vision 1.2). 🛡 Автоматической смены
|
||
активной версии нет (vision 2.4).
|
||
4. Делоад: если `Deload.MaxWeeks` недель подряд без делоада, правило
|
||
предлагает делоад-версию (объём −40–50 %) — тем же механизмом
|
||
диффа (дизайн LLM-планирования, разд. 6).
|
||
5. Красные флаги и лёгкая неделя (документ метрик, правила 1, 6, v1);
|
||
источник данных — `daily_log` (тренд пульса покоя и самочувствия
|
||
за 2 дня, Р1-А):
|
||
- пульс покоя ≥ обычного +8 уд/мин два дня подряд или самочувствие
|
||
≤ 2 → следующая тренировка помечается лёгкой (60–70 % весов,
|
||
2 сета); 3+ дня — предложение отмены и совет к врачу;
|
||
- боль в колене ≥ 1 два занятия подряд → предложение замены
|
||
упражнения из той же группы (пара-замена из атрибутов упражнения).
|
||
|
||
6. Офлайн-режим сервера: правила выполняются при следующем запуске
|
||
`serve`/`import` — телефон при этом полнофункционален (учёт и
|
||
локальные планы работают без сервера).
|
||
|
||
### Ф-13. Самоконтроль и допуск к тренировке (телефон; изменение 1)
|
||
|
||
Источник порогов — документ метрик
|
||
(`..\..\Метрики_тренировок_и_самоконтроля.md`, Жижин гл. 2–3, 6;
|
||
Аттия гл. 10, 13; МакРоберт; CDC). Состав v1 — MVP-приоритет документа
|
||
(его п. 5).
|
||
|
||
1. **Дневник-минимум (день, дом, 2–3 минуты):** АД (сист./диаст.),
|
||
вес тела, пульс покоя (утро, до подъёма — Р1-А), самочувствие 1–5;
|
||
экран «Сегодня» с вводом за 2–3 касания. Данные — в `LocalDb`
|
||
и на сервер (`daily_log`); пульс покоя и самочувствие — данные
|
||
правила 1 (Ф-12.5, КУ-7).
|
||
2. **Предстартовый допуск:** перед стартом силовой тренировки — экран
|
||
допуска: пульс сидя (с часов, если связаны, иначе ввод вручную)
|
||
и АД за сегодня. Пороги:
|
||
- АД > 150/90 → тренировка не начинается: предложение прогулки,
|
||
кнопка «Отменить» (🛡);
|
||
- АД 140–150/90–95 → только лёгкое кардио/дыхание (силовые заблокированы);
|
||
- пульс сидя > 90 уд/мин → предложение перенести тренировку
|
||
(старт — явным подтверждением «всё равно начать»).
|
||
3. **По сетам:** RPE (цель 6–8) — голосом «РПЭ **N**» для последнего
|
||
подхода или тапом 1–10; боль в колене 0–10 — тапом, только для
|
||
упражнений группы «ноги»; сохраняется с подходом (`workout_set`).
|
||
4. **После тренировки:** время возврата пульса к < 90 уд/мин — считается
|
||
автоматически из .fit (Record после последнего Lap); показывается
|
||
в карточке и попадает в метрики.
|
||
5. 🛡 Пороги допуска — конфигурируемые в настройках телефона
|
||
(`Safety.*`, умолчания — документ метрик; Р3-А); серверные
|
||
`Safety.*` применяются серверными правилами (`red_flags`);
|
||
обход блокировки «Отменить» логируется.
|
||
6. Талия и сон (утренняя телеметрия) и правила координации 7–10 — v2;
|
||
контрольные тесты — v3 (документ метрик, п. 5).
|
||
7. **Во время силовой части:** пульс > `Safety.HrStrengthMax`
|
||
(умолчание 120) → предупреждение на телефоне (баннер + вибро,
|
||
спокойная формулировка по UI/UX-стандарту); на часы порог
|
||
передаётся в объёме канала (ОВ-3). Источник — документ метрик
|
||
(п. 2.3); значение перенесено в параметр конфигурации
|
||
`Safety.HrStrengthMax` (разд. 3, умолчание 120). Программа
|
||
тренировок — импортируемые данные атлета (см. открытый вопрос 12);
|
||
нормативным источником требований не является (З-30, уточнение
|
||
владельца 2026-10-17).
|
||
|
||
### Ф-14. Перерывы и возврат в режим (телефон + сервер; изменение 2)
|
||
|
||
Источник — документ `Перерывы_подводные_камни_и_рекомендации.md`
|
||
(МакРоберт, CDC «Staying on Track», Жижин гл. 5–6, Аттия). Модель:
|
||
`active → break → return → active`.
|
||
|
||
1. 🛡 **Break вычисляется автоматически:** пропущены все
|
||
запланированные сессии за 4 дня подряд → программа переводится
|
||
в статус `break` (Жижин гл. 6: эффект тает с 4-го дня). Break ставится
|
||
автоматически по пропускам (основной путь); атлет может объявить
|
||
перерыв заранее (Ф-14.2, «уезжаю в отпуск») — такой break создаётся
|
||
с `source=manual`; произвольного ручного включения без объявления
|
||
нет.
|
||
2. **Причина запрашивается у атлета:** при срабатывании автомат
|
||
показывает вопрос «Вы пропустили тренировки — что случилось?»
|
||
с выбором типа (болезнь / отпуск / аврал-недосып / травма /
|
||
другое) и предполагаемой даты возврата; до ответа тип —
|
||
«не указана» (правила 12–14 применяются после ответа). При типе
|
||
«болезнь с температурой» — минимум 3 дня после нормальной
|
||
температуры до первой сессии (правило 12). Атлет может объявить
|
||
break заранее («уезжаю в отпуск») — запланированный простой
|
||
попадает в тот же автомат. Примечание (З-39, решение владельца
|
||
2026-10-18): признака «с температурой» в данных `breaks` нет —
|
||
в v1 любой break `kind=illness` трактуется правилом возврата как
|
||
перезапуск (коэффициент нижней границы, spec 04 m05); различение
|
||
«с температурой» — кандидат v2.
|
||
3. **Формула возврата** (детерминированная, коэффициент от рабочего
|
||
веса, документ перерывов разд. 3.2):
|
||
|
||
| Простой | Коэффициент | Режим |
|
||
|---|---|---|
|
||
| 4–6 дней | 100 % | первая сессия: −1 подход, RPE-цель 5–6 |
|
||
| 7–13 дней | −10 % | 1–2 недели возврата |
|
||
| 14–27 дней | −15 % | 1 вводная неделя (техника, без отказа) |
|
||
| 28–59 дней | −25 % | 2 вводные недели |
|
||
| 60+ дней / болезнь с температурой / операция | −30…−40 % | перезапуск цикла, контроль АД первые 2 недели |
|
||
|
||
Кардио (зона 2) возвращается со 100 % длительности сразу
|
||
(митохондриальная пластичность); силу — по формуле.
|
||
4. **Return:** при первой сессии после break создаётся новая версия
|
||
программы с формульными весами (статус `draft`, `author=rules`,
|
||
`rationale` — причина снижения); подтверждение — по Ф-12.3. 🛡
|
||
Неизменяемость версий не нарушается.
|
||
5. **`session_kind`:** каждая тренировка помечается
|
||
`normal | return | deload | test` — снижение весов при возврате
|
||
не выглядит в истории как регресс.
|
||
6. **Домашний режим:** тип break «отпуск, зал недоступен» →
|
||
переключатель активирует заготовленный поддерживающий комплекс
|
||
(отжимания, приседания без веса, тяга резинки, планка, ходьба)
|
||
с пометкой «поддержание, не прогресс» (правило 14).
|
||
7. **Правила координации 12–17** (документ перерывов, разд. 3.3):
|
||
болезнь с температурой (12), аврал/недосып → средняя тренировка
|
||
вместо тяжёлой (13), домашний комплекс (14), break ≥ 14 дней →
|
||
ужесточение порогов АД на 2 недели (отмена при > 145/90) (15),
|
||
2+ перерыва ≥ 7 дней за месяц → план 2 дня/нед до adherence ≥ 80 %
|
||
(16), возврат после break ≥ 14 дней → тест-сессия при RPE ≤ 6 (17).
|
||
Считает `ProgressionRules` вместе с правилами 1–11.
|
||
|
||
### Логирование (телефон и сервер)
|
||
|
||
1. `Info`: старт/остановка тренировки, каждая команда (Ф-1.5), пакеты
|
||
синхронизации (число записей, результат), забор/парсинг .fit
|
||
(файл, результат), применённые версии программ.
|
||
2. `Error`: все ошибки с текстом исключения (этап, объект, причина).
|
||
3. 🛡 В логах нет пароля Garmin, API-ключей и содержимого речи
|
||
(только распознанный текст команды — он нужен для диагностики
|
||
распознавания).
|
||
|
||
## 5. Контракты и инварианты
|
||
|
||
- 🛡 **Независимость частей:** часы, телефон и сервер работают при
|
||
отсутствии остальных; ни одна часть не блокирует другую (БЦ-4).
|
||
- 🛡 **Офлайн-first:** запись (голос, часы) и просмотр истории никогда
|
||
не ждут сеть; сеть — только синхронизация (КУ-3).
|
||
- 🛡 **Идемпотентность:** повтор отправки пакета (по `client_guid`)
|
||
и повторный `fetch`/`import` не создают дублей (КУ-3, Ф-8.1).
|
||
- 🛡 **Неизменяемость истории:** версии программ неизменяемы; факт-записи
|
||
сервер не переписывает; удаление упражнения — только логическое.
|
||
- 🛡 **Изоляция атлетов:** API-ключ идентифицирует атлета; ни API,
|
||
ни веб не отдают атлету чужие данные; метрики, правила и программы
|
||
считаются по атлетам независимо (изменение vision 1.1).
|
||
- 🛡 **Тайны:** пароль Garmin, API-ключи и хэш пароля владельца — только
|
||
в настройках/БД; в логах их нет.
|
||
- 🛡 **Безопасность нагрузки:** автоматические изменения программы
|
||
ограничены лимитами правил (Ф-12.2, Ф-12.4); смена активной версии —
|
||
только вручную (БР-5, vision 2.4).
|
||
- **Отмена тренировки:** «Сбросить» сохраняет тренировку как
|
||
`cancelled` — молчаливой потери данных нет.
|
||
|
||
## 6. Трассировка
|
||
|
||
| Функция | Бизнес-цель | Критерий успеха | Версия |
|
||
|---|---|---|---|
|
||
| Ф-1 голосовой учёт | БЦ-1 | КУ-1 | v1 |
|
||
| Ф-2 поле данных на часах | БЦ-2 | КУ-2 | v1 |
|
||
| Ф-3 экран тренировки | БЦ-1, БЦ-2 | КУ-1 | v1 |
|
||
| Ф-4 библиотека | БЦ-3 | — | v1 |
|
||
| Ф-5 история (телефон) | БЦ-3 | — | v1 |
|
||
| Ф-6 профиль и зоны | БЦ-2 | КУ-2 | v1 |
|
||
| Ф-7 офлайн + синхронизация | БЦ-4 | КУ-3 | v1 |
|
||
| Ф-8 сервер (.fit, API) | БЦ-5 | КУ-2, КУ-4 | v1 |
|
||
| Ф-9 веб + CSV | БЦ-5 | КУ-4 | v1 |
|
||
| Ф-10 программы | БЦ-3 | КУ-5 | v1 |
|
||
| Ф-11 план/факт | БЦ-3 | КУ-5 | v1 |
|
||
| Ф-12 правила прогрессии | БЦ-3 | КУ-6 | v1 |
|
||
| Ф-13 самоконтроль и допуск | БЦ-6 | КУ-7 | v1 |
|
||
| Ф-12 LLM-контур (еженедельно) | БЦ-3 | — | v2 |
|
||
| Ф-13 полная телеметрия, правила 7–10 | БЦ-6 | — | v2 |
|
||
| Ф-14 перерывы и возврат | БЦ-3, БЦ-6 | КУ-6, КУ-7 | v1 |
|
||
| Контрольные тесты | БЦ-6 | — | v3 |
|
||
| Wake-word (Ф-1) | БЦ-1 | — | v2 |
|
||
| iOS | БЦ-1, БЦ-2 | — | v2 |
|
||
| Бег/вело | — | — | v3+ |
|
||
|
||
## 7. Открытые вопросы
|
||
|
||
| # | Вопрос | Контекст | Решение |
|
||
|---|---|---|---|
|
||
| 1 | Формула максимальной ЧСС | Умолчание для зон (Ф-6.2): 220 − возраст (классика) или Танака (208 − 0,7×возраст, точнее для пожилых); можно — ручной ввод `Hr.MaxBpm` | Закрыт шагом 5 |
|
||
| 2 | Офлайн-движок распознавания | Vosk (модель ~50 МБ, ru) против sherpa-onnx; грамматика цифр шумного зала; влияет на точность КУ-1. Кандидаты проверяются прототипом | Закрыт шагом 5 |
|
||
| 3 | Канал телефон ↔ часы | Connect IQ companion (Communications): что передаётся реально (пульс live, зоны, таймер отдыха, заряд); ограничения канала определяют Ф-2.4, Ф-3.2, Ф-6.3. Прототип на шаге 4 | Закрыт шагом 4 |
|
||
| 4 | Расписание `fetch` | cron/systemd-таймер на сервере против внутреннего планировщика; предпочтителен внешний планировщик | Закрыт шагом 5 |
|
||
| 5 | Способ сопоставления тренировок телефон ↔ .fit | Окно ± 15 минут (Ф-8.4) против точной метки начала из sync | ✅ Закрыт в этом документе (раздел 4, Ф-8.4) |
|
||
| 6 | Доступ в веб-интерфейс | ✅ Закрыт изменением vision 1.2: веб — административная консоль владельца, вход по логину и паролю (раздел 4, Ф-9); атлеты — только телефон | ✅ Закрыт (изменение vision 1.2) |
|
||
| 7 | Формула оценочного 1ПМ | Эпли против Бжицки (разд. 4 дизайна LLM-планирования); различие ~5 % на малых повторениях | Закрыт шагом 5 |
|
||
| 8 | Перечень моделей часов v1 | Якорь — Fenix 6X, 280×280 (изменение vision 1.1; уточнение владельца 2026-10-17 — vision 1.5); каждая модель — конфигурация сборки (экран, память). Кандидаты: модели с профилем «Силовая» и Connect IQ 4+ у участников; список фиксируется по фактическому парку устройств | Закрыт шагом 4 |
|
||
| 9 | Способ заведения атлетов | ✅ Закрыт изменением vision 1.2: раздел «Атлеты» веб-консоли владельца (Ф-9.1) — имя + генерация ключа, смена ключа, блокировка; стартовые данные владельца — в конфиге | ✅ Закрыт (изменение vision 1.2) |
|
||
| 10 | Источник .fit для атлетов без учётки владельца | `fetch` идёт под учётной записью владельца (опросник 4.3); другие атлеты: (а) делят доступ к своему Garmin (передача файла в папку импорта), (б) свой экземпляр учётки в конфиге, (в) только ручной импорт. Кандидат для v1 — (в) папка импорта | ✅ Закрыт (ревью №2, О-1-А): v1 — только папка импорта (`--mode import`); `fetch` — под единственной учёткой владельца; собственные учётки атлетов в конфиге — кандидат v2 (tech-requirements, ОВ-10). Уточнение владельца: атлет регистрируется на телефоне (имя), привязка — по uuid клиентского приложения |
|
||
| 12 | Импорт пакета программы (`program-import.json`) | Пакет — механизм эксплуатации (уточнение владельца 2026-10-17); формат, маппинг на `src.program`/`program_version.body`, поведение при повторном импорте | ✅ Закрыт (ревью №3, О-4-Б): импорт в v1 через `--mode import`; формат — `3-tech\program-import-format.md`; `external_id` — идентификатор пакета; секция `profile` в v1 игнорируется |
|
||
| 11 | Общий справочник упражнений | Сейчас библиотека у каждого атлета своя (Ф-4.4); общий справочник + личные дополнения — кандидат v2 (дубли при малом числе атлетов не критичны) | Открыт (до v2) |
|