77 lines
5.5 KiB
Markdown
77 lines
5.5 KiB
Markdown
# Спецификация 02: микроправка весов — ProgressionRules.EvaluateAfterWorkout
|
||
|
||
Источники: Ф-12.2–12.3, документ метрик правила 3–5, ограничения —
|
||
решение гейта 1.3 (module_reqs разд. 2.4).
|
||
|
||
## 1. Сигнатура
|
||
|
||
```csharp
|
||
public ProgramVersion? EvaluateAfterWorkout(int athleteId, Guid workoutId)
|
||
```
|
||
|
||
Возврат: созданная draft-версия или null (изменений нет / черновик
|
||
уже существует). Дедупликация: rules-draft со статусом `draft` и
|
||
`based_on_version ≥` номера активной версии блокирует новые
|
||
предложения (покрывает и пользовательские draft поверх активной).
|
||
|
||
## 2. Входные данные
|
||
|
||
- тренировка `workout` + подходы `workout_set` (репозиторий m01);
|
||
- предыдущие тренировки атлета (`WorkoutFilter`, по убыванию
|
||
`started_at`) — для правила 1 («2 тренировки подряд»);
|
||
- активная версия `ProgramStore.GetActive(athleteId)` — нет → null;
|
||
- библиотека атлета `IExerciseRepository.Select` (с `ReplacementIds`)
|
||
— сопоставление и ограничения.
|
||
|
||
Сопоставление план↔факт: `body.exercises[].exercise_id` ==
|
||
`src.exercise.name` (exact, Ordinal); упражнение не найдено → решение
|
||
не генерируется, пометка в rationale. `workout_set.exercise_id`
|
||
(uuid) → `src.exercise` → имя → элемент body.
|
||
|
||
## 3. Решения по упражнениям (проверяются по порядку, срабатывает первое)
|
||
|
||
По каждому упражнению тренировки, присутствующему в активной версии
|
||
и активному (`is_active=true`):
|
||
|
||
| # | Условие | Действие |
|
||
|---|---|---|
|
||
| 1 | в двух последних тренировках **этого упражнения** все плановые подходы `in_plan=true` и `rpe ≤ 7` (rpe во всех подходах указан) | плановый вес `+BaseStepKg` (`is_base=true`) / `+IsoStepKg` (`is_base=false`) |
|
||
| 2 | в последней тренировке упражнения `rpe ≥ 9` в ≥ 2 подходах | плановый вес `× (1 − BackoffPercent/100)` |
|
||
| 3 | все плановые подходы закрыты «по плану» и плановые повторения ≥ 15 (верхняя граница диапазона 15–20) | вес `+шаг прогрессии`, плановые повторения → 12 |
|
||
| 4 | в последней тренировке ≥ 2 подходов с `reps` < плановых | плановый вес `× (1 − BackoffPercent/100)` |
|
||
| 5 | иначе | без изменения |
|
||
|
||
- «Предыдущая тренировка упражнения» — предыдущая по `started_at`
|
||
тренировка атлета, содержащая подходы этого упражнения.
|
||
- Результат веса округляется к 0.5 кг (`Math.Round(x*2)/2`).
|
||
- Подходы `source=edit` в расчёт не берутся (правка = новая запись,
|
||
история: берём исходные voice/watch записи).
|
||
- Вес плана `null` (калибровка) → решение не генерируется.
|
||
|
||
## 4. Ограничения (module_reqs разд. 2.4)
|
||
|
||
- `is_active=false` → не попадает в draft; если в активной версии
|
||
сессия состоит только из неактивных — сессия исключается;
|
||
- у исключаемого упражнения есть активная пара-замена
|
||
(`ReplacementIds`, замена активна, та же группа) → подставляется
|
||
замена (имя замены в `exercise_id` body, веса исходного плана,
|
||
пометка в rationale). Примечание (З-40): «та же группа» гарантируется
|
||
`exercise_replacement_insert` (проверка на вставке пары); повторная
|
||
runtime-проверка в `ApplyExclusion` не дублируется;
|
||
- `body.profile.limitations` переносится без изменений.
|
||
|
||
## 5. Формирование draft
|
||
|
||
1. Нет ни одного решения с изменением и ни одного исключения/замены →
|
||
`null` (черновик не нужен).
|
||
2. Deep-copy активного body (JsonNode), применение решений к week 1+
|
||
(все недели: v1 ротации нет — одно упражнение = один плановый вес).
|
||
3. Дедупликация: существует rules-draft с `based_on_version` == номер
|
||
активной версии → `null` (черновик не плодится между sync).
|
||
4. `ProgramStore.CreateDraft(athleteId, newBody, "rules", rationale)`;
|
||
rationale — человекочитаемо: по упражнению строка
|
||
«жим: 100 → 102.5 (+2.5: 2 тренировки подряд закрыты при RPE≤7)»,
|
||
«присед: исключено (is_active=false), замена — жим ногами».
|
||
|
||
🛡 Активная версия не меняется; активации нет (vision 2.4).
|