diff --git a/1-docs/0-mng/plan.md b/1-docs/0-mng/plan.md index 59d8854..a2b18bd 100644 --- a/1-docs/0-mng/plan.md +++ b/1-docs/0-mng/plan.md @@ -8,8 +8,8 @@ | Шаг процесса | Артефакты | Статус | |---|---|---| | 1. Инициация разработки | `1-docs\1-reqs\vision.md` (утверждён 2026-10-17, v1.0), `1-docs\0-mng\plan.md` | ✅ | -| 2. Создание рабочей области | `readme.md` системы, структура репозитория, `.gitignore` | 🔄 | -| 3. Разработка функциональных требований | `1-docs\1-reqs\requirements.md` | ⬜ | +| 2. Создание рабочей области | `readme.md` системы, структура репозитория, `.gitignore` | ✅ | +| 3. Разработка функциональных требований | `1-docs\1-reqs\requirements.md` | 🔄 | | 4. Разработка архитектуры | `1-docs\2-arch\architecture.md`, `hld.puml` (+ рендер) | ⬜ | | 5. Разработка технических требований | `1-docs\3-tech\tech-requirements.md`, спецификации библиотек | ⬜ | | 6. Проектирование баз данных | решение о составе данных (фиксируется здесь) | ⬜ | diff --git a/1-docs/1-reqs/requirements.md b/1-docs/1-reqs/requirements.md new file mode 100644 index 0000000..0d8113c --- /dev/null +++ b/1-docs/1-reqs/requirements.md @@ -0,0 +1,365 @@ +# Функциональные требования: Феникс 6X v1 + +> Шаг 3 процесса разработки (`std1\_process\process.md`). +> Источник: [vision.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). +> Версия: черновик 1 (2026-10-17). Формулировки — проверяемые (d-req-7). + +## 1. Назначение и границы + +Версия 1.0 реализует полный цикл «программа → тренировка без рук → +архив → план/факт» (Ф-1–Ф-11, правила прогрессии Ф-12): + +- **В границах:** голосовой офлайн-учёт подходов на Android-телефоне; + поле данных на Fenix 6 в профиле «Силовая» (пульс, зоны, Lap); + офлайн-учёт с автосинхронизацией телефон ↔ сервер; забор .fit + из Garmin Connect неофициальным клиентом и парсинг на сервере; + REST API со статическим ключом; веб-история с CSV; программы + (версионированный JSON) с контролем «план/факт» и правилами + прогрессии (без LLM). +- **Вне границ v1:** LLM-планирование — еженедельный контур (v2, + опросник 4.6), мезоциклы и «генератор+ревьюер» (v2.x); wake-word + «Феникс, записывай» (v2, опросник 4.5); iOS (v2, опросник 4.2); + экспорт FIT из приложения и доставка программ на часы (v2); + трековые виды спорта — бег/вело (v3+, опросник 4.7); полная + автоматическая смена программы без подтверждения (vision 2.4). + +## 2. Состав системы, точки входа и режимы + +Система — три компонента. Каждый работает автономно от остальных +(vision 2.2, «независимость частей»). + +### 2.1. Часы — поле данных «Феникс» (Monkey C, Connect IQ) + +Точка входа: добавление поля данных в штатный профиль «Силовая» +(решение — опросник 4.1). Отдельных режимов нет: поле живёт в течение +активности. Кнопки не перехватываются (Lap и Start/Stop обрабатывает +Garmin штатно — Ф-2.5, Ф-2.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.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. Голосовой учёт подходов (телефон) + +1. Активация — кнопкой микрофона на экране активной тренировки + (решение — опросник 4.5); повторное нажатие выключает прослушивание. + Пока экран активной тренировки открыт, распознавание работает + офлайн, без сети. +2. Грамматика команд (голос → семантика): + - «Подход **W** на **R**» → подход: вес `W` кг × `R` повторений + (целые числа; `W` — 0–500, `R` — 1–100); + - «Вес **W**» → рабочий вес `W` кг для следующих подходов; + - «Отдых **S** (секунд)» → таймер отдыха `S` секунд (5–600). +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. + +### Ф-3. Экран активной тренировки (телефон) + +1. Статусная строка: связь с часами (зелёный/красный/серый — серый = + связь недоступна, приложение не блокируется), заряд часов (если + передаётся), статус сети (онлайн/офлайн). +2. Пульс: крупное текущее значение + линия за последний подход; + источник — канал телефон ↔ часы (открытый вопрос 3). Если связи + нет — прочерк; запись голосом работает (Ф-1.1). +3. Таймер отдыха: обратный отсчёт, фон — красный (идёт отдых), + жёлтый (последние 10 с), зелёный (время начинать); старт — командой + «Отдых S» или кнопкой (умолчание `Rest.DefaultSec`). +4. Блок упражнения: текущее упражнение (из активной программы, Ф-11.2) + и текущий рабочий вес. +5. Кнопки нижней панели: микрофон (Ф-1.1), «Сбросить» (отмена текущей + тренировки с подтверждением — тренировка помечается `cancelled` + и сохраняется в историю). +6. На экране одновременно ≤ 4 активных элемента, тёмная тема, + крупные зоны касания (vision 2.2). + +### Ф-4. Библиотека упражнений (телефон, синхронизируется на сервер) + +1. Сущность «Упражнение»: название (уникальное), двигательная группа + (жим/тяга/ноги/… — из фиксированного справочника, нужна для правил + замены и тоннажа), признак «базовое/изоляция». +2. Операции: добавить, переименовать, удалить. 🛡 Удаление упражнения, + на которое ссылаются программа или тренировки, — только логическое + (`is_active=0`): история остаётся читаемой. +3. 🛡 Идентификаторы упражнений (`exercise_id`) неизменяемы: программы + и факты ссылаются по ID (дизайн LLM-планирования, разд. 2). + +### Ф-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 записей. +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. Fallback-импорт (`--mode import`): файлы из `Import.Dir` (.fit, .tcx, + .gpх — по документу форматов) парсятся тем же конвейером; + обработанные перемещаются в `Import.ArchiveDir`; необработанные + остаются с записью в логе (код возврата 2). +4. Сопоставление факт ↔ голосовой учёт: тренировка .fit сопоставляется + с тренировкой телефона по времени начала (± 15 минут) — + сопоставлённая пара связывается; несопоставленные остаются + раздельными (сопоставление не удаляет и не переписывает данные + — 🛡). +5. REST API (HTTPS, заголовок `X-Api-Key` = `Api.Key`; без ключа — + `401`): `POST /api/v1/sync` (Ф-7.3); `GET /api/v1/workouts` + (список с пагинацией); `GET /api/v1/workouts/{id}` (карточка); + `GET /api/v1/programs/active` (активная версия программы). +6. 🛡 API пишет факт-данные только из sync и импорта; ручного ввода + на сервере нет. + +### Ф-9. Веб-интерфейс сервера + +1. Страница истории: таблица тренировок (дата, длительность, средний + пульс, подходы, упражнений), сортировка по дате, пагинация. +2. Карточка тренировки: подходы, пульс-тренд, соответствие программе + (план/факт — Ф-11.4). +3. Выгрузка CSV текущей выборки (КУ-4). Доступ — по тому же + `X-Api-Key` (формa входа — ввод ключа; открытый вопрос 6). + +### Ф-10. Программы тренировок (сервер + телефон) + +1. Программа — версионированный JSON-документ (схема — дизайн + LLM-планирования, разд. 2): недели → тренировки (день, фокус) → + упражнения (`exercise_id` из библиотеки) → подходы-план (вес, + повторения, `target_rpe`, отдых). Хранение — таблица `programs` + (PostgreSQL); каждая версия неизменяема (🛡). +2. В v1 создание/правка версии — вручную в приложении на телефоне + (редактор программы) с отправкой на сервер; `author=user`. +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). +2. Микроправка весов на следующее занятие (правила, детерминированно): + - все запланированные подходы закрыты «по плану» → вес + + `Progression.BaseStepKg` на базовых, + `BaseStepKg`/2 на изоляции; + - ≥ 2 подходов не дотянуты по повторениям → вес − + `Progression.BackoffPercent` %; + - иначе (частичное выполнение) — вес без изменения. +3. Правила применяются только к `active` версии; результат — + предложение следующей версии программы со статусом `draft` + (`author=rules`), показывается в приложении диффом; применение — + подтверждением пользователя (Ф-10.3). 🛡 Автоматической смены + активной версии нет (vision 2.4). +4. Делоад: если `Deload.MaxWeeks` недель подряд без делоада, правило + предлагает делоад-версию (объём −40–50 %) — тем же механизмом + диффа (дизайн LLM-планирования, разд. 6). +5. Офлайн-режим сервера: правила выполняются при следующем запуске + `serve`/`import` — телефон при этом полнофункционален (учёт и + локальные планы работают без сервера). + +### Логирование (телефон и сервер) + +1. `Info`: старт/остановка тренировки, каждая команда (Ф-1.5), пакеты + синхронизации (число записей, результат), забор/парсинг .fit + (файл, результат), применённые версии программ. +2. `Error`: все ошибки с текстом исключения (этап, объект, причина). +3. 🛡 В логах нет пароля Garmin, API-ключей и содержимого речи + (только распознанный текст команды — он нужен для диагностики + распознавания). + +## 5. Контракты и инварианты + +- 🛡 **Независимость частей:** часы, телефон и сервер работают при + отсутствии остальных; ни одна часть не блокирует другую (БЦ-4). +- 🛡 **Офлайн-first:** запись (голос, часы) и просмотр истории никогда + не ждут сеть; сеть — только синхронизация (КУ-3). +- 🛡 **Идемпотентность:** повтор отправки пакета (по `client_guid`) + и повторный `fetch`/`import` не создают дублей (КУ-3, Ф-8.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 |