# Спецификация 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).