69 lines
4.3 KiB
Markdown
69 lines
4.3 KiB
Markdown
# Спецификация 04: перерывы — BreakEngine (Ф-14, документ перерывов 3.1–3.3)
|
||
|
||
## 1. Сигнатуры
|
||
|
||
```csharp
|
||
public bool DetectBreak(int athleteId, DateOnly today); // true — break открыт
|
||
public ProgramVersion? CloseOnReturn(int athleteId, Guid workoutId, DateOnly today);
|
||
```
|
||
|
||
(`today` — явный параметр для тестируемости; sync передаёт текущую
|
||
дату UTC. `CloseOnReturn` сам читает тренировку по id — в sync есть
|
||
только идентификатор.)
|
||
|
||
Вызов из sync (spec 05): `DetectBreak` — до записи пакета не нужен;
|
||
вызывается после коммита пакета вместе с правилами. `CloseOnReturn` —
|
||
когда пакет содержит `workout` (первая сессия после break).
|
||
|
||
## 2. DetectBreak — авто-детект (Ф-14.1)
|
||
|
||
1. Открытый break есть (`BreakRepository.Select`, `ended_on = NULL`,
|
||
последний по `started_on`) → false.
|
||
2. Активной версии нет → false.
|
||
3. Окно `[today − MissDaysForBreak + 1 .. today]` (умолчание 4 дня).
|
||
Плановые дни недели в окне — по `body.weeks[].sessions[].day`
|
||
(v1 все недели одинаковы). День плановый и по нему нет тренировки
|
||
(`workout` с датой `started_at::date` = день) — пропуск.
|
||
4. Пропущены **все** плановые дни окна → `BreakRepository.Insert`
|
||
(source=`auto`, kind=NULL, started_on = первый пропущенный день,
|
||
note «авто: пропущены все сессии за N дней»). → true.
|
||
5. Есть хотя бы одна тренировка в окне → false.
|
||
|
||
Атлет может объявить break заранее — телефон вставляет
|
||
source=`manual` (m09); авто-детект видит открытый manual и не дублирует.
|
||
|
||
## 3. CloseOnReturn — закрытие и формула возврата (Ф-14.3–14.5, правило 17)
|
||
|
||
Первый `workout` в пакете при открытом break:
|
||
|
||
1. `break_update`: `ended_on` = дата тренировки (kind/note не трогаем
|
||
— ответ атлета придёт отдельным каналом, m08/m09).
|
||
2. Длительность простоя `days = ended_on − started_on`; коэффициент
|
||
по таблице документа перерывов (разд. 3.2), фактор зажат конфигом
|
||
`[ReturnMinFactor; ReturnMaxFactor]`:
|
||
|
||
| Простой | Коэффициент | Режим |
|
||
|---|---|---|
|
||
| kind=illness (любой длительности) | 0.6 (нижняя граница) | перезапуск, rationale «болезнь» |
|
||
| 4–6 дней | 1.0 (веса без снижения) | первая сессия: подходы −1, target_rpe 5 |
|
||
| 7–13 | 0.9 | возврат 1–2 недели |
|
||
| 14–27 | 0.85 | 1 вводная неделя |
|
||
| 28–59 | 0.75 | 2 вводные недели |
|
||
| 60+ | ReturnMinFactor (0.6) | перезапуск цикла |
|
||
|
||
3. Draft-версия возврата (одна на break; повторный вызов того же
|
||
break — дедупликация по rules-draft на активную версию, как в
|
||
spec 02/03): deep-copy body, week 1:
|
||
- веса `× коэффициент`, округление к 0.5 кг; вес `null` не трогаем;
|
||
- `session_kind`: `days < 14` → `"return"`, `days ≥ 14` → `"test"`
|
||
(правило 17), для illness → `"return"`;
|
||
- simple 4–6 дней: подходы `−1` (мин 1), `target_rpe` = 5;
|
||
- `cardio_after_min` не меняется (кардио возвращается со 100 %,
|
||
Аттия);
|
||
- rationale — «возврат после break N дней (тип T): веса ×K».
|
||
4. `SessionKinds` (domain Workout) — константы normal/return/deload/test;
|
||
в body week 1 sessions пишется `"session_kind"`; телефон при старте
|
||
тренировки проставит `workout.session_kind` из активной версии.
|
||
|
||
🛡 Версии неизменяемы; активация — только человеком (Ф-14.4).
|