VEU.WathBody/1-docs/4-modules/m04-metrics-engine/module_reqs.md

89 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Требования к модулю: 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` — зелёные;
сборка решения без ошибок.