34 KiB
Функциональные требования: Феникс 6X v1
Шаг 3 процесса разработки (
std1\_process\process.md). Источник: vision.md (утверждён 2026-10-17, v1.0), разделы 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) — несколько атлетов на сервере, разные модели часов. Версия: черновик 2 (2026-10-17). Формулировки — проверяемые (d-req-7).
1. Назначение и границы
Версия 1.0 реализует полный цикл «программа → тренировка без рук → архив → план/факт» (Ф-1–Ф-11, правила прогрессии Ф-12):
- В границах: голосовой офлайн-учёт подходов на Android-телефоне; поле данных на Fenix 6 в профиле «Силовая» (пульс, зоны, Lap); офлайн-учёт с автосинхронизацией телефон ↔ сервер; забор .fit из Garmin Connect неофициальным клиентом и парсинг на сервере; REST API со статическим ключом; веб-история с CSV; программы (версионированный JSON) с контролем «план/факт» и правилами прогрессии (без LLM); поддержка нескольких атлетов на сервере — у каждого свои программы, профиль и данные (изменение vision 1.1); поддержка разных моделей часов Garmin (мультитаргет-сборка одного поля данных).
- Вне границ 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).
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 6.
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 fetch |
нет | выкл | разовый забор .fit из Garmin Connect и выход (расписание — внешний планировщик, открытый вопрос 5) |
--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 |
Сервер (appsettings.json):
| Параметр | Семантика | Умолчание |
|---|---|---|
Api.Key |
статический API-ключ по умолчанию (владелец) | — (обязательный) |
Api.Athletes |
список атлетов: { name, apiKey } — ключ идентифицирует атлета (изменение vision 1.1; способ заведения — открытый вопрос 9) |
— (минимум владелец) |
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.BackoffPercent |
снижение при недовыполнении, % | 5 |
Deload.MaxWeeks |
недель между делоадами | 6 |
Log.Path / Log.Level |
логи | logs\, Info |
🛡 Пароль Garmin и API-ключ — только в настройках, не в коде; логи их не содержат (З-2, З-6).
4. Функциональные требования
Ф-1. Голосовой учёт подходов (телефон)
- Активация — кнопкой микрофона на экране активной тренировки (решение — опросник 4.5); повторное нажатие выключает прослушивание. Пока экран активной тренировки открыт, распознавание работает офлайн, без сети.
- Грамматика команд (голос → семантика):
- «Подход W на R» → подход: вес
Wкг ×Rповторений (целые числа;W— 0–500,R— 1–100); - «Вес W» → рабочий вес
Wкг для следующих подходов; - «Отдых S (секунд)» → таймер отдыха
Sсекунд (5–600).
- «Подход W на R» → подход: вес
- Команда вне грамматики или с числом вне диапазона — отклоняется: короткий сигнал ошибки, состояние не меняется, в лог пишется распознанный текст.
- Принятая команда: сохраняется в текущую тренировку, проигрывается короткий «щелчок» и голосовое подтверждение («Принято, подход 120 на 8» — TTS, офлайн).
- 🛡 Каждая команда фиксируется в журнале команд: исходный распознанный текст, семантика, время (секунды от начала тренировки) — журнал нужен для сопоставления с .fit (Ф-7.6) и не редактируется.
- Правка записанного подхода — не более 2 касаний (тап по подходу → правка веса/повторов), КУ-1.
Ф-2. Поле данных на часах (Monkey C)
- Метрики на экране: текущий пульс (шрифт 40–48 pt), средний пульс текущего подхода (от начала подхода до текущего момента), время текущего подхода (под нагрузкой).
- Зона пульса — цвет ободка/фона: синий < 50 %, зелёный 50–70 %,
жёлтый 70–85 %, красный > 85 % от
Hr.MaxBpm; границы — из настроек поля данных (Connect IQ settings; автоматическая передача с телефона — открытый вопрос 3). - Границы подхода: поле считает подход открытым от штатного события нового подхода (Strength Set) до события Lap; метрики подхода обнуляются на границе.
- Вибрация: однократная лёгкая — конец таймера отдыха, переданного с телефона (если связь есть); двойная — выход пульса за красную зону. При отсутствии связи с телефоном вибраций отдыха нет — часы не блокируются (не зависимость от телефона).
- Кнопка Lap — штатная: фиксирует конец подхода/круга в записи активности (требование — метки Lap попадают в .fit, КУ-2).
- Кнопка Start/Stop — штатная пауза/стоп активности; поле не требует подтверждений и не блокирует штатное сохранение тренировки.
- 🛡 Поле не пишет в сеть и не хранит историю: только отображение; запись ведёт сама платформа Garmin.
- Разметка экрана — на ресурсы макета по семействам разрешений; функциональное поведение (Ф-2.1–Ф-2.7) одинаково на всех целевых моделях; функциональность, отсутствующая на модели (например, вибрация), молча пропускается — поле не падает (изменение vision 1.1).
Ф-3. Экран активной тренировки (телефон)
- Статусная строка: связь с часами (зелёный/красный/серый — серый = связь недоступна, приложение не блокируется), заряд часов (если передаётся), статус сети (онлайн/офлайн).
- Пульс: крупное текущее значение + линия за последний подход; источник — канал телефон ↔ часы (открытый вопрос 3). Если связи нет — прочерк; запись голосом работает (Ф-1.1).
- Таймер отдыха: обратный отсчёт, фон — красный (идёт отдых),
жёлтый (последние 10 с), зелёный (время начинать); старт — командой
«Отдых S» или кнопкой (умолчание
Rest.DefaultSec). - Блок упражнения: текущее упражнение (из активной программы, Ф-11.2) и текущий рабочий вес.
- Кнопки нижней панели: микрофон (Ф-1.1), «Сбросить» (отмена текущей
тренировки с подтверждением — тренировка помечается
cancelledи сохраняется в историю). - На экране одновременно ≤ 4 активных элемента, тёмная тема, крупные зоны касания (vision 2.2).
Ф-4. Библиотека упражнений (телефон, синхронизируется на сервер)
- Сущность «Упражнение»: название (уникальное), двигательная группа (жим/тяга/ноги/… — из фиксированного справочника, нужна для правил замены и тоннажа), признак «базовое/изоляция».
- Операции: добавить, переименовать, удалить. 🛡 Удаление упражнения,
на которое ссылаются программа или тренировки, — только логическое
(
is_active=0): история остаётся читаемой. - 🛡 Идентификаторы упражнений (
exercise_id) неизменяемы: программы и факты ссылаются по ID (дизайн LLM-планирования, разд. 2). - 🛡 Библиотека принадлежит атлету (изоляция, изменение vision 1.1): упражнения другого атлета недоступны; общий справочник на всех — кандидат v2 (открытый вопрос 11).
Ф-5. История тренировок (телефон)
- Список завершённых тренировок: дата/время, длительность, средний пульс, число подходов, названия упражнений.
- Карточка тренировки: список подходов (упражнение, вес × повторения), пометки источника («голос», «часы», «правка»), средний пульс по подходам.
- Офлайн список строится из локальной БД (без сети).
Ф-6. Профиль и зоны пульса (телефон)
- Поля профиля: пол, возраст, вес (ввод однократно, редактируется);
хранение — локально, синхронизируется на сервер (
athlete_state). - Зоны: 50/70/85 % от
Hr.MaxBpm(п. 3); расчёт по умолчанию — формула максимальной ЧСС (открытый вопрос 1). - Зоны используются телефоном (график, статусы) и передаются на часы в объёме, доступном каналу (открытый вопрос 3).
Ф-7. Офлайн-учёт и синхронизация (телефон ↔ сервер)
- 🛡 Все данные сначала пишутся в локальную БД телефона; сеть не стоит на пути записи (аналог КУ-3).
- Очередь
sync_pending: каждая новая/изменённая сущность получаетclient_guid(UUID v4, генерирует телефон) и попадает в очередь; порядок — по времени создания; отправка — пакетами ≤ 50 записей. 🛡 Атлета определяет API-ключ: все записи пакета пишутся на сервере от его имени; телефон не передаёт идентификатор атлета в данных. - Отправка:
POST /api/v1/sync(пакет записей +client_guidкаждой); успех записи на сервере подтверждается ответом (принятые GUID); подтверждённые удаляются из очереди. Неудача сети — тихий баннер «Синхронизация временно недоступна», повтор по восстановлению сети (и поSync.WifiOnly). - 🛡 Идемпотентность: сервер считает запись по
client_guid— повтор пакета не создаёт дублей (КУ-3); ответ идемпотентен. - После первой успешной синхронизации — тихий баннер «Данные успешно синхронизированы».
- Восстановление связи с часами: телефон запрашивает пропущенный период пульса (в объёме возможностей канала — открытый вопрос 3) и сопоставляет его с локально записанными подходами по времени (границы подхода из журнала команд Ф-1.5); пульс, не попавший в окно подхода, к подходам не относится.
- Конфликтов правок нет: один пользователь, единственный источник правки — телефон; сервер не меняет факт-записи (🛡).
Ф-8. Сервер: забор и парсинг .fit, REST API
--mode fetch/ расписание: неофициальный клиент Garmin Connect под учётной записью владельца (решение — опросник 4.3) скачивает .fit новых силовых тренировок вGarmin.DownloadDir(список уже скачанных — поfit_file_idв БД; повторный запуск не качает дубли — 🛡).- Парсинг .fit (Garmin FIT SDK): извлекаются
Session(типstrength_training, время),Lap(границы кругов/подходов),Record(дискретный пульс),Set(повторения/вес, если есть); результат — канонический JSON тренировки вworkout_facts(дизайн LLM-планирования, разд. 4). - Fallback-импорт (
--mode import): файлы изImport.Dir(.fit, .tcx, .gpх — по документу форматов) парсятся тем же конвейером; обработанные перемещаются вImport.ArchiveDir; необработанные остаются с записью в логе (код возврата 2). - Сопоставление факт ↔ голосовой учёт: тренировка .fit сопоставляется с тренировкой телефона по времени начала (± 15 минут) — сопоставлённая пара связывается; несопоставленные остаются раздельными (сопоставление не удаляет и не переписывает данные — 🛡).
- 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). - 🛡 API пишет факт-данные только из sync и импорта; ручного ввода на сервере нет.
Ф-9. Веб-интерфейс сервера
- Страница истории: таблица тренировок (дата, длительность, средний
пульс, подходы, упражнений), сортировка по дате, пагинация;
владелец (ключ
Api.Key) видит все данные, атлет — только свои; фильтр по атлету для владельца (изменение vision 1.1). - Карточка тренировки: подходы, пульс-тренд, соответствие программе (план/факт — Ф-11.4).
- Выгрузка CSV текущей выборки (КУ-4). Доступ — по ключу атлета (форма входа — ввод ключа; открытый вопрос 6).
Ф-10. Программы тренировок (сервер + телефон)
- Программа — версионированный JSON-документ (схема — дизайн
LLM-планирования, разд. 2): недели → тренировки (день, фокус) →
упражнения (
exercise_idиз библиотеки) → подходы-план (вес, повторения,target_rpe, отдых). Хранение — таблицаprograms(PostgreSQL); каждая версия неизменяема (🛡). 🛡 Программы принадлежат атлету — у каждого свои (изменение vision 1.1). - В v1 создание/правка версии — вручную в приложении на телефоне
(редактор программы) с отправкой на сервер;
author=user. - Ровно одна версия в статусе
active(🛡); смена активной — явным действием пользователя; выполненные тренировки ссылаются на версию, которая была активна на их начало (история не «поплывёт»). - Статусы версии:
draft→active→archived; переходы — действиями пользователя.
Ф-11. Контроль исполнения программы (план/факт)
- На экране активной тренировки — текущая позиция активной программы: упражнение, план подходов (вес × повторения), отметка выполненных.
- Подход фиксируется голосом (Ф-1) и помечается: «по плану» (вес и повторения = план ± допустимое отклонение: вес ± 2,5 кг, повторения ± 0) или «отклонение» (всё остальное).
- По завершении тренировки — отчёт план/факт: по каждому упражнению план (подходы × вес × повторения) против факта, доля закрытых подходов (adherence) (КУ-5).
- Отчёт доступен на телефоне и в веб-интерфейсе (Ф-9.2).
Ф-12. Правила прогрессии (без LLM, v1)
- После каждой синхронизированной тренировки MetricsEngine (сервер)
считает по данным этого атлета: adherence (доля закрытых подходов),
тоннаж по упражнению и группе, оценочный 1ПМ (формула — открытый
вопрос 7) — в
workout_metrics(дизайн LLM-планирования, разд. 4). 🛡 Метрики и правила считаются по каждому атлету независимо (изоляция, изменение vision 1.1). - Микроправка весов на следующее занятие (правила, детерминированно):
- все запланированные подходы закрыты «по плану» → вес +
Progression.BaseStepKgна базовых, +BaseStepKg/2 на изоляции; - ≥ 2 подходов не дотянуты по повторениям → вес −
Progression.BackoffPercent%; - иначе (частичное выполнение) — вес без изменения.
- все запланированные подходы закрыты «по плану» → вес +
- Правила применяются только к
activeверсии; результат — предложение следующей версии программы со статусомdraft(author=rules), показывается в приложении диффом; применение — подтверждением пользователя (Ф-10.3). 🛡 Автоматической смены активной версии нет (vision 2.4). - Делоад: если
Deload.MaxWeeksнедель подряд без делоада, правило предлагает делоад-версию (объём −40–50 %) — тем же механизмом диффа (дизайн LLM-планирования, разд. 6). - Офлайн-режим сервера: правила выполняются при следующем запуске
serve/import— телефон при этом полнофункционален (учёт и локальные планы работают без сервера).
Логирование (телефон и сервер)
Info: старт/остановка тренировки, каждая команда (Ф-1.5), пакеты синхронизации (число записей, результат), забор/парсинг .fit (файл, результат), применённые версии программ.Error: все ошибки с текстом исключения (этап, объект, причина).- 🛡 В логах нет пароля 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 |
| Ф-12 LLM-контур (еженедельно) | БЦ-3 | — | v2 |
| 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 | Доступ в веб-интерфейс | Ввод ключа в форме против Basic Auth; личное использование — проще форма с ключом | Закрыт шагом 5 |
| 7 | Формула оценочного 1ПМ | Эпли против Бжицки (разд. 4 дизайна LLM-планирования); различие ~5 % на малых повторениях | Закрыт шагом 5 |
| 8 | Перечень моделей часов v1 | Якорь — Fenix 6 (изменение vision 1.1); каждая модель — конфигурация сборки (экран, память). Кандидаты: модели с профилем «Силовая» и Connect IQ 4+ у участников; список фиксируется по фактическому парку устройств | Закрыт шагом 4 |
| 9 | Способ заведения атлетов | Список {name, apiKey} в appsettings.json (просто, но правка — с перезапуском) против раздела веб-администрирования владельца (удобнее, дороже). Для v1 — конфиг с последующим переносом в веб без смены схемы БД |
Закрыт шагом 5 |
| 10 | Источник .fit для атлетов без учётки владельца | fetch идёт под учётной записью владельца (опросник 4.3); другие атлеты: (а) делят доступ к своему Garmin (передача файла в папку импорта), (б) свой экземпляр учётки в конфиге, (в) только ручной импорт. Кандидат для v1 — (в) папка импорта |
Закрыт шагом 5 |
| 11 | Общий справочник упражнений | Сейчас библиотека у каждого атлета своя (Ф-4.4); общий справочник + личные дополнения — кандидат v2 (дубли при малом числе атлетов не критичны) | Открыт (до v2) |