VEU.WathBody/1-docs/4-modules/m05-progression-rules/api_specs/02-micro-adjust.md

5.5 KiB
Raw Permalink Blame History

Спецификация 02: микроправка весов — ProgressionRules.EvaluateAfterWorkout

Источники: Ф-12.2–12.3, документ метрик правила 3–5, ограничения — решение гейта 1.3 (module_reqs разд. 2.4).

1. Сигнатура

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).