89 lines
7.5 KiB
Markdown
89 lines
7.5 KiB
Markdown
# Требования к модулю: m04-metrics-engine
|
||
|
||
> Модуль — сервис `MetricsEngine` в `Server.WatchBody.Core`:
|
||
> детерминированный расчёт метрик тренировки (adherence, тоннаж,
|
||
> оценочный 1ПМ, пульс-метрики, красные флаги) после каждой
|
||
> синхронизированной тренировки → `src.workout_metrics` (1:1, upsert).
|
||
> Источники: Ф-12.1/Ф-12.5, Ф-13.5, документ метрик
|
||
> (`..\..\..\Метрики_тренировок_и_самоконтроля.md`, п. 2.5, правила
|
||
> 1–6), архитектура (строка `MetricsEngine`), дизайн LLM-планирования
|
||
> разд. 4. Самодостаточная копия требований (d-dev-6).
|
||
|
||
## 1. Назначение и границы
|
||
|
||
**В границах модуля:**
|
||
|
||
- класс `MetricsEngine` (`Server.WatchBody.Core`, каталог `Metrics\`):
|
||
расчёт метрик одной тренировки атлета по его данным и запись в
|
||
`workout_metrics` (upsert — пересчёт разрешён);
|
||
- вызов из m02: после успешного коммита пакета sync MetricsEngine
|
||
пересчитывает затронутые тренировки (Ф-12.1 «после каждой
|
||
синхронизированной тренировки»); сбой расчёта не откатывает sync
|
||
(метрики — производные данные, пересчитываются повторно);
|
||
- пороги красных флагов — из конфигурации `Safety.*` сервера
|
||
(Р3-А; умолчания — документ метрик, Ф-13.5);
|
||
- тесты сервиса (интеграционные, docker `watchbody-pg`).
|
||
|
||
**Вне границ модуля:** правила прогрессии/делоада и создание
|
||
draft-версий (m05 — читает `workout_metrics` и `workout_set`);
|
||
парсинг .fit (m06 — кладёт факты в `workout.facts_json`/`avg_hr`/
|
||
`hr_recovery_s` до расчёта); отображение метрик (телефон m09/m10,
|
||
AdminWeb m08 — читают `workout_metrics_select`); расчёт adherence
|
||
«за период» и еженедельные сводки (п. 2.5 документа метрик, не
|
||
названные в v1) — по потребности m05/m08; перерывы `auto` по
|
||
пропускам (Ф-14.1 — отдельная логика, не метрика).
|
||
|
||
## 2. Состав метрик (контракт документа метрик, v1)
|
||
|
||
| Поле `workout_metrics` | Расчёт (детерминированно) |
|
||
|---|---|
|
||
| `adherence` | % подходов «по плану» (`workout_set.in_plan`) от планового числа подходов сессии в активной версии программы (body, схема дизайна LLM-планирования разд. 2); план/сессию не удалось сопоставить → % `in_plan` среди подходов тренировки |
|
||
| `tonnage` | Σ (weight_kg × reps) по всем подходам с weight>0, reps>0 |
|
||
| `tonnage_by_group` | тот же Σ по группам упражнений (`exercise.group`), код группы → кг |
|
||
| `e1pm_by_exercise` | формула Эпли (ОВ-7): `w × (1 + reps/30)` по лучшему (максимальному весу) подходу каждого упражнения |
|
||
| `hr_metrics` | из `workout.facts_json` (.fit, если уже импортирован): средний/пиковый пульс, отдых между подходами; нет фактов → `{avg_hr, hr_recovery_s}` из `workout` (то, что прислал телефон/.fit-импорт) |
|
||
| `red_flags` | сработавшие красные флаги (см. разд. 3) |
|
||
|
||
## 3. Красные флаги (v1 — документ метрик, правила 1, 6; Ф-12.5)
|
||
|
||
| Флаг | Условие | Источник данных |
|
||
|---|---|---|
|
||
| `bp_over_150_90` | АД дня тренировки > 150/90 (пороги `Safety.BpCancelSys/Dias`) | `daily_log` дня тренировки |
|
||
| `bp_elevated` | АД в коридоре 140–150/90–95 | `daily_log` дня тренировки |
|
||
| `hr_resting_up` | пульс покоя ≥ базового +8 уд/мин (`Safety.HrRestingRise`) 2 дня подряд | `daily_log` (тренд за 2 дня до дня тренировки; базовый = среднее за предыдущие 14 дней, при <3 значениях базового флаг не ставится) |
|
||
| `wellbeing_low` | самочувствие ≤ 2 (`Safety.WellbeingLow`) 2 дня подряд | `daily_log` |
|
||
| `cancel_recommendation` | `hr_resting_up`/`wellbeing_low` держится 3+ дня → предложение отмены и совет к врачу | `daily_log` |
|
||
| `knee_pain_repeat` | боль в колене ≥ 1 (`Safety.KneePainMin`) в двух занятиях подряд (сеты группы «ноги») | `workout_set.knee_pain` предыдущей и текущей тренировки |
|
||
|
||
Флаги — накопленный список фактов сработавших условий с датами;
|
||
значения порогов в `red_flags` не дублируются. 🛡 Пороги — только из
|
||
конфигурации, в коде не хардкодятся (Р3-А).
|
||
|
||
## 4. Инварианты (🛡 — нарушать запрещено)
|
||
|
||
1. **Изоляция атлета** — расчёт только по данным одного `athleteId`;
|
||
все входные выборки — по нему (репозитории m01).
|
||
2. **Метрики — производные данные** — upsert разрешён всегда
|
||
(пересчёт); sync атлета не может «сломать» метрики другого.
|
||
3. **Без побочных эффектов на план** — MetricsEngine ничего не пишет
|
||
в `workout`/`workout_set`/`program_version` (это m05); только
|
||
`workout_metrics`.
|
||
4. **Сбой расчёта не ломает sync** — исключение расчёта метрик
|
||
логируется, пакет sync остаётся принятым.
|
||
5. **Детерминированность** — тот же вход → тот же выход; никакого LLM.
|
||
|
||
## 5. Критерии приёмки модуля
|
||
|
||
1. После sync тренировки с подходами `workout_metrics` заполнен:
|
||
adherence/tonnage/tonnage_by_group/e1pm (Эпли проверяется вручную
|
||
посчитанным значением)/hr_metrics.
|
||
2. Повторный расчёт (повторный sync того же пакета) не создаёт
|
||
вторую строку (upsert 1:1).
|
||
3. Красные флаги: сценарии АД>150/90, пульс покоя +8 два дня,
|
||
самочувствие ≤2, колено ≥1 два занятия — флаги ставятся; чистые
|
||
данные — `red_flags` пустой.
|
||
4. Изоляция: расчёт атлета А не видит данные атлета Б.
|
||
5. Сбой расчёта метрик не мешает принятию пакета sync.
|
||
6. Тесты интеграционные против docker `watchbody-pg` — зелёные;
|
||
сборка решения без ошибок.
|