Ревью №2 утверждено: uuid-FK (Р2-Б доведён), контур break (planned_return, source auto|manual), якорь Fenix 6X (vision 1.5), ОВ-10, Safety.HrStrengthMax, fetch 15 мин, «с половиной» (О-2-Б), IsoStepKg (О-3-А), migrate-режим, src.migration

This commit is contained in:
virus-home 2026-10-17 23:03:28 +05:00
parent e6743186da
commit 0e8e3a6ad6
11 changed files with 385 additions and 71 deletions

View File

@ -1,7 +1,7 @@
# План проекта: WatchBody («Феникс 6X») # План проекта: WatchBody («Феникс 6X»)
> Код системы — WatchBody (репозиторий > Код системы — WatchBody (репозиторий
> `https://git.mind01.ru/VEU/WatchBody.git`); название продукта — > `https://git.mind01.ru/VEU/VEU.WathBody.git`); название продукта —
> «Феникс 6X». План проекта — формат по sd-init-5 > «Феникс 6X». План проекта — формат по sd-init-5
> (`std1\_rules\sd\sd-init.md`); шаги процесса — > (`std1\_rules\sd\sd-init.md`); шаги процесса —
> `std1\_process\process.md`. Статусы: ⬜ — не начат, 🔄 — в работе, > `std1\_process\process.md`. Статусы: ⬜ — не начат, 🔄 — в работе,
@ -29,8 +29,11 @@ JSON-тела (версии программ, факты .fit) — `jsonb` вн
## Состав версий (vision 2.2–2.4, решения опросников) ## Состав версий (vision 2.2–2.4, решения опросников)
- **v1.0:** полный MVP-цикл (Ф-1–Ф-9) + программы без LLM (Ф-10, Ф-11, - **v1.0:** полный MVP-цикл (Ф-1–Ф-9) + программы без LLM (Ф-10, Ф-11,
правила прогрессии). Платформы: Android (Kotlin), Fenix 6 (Monkey C, правила прогрессии), Ф-13 (MVP-состав документа метрик: дневник АД/вес/
поле данных), сервер C#/.NET 10 + PostgreSQL. пульс покоя/самочувствие, допуск, RPE/боль в колене, правила 1–6) и
Ф-14 (перерывы, vision 2.2). Платформы: Android (Kotlin), Fenix 6X
(Monkey C, поле данных; изменение vision 1.5), сервер C#/.NET 10 +
PostgreSQL.
- **v2:** LLM-планирование (еженедельно), wake-word, iOS, экспорт FIT, - **v2:** LLM-планирование (еженедельно), wake-word, iOS, экспорт FIT,
программы на часы (FIT workout). программы на часы (FIT workout).
- **v2.x:** мезоциклы LLM, «генератор+ревьюер», опрос RPE, аналитика. - **v2.x:** мезоциклы LLM, «генератор+ревьюер», опрос RPE, аналитика.

View File

@ -0,0 +1,251 @@
# Ревью документации №2 от 2026-10-17 — задания к исполнению
> **Исполнитель:** агент сопровождения документации WatchBody.
> **Источник:** вторая архитектурная проверка документации шагов 1–6
> (после выполнения волн 1–2 ревью №1, коммит `e674318`): сверка с исходной
> задачей (`Техническое задание.md`, `Требование.md`), документами-
> рекомендациями (`Метрики_тренировок_и_самоконтроля.md`,
> `Перерывы_подводные_камни_и_рекомендации.md`,
> `Форматы_тренировок_и_рекомендации.md`,
> `Архитектура_LLM_планирования_тренировок.md`,
> `program-trenirovok.md`), UI/UX-рекомендациями
> (`3-tech\ui-ux-recommendations.md`) и внутренними противоречиями.
> **Уточнение владельца (чат, 2026-10-17):** якорные часы — **Fenix 6X**
> (280×280 MIP). В ТЗ указан «Fenix 6, 260×260» — считать опиской исходника,
> документы разработки приводятся к Fenix 6X (З-18).
> **Состояние репозитория на момент проверки:** коммит `e674318`, ветка
> `main`, статус чистый; ссылки на строки — по нему, после правок
> ориентироваться по разделам.
> **Статусная модель заданий:** ⬜ не начато → 🔄 в работе → ✅ готово
> (утверждено владельцем). Отметки — в разделе 6 по мере работы.
> **Общие правила исполнения:**
> 1. Правки вносятся новыми версиями утверждённых документов (перечень —
> раздел 5). Каждая новая версия — точка утверждения **Пользователь!**
> (d-dev-4).
> 2. Содержание утверждённых документов меняется только в объёме заданий;
> инварианты 🛡 не ослабляются; трассировка «Ф → БЦ → КУ → версия»
> (раздел 6 requirements) сохраняется.
> 3. Задания, зависящие от решений О-1…О-3 (раздел 1), не вносятся до выбора
> владельцем; остальные можно выполнять сразу. Если владелец не выбрал —
> подготовить дифф по рекомендованному варианту и вынести на утверждение.
> 4. Код (`3-app\`, `2-db\`) не трогается — задание касается только `1-docs\`
> и корневых `readme.md`/`plan.md`-упоминаний.
> 5. Коммиты пушатся после каждой волны (credentials настроены).
> 6. Исходные документы задачи в корне (`Техническое задание.md`, документы-
> рекомендации, `program-trenirovok.*`) **не редактируются** — это вход,
> а не документация разработки.
## 0. Сводка ревью №2
Соответствие исходной задаче и рекомендациям в целом подтверждено: все 9
позиций MVP ТЗ покрыты (Ф-1–Ф-9), стеки соответствуют `Требование.md`
(C#/.NET 10 + PostgreSQL по std1, Kotlin, Monkey C), решения ревью №1
(Р1-А/Р2-Б/Р3-А, З-1…З-8) внесены в документы, правила книг перенесены
(допуск АД/пульс, правила 1–6 в v1 / 7–10 в v2, формула возврата и правила
12–17 дословно, FIT/JSON/CSV, шаг 1 LLM-дизайна в v1, UI/UX-стандарт).
Найдено: **2 существенных дефекта (P1)** — Р2-Б доведён не до конца
(FK-колонки и типы возврата остались `int` при uuid-PK) и контур break не
имеет пути записи ответа атлета; **4 средних (P2)**; **8 мелких (P3,
гигиена)**, включая неактуализированный чек-лист ревью №1.
## 1. Решения владельца (блокируют часть заданий)
| Код | Вопрос | Варианты | Рекомендация ревью |
|---|---|---|---|
| **О-1** | ОВ-10 requirements (источник .fit для атлетов без учётки владельца) помечен «закрыт шагом 5», но решения в tech-requirements нет | **А:** v1 — только папка импорта `--mode import` для таких атлетов; собственные учётки в конфиге — кандидат v2. **Б:** список учётных записей Garmin в `appsettings.json` (по атлету) уже в v1 | **А** (не раздувать v1; деградация уже работает) |
| **О-2** | П-2 (vision) «вес — целые числа» противоречит пикеру шага 2,5 кг (Ф-3.6): голос не введёт 22,5 кг — запишет 22 (искажение план/факт и тоннажа) | **А:** зафиксировать ограничение: голос — целые кг, дробные — правка тапом (примечание в Ф-1.2 и П-2). **Б:** расширить грамматику: «…с половиной» → +0,5 кг (словарь Vosk + постобработка, tech-req §5) | **А** для v1 (просто и предсказуемо); **Б** — кандидат v2 |
| **О-3** | Шаг прогрессии изоляции `BaseStepKg/2` = 1,25 кг (при умолчании 2,5) выходит из коридора метрик «руки +0,5–1 кг» (правило 4) | **А:** отдельный параметр `Progression.IsoStepKg` (умолчание 1,0; валидация 0,25–5). **Б:** оставить 1,25 с примечанием «осознанное решение владельца» | **А** (коридор метрик соблюдается без ручной подгонки) |
## 2. Задания P1 (существенные, блокируют шаг 7)
### З-16. Доведение Р2-Б: uuid-FK и типы возврата функций ⬜
1. `1-docs\2-db\src\tables.md` — колонки-ссылки на uuid-PK сменить `int` →
`uuid NOT NULL` (или NULL по смыслу):
- `src.workout.program_version_id`: `uuid NULL → src.program_version.id`;
- `src.workout_set.exercise_id`: `uuid NULL → src.exercise.id`;
- `src.exercise_replacement.exercise_id`, `replacement_id`:
`uuid → src.exercise.id`.
2. `1-docs\2-db\src\functions.md` — тип возврата для uuid-сущностей
«int — id» → «uuid — id»: `exercise_insert`,
`program_version_insert`, `workout_insert`, `workout_set_insert`,
`command_log_insert`, `break_insert`. Для `exercise_replacement_insert`
(ММ, составной PK, id нет) — «int — число строк».
3. Самопроверка: в tables.md не осталось ссылок `int →` на таблицы с
`uuid PK`; каждой функции вставки uuid-сущности соответствует возврат
`uuid`.
### З-17. Контур break: путь записи ответа атлета ⬜
Противоречие: Ф-14.2 собирает причину и дату возврата, sync-сущность
`break` передаёт `planned_return`, но в `src.breaks` колонки нет,
`source` объявлен «🛡 всегда auto», а `break_update` принимает только
`p_ended_on` — ответу атлета некуда и нечем записаться; правила 12–14 без
`kind` не работают.
1. `1-docs\2-db\src\tables.md`, `src.breaks`:
- добавить колонку `planned_return | date NULL | предполагаемая дата
возврата (Ф-14.2)`;
- `source` — снять пометку «🛡 всегда auto», описать:
`auto` — сервер по пропускам (Ф-14.1); `manual` — запланированный
простой, объявленный атлетом заранее (Ф-14.2). Значение из sync —
только `manual`; `auto` проставляет сервер.
2. `1-docs\2-db\src\functions.md`:
- `break_insert` — параметры `p_kind, p_source, p_started_on,
p_planned_return, p_note`; комментарий: «auto — сервер по пропускам;
manual — телефон (объявление заранее)»;
- `break_update` — параметры `p_kind, p_note, p_planned_return,
p_ended_on`; комментарий: «ответ атлета на вопрос о причине
(Ф-14.2) + закрытие первой сессией после возврата».
3. `1-docs\1-reqs\requirements.md`, Ф-14.1 — снять натяжку «Ручного
включения break нет» против Ф-14.2: переформулировать «Break ставится
автоматически по пропускам (основной путь); атлет может объявить
перерыв заранее (Ф-14.2) — такой break создаётся с `source=manual`;
произвольного ручного включения без объявления нет».
4. `1-docs\3-tech\tech-requirements.md`, п. 2.1, сущность `break` —
привести к таблице: `kind` (NULL до ответа), `source` (телефон
передаёт `manual`), `started_on`, `planned_return`, `note`;
`ended_on` проставляет сервер первой сессией после возврата.
## 3. Задания P2
### З-18. Якорные часы — Fenix 6X (уточнение владельца от 2026-10-17) ⬜
Заменить «Fenix 6 (260×260)» → «Fenix 6X (280×280)» и убрать ложное
«одно разрешение для 6/6S/6X» (фактически: 6S — 240×240, 6 — 260×260,
6X — 280×280):
1. `1-docs\1-reqs\vision.md`: «Что уже есть» (Fenix 6, 260×260 MIP →
Fenix 6X, 280×280 MIP); БР-3 («якорное устройство — Fenix 6»);
З-3 п. 1.7 («якорь — Fenix 6»); п. 2.4 («якорное устройство v1 —
Fenix 6 (260×260 MIP, кнопки)»). Оформить как **изменение 1.5**
(уточнение владельца 2026-10-17: якорь — Fenix 6X; в ТЗ указан
Fenix 6/260×260 — описка исходника) → версия vision **1.5**.
2. `1-docs\1-reqs\requirements.md`: раздел 2.1 («Якорное устройство —
Fenix 6») + примечание об уточнении владельца; открытый вопрос 8
(«Якорь — Fenix 6»).
3. `1-docs\3-tech\tech-requirements.md`, решение ОВ-7 — переписать:
«Таргет v1 — `fenix6x` (280×280, MIP, кнопки). Fenix 6 (260×260) и
6S (240×240) добавляются отдельными таргетами с собственными макетами
(Ф-2.8) по фактическому парку». Утверждение — Пользователь!
4. Корневой `readme.md` («часы Garmin Fenix 6», состав системы) и
`1-docs\0-mng\plan.md` (строка версий, «Fenix 6 (Monkey C…)») —
привести к Fenix 6X.
5. `1-docs\3-tech\ui-ux-recommendations.md` не трогать: там Fenix 6X /
280×280 указаны верно.
6. ТЗ (`..\..\Техническое задание.md`) не редактировать (вход).
### З-19. Закрытие ОВ-10 (после О-1; по О-1-А) ⬜
`1-docs\3-tech\tech-requirements.md`, раздел 1 — добавить строку ОВ-10:
«Источник .fit атлетов без учётки владельца — только папка импорта
(`--mode import`); `fetch` работает под единственной учёткой владельца;
собственные учётки атлетов в конфиге — кандидат v2». В requirements
раздел 7 (ОВ-10) — проставить финальное решение со ссылкой на
tech-requirements.
### З-20. Порог пульса силовой части (`Safety.HrStrengthMax`) ⬜
Ограничение профиля «пульс при силовой ≤ 110–120 уд/мин»
(`program-trenirovok.md`, документ метрик п. 2.3) в требованиях не
отражено; зоны 50/70/85 % дают жёлтый от ~123 и красный от ~150
(Танака, 45 лет) — для гипертоника 45+ ключевой порог не контролируется.
1. `1-docs\1-reqs\requirements.md`:
- раздел 3, таблица телефона: `Safety.HrStrengthMax` — «порог пульса
в силовой части (предупреждение)», умолчание `120`; та же строка —
в серверной таблице (для `red_flags`);
- Ф-13 — новый пункт 7 (без перенумерации 4–6, на них есть ссылки):
«Во время силовой части: пульс > `Safety.HrStrengthMax` →
предупреждение на телефоне (баннер + вибро, спокойная формулировка
по UI/UX-стандарту); на часы порог передаётся в объёме канала
(ОВ-3). Источник — программа тренировок и документ метрик (п. 2.3)».
2. `1-docs\3-tech\tech-requirements.md`: п. 4.1 и 4.2 — строка
`Safety.HrStrengthMax | int | 100–180 (умолчание 120)`.
3. Трассировку раздела 6 не менять (Ф-13 уже привязан к БЦ-6/КУ-7).
### З-21. Период запуска `fetch` (проверяемость БЦ-5) ⬜
БЦ-5 требует «архив на сервере ≤ 30 минут после тренировки», решение
ОВ-4 — внешний планировщик — интервал не задан нигде, критерий
непроверяем.
`1-docs\3-tech\tech-requirements.md`, решение ОВ-4 — дополнить: «Интервал
вызова — 15 минут (параметр планировщика ОС, в код не входит); вместе с
лагом синхронизации часов с Garmin Connect укладывается в БЦ-5 (≤ 30 мин)».
## 4. Задания P3 (гигиена)
| Код | Задание | Где |
|---|---|---|
| З-22 | Шапка-источник: «vision v1.3» → «vision v1.4»; в разделе 2.3 («--mode fetch») «открытый вопрос 5» → «открытый вопрос 4» | requirements.md:4, :85 |
| З-23 | URL репозитория → `https://git.mind01.ru/VEU/VEU.WathBody.git` (как в readme, по фактическому remote; З-12 ревью №1 был закрыт ошибочно); в «Составе версий» указать: v1.0 включает Ф-13 (MVP-состав документа метрик) и Ф-14 (vision 2.2) | plan.md:3–4, раздел «Состав версий» |
| З-24 | Чек-лист ревью №1 актуализировать по факту: Р1-А/Р2-Б/Р3-А — решены; З-1, З-3, З-4, З-7, З-8 — ✅; З-2 — ✅ (architecture v1.1 утверждена); З-12 — частично переоткрыт (plan.md, см. З-23); З-13 🔄 и З-15 ⬜ остаются | review-2026-10-17.md, раздел 6 |
| З-25 | Опечатка «2-dbuild.ps1» → «2-db\build.ps1»; режим `--mode migrate` добавить в таблицу режимов requirements 2.3 (семантика: применение миграций из `2-db\migrations\`; коды возврата — как у import); таблицу `src.migration` (id serial PK, filename UNIQUE, applied_at; функции migration_insert/migration_select, CUSTOM) добавить в перечень и описания tables.md | 2-db\readme.md:47–48, requirements 2.3, 2-db\src\tables.md |
| З-26 | Дробные веса (после О-2): по А — примечание в Ф-1.2 («голос — целые кг; дробные (шаг 2,5) — правка тапом, Ф-1.6») и уточнение П-2 в vision; по Б — грамматика «…с половиной» → +0,5 кг в Ф-1.2 и словарь/постобработка в tech-req §5 (VoiceInput) | requirements Ф-1.2, vision П-2, tech-req §5 |
| З-27 | Шаг прогрессии изоляции (после О-3): по А — параметр `Progression.IsoStepKg` (умолчание 1,0; валидация 0,25–5) в раздел 3 и п. 4.2, Ф-12.2 — «+ `IsoStepKg` (изоляция)»; по Б — примечание в Ф-12.2 «умолчание даёт 1,25 кг — выше коридора метрик 0,5–1 кг, решение владельца» | requirements 3, Ф-12.2; tech-req 4.2 |
| З-28 | UI/UX-рекомендации → версия 1.1: §3 и §6.3 — «id-mapping» заменить на «UUID PK клиентских сущностей (Р2-Б)»; §2.2 и §6.2 — пометить «ввод RPE/боли на часах» как неприменимое к форме «поле данных» (кнопки обрабатывает Garmin, Ф-2.5–2.6; ввод RPE/боли — телефон, Ф-13.3); §2.1/§8 (Fenix 6X, 280×280) — верно, не менять | 3-tech\ui-ux-recommendations.md |
| З-29 | Перечень `LocalDb` в архитектуре дополнить: дневник самоконтроля (`daily_log`) и перерывы (`breaks`); версия architecture → 1.2 (мелкая правка перечня) | architecture.md, раздел 1.2 |
## 5. Порядок исполнения и версии
**Волна 1 — P1 + P2 (З-16, З-17, З-18, З-20, З-21 сразу; З-19 после О-1).**
Один пакет «Ревью №2: uuid-FK (Р2-Б доведён), контур break, якорь Fenix 6X,
ОВ-10, Safety.HrStrengthMax, период fetch» → утверждение владельцем (одно
утверждение на пакет) → коммит и push. Версии: vision → 1.5 (изменение 1.5),
requirements → 1.4, tech-requirements → 1.4, tables/functions → новая
редакция (число функций не меняется — меняются типы/параметры), 2-db readme
→ новая редакция.
**Волна 2 — P3 errata (З-22, З-23, З-24, З-25, З-29 сразу; З-26, З-27
после О-2/О-3; З-28 сразу).** Пакет «Ревью №2: errata (шапки, plan.md,
чек-лист ревью №1, migrate-mode, ui-ux v1.1, LocalDb)» → утверждение →
коммит и push. Версии: ui-ux-recommendations → 1.1, architecture → 1.2;
остальные правки — внутри версий волны 1.
**После каждой волны — самопроверка (раздел 7) и отметки в разделе 6.**
## 6. Чек-лист
| Задание | P | Зависит от | Статус |
|---|---|---|---|
| О-1…О-3 решения владельца | — | — | ✅ О-1 — ответ владельца: атлеты саморегистрируются на телефоне (имя), привязка — по uuid клиентского приложения (для .fit применён О-1-А — папка импорта); О-2-Б; О-3-А (2026-10-17) |
| З-16 uuid-FK + типы возврата | 1 | — | ✅ (2026-10-17) |
| З-17 контур break (planned_return, source, update) | 1 | — | ✅ (2026-10-17) |
| З-18 якорь Fenix 6X (vision 1.5, requirements, tech-req, readme, plan) | 2 | — (уточнение владельца дано) | ✅ (2026-10-17) |
| З-19 закрытие ОВ-10 | 2 | О-1 | ✅ (2026-10-17, О-1-А + уточнение владельца о регистрации) |
| З-20 Safety.HrStrengthMax | 2 | — | ✅ (2026-10-17) |
| З-21 период fetch (БЦ-5) | 2 | — | ✅ (2026-10-17, интервал 15 мин) |
| З-22 шапка requirements, ОВ-нумерация | 3 | — | ✅ (2026-10-17, сразу vision v1.5) |
| З-23 plan.md URL + состав версий | 3 | — | ✅ (2026-10-17) |
| З-24 чек-лист ревью №1 | 3 | — | ✅ (2026-10-17) |
| З-25 migrate-режим, src.migration, опечатка | 3 | — | ✅ (2026-10-17; опечатка в readme не подтвердилась — путь верен) |
| З-26 дробные веса | 3 | О-2 | ✅ (2026-10-17, по О-2-Б) |
| З-27 шаг изоляции | 3 | О-3 | ✅ (2026-10-17, по О-3-А) |
| З-28 ui-ux-recommendations v1.1 | 3 | — | ✅ (2026-10-17) |
| З-29 LocalDb перечень (architecture 1.2) | 3 | — | ✅ (2026-10-17) |
## 7. Самопроверка после правок
1. **tables.md / functions.md:** нет ссылок `int →` на uuid-PK; возвраты
insert-функций uuid-сущностей — `uuid`; у `src.breaks` есть
`planned_return`, `source` описан как `auto|manual`; перечень таблиц
↔ список функций совпадают 1:1 (включая `src.migration`).
2. **requirements.md:** Ф-14.1 согласован с Ф-14.2; Ф-13.7 ссылается на
`Safety.HrStrengthMax`; строки раздела 3 (телефон и сервер) содержат
`Safety.HrStrengthMax`; шапка — vision v1.4 (после волны 1 — v1.5);
трассировка раздела 6 не нарушена; Ф-13.4/Ф-13.5/Ф-13.6 сохранили
номера.
3. **tech-requirements.md:** ОВ-7 — таргет `fenix6x` (280×280); ОВ-4 —
интервал 15 минут; ОВ-10 присутствует и закрыт по О-1; п. 4.1/4.2
содержат `Safety.HrStrengthMax` с одинаковой валидацией; сущность
`break` (п. 2.1) совпадает с `src.breaks` по полям.
4. **vision.md:** изменение 1.5 добавлено в шапку; упоминания «Fenix 6 /
260×260» в п. 1.1, БР-3, З-3, п. 2.4 заменены на Fenix 6X / 280×280.
5. **Согласованность устройств:** «Fenix 6X / 280×280» единообразно в
vision, requirements, tech-requirements, architecture (если
упоминается), корневом readme.md, plan.md; в ui-ux-recommendations
не изменено (уже верно).
6. **Общее:** git-статус чистый после коммитов; исходники задачи в корне
не редактировались; в логах/доках нет секретов; push выполнен после
каждой волны.

View File

@ -177,19 +177,19 @@ architecture → 1.1, tech-requirements → 1.3, tables/functions → новая
| Задание | P | Зависит от | Статус | | Задание | P | Зависит от | Статус |
|---|---|---|---| |---|---|---|---|
| Р1–Р3 решения владельца | — | — | ⬜ | | Р1–Р3 решения владельца | — | — | ✅ Р1-А, Р2-Б, Р3-А (2026-10-17) |
| З-1 правило 1: данные v1 | 1 | Р1 | ⬜ | | З-1 правило 1: данные v1 | 1 | Р1 | ✅ (2026-10-17, Р1-А) |
| З-2 архитектура + hld.puml + рендер | 1 | — (текст), рендер после правок | 🔄 (2026-10-17: Ф-13, Ф-14 и волна 2 внесены; черновик v1.1 — на утверждении) | | З-2 архитектура + hld.puml + рендер | 1 | — | ✅ (2026-10-17, architecture v1.1 утверждена, hld отрендерен) |
| З-3 client_guid + маппинг id | 1 | Р2 | ⬜ | | З-3 client_guid + маппинг id | 1 | Р2 | ✅ (2026-10-17, Р2-Б — UUID PK; уточнено ревью №2 З-16) |
| З-4 Safety-пороги телефона | 1 | Р3 | ⬜ | | З-4 Safety-пороги телефона | 1 | Р3 | ✅ (2026-10-17, Р3-А) |
| З-5 fit_file_id / fit_workout_id | 2 | — | ✅ (2026-10-17) | | З-5 fit_file_id / fit_workout_id | 2 | — | ✅ (2026-10-17) |
| З-6 sync-enum (set_note/daily_log) | 2 | — | ✅ (2026-10-17) | | З-6 sync-enum (set_note/daily_log) | 2 | — | ✅ (2026-10-17) |
| З-7 диапазон RPE | 2 | — | ⬜ | | З-7 диапазон RPE | 2 | — | ✅ (2026-10-17, единый 1–10) |
| З-8 грамматика «РПЭ N» | 2 | З-7 | ⬜ | | З-8 грамматика «РПЭ N» | 2 | З-7 | ✅ (2026-10-17) |
| З-9 Ф-12: «io header fix», нумерация | 3 | — | ✅ (2026-10-17) | | З-9 Ф-12: «io header fix», нумерация | 3 | — | ✅ (2026-10-17) |
| З-10 .gpх → .gpx | 3 | — | ✅ (2026-10-17) | | З-10 .gpх → .gpx | 3 | — | ✅ (2026-10-17) |
| З-11 шапки-источники → vision v1.3 | 3 | — | ✅ (2026-10-17, сразу v1.4) | | З-11 шапки-источники → vision v1.3 | 3 | — | ✅ (2026-10-17, сразу v1.4) |
| З-12 URL репозитория | 3 | — | ✅ (2026-10-17, remote = VEU/VEU.WathBody.git, оба файла выверены) | | З-12 URL репозитория | 3 | — | ✅/переоткрыт частично: readme был верен; plan.md исправлен ревью №2 (З-23) — 2026-10-17 |
| З-13 маркер «MM» + письмо std1 | 3 | — | 🔄 (комментарий добавлен; письмо std1 — вне репо) | | З-13 маркер «MM» + письмо std1 | 3 | — | 🔄 (комментарий добавлен; письмо std1 — вне репо) |
| З-14 план миграции в 2-db readme | 3 | — | ✅ (2026-10-17) | | З-14 план миграции в 2-db readme | 3 | — | ✅ (2026-10-17) |
| З-15 сообщение владельцу std1 о process.md | 3 | — | ⬜ | | З-15 сообщение владельцу std1 о process.md | 3 | — | ⬜ |

View File

@ -1,7 +1,7 @@
# Функциональные требования: Феникс 6X v1 # Функциональные требования: Феникс 6X v1
> Шаг 3 процесса разработки (`std1\_process\process.md`). > Шаг 3 процесса разработки (`std1\_process\process.md`).
> Источник: [vision.md](vision.md) (утверждён 2026-10-17, v1.3), разделы > Источник: [vision.md](vision.md) (утверждён 2026-10-17, v1.5), разделы
> 1.3 (БЦ), 1.4 (КУ), 2.1–2.4; решения опросников 4.1–4.7; ТЗ > 1.3 (БЦ), 1.4 (КУ), 2.1–2.4; решения опросников 4.1–4.7; ТЗ
> (`..\..\Техническое задание.md`), дизайн LLM-планирования (разд. 2–3, > (`..\..\Техническое задание.md`), дизайн LLM-планирования (разд. 2–3,
> 6–8, 11 — шаг 1); изменение vision 1.1 (2026-10-17) — несколько > 6–8, 11 — шаг 1); изменение vision 1.1 (2026-10-17) — несколько
@ -21,6 +21,12 @@
> 1–10), З-8 (команда «РПЭ N»); интеграция UI/UX-рекомендаций > 1–10), З-8 (команда «РПЭ N»); интеграция UI/UX-рекомендаций
> (`3-tech\ui-ux-recommendations.md`). Версии 1.2, 1.3 — > (`3-tech\ui-ux-recommendations.md`). Версии 1.2, 1.3 —
> **УТВЕРЖДЕНЫ** (2026-10-17). > **УТВЕРЖДЕНЫ** (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).
## 1. Назначение и границы ## 1. Назначение и границы
@ -28,7 +34,7 @@
архив → план/факт» (Ф-1–Ф-11, правила прогрессии Ф-12): архив → план/факт» (Ф-1–Ф-11, правила прогрессии Ф-12):
- **В границах:** голосовой офлайн-учёт подходов на Android-телефоне; - **В границах:** голосовой офлайн-учёт подходов на Android-телефоне;
поле данных на Fenix 6 в профиле «Силовая» (пульс, зоны, Lap); поле данных на Fenix 6X в профиле «Силовая» (пульс, зоны, Lap);
офлайн-учёт с автосинхронизацией телефон ↔ сервер; забор .fit офлайн-учёт с автосинхронизацией телефон ↔ сервер; забор .fit
из Garmin Connect неофициальным клиентом и парсинг на сервере; из Garmin Connect неофициальным клиентом и парсинг на сервере;
REST API со статическим ключом; веб-история с CSV; программы REST API со статическим ключом; веб-история с CSV; программы
@ -65,7 +71,8 @@ API-ключом.
Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных переносимый: сборки под Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных переносимый: сборки под
разные модели Garmin — конфигурациями одного проекта (разметка под разные модели Garmin — конфигурациями одного проекта (разметка под
размеры/разрешения экранов); перечень моделей — открытый вопрос 8. размеры/разрешения экранов); перечень моделей — открытый вопрос 8.
Якорное устройство — Fenix 6. Якорное устройство — Fenix 6X (уточнение владельца 2026-10-17:
в ТЗ указан Fenix 6/260×260 — описка исходника; изменение vision 1.5).
### 2.2. Телефон — приложение «Феникс» (Android, Kotlin) ### 2.2. Телефон — приложение «Феникс» (Android, Kotlin)
@ -82,7 +89,8 @@ Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных перен
|---|---|---|---| |---|---|---|---|
| `--mode serve` | нет | этот режим | REST API + веб-интерфейс | | `--mode serve` | нет | этот режим | REST API + веб-интерфейс |
| `--mode import` | нет | выкл | разовый импорт .fit/TCX/GPX из папки импорта и выход | | `--mode import` | нет | выкл | разовый импорт .fit/TCX/GPX из папки импорта и выход |
| `--mode fetch` | нет | выкл | разовый забор .fit из Garmin Connect и выход (расписание — внешний планировщик, открытый вопрос 5) | | `--mode migrate` | нет | выкл | применение миграций из `2-db\migrations\` и выход (журнал — `src.migration`); коды возврата — как у import |
| `--mode fetch` | нет | выкл | разовый забор .fit из Garmin Connect и выход (расписание — внешний планировщик, открытый вопрос 4) |
| `--config` | нет | `appsettings.json` рядом с исполняемым файлом | путь к файлу настроек | | `--config` | нет | `appsettings.json` рядом с исполняемым файлом | путь к файлу настроек |
**Коды возврата (`--mode import|fetch`):** `0` — успех (в т.ч. «новых **Коды возврата (`--mode import|fetch`):** `0` — успех (в т.ч. «новых
@ -107,6 +115,7 @@ Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных перен
| `Safety.BpCancel` | порог отмены тренировки по АД (сист./диаст.) | `150/90` | | `Safety.BpCancel` | порог отмены тренировки по АД (сист./диаст.) | `150/90` |
| `Safety.BpCardio` | порог «только кардио» по АД | `140/90` | | `Safety.BpCardio` | порог «только кардио» по АД | `140/90` |
| `Safety.RestingHrSitting` | порог пульса сидя для переноса тренировки | `90` | | `Safety.RestingHrSitting` | порог пульса сидя для переноса тренировки | `90` |
| `Safety.HrStrengthMax` | порог пульса в силовой части (предупреждение) | `120` |
| `Safety.LightWeekHrRise` | рост пульса покоя (2 дня) для лёгкой недели | `+8` | | `Safety.LightWeekHrRise` | рост пульса покоя (2 дня) для лёгкой недели | `+8` |
**Сервер (`appsettings.json`):** **Сервер (`appsettings.json`):**
@ -123,11 +132,13 @@ Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных перен
| `Import.Dir` | папка импорта fallback (ручной экспорт, опросник 4.3) | `data\import` | | `Import.Dir` | папка импорта fallback (ручной экспорт, опросник 4.3) | `data\import` |
| `Import.ArchiveDir` | папка обработанных файлов импорта | `data\import_done` | | `Import.ArchiveDir` | папка обработанных файлов импорта | `data\import_done` |
| `Progression.BaseStepKg` | шаг прогрессии веса, кг | `2.5` | | `Progression.BaseStepKg` | шаг прогрессии веса, кг | `2.5` |
| `Progression.IsoStepKg` | шаг прогрессии изоляции, кг (умолчание 1,0; валидация 0,25–5 — коридор метрик «+0,5–1 кг»; решение владельца О-3-А) | `1.0` |
| `Progression.BackoffPercent` | снижение при недовыполнении, % | `5` | | `Progression.BackoffPercent` | снижение при недовыполнении, % | `5` |
| `Deload.MaxWeeks` | недель между делоадами | `6` | | `Deload.MaxWeeks` | недель между делоадами | `6` |
| `Safety.BpCancel` | порог отмены тренировки по АД (сист./диаст.) | `150/90` | | `Safety.BpCancel` | порог отмены тренировки по АД (сист./диаст.) | `150/90` |
| `Safety.BpCardio` | порог «только кардио» по АД | `140/90` | | `Safety.BpCardio` | порог «только кардио» по АД | `140/90` |
| `Safety.RestingHrSitting` | порог пульса сидя для переноса тренировки | `90` | | `Safety.RestingHrSitting` | порог пульса сидя для переноса тренировки | `90` |
| `Safety.HrStrengthMax` | порог пульса в силовой части (для `red_flags`) | `120` |
| `Safety.LightWeekHrRise` | рост пульса покоя (2 дня) для лёгкой недели | `+8` | | `Safety.LightWeekHrRise` | рост пульса покоя (2 дня) для лёгкой недели | `+8` |
| `Log.Path / Log.Level` | логи | `logs\`, `Info` | | `Log.Path / Log.Level` | логи | `logs\`, `Info` |
@ -144,7 +155,8 @@ Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных перен
офлайн, без сети. офлайн, без сети.
2. Грамматика команд (голос → семантика): 2. Грамматика команд (голос → семантика):
- «Подход **W** на **R**» → подход: вес `W` кг × `R` повторений - «Подход **W** на **R**» → подход: вес `W` кг × `R` повторений
(целые числа; `W` — 0–500, `R` — 1–100); (вес — целые кг либо «…с половиной» → +0,5 кг, шаг 0,5;
`W` — 0–500, `R` — 1–100; решение владельца О-2-Б);
- «Вес **W**» → рабочий вес `W` кг для следующих подходов; - «Вес **W**» → рабочий вес `W` кг для следующих подходов;
- «Отдых **S** (секунд)» → таймер отдыха `S` секунд (5–600); - «Отдых **S** (секунд)» → таймер отдыха `S` секунд (5–600);
- «РПЭ **N**» → RPE последнего подхода, N 1–10 (Ф-13.3, З-8). - «РПЭ **N**» → RPE последнего подхода, N 1–10 (Ф-13.3, З-8).
@ -377,7 +389,7 @@ Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных перен
2. Микроправка весов на следующее занятие (правила, детерминированно): 2. Микроправка весов на следующее занятие (правила, детерминированно):
- все запланированные подходы закрыты «по плану» при RPE ≤ 7 - все запланированные подходы закрыты «по плану» при RPE ≤ 7
(2 тренировки подряд) → вес + `Progression.BaseStepKg` (базовые), (2 тренировки подряд) → вес + `Progression.BaseStepKg` (базовые),
+ `BaseStepKg`/2 (изоляция); + `Progression.IsoStepKg` (изоляция, З-27/О-3-А);
- RPE ≥ 9 в 2+ сетах → вес − `Progression.BackoffPercent` %; - RPE ≥ 9 в 2+ сетах → вес − `Progression.BackoffPercent` %;
- повторения достигли верхней границы диапазона (15–20) → вес ↑, - повторения достигли верхней границы диапазона (15–20) → вес ↑,
повторения к 12; повторения к 12;
@ -440,6 +452,11 @@ Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных перен
обход блокировки «Отменить» логируется. обход блокировки «Отменить» логируется.
6. Талия и сон (утренняя телеметрия) и правила координации 7–10 — v2; 6. Талия и сон (утренняя телеметрия) и правила координации 7–10 — v2;
контрольные тесты — v3 (документ метрик, п. 5). контрольные тесты — v3 (документ метрик, п. 5).
7. **Во время силовой части:** пульс > `Safety.HrStrengthMax`
(умолчание 120) → предупреждение на телефоне (баннер + вибро,
спокойная формулировка по UI/UX-стандарту); на часы порог
передаётся в объёме канала (ОВ-3). Источник — программа тренировок
и документ метрик (п. 2.3).
### Ф-14. Перерывы и возврат в режим (телефон + сервер; изменение 2) ### Ф-14. Перерывы и возврат в режим (телефон + сервер; изменение 2)
@ -449,8 +466,11 @@ Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных перен
1. 🛡 **Break вычисляется автоматически:** пропущены все 1. 🛡 **Break вычисляется автоматически:** пропущены все
запланированные сессии за 4 дня подряд → программа переводится запланированные сессии за 4 дня подряд → программа переводится
в статус `break` (Жижин гл. 6: эффект тает с 4-го дня). Ручного в статус `break` (Жижин гл. 6: эффект тает с 4-го дня). Break ставится
включения break нет — только расчёт. автоматически по пропускам (основной путь); атлет может объявить
перерыв заранее (Ф-14.2, «уезжаю в отпуск») — такой break создаётся
с `source=manual`; произвольного ручного включения без объявления
нет.
2. **Причина запрашивается у атлета:** при срабатывании автомат 2. **Причина запрашивается у атлета:** при срабатывании автомат
показывает вопрос «Вы пропустили тренировки — что случилось?» показывает вопрос «Вы пропустили тренировки — что случилось?»
с выбором типа (болезнь / отпуск / аврал-недосып / травма / с выбором типа (болезнь / отпуск / аврал-недосып / травма /
@ -559,7 +579,7 @@ Garmin штатно — Ф-2.5, Ф-2.6). Код поля данных перен
| 5 | Способ сопоставления тренировок телефон ↔ .fit | Окно ± 15 минут (Ф-8.4) против точной метки начала из sync | ✅ Закрыт в этом документе (раздел 4, Ф-8.4) | | 5 | Способ сопоставления тренировок телефон ↔ .fit | Окно ± 15 минут (Ф-8.4) против точной метки начала из sync | ✅ Закрыт в этом документе (раздел 4, Ф-8.4) |
| 6 | Доступ в веб-интерфейс | ✅ Закрыт изменением vision 1.2: веб — административная консоль владельца, вход по логину и паролю (раздел 4, Ф-9); атлеты — только телефон | ✅ Закрыт (изменение vision 1.2) | | 6 | Доступ в веб-интерфейс | ✅ Закрыт изменением vision 1.2: веб — административная консоль владельца, вход по логину и паролю (раздел 4, Ф-9); атлеты — только телефон | ✅ Закрыт (изменение vision 1.2) |
| 7 | Формула оценочного 1ПМ | Эпли против Бжицки (разд. 4 дизайна LLM-планирования); различие ~5 % на малых повторениях | Закрыт шагом 5 | | 7 | Формула оценочного 1ПМ | Эпли против Бжицки (разд. 4 дизайна LLM-планирования); различие ~5 % на малых повторениях | Закрыт шагом 5 |
| 8 | Перечень моделей часов v1 | Якорь — Fenix 6 (изменение vision 1.1); каждая модель — конфигурация сборки (экран, память). Кандидаты: модели с профилем «Силовая» и Connect IQ 4+ у участников; список фиксируется по фактическому парку устройств | Закрыт шагом 4 | | 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) | | 9 | Способ заведения атлетов | ✅ Закрыт изменением vision 1.2: раздел «Атлеты» веб-консоли владельца (Ф-9.1) — имя + генерация ключа, смена ключа, блокировка; стартовые данные владельца — в конфиге | ✅ Закрыт (изменение vision 1.2) |
| 10 | Источник .fit для атлетов без учётки владельца | `fetch` идёт под учётной записью владельца (опросник 4.3); другие атлеты: (а) делят доступ к своему Garmin (передача файла в папку импорта), (б) свой экземпляр учётки в конфиге, (в) только ручной импорт. Кандидат для v1 — (в) папка импорта | Закрыт шагом 5 | | 10 | Источник .fit для атлетов без учётки владельца | `fetch` идёт под учётной записью владельца (опросник 4.3); другие атлеты: (а) делят доступ к своему Garmin (передача файла в папку импорта), (б) свой экземпляр учётки в конфиге, (в) только ручной импорт. Кандидат для v1 — (в) папка импорта | ✅ Закрыт (ревью №2, О-1-А): v1 — только папка импорта (`--mode import`); `fetch` — под единственной учёткой владельца; собственные учётки атлетов в конфиге — кандидат v2 (tech-requirements, ОВ-10). Уточнение владельца: атлет регистрируется на телефоне (имя), привязка — по uuid клиентского приложения |
| 11 | Общий справочник упражнений | Сейчас библиотека у каждого атлета своя (Ф-4.4); общий справочник + личные дополнения — кандидат v2 (дубли при малом числе атлетов не критичны) | Открыт (до v2) | | 11 | Общий справочник упражнений | Сейчас библиотека у каждого атлета своя (Ф-4.4); общий справочник + личные дополнения — кандидат v2 (дубли при малом числе атлетов не критичны) | Открыт (до v2) |

View File

@ -1,8 +1,13 @@
# Vision: Феникс 6X — контроль силовых тренировок (телефон + Garmin Fenix 6 + личный сервер) # Vision: Феникс 6X — контроль силовых тренировок (телефон + Garmin Fenix 6X + личный сервер)
> Версия 1.4 — **УТВЕРЖДЕНО** (2026-10-17; вопросы опросника 4.1–4.7 > Версия 1.5 — **УТВЕРЖДЕНО** (2026-10-17; ревью №2, З-18). Версия 1.4 —
> **УТВЕРЖДЕНО** (2026-10-17; вопросы опросника 4.1–4.7
> отвечены, ответы применены; утверждение — 2026-10-17; изменения > отвечены, ответы применены; утверждение — 2026-10-17; изменения
> 1.1–1.4 — указания пользователя от 2026-10-17). > 1.1–1.4 — указания пользователя от 2026-10-17).
> Изменение 1.5 (уточнение владельца 2026-10-17): якорные часы —
> **Fenix 6X** (280×280 MIP); в ТЗ указан «Fenix 6, 260×260» — считать
> опиской исходника; остальные модели (Fenix 6 — 260×260, 6S — 240×240)
> добавляются отдельными конфигурациями сборки (пп. 1.1, БР-3, З-3, 2.4).
> Изменение 1.1: серверная часть > Изменение 1.1: серверная часть
> поддерживает нескольких атлетов — у каждого свои программы, профиль > поддерживает нескольких атлетов — у каждого свои программы, профиль
> и данные, с собственных телефона и часов (пп. 2.2, 2.4, 3.1); > и данные, с собственных телефона и часов (пп. 2.2, 2.4, 3.1);
@ -95,8 +100,8 @@
Monkey C); полная запись тренировки ведётся самими часами, источник Monkey C); полная запись тренировки ведётся самими часами, источник
архивных данных — облако Garmin Connect. архивных данных — облако Garmin Connect.
Что уже есть: часы Garmin Fenix 6 (Connect IQ 4+, пульсометр Elevate Что уже есть: часы Garmin Fenix 6X (Connect IQ 4+, пульсометр Elevate
Gen 3, экран 260×260 MIP, управление кнопками), смартфон, аккаунт Gen 3, экран 280×280 MIP, управление кнопками), смартфон, аккаунт
Garmin Connect, личный сервер; стандарт разработки std1 (процесс из Garmin Connect, личный сервер; стандарт разработки std1 (процесс из
7 шагов, правила C#/.NET 10 + PostgreSQL, навыки). 7 шагов, правила C#/.NET 10 + PostgreSQL, навыки).
@ -217,7 +222,7 @@ Garmin, наш продукт фиксирует результаты без р
приложения, разные экраны/возможности моделей). Смягчение: приложения, разные экраны/возможности моделей). Смягчение:
минимальное поле данных, метрики — штатными источниками платформы; минимальное поле данных, метрики — штатными источниками платформы;
переносимый код + конфигурации сборки под модели (изменение 1.1); переносимый код + конфигурации сборки под модели (изменение 1.1);
якорное устройство — Fenix 6. якорное устройство — Fenix 6X (изменение 1.5).
- **БР-4. Ограниченный канал телефон ↔ часы в реальном времени** - **БР-4. Ограниченный канал телефон ↔ часы в реальном времени**
(companion-канал Connect IQ, не прямой BLE). Смягчение: часы автономны (companion-канал Connect IQ, не прямой BLE). Смягчение: часы автономны
(ТЗ п. 2.2), телефон показывает то, что канал передаёт; потеря канала (ТЗ п. 2.2), телефон показывает то, что канал передаёт; потеря канала
@ -239,9 +244,11 @@ Garmin, наш продукт фиксирует результаты без р
тренировке; смартфон обычно находится в зоне доступа микрофона тренировке; смартфон обычно находится в зоне доступа микрофона
(сумка, подставка), но его присутствие и связь с ним не обязательны: (сумка, подставка), но его присутствие и связь с ним не обязательны:
тренировка фиксируется часами и без телефона. тренировка фиксируется часами и без телефона.
- **Предположение П-2.** Вес и повторения выражаются целыми числами; - **Предположение П-2.** Вес выражается целыми килограммами либо
словарь команд ограничен набором ТЗ (подход/вес/отдых, активация) — конструкцией «…с половиной» (+0,5 кг, решение владельца О-2-Б);
достаточно офлайн-модели малого словаря. повторения — целыми числами; словарь команд ограничен набором ТЗ
(подход/вес/отдых, активация) — достаточно офлайн-модели малого
словаря.
- **Предположение П-3.** Личный сервер (VPS или домашний) доступен, - **Предположение П-3.** Личный сервер (VPS или домашний) доступен,
на нём устанавливаются HTTPS и PostgreSQL. на нём устанавливаются HTTPS и PostgreSQL.
- **Зависимость З-1.** Стандарт разработки std1 (`I:\Kit\Kit.DevAi\std1`): - **Зависимость З-1.** Стандарт разработки std1 (`I:\Kit\Kit.DevAi\std1`):
@ -251,7 +258,7 @@ Garmin, наш продукт фиксирует результаты без р
в конфигурации сервера, не в коде. в конфигурации сервера, не в коде.
- **Зависимость З-3.** Connect IQ SDK (Monkey C) и целевые устройства - **Зависимость З-3.** Connect IQ SDK (Monkey C) и целевые устройства
Garmin для сборки и отладки части часов; список моделей — открытый Garmin для сборки и отладки части часов; список моделей — открытый
вопрос шага 4 (якорь — Fenix 6). вопрос шага 4 (якорь — Fenix 6X, изменение 1.5).
- **Зависимость З-4.** Garmin FIT SDK (официальный, бесплатный, есть - **Зависимость З-4.** Garmin FIT SDK (официальный, бесплатный, есть
порт для C#) — парсинг .fit на сервере; Garmin может изменять порт для C#) — парсинг .fit на сервере; Garmin может изменять
интерфейсы Garmin Connect — риск БР-1. интерфейсы Garmin Connect — риск БР-1.
@ -407,7 +414,8 @@ LLM-планирования, без LLM; Ф-13 в MVP-составе докум
одноатлетная: устройство принадлежит одному человеку; одноатлетная: устройство принадлежит одному человеку;
- часы — модели Garmin с Connect IQ: одно поле данных собирается - часы — модели Garmin с Connect IQ: одно поле данных собирается
под разные модели (размеры/разрешения экранов — конфигурации сборки под разные модели (размеры/разрешения экранов — конфигурации сборки
одного кода); якорное устройство v1 — Fenix 6 (260×260 MIP, кнопки), одного кода); якорное устройство v1 — Fenix 6X (280×280 MIP, кнопки;
изменение 1.5),
добавление моделей — новой конфигурацией сборки без переделки кода добавление моделей — новой конфигурацией сборки без переделки кода
(перечень устройств v1+ — открытый вопрос шага 4); (перечень устройств v1+ — открытый вопрос шага 4);
- пульсофическая телеметрия силовых тренировок — только из FIT - пульсофическая телеметрия силовых тренировок — только из FIT

View File

@ -2,7 +2,7 @@
> Шаг 4 процесса разработки (`std1\_process\process.md`). > Шаг 4 процесса разработки (`std1\_process\process.md`).
> Источники: [vision.md](../1-reqs/vision.md) (v1.4, изменения 1.1–1.4), > Источники: [vision.md](../1-reqs/vision.md) (v1.4, изменения 1.1–1.4),
> [requirements.md](../1-reqs/requirements.md) v1.3 (утверждён), > [requirements.md](../1-reqs/requirements.md) v1.4 (черновик, ревью №2),
> дизайн LLM-планирования (разд. 8), документ форматов, документы > дизайн LLM-планирования (разд. 8), документ форматов, документы
> «Метрики…» и «Перерывы…». > «Метрики…» и «Перерывы…».
> Схема компонентов: [hld.puml](hld.puml) (+ рендер [hld.svg](hld.svg), > Схема компонентов: [hld.puml](hld.puml) (+ рендер [hld.svg](hld.svg),
@ -11,6 +11,8 @@
> Версия 1.1 — **УТВЕРЖДЕНО** (2026-10-17): актуализация под Ф-13 > Версия 1.1 — **УТВЕРЖДЕНО** (2026-10-17): актуализация под Ф-13
> (ревью З-2) и Ф-14 — перерывы (изменение vision 1.4); волна 2 ревью > (ревью З-2) и Ф-14 — перерывы (изменение vision 1.4); волна 2 ревью
> (Р1–Р3). > (Р1–Р3).
> Версия 1.2 — **УТВЕРЖДЕНО** (2026-10-17): ревью №2, З-29 — перечень `LocalDb`
> дополнен (`daily_log`, `breaks`); источник — requirements v1.4.
## 1. Компоненты и размещение ## 1. Компоненты и размещение
@ -41,7 +43,8 @@ vision 1.1); перечень моделей — открытый вопрос 8
автомат перерывов и формула возврата (Ф-14); единственный писатель автомат перерывов и формула возврата (Ф-14); единственный писатель
`LocalDb`. `LocalDb`.
- `LocalDb` — Room/SQLite: текущая тренировка, история, программы, - `LocalDb` — Room/SQLite: текущая тренировка, история, программы,
библиотека, профиль, очередь. библиотека, профиль, дневник самоконтроля (`daily_log`), перерывы
(`breaks`), очередь.
- `SyncQueue` — исходящие записи с `client_guid`, пакеты ≤ 50, - `SyncQueue` — исходящие записи с `client_guid`, пакеты ≤ 50,
идемпотентная отправка `POST /api/v1/sync`; тихие баннеры статуса. идемпотентная отправка `POST /api/v1/sync`; тихие баннеры статуса.
- `WatchChannel` — companion-канал Connect IQ. - `WatchChannel` — companion-канал Connect IQ.

View File

@ -10,9 +10,10 @@
> Новая редакция (2026-10-17): + `breaks` (break_insert/update/select), > Новая редакция (2026-10-17): + `breaks` (break_insert/update/select),
> `session_kind` в workout_insert/update — интеграция документа > `session_kind` в workout_insert/update — интеграция документа
> перерывов (Ф-14, изменение vision 1.4). > перерывов (Ф-14, изменение vision 1.4).
> Волна 2 (Р2-Б): insert клиентских сущностей принимает `p_id (uuid)` > Ревью №2 (2026-10-17): возвраты insert-функций uuid-сущностей — `uuid`,
> (PK — UUID); Р1-А: `daily_log_upsert` — `p_hr_resting`, `p_wellbeing`. > FK-параметры — uuid (З-16); `break_insert`/`break_update` —
> Утверждение новой редакции — Пользователь! > `p_planned_return`, `break_update` принимает ответ атлета (З-17);
> + `migration_insert`/`migration_select` (З-25). **УТВЕРЖДЕНО** (2026-10-17).
## Список функций ## Список функций
@ -24,32 +25,34 @@
| athlete | athlete_get | PLAN | p_id | TABLE — одна строка | - | - | — | | athlete | athlete_get | PLAN | p_id | TABLE — одна строка | - | - | — |
| athlete_profile | athlete_profile_upsert | PLAN | p_athlete_id, p_sex, p_birth_year, p_weight_kg, p_hr_max | int — id | - | - | 1:1 — `ON CONFLICT (athlete_id) DO UPDATE` | | athlete_profile | athlete_profile_upsert | PLAN | p_athlete_id, p_sex, p_birth_year, p_weight_kg, p_hr_max | int — id | - | - | 1:1 — `ON CONFLICT (athlete_id) DO UPDATE` |
| athlete_profile | athlete_profile_get | PLAN | p_athlete_id | TABLE — одна строка | - | - | — | | athlete_profile | athlete_profile_get | PLAN | p_athlete_id | TABLE — одна строка | - | - | — |
| exercise | exercise_insert | PLAN | p_athlete_id, p_name, p_group_id, p_is_base | int — id упражнения | - | - | дубликат имени у атлета — исключение UNIQUE | | exercise | exercise_insert | PLAN | p_athlete_id, p_id (uuid), p_name, p_group_id, p_is_base | uuid — id упражнения | - | - | дубликат имени у атлета — исключение UNIQUE |
| exercise | exercise_update | PLAN | p_athlete_id, p_id, p_name, p_group_id, p_is_base | int — число строк | - | - | `exercise_id` не меняется (Ф-4.3, 🛡) | | exercise | exercise_update | PLAN | p_athlete_id, p_id, p_name, p_group_id, p_is_base | int — число строк | - | - | `exercise_id` не меняется (Ф-4.3, 🛡) |
| exercise | exercise_delete | PLAN | p_athlete_id, p_id | int — число строк | - | - | логическое: `is_active=0` (Ф-4.2) | | exercise | exercise_delete | PLAN | p_athlete_id, p_id | int — число строк | - | - | логическое: `is_active=0` (Ф-4.2) |
| exercise | exercise_select | PLAN | p_athlete_id, p_name, p_group_id, p_is_base, p_is_active | TABLE — строки + пары замен (MM) | - | - | по умолчанию только `is_active`; замены — денормализация (d-db-3) | | exercise | exercise_select | PLAN | p_athlete_id, p_name, p_group_id, p_is_base, p_is_active | TABLE — строки + пары замен (MM) | - | - | по умолчанию только `is_active`; замены — денормализация (d-db-3) |
| exercise | exercise_get | PLAN | p_athlete_id, p_id | TABLE — одна строка + замены | - | - | — | | exercise | exercise_get | PLAN | p_athlete_id, p_id | TABLE — одна строка + замены | - | - | — |
| exercise_replacement | exercise_replacement_insert | PLAN | p_athlete_id, p_exercise_id, p_replacement_id | int — id | - | - | оба упражнения — атлета, группа равна (проверка в функции) | | exercise_replacement | exercise_replacement_insert | PLAN | p_athlete_id, p_exercise_id, p_replacement_id | int — число строк | - | - | оба упражнения — атлета, группа равна (проверка в функции) |
| exercise_replacement | exercise_replacement_delete | PLAN | p_athlete_id, p_exercise_id, p_replacement_id | int — число строк | - | - | — | | exercise_replacement | exercise_replacement_delete | PLAN | p_athlete_id, p_exercise_id, p_replacement_id | int — число строк | - | - | — |
| program | program_insert | PLAN | p_athlete_id, p_title | int — id программы | - | - | вторая программа атлета — исключение UNIQUE | | program | program_insert | PLAN | p_athlete_id, p_title | int — id программы | - | - | вторая программа атлета — исключение UNIQUE |
| program | program_select | PLAN | p_athlete_id | TABLE — строки + активная версия | - | - | денормализация активной версии (d-db-3) | | program | program_select | PLAN | p_athlete_id | TABLE — строки + активная версия | - | - | денормализация активной версии (d-db-3) |
| program | program_get | PLAN | p_athlete_id, p_program_id | TABLE — одна строка | - | - | — | | program | program_get | PLAN | p_athlete_id, p_program_id | TABLE — одна строка | - | - | — |
| program_version | program_version_insert | PLAN | p_athlete_id, p_program_id, p_based_on_version, p_author, p_body, p_rationale | int — id версии | - | - | `version` = max+1; `status='draft'`; body — jsonb | | program_version | program_version_insert | PLAN | p_athlete_id, p_id (uuid), p_program_id, p_based_on_version, p_author, p_body, p_rationale | uuid — id версии | - | - | `version` = max+1; `status='draft'`; body — jsonb |
| program_version | program_version_update | PLAN | p_athlete_id, p_id, p_status | int — число строк | - | - | 🛡 меняется только статус; переходы draft→active→archived; активной становится ровно одна (сдвиг прежней в archived) | | program_version | program_version_update | PLAN | p_athlete_id, p_id, p_status | int — число строк | - | - | 🛡 меняется только статус; переходы draft→active→archived; активной становится ровно одна (сдвиг прежней в archived) |
| program_version | program_version_select | PLAN | p_athlete_id, p_program_id, p_status, p_author | TABLE — строки (без body) | - | - | body — отдельным get (крупный jsonb) | | program_version | program_version_select | PLAN | p_athlete_id, p_program_id, p_status, p_author | TABLE — строки (без body) | - | - | body — отдельным get (крупный jsonb) |
| program_version | program_version_get | PLAN | p_athlete_id, p_id | TABLE — одна строка с body | - | - | — | | program_version | program_version_get | PLAN | p_athlete_id, p_id | TABLE — одна строка с body | - | - | — |
| workout | workout_insert | PLAN | p_athlete_id, p_id (uuid), p_program_version_id, p_started_at, p_session_kind | int — id | - | - | повтор `client_guid` возвращает существующий id (🛡 идемпотентность) | | workout | workout_insert | PLAN | p_athlete_id, p_id (uuid), p_program_version_id, p_started_at, p_session_kind | uuid — id | - | - | повтор `client_guid` возвращает существующий id (🛡 идемпотентность) |
| workout | workout_update | PLAN | p_athlete_id, p_id, p_ended_at, p_status, p_session_kind, p_avg_hr, p_facts_json, p_fit_file_id, p_hr_recovery_s | int — число строк | - | - | факты .fit дописывает FitParser; клиент — только ended/status/session_kind/avg_hr | | workout | workout_update | PLAN | p_athlete_id, p_id, p_ended_at, p_status, p_session_kind, p_avg_hr, p_facts_json, p_fit_file_id, p_hr_recovery_s | int — число строк | - | - | факты .fit дописывает FitParser; клиент — только ended/status/session_kind/avg_hr |
| workout | workout_select | PLAN | p_athlete_id, p_status, p_date_from, p_date_to | TABLE — карточки + подходы (агрегат jsonb) | - | - | мультикурсор/агрегат: карточка + sets (d-db-6) | | workout | workout_select | PLAN | p_athlete_id, p_status, p_date_from, p_date_to | TABLE — карточки + подходы (агрегат jsonb) | - | - | мультикурсор/агрегат: карточка + sets (d-db-6) |
| workout | workout_get | PLAN | p_athlete_id, p_id | TABLE — одна строка + sets + command_log | - | - | полная карточка (Ф-9.2) | | workout | workout_get | PLAN | p_athlete_id, p_id | TABLE — одна строка + sets + command_log | - | - | полная карточка (Ф-9.2) |
| workout_set | workout_set_insert | PLAN | p_athlete_id, p_id (uuid), p_workout_id, p_exercise_id, p_seq, p_weight_kg, p_reps, p_done_at, p_source, p_in_plan, p_rpe, p_knee_pain | int — id | - | - | идемпотентность по client_guid | | workout_set | workout_set_insert | PLAN | p_athlete_id, p_id (uuid), p_workout_id, p_exercise_id, p_seq, p_weight_kg, p_reps, p_done_at, p_source, p_in_plan, p_rpe, p_knee_pain | uuid — id | - | - | идемпотентность по PK uuid (Р2-Б) |
| workout_set | workout_set_get | PLAN | p_athlete_id, p_id | TABLE — одна строка | - | - | — | | workout_set | workout_set_get | PLAN | p_athlete_id, p_id | TABLE — одна строка | - | - | — |
| command_log | command_log_insert | PLAN | p_athlete_id, p_id (uuid), p_workout_id, p_t_offset_s, p_recognized_text, p_semantics, p_accepted | int — id | - | - | 🛡 не редактируется (Ф-1.5) | | command_log | command_log_insert | PLAN | p_athlete_id, p_id (uuid), p_workout_id, p_t_offset_s, p_recognized_text, p_semantics, p_accepted | uuid — id | - | - | 🛡 не редактируется (Ф-1.5) |
| command_log | command_log_select | PLAN | p_athlete_id, p_workout_id | TABLE — строки | - | - | для сопоставления с .fit | | command_log | command_log_select | PLAN | p_athlete_id, p_workout_id | TABLE — строки | - | - | для сопоставления с .fit |
| daily_log | daily_log_upsert | PLAN | p_athlete_id, p_id (uuid), p_date, p_bp_sys, p_bp_dias, p_weight_kg, p_hr_resting, p_wellbeing | uuid — id | - | - | `UNIQUE(athlete_id, date)` — upsert; hr_resting/wellbeing — поля v1 (Р1-А) | | daily_log | daily_log_upsert | PLAN | p_athlete_id, p_id (uuid), p_date, p_bp_sys, p_bp_dias, p_weight_kg, p_hr_resting, p_wellbeing | uuid — id | - | - | `UNIQUE(athlete_id, date)` — upsert; hr_resting/wellbeing — поля v1 (Р1-А) |
| daily_log | daily_log_select | PLAN | p_athlete_id, p_date_from, p_date_to | TABLE — строки | - | - | для сводки метрик и AdminWeb | | daily_log | daily_log_select | PLAN | p_athlete_id, p_date_from, p_date_to | TABLE — строки | - | - | для сводки метрик и AdminWeb |
| workout_metrics | workout_metrics_upsert | PLAN | p_athlete_id, p_workout_id, p_adherence, p_tonnage, p_tonnage_by_group, p_e1pm_by_exercise, p_hr_metrics, p_red_flags | int — id | - | - | 1:1, пересчёт MetricsEngine | | workout_metrics | workout_metrics_upsert | PLAN | p_athlete_id, p_workout_id, p_adherence, p_tonnage, p_tonnage_by_group, p_e1pm_by_exercise, p_hr_metrics, p_red_flags | int — id | - | - | 1:1, пересчёт MetricsEngine |
| workout_metrics | workout_metrics_select | PLAN | p_athlete_id, p_workout_id, p_date_from, p_date_to | TABLE — строки | - | - | графики AdminWeb (Ф-9.4) | | workout_metrics | workout_metrics_select | PLAN | p_athlete_id, p_workout_id, p_date_from, p_date_to | TABLE — строки | - | - | графики AdminWeb (Ф-9.4) |
| breaks | break_insert | PLAN | p_athlete_id, p_id (uuid), p_kind, p_source, p_started_on, p_note | int — id | - | - | auto-ставит сервер по пропускам; manual — телефон | | breaks | break_insert | PLAN | p_athlete_id, p_id (uuid), p_kind, p_source, p_started_on, p_planned_return, p_note | uuid — id | - | - | `auto` — сервер по пропускам (Ф-14.1); `manual` — телефон, объявление простоя заранее (Ф-14.2) |
| breaks | break_update | PLAN | p_athlete_id, p_id, p_ended_on | int — число строк | - | - | закрытие break первой сессией после возврата | | breaks | break_update | PLAN | p_athlete_id, p_id, p_kind, p_note, p_planned_return, p_ended_on | int — число строк | - | - | ответ атлета на вопрос о причине (Ф-14.2) + закрытие первой сессией после возврата |
| breaks | break_select | PLAN | p_athlete_id, p_kind, p_date_from, p_date_to | TABLE — строки | - | - | журнал перерывов; CSV-выгрузка (документ перерывов разд. 4) | | breaks | break_select | PLAN | p_athlete_id, p_kind, p_date_from, p_date_to | TABLE — строки | - | - | журнал перерывов; CSV-выгрузка (документ перерывов разд. 4) |
| migration | migration_insert | PLAN | p_filename | int — id | - | - | журнал миграций (d-db-1); пишет `--mode migrate` |
| migration | migration_select | PLAN | p_filename | TABLE — одна строка | - | - | проверка «применена ли миграция» |

View File

@ -19,12 +19,13 @@
| `src.command_log` | сущность | command_log_insert, command_log_select | CUSTOM | журнал голосовых команд (Ф-1.5, 🛡 не редактируется); update/delete нет | | `src.command_log` | сущность | command_log_insert, command_log_select | CUSTOM | журнал голосовых команд (Ф-1.5, 🛡 не редактируется); update/delete нет |
| `src.daily_log` | сущность | daily_log_upsert, daily_log_select | CUSTOM | день уникален для атлета (`UNIQUE(athlete_id, date)`) — upsert | | `src.daily_log` | сущность | daily_log_upsert, daily_log_select | CUSTOM | день уникален для атлета (`UNIQUE(athlete_id, date)`) — upsert |
| `src.workout_metrics` | сущность | workout_metrics_upsert, workout_metrics_select | CUSTOM | 1:1 к `workout`, считает сервер (MetricsEngine) — телефон только читает; upsert (пересчёт) | | `src.workout_metrics` | сущность | workout_metrics_upsert, workout_metrics_select | CUSTOM | 1:1 к `workout`, считает сервер (MetricsEngine) — телефон только читает; upsert (пересчёт) |
| `src.breaks` | сущность | break_insert, break_update, break_select | CUSTOM | перерывы (Ф-14); закрытие = update (`ended_at`); delete нет — журнал | | `src.breaks` | сущность | break_insert, break_update, break_select | CUSTOM | перерывы (Ф-14); закрытие = update (`ended_on`); delete нет — журнал |
| `src.migration` | служебная | migration_insert, migration_select | CUSTOM | журнал применённых миграций (d-db-1); пишет `--mode migrate` |
> Новая редакция (2026-10-17, волна 2 ревью, решения владельца Р1-А/Р2-Б): > Новая редакция (2026-10-17, ревью №2): FK-колонки приведены к uuid
> PK клиентских сущностей — UUID (Р2-Б); `hr_resting`/`wellbeing` — > (Р2-Б доведён, З-16); `src.breaks` — `planned_return`, `source` = auto|manual
> поля v1 (Р1-А); `src.breaks`, `session_kind` — Ф-14. Утверждение — > (З-17); `src.migration` — журнал миграций (З-25). УТВЕРЖДЕНО
> Пользователь! > (2026-10-17).
## Описания ## Описания
@ -65,8 +66,8 @@
| Поле | Тип | Описание | | Поле | Тип | Описание |
|---|---|---| |---|---|---|
| `exercise_id` | int | → `src.exercise.id` | | `exercise_id` | uuid | → `src.exercise.id` |
| `replacement_id` | int | → `src.exercise.id`; `CHECK (exercise_id <> replacement_id)`; `PK (exercise_id, replacement_id)` | | `replacement_id` | uuid | → `src.exercise.id`; `CHECK (exercise_id <> replacement_id)`; `PK (exercise_id, replacement_id)` |
Замена допустима только внутри группы — проверяет приложение Замена допустима только внутри группы — проверяет приложение
(группы у обоих равны); БД фиксирует пары. (группы у обоих равны); БД фиксирует пары.
@ -100,7 +101,7 @@
|---|---|---| |---|---|---|
| `id` | uuid PK | UUID — телефон (строки «только .fit» — сервер); идемпотентность (Ф-7.4, Р2-Б) | | `id` | uuid PK | UUID — телефон (строки «только .fit» — сервер); идемпотентность (Ф-7.4, Р2-Б) |
| `athlete_id` | int NOT NULL | → `src.athlete.id` | | `athlete_id` | int NOT NULL | → `src.athlete.id` |
| `program_version_id` | int NULL | → `src.program_version.id`; версия, активная на начало (Ф-10.3) | | `program_version_id` | uuid NULL | → `src.program_version.id`; версия, активная на начало (Ф-10.3) |
| `started_at` | timestamptz NOT NULL | начало (UTC) | | `started_at` | timestamptz NOT NULL | начало (UTC) |
| `ended_at` | timestamptz NULL | конец | | `ended_at` | timestamptz NULL | конец |
| `status` | text NOT NULL DEFAULT 'active' | `active` \| `finished` \| `cancelled` (Ф-3.5) | | `status` | text NOT NULL DEFAULT 'active' | `active` \| `finished` \| `cancelled` (Ф-3.5) |
@ -110,9 +111,11 @@
| `fit_file_id` | text NULL | дедупликация fetch (Ф-8.1); уникальность — составная `UNIQUE(athlete_id, fit_file_id)` | | `fit_file_id` | text NULL | дедупликация fetch (Ф-8.1); уникальность — составная `UNIQUE(athlete_id, fit_file_id)` |
| `hr_recovery_s` | int NULL | возврат пульса к <90 (Ф-13.4, из .fit) | | `hr_recovery_s` | int NULL | возврат пульса к <90 (Ф-13.4, из .fit) |
`UNIQUE(athlete_id, fit_file_id)` — единственная уникальность `fit_file_id`; сопоставление телефон ↔ .fit — окно `UNIQUE(athlete_id, fit_file_id)` — единственная уникальность
±15 минут по `started_at` (Ф-8.4): факты пишутся в ту же строку `fit_file_id`; сопоставление телефон ↔ .fit — окно ±15 минут по
(`facts_json`), либо создается строка «только .fit» строкой «только .fit» — тогда UUID `UUID генерирует сервер (Р2-Б). `started_at` (Ф-8.4): факты пишутся в ту же строку (`facts_json`); если
тренировки с телефона не было — создаётся строка «только .fit» с UUID,
который генерирует сервер (Р2-Б).
### `src.workout_set` — подход (телефон) ### `src.workout_set` — подход (телефон)
@ -121,7 +124,7 @@
| `id` | uuid PK | UUID — телефон (Р2-Б) | | `id` | uuid PK | UUID — телефон (Р2-Б) |
| `workout_id` | uuid NOT NULL | → `src.workout.id` | | `workout_id` | uuid NOT NULL | → `src.workout.id` |
| `athlete_id` | int NOT NULL | денормализация для изоляции выборок | | `athlete_id` | int NOT NULL | денормализация для изоляции выборок |
| `exercise_id` | int NULL | → `src.exercise.id`; NULL — упражнение не выбрано | | `exercise_id` | uuid NULL | → `src.exercise.id`; NULL — упражнение не выбрано |
| `seq` | int NOT NULL | порядковый номер подхода в тренировке | | `seq` | int NOT NULL | порядковый номер подхода в тренировке |
| `weight_kg` | numeric(6,2) NOT NULL | вес | | `weight_kg` | numeric(6,2) NOT NULL | вес |
| `reps` | int NOT NULL | повторения | | `reps` | int NOT NULL | повторения |
@ -164,8 +167,9 @@
| `id` | uuid PK | UUID — телефон/сервер (Р2-Б) | | `id` | uuid PK | UUID — телефон/сервер (Р2-Б) |
| `athlete_id` | int NOT NULL | → `src.athlete.id` | | `athlete_id` | int NOT NULL | → `src.athlete.id` |
| `kind` | text NULL | ответ атлета: `illness` \| `vacation` \| `workload` \| `injury` \| `other`; NULL — ещё не отвечен (правила 12–14 ждут ответа) | | `kind` | text NULL | ответ атлета: `illness` \| `vacation` \| `workload` \| `injury` \| `other`; NULL — ещё не отвечен (правила 12–14 ждут ответа) |
| `source` | text NOT NULL DEFAULT 'auto' | 🛡 всегда `auto` — break вычисляется по пропускам (Ф-14.1) | | `source` | text NOT NULL DEFAULT 'auto' | `auto` — сервер по пропускам (Ф-14.1); `manual` — запланированный простой, объявленный атлетом заранее с телефона (Ф-14.2). Из sync принимается только `manual`; `auto` проставляет сервер |
| `started_on` | date NOT NULL | первый пропущенный день | | `started_on` | date NOT NULL | первый пропущенный день |
| `planned_return` | date NULL | предполагаемая дата возврата (ответ атлета, Ф-14.2) |
| `ended_on` | date NULL | возврат (заполняется первой сессией после break) | | `ended_on` | date NULL | возврат (заполняется первой сессией после break) |
| `note` | text | причина-текст пользователя | | `note` | text | причина-текст пользователя |
@ -183,3 +187,11 @@
| `hr_metrics` | jsonb | средний/пиковый пульс подхода, отдых между подходами | | `hr_metrics` | jsonb | средний/пиковый пульс подхода, отдых между подходами |
| `red_flags` | jsonb | сработавшие красные флаги (АД, пульс, колено — документ метрик) | | `red_flags` | jsonb | сработавшие красные флаги (АД, пульс, колено — документ метрик) |
| `computed_at` | timestamptz NOT NULL | — | | `computed_at` | timestamptz NOT NULL | — |
### `src.migration` — журнал применённых миграций (служебная, d-db-1)
| Поле | Тип | Описание |
|---|---|---|
| `id` | serial PK | — |
| `filename` | text NOT NULL UNIQUE | имя применённого psql-файла из `2-db\migrations\` |
| `applied_at` | timestamptz NOT NULL DEFAULT now() | момент применения |

View File

@ -1,7 +1,7 @@
# Технические требования: WatchBody («Феникс 6X») v1 # Технические требования: WatchBody («Феникс 6X») v1
> Шаг 5 процесса разработки (`std1\_process\process.md`). > Шаг 5 процесса разработки (`std1\_process\process.md`).
> Источники: [vision.md](../1-reqs/vision.md) v1.4, > Источники: [vision.md](../1-reqs/vision.md) v1.5,
> [requirements.md](../1-reqs/requirements.md) (утверждён, раздел 7 — > [requirements.md](../1-reqs/requirements.md) (утверждён, раздел 7 —
> открытые вопросы), [architecture.md](../2-arch/architecture.md) > открытые вопросы), [architecture.md](../2-arch/architecture.md)
> (утверждена), дизайн LLM-планирования, документ форматов, > (утверждена), дизайн LLM-планирования, документ форматов,
@ -15,6 +15,12 @@
> поля), Р2-Б (UUID PK клиентских сущностей, маппинг id не нужен), > поля), Р2-Б (UUID PK клиентских сущностей, маппинг id не нужен),
> Р3-А (Safety на телефоне), З-7 (RPE 1–10); UI/UX-стандарт > Р3-А (Safety на телефоне), З-7 (RPE 1–10); UI/UX-стандарт
> (`ui-ux-recommendations.md`). Утверждение — Пользователь! > (`ui-ux-recommendations.md`). Утверждение — Пользователь!
> Версия 1.4 — **УТВЕРЖДЕНО** (2026-10-17): ревью №2
> (`0-mng\review-2026-10-17-2.md`) — сущность `break` согласована с
> `src.breaks` (З-17), ОВ-7 — таргет `fenix6x` 280×280 (З-18),
> ОВ-10 закрыт (З-19, О-1-А), `Safety.HrStrengthMax` (З-20),
> интервал fetch 15 минут (З-21), грамматика «…с половиной» (З-26,
> О-2-Б), `Progression.IsoStepKg` (З-27, О-3-А).
## 1. Решения открытых вопросов (закрытие раздела 7 requirements) ## 1. Решения открытых вопросов (закрытие раздела 7 requirements)
@ -24,10 +30,11 @@
| 2. Офлайн-STT | **Vosk** (малая русская модель ~50 МБ) + узкая грамматика команд (Ф-1.2: числа и слова «подход/вес/отдых/секунд/РПЭ» — «РПЭ 8» ставит RPE последнему | 2. Офлайн-STT | **Vosk** (малая русская модель ~50 МБ) + узкая грамматика команд (Ф-1.2: числа и слова «подход/вес/отдых/секунд/РПЭ» — «РПЭ 8» ставит RPE последнему
подходу, Ф-13.3); TTS-подтверждения — системный Android TTS | Работа без сети, малый словарь → устойчивость к шуму; sherpa-onnx — запасная кандидатура, если точность < КУ-1 на контрольной тренировке | подходу, Ф-13.3); TTS-подтверждения — системный Android TTS | Работа без сети, малый словарь → устойчивость к шуму; sherpa-onnx — запасная кандидатура, если точность < КУ-1 на контрольной тренировке |
| 3. Канал телефон ↔ часы | Connect IQ **Communications SDK** (companion-канал через приложение Garmin Connect Mobile на телефоне). Передаётся: текущий пульс (≈1 Гц), зона, таймер отдыха (секунды до конца), заряд часов. Ограничение: на телефоне должно быть установлено и залогинено Garmin Connect Mobile; канал может не доставлять — обе стороны деградируют молча (Ф-2.4, Ф-3.2) | Единственный штатный канал сторонних приложений Garmin; проверка объёма передачи — прототипом в итерации 1 шага 7 (критерий: пульс и таймер доходят при экране часов активным) | | 3. Канал телефон ↔ часы | Connect IQ **Communications SDK** (companion-канал через приложение Garmin Connect Mobile на телефоне). Передаётся: текущий пульс (≈1 Гц), зона, таймер отдыха (секунды до конца), заряд часов. Ограничение: на телефоне должно быть установлено и залогинено Garmin Connect Mobile; канал может не доставлять — обе стороны деградируют молча (Ф-2.4, Ф-3.2) | Единственный штатный канал сторонних приложений Garmin; проверка объёма передачи — прототипом в итерации 1 шага 7 (критерий: пульс и таймер доходят при экране часов активным) |
| 4. Расписание fetch | Внешний планировщик ОС: systemd timer (Linux-сервер) / Планировщик задач (Windows) вызывает `--mode fetch`; внутри сервера планировщика нет | Проще, надёжнее, не дублирует средства ОС | | 4. Расписание fetch | Внешний планировщик ОС: systemd timer (Linux-сервер) / Планировщик задач (Windows) вызывает `--mode fetch`; внутри сервера планировщика нет. Интервал вызова — 15 минут (параметр планировщика ОС, в код не входит); вместе с лагом синхронизации часов с Garmin Connect укладывается в БЦ-5 (архив ≤ 30 минут после тренировки) | Проще, надёжнее, не дублирует средства ОС |
| 10. Источник .fit атлетов без учётки владельца | v1 — только папка импорта (`--mode import`); `fetch` работает под единственной учёткой владельца; собственные учётки атлетов в конфиге — кандидат v2. Уточнение владельца: атлет регистрируется на телефоне (имя), привязка — по uuid клиентского приложения | Не раздувать v1; деградация уже работает (решение владельца О-1-А, З-19) |
| 5. Формула 1ПМ | **Эпли**: `1ПМ = w × (1 + reps/30)` | Стандарт индустрии; при reps ≤ 10 различие с Бжицки < 2 % | | 5. Формула 1ПМ | **Эпли**: `1ПМ = w × (1 + reps/30)` | Стандарт индустрии; при reps ≤ 10 различие с Бжицки < 2 % |
| 6. UI телефона | **Jetpack Compose** + Material 3 (тёмная тема по умолчанию) | Актуальный стек Kotlin; крупные зоны касания — Min touch target 48 dp (Ф-3.6) | | 6. UI телефона | **Jetpack Compose** + Material 3 (тёмная тема по умолчанию) | Актуальный стек Kotlin; крупные зоны касания — Min touch target 48 dp (Ф-3.6) |
| 7. Модели часов v1 | Таргет `fenix6` (Fenix 6/6S/6X, включая Pro/Sapphire — одно разрешение 260×260); добавление моделей — новыми таргетами по фактическому парку (шаг 7) | Якорное устройство ТЗ; 6S/6X отличаются размером корпуса, не экраном | | 7. Модели часов v1 | Таргет v1 — `fenix6x` (280×280, MIP, кнопки). Fenix 6 (260×260) и 6S (240×240) добавляются отдельными таргетами с собственными макетами (Ф-2.8) по фактическому парку (уточнение владельца 2026-10-17, изменение vision 1.5) | Якорные часы владельца — Fenix 6X; «одно разрешение 260×260» в ТЗ — описка исходника |
| 8. Сопоставление телефон ↔ .fit | Окно ± 15 минут по времени начала (Ф-8.4); при нескольких кандидатах — ближайший по времени | ✅ закрыт в requirements | | 8. Сопоставление телефон ↔ .fit | Окно ± 15 минут по времени начала (Ф-8.4); при нескольких кандидатах — ближайший по времени | ✅ закрыт в requirements |
## 2. Форматы обмена ## 2. Форматы обмена
@ -64,9 +71,11 @@
`done_at`, `source` (voice|watch|edit), `in_plan` (bool), `rpe` (int? 1–10, З-7), `done_at`, `source` (voice|watch|edit), `in_plan` (bool), `rpe` (int? 1–10, З-7),
`knee_pain` (int? 0–10, только группа «ноги»); `knee_pain` (int? 0–10, только группа «ноги»);
- `profile`: `sex`, `birth_year`, `weight_kg`, `hr_max` (nullable); - `profile`: `sex`, `birth_year`, `weight_kg`, `hr_max` (nullable);
- `break`: `kind` (NULL до ответа атлета), `started_on`, `note`, - `break`: `kind` (NULL до ответа атлета), `source` (телефон передаёт
`planned_return` — break ставится автоматически (детекция по `manual` — объявление простоя заранее; `auto` проставляет сервер по
пропускам ≥ 4 дней), причина запрашивается у атлета (Ф-14.1–2); пропускам ≥ 4 дней, Ф-14.1), `started_on`, `planned_return`, `note`;
`ended_on` проставляет сервер первой сессией после возврата
(совпадает с `src.breaks`, З-17);
- `daily_log`: `date` (YYYY-MM-DD), `bp_sys`, `bp_dias`, `weight_kg`, - `daily_log`: `date` (YYYY-MM-DD), `bp_sys`, `bp_dias`, `weight_kg`,
`hr_resting`, `wellbeing` (1–5 — Р1-А, поля v1; v2 добавит `hr_resting`, `wellbeing` (1–5 — Р1-А, поля v1; v2 добавит
`waist_cm`, `sleep_h`); `waist_cm`, `sleep_h`);
@ -112,6 +121,7 @@
| `Safety.BpCancelSys / BpCancelDias` | int | умолчания 150/90 (документ метрик, Р3-А) | | `Safety.BpCancelSys / BpCancelDias` | int | умолчания 150/90 (документ метрик, Р3-А) |
| `Safety.BpCardioSys / BpCardioDias` | int | умолчания 140/90; ≤ BpCancel | | `Safety.BpCardioSys / BpCardioDias` | int | умолчания 140/90; ≤ BpCancel |
| `Safety.RestingHrSitting` | int | умолчание 90 | | `Safety.RestingHrSitting` | int | умолчание 90 |
| `Safety.HrStrengthMax` | int | 100–180 (умолчание 120, З-20) |
| `Safety.LightWeekHrRise` | int | умолчание 8 | | `Safety.LightWeekHrRise` | int | умолчание 8 |
🛡 Р3-А: телефонные `Safety.*` — источник экрана допуска (офлайн); 🛡 Р3-А: телефонные `Safety.*` — источник экрана допуска (офлайн);
@ -130,11 +140,13 @@
| `Garmin.Login/Password` | string | обязательны для `--mode fetch` (иначе код возврата 1) | | `Garmin.Login/Password` | string | обязательны для `--mode fetch` (иначе код возврата 1) |
| `Garmin.DownloadDir / Import.Dir / Import.ArchiveDir` | path | создание при старте; недоступность — фатально | | `Garmin.DownloadDir / Import.Dir / Import.ArchiveDir` | path | создание при старте; недоступность — фатально |
| `Progression.BaseStepKg` | double | 0,5–10 | | `Progression.BaseStepKg` | double | 0,5–10 |
| `Progression.IsoStepKg` | double | 0,25–5 (умолчание 1,0 — коридор метрик «+0,5–1 кг», решение владельца О-3-А, З-27) |
| `Progression.BackoffPercent` | int | 1–20 | | `Progression.BackoffPercent` | int | 1–20 |
| `Deload.MaxWeeks` | int | 3–12 | | `Deload.MaxWeeks` | int | 3–12 |
| `Safety.BpCancelSys / BpCancelDias` | int | 120–200 / 70–130 (умолчания 150/90) | | `Safety.BpCancelSys / BpCancelDias` | int | 120–200 / 70–130 (умолчания 150/90) |
| `Safety.BpCardioSys / BpCardioDias` | int | ≤ BpCancel (умолчания 140/90) | | `Safety.BpCardioSys / BpCardioDias` | int | ≤ BpCancel (умолчания 140/90) |
| `Safety.RestingHrSitting` | int | 60–120 (умолчание 90) | | `Safety.RestingHrSitting` | int | 60–120 (умолчание 90) |
| `Safety.HrStrengthMax` | int | 100–180 (умолчание 120, З-20) |
| `Safety.LightWeekHrRise` | int | 3–15 (умолчание 8) | | `Safety.LightWeekHrRise` | int | 3–15 (умолчание 8) |
| `Log.Path / Log.Level` | — | уровень: Debug/Info/Error | | `Log.Path / Log.Level` | — | уровень: Debug/Info/Error |
@ -155,7 +167,8 @@
`Sync.WifiOnly`); ретрай — экспоненциально, 1 мин → 64 мин. `Sync.WifiOnly`); ретрай — экспоненциально, 1 мин → 64 мин.
- **`VoiceInput`**: Vosk-API (Kaldi) как service в процессе приложения; - **`VoiceInput`**: Vosk-API (Kaldi) как service в процессе приложения;
грамматика — JSON-словарь `[«подход», «вес», «отдых», «на», «секунд», грамматика — JSON-словарь `[«подход», «вес», «отдых», «на», «секунд»,
0–999]`; числа — постобработка («сто двадцать» → 120). «с половиной», 0–999]`; числа — постобработка («сто двадцать» → 120;
«…с половиной» → +0,5 кг — решение владельца О-2-Б, З-26).
- **`AdminWeb`**: Razor Pages, сессия по cookie после логина; - **`AdminWeb`**: Razor Pages, сессия по cookie после логина;
пароль — PBKDF2 (см. 4.2); графики — Chart.js (локальный ассет, пароль — PBKDF2 (см. 4.2); графики — Chart.js (локальный ассет,
без CDN). без CDN).

View File

@ -1,6 +1,7 @@
# UI/UX-рекомендации для разработки WatchBody # UI/UX-рекомендации для разработки WatchBody
Версия 1.0 · 2026-10-17 Версия 1.1 · 2026-10-17, УТВЕРЖДЕНО (ревью №2, З-28: синхронизация — UUID PK
клиентских сущностей (Р2-Б); ввод RPE/боли — на телефоне, не на часах)
Основа: исследование книг и гайдов по wearable- и mobile-UX (Sullivan «Designing for Wearables», Wroblewski «Mobile First», Nielsen/Budiu «Mobile Usability», Krug «Don't Make Me Think», Norman «The Design of Everyday Things», гайды Android Wear OS, ProtoPie, UsabilityGeek, MDPI 2024). Ниже — применение этих принципов к составу WatchBody: приложение на часах (Garmin Fenix 6X, Connect IQ), компаньон на телефоне, веб-админ-консоль владельца. Основа: исследование книг и гайдов по wearable- и mobile-UX (Sullivan «Designing for Wearables», Wroblewski «Mobile First», Nielsen/Budiu «Mobile Usability», Krug «Don't Make Me Think», Norman «The Design of Everyday Things», гайды Android Wear OS, ProtoPie, UsabilityGeek, MDPI 2024). Ниже — применение этих принципов к составу WatchBody: приложение на часах (Garmin Fenix 6X, Connect IQ), компаньон на телефоне, веб-админ-консоль владельца.
@ -25,7 +26,7 @@
### 2.2. Поток тренировки ### 2.2. Поток тренировки
- **Во время подхода** — не требуем никакого взаимодействия: часы показывают текущее упражнение/сет и вибрируют по завершении отдыха. - **Во время подхода** — не требуем никакого взаимодействия: часы показывают текущее упражнение/сет и вибрируют по завершении отдыха.
- **Ввод RPE/боли по сетам** — упрощённая шкала (вариант кнопками UP/DOWN по значению из плана ±, подтверждение START); значение по умолчанию = последнее для этого упражнения. - **Ввод RPE/боли по сетам** — упрощённая шкала (кнопки UP/DOWN ±, подтверждение START). Примечание (З-28): к форме «поле данных» неприменимо — кнопки обрабатывает Garmin (Ф-2.5–2.6); ввод RPE/боли выполняется на телефоне (Ф-13.3).
- **Отдых между подходами** — крупнейший таймер на экране, вибрация за 5 с и в конце; на том же экране — следующий подход («3×10 · 40 кг»), чтобы атлет готовил вес заранее. - **Отдых между подходами** — крупнейший таймер на экране, вибрация за 5 с и в конце; на том же экране — следующий подход («3×10 · 40 кг»), чтобы атлет готовил вес заранее.
- **Safety-пороги** (по техтребованиям): предупреждение — вибрация + экран с крупным текстом и одной кнопкой «OK»; порог «стоп» — красный/инвертированный экран, требующий явного подтверждения. Никаких предупреждений очередями. - **Safety-пороги** (по техтребованиям): предупреждение — вибрация + экран с крупным текстом и одной кнопкой «OK»; порог «стоп» — красный/инвертированный экран, требующий явного подтверждения. Никаких предупреждений очередями.
- Экран пробуждения/приветствия перед стартом тренировки: название тренировки дня, кол-во упражнений, ожидаемое время — и START. - Экран пробуждения/приветствия перед стартом тренировки: название тренировки дня, кол-во упражнений, ожидаемое время — и START.
@ -41,7 +42,7 @@
- **Одна рука**: ключевые действия (старт/пауза тренировки, отметка подхода) — в нижней трети экрана, в зоне большого пальца. - **Одна рука**: ключевые действия (старт/пауза тренировки, отметка подхода) — в нижней трети экрана, в зоне большого пальца.
- **Целевая аудитория 45+**: базовый размер шрифта ≥ 16 pt, контраст по WCAG AA (≥ 4.5:1), крупные цели касания ≥ 44×44 pt, без тонких шрифтов и «серого на сером». Это же лечит использование в зале при ярком свете. - **Целевая аудитория 45+**: базовый размер шрифта ≥ 16 pt, контраст по WCAG AA (≥ 4.5:1), крупные цели касания ≥ 44×44 pt, без тонких шрифтов и «серого на сером». Это же лечит использование в зале при ярком свете.
- **Ввод данных**: числовые поля — пикеры с шагом (2.5 кг), а не клавиатура; последний использованный вес — значение по умолчанию; для RPE/боли — сегмент-контролы, не слайдеры. - **Ввод данных**: числовые поля — пикеры с шагом (2.5 кг), а не клавиатура; последний использованный вес — значение по умолчанию; для RPE/боли — сегмент-контролы, не слайдеры.
- **Офлайн-first**: все данные тренировки пишутся локально и синхронизируются позже (sync по client_guid/id-mapping — см. архитектуру); UI честно показывает статус синхронизации. - **Офлайн-first**: все данные тренировки пишутся локально и синхронизируются позже (sync — UUID PK клиентских сущностей, Р2-Б; см. архитектуру); UI честно показывает статус синхронизации.
- Уведомления — только значимые: напоминание о тренировке, итоги, нарушение Safety-порога. Никакого «стreak-спама». - Уведомления — только значимые: напоминание о тренировке, итоги, нарушение Safety-порога. Никакого «стreak-спама».
## 4. Веб-админ-консоль владельца ## 4. Веб-админ-консоль владельца
@ -77,7 +78,7 @@
- **Glanceable surfaces**: основное содержимое — в виде «взгляд-поверхностей» (у Garmin аналог — data fields в активити и watch face complications: можно вывести «текущее упражнение / подход X из Y» прямо на циферблат в дни тренировок). - **Glanceable surfaces**: основное содержимое — в виде «взгляд-поверхностей» (у Garmin аналог — data fields в активити и watch face complications: можно вывести «текущее упражнение / подход X из Y» прямо на циферблат в дни тренировок).
- **Always relevant**: контент подстраивается под контекст (время суток, день недели, запланированная тренировка). - **Always relevant**: контент подстраивается под контекст (время суток, день недели, запланированная тренировка).
- **Works offline**: часы и телефон обязаны полноценно работать без сети (спорт-сценарий) — совпадает с офлайн-first требованием проекта. - **Works offline**: часы и телефон обязаны полноценно работать без сети (спорт-сценарий) — совпадает с офлайн-first требованием проекта.
- Полезные референсы из этого гайда: non-scrolling layouts для потоков «подход/отдых», confirmation overlay для подтверждений, dialogs/steppers/pickers как канонические компоненты — переносить паттерны на кнопочную навигацию Fenix. - Полезные референсы из этого гайда: non-scrolling layouts для потоков «подход/отдых», confirmation overlay для подтверждений, dialogs/steppers/pickers как канонические компоненты — переносить паттерны на кнопочную навигацию Fenix (ввод RPE/боли — на телефоне, Ф-13.3).
### 6.3. MDPI 2024 — исследование юзабилити смарт-часов ### 6.3. MDPI 2024 — исследование юзабилити смарт-часов
@ -91,7 +92,7 @@
- сообщения об ошибках исчезают быстрее, чем их успевают прочитать (GW4) → все предупреждения Safety закрываются только явным нажатием кнопки; - сообщения об ошибках исчезают быстрее, чем их успевают прочитать (GW4) → все предупреждения Safety закрываются только явным нажатием кнопки;
- нет автоматического переподключения после разрыва связи (типовая жалоба) → устойчивый reconnect часы↔телефон↔сервер и честный индикатор статуса; - нет автоматического переподключения после разрыва связи (типовая жалоба) → устойчивый reconnect часы↔телефон↔сервер и честный индикатор статуса;
- нельзя поменять размер шрифта → настройки размера шрифта на телефоне; - нельзя поменять размер шрифта → настройки размера шрифта на телефоне;
- сброс настроек/сопряжения после обновлений → сохранение конфигурации на сервере (id-mapping), а не только на устройстве. - сброс настроек/сопряжения после обновлений → хранение конфигурации на сервере, а не только на устройстве (id передаётся в UUID-ключах записей — Р2-Б).
## 7. Процесс проверки ## 7. Процесс проверки

View File

@ -6,7 +6,7 @@
приложение атлета), **Server.WatchBody** (серверная часть); поле данных приложение атлета), **Server.WatchBody** (серверная часть); поле данных
часов — WatchBody Data Field. часов — WatchBody Data Field.
Персональный комплекс «часы Garmin Fenix 6 + телефон Android + личный Персональный комплекс «часы Garmin Fenix 6X + телефон Android + личный
сервер»: голосовой учёт подходов, кардиоконтроль на запястье, ведение сервер»: голосовой учёт подходов, кардиоконтроль на запястье, ведение
и корректировка программ тренировок, полный архив с пульсом в и корректировка программ тренировок, полный архив с пульсом в
собственной базе. собственной базе.
@ -41,7 +41,7 @@ Garmin, наш продукт фиксирует результаты без р
## Состав системы и архитектурные фиксации ## Состав системы и архитектурные фиксации
- **Часы Garmin Fenix 6** — Monkey C (Connect IQ SDK): поле данных - **Часы Garmin Fenix 6X** — Monkey C (Connect IQ SDK): поле данных
в штатном профиле «Силовая»; пульс, зоны (50/70/85 %), Lap — конец в штатном профиле «Силовая»; пульс, зоны (50/70/85 %), Lap — конец
подхода; полностью автономны от телефона. подхода; полностью автономны от телефона.
- **Телефон** — нативный Android (Kotlin, Room, офлайн-STT - **Телефон** — нативный Android (Kotlin, Room, офлайн-STT