Spaces:
Sleeping
Sleeping
| """Evidence-based first-session external loads (novice-friendly bands). | |
| This maps each catalog exercise to a **starter load** using: | |
| 1. Optional per-row overrides on ``Exercise`` (``starting_bodyweight_fraction``, | |
| ``starting_load_kg``, ``starting_load_reference``). | |
| 2. Otherwise **movement-pattern defaults**: total external load as a fraction of | |
| **body mass** (for dumbbell work, total load for both hands combined), or a | |
| small fixed load when %BW does not apply. | |
| **Sources** (operationalize "light to moderate" first sessions): | |
| - American College of Sports Medicine. Progression models in resistance training | |
| for healthy adults. *Med Sci Sports Exerc.* 2009;41(3):687-708 (initial intensity | |
| light–moderate, progressive overload). | |
| - Rhea MR, et al. A meta-analysis to determine the dose response for strength | |
| development. *Med Sci Sports Exerc.* 2003;35(3):456-464 (intensity must be | |
| individualized; novices use lower bands). | |
| - Kraemer WJ, Ratamess NA. Fundamentals of resistance training: progression and | |
| exercise prescription. *Med Sci Sports Exerc.* 2004;36(4):674-688 (exercise | |
| selection and loading vary by movement). | |
| Fractions are **conservative** midpoints within common coaching ranges for | |
| **total** dumbbell load / body mass for bilateral dumbbell work. They are not | |
| trial outcomes; users should adjust to RPE and equipment jumps. | |
| If body weight is unknown, return no computed kg and instruct the user to log | |
| body weight (used as the scalar for %BW). | |
| Optional ``calibration_*_lb_per_hand`` in config scales %BW dumbbell starters | |
| toward your tested bilateral dumbbell loads (bench pattern vs squat pattern | |
| fractions in ``default_bodyweight_fraction_total_load``). | |
| """ | |
| from __future__ import annotations | |
| import math | |
| from tracker.models import Exercise | |
| # Exact NIST definition used for lb/kg conversion in UI and calibration. | |
| LB_TO_KG = 0.45359237 | |
| def _quantize_half_kg(value: float) -> float: | |
| return math.floor(value * 2 + 0.5) / 2 | |
| REFERENCE_SHORT = ( | |
| "ACSM 2009 novice light–moderate band; %BW mapping from NSCA-style " | |
| "movement patterns (approximate)." | |
| ) | |
| def equipment_has_dumbbell(equipment: str) -> bool: | |
| return "dumbbell" in equipment.lower() | |
| def equipment_is_bodyweight_only(equipment: str) -> bool: | |
| normalized = equipment.strip().lower() | |
| return normalized == "none" or normalized == "" | |
| def default_bodyweight_fraction_total_load(exercise: Exercise) -> tuple[float | None, str]: | |
| """Total external load / body mass, or None if not applicable.""" | |
| if equipment_is_bodyweight_only(exercise.equipment): | |
| return None, REFERENCE_SHORT | |
| if equipment_has_dumbbell(exercise.equipment): | |
| muscle = exercise.muscle_group.strip().lower() | |
| if muscle in ("chest", "shoulders", "triceps"): | |
| return 0.28, REFERENCE_SHORT | |
| if muscle == "biceps": | |
| return 0.12, REFERENCE_SHORT | |
| if muscle in ("lats", "middle back", "traps"): | |
| return 0.30, REFERENCE_SHORT | |
| if muscle in ("quads", "hamstrings", "glutes", "calves"): | |
| return 0.24, REFERENCE_SHORT | |
| if muscle in ("forearms", "neck"): | |
| return 0.08, REFERENCE_SHORT | |
| if muscle in ("abs", "lower back"): | |
| return 0.06, REFERENCE_SHORT | |
| return 0.22, REFERENCE_SHORT | |
| equipment_lower = exercise.equipment.lower() | |
| if "bench" in equipment_lower and not equipment_has_dumbbell(exercise.equipment): | |
| return None, REFERENCE_SHORT | |
| return None, REFERENCE_SHORT | |
| def apply_anchor_calibration_to_research_kg( | |
| research_kg: float, | |
| exercise: Exercise, | |
| body_weight_kg: float, | |
| bench_lb_per_hand: float | None, | |
| squat_lb_per_hand: float | None, | |
| ) -> float: | |
| """Scale a %BW dumbbell starter toward tested bilateral dumbbell anchors. | |
| Anchors are **per-hand** lb for bilateral dumbbells (total external load = 2 × hand). | |
| Bench anchor scales upper-body patterns that use the same 0.28 fraction as chest in | |
| ``default_bodyweight_fraction_total_load``; squat anchor scales leg patterns (0.24). | |
| Back uses the mean of the two scale factors when both are set (pulling between | |
| upper and lower strength). See module docstring for research fractions. | |
| """ | |
| if not equipment_has_dumbbell(exercise.equipment): | |
| return research_kg | |
| if bench_lb_per_hand is None and squat_lb_per_hand is None: | |
| return research_kg | |
| bench_total_kg = 2.0 * bench_lb_per_hand * LB_TO_KG if bench_lb_per_hand is not None else None | |
| squat_total_kg = 2.0 * squat_lb_per_hand * LB_TO_KG if squat_lb_per_hand is not None else None | |
| bench_scale = bench_total_kg / (0.28 * body_weight_kg) if bench_total_kg is not None else None | |
| squat_scale = squat_total_kg / (0.24 * body_weight_kg) if squat_total_kg is not None else None | |
| muscle = exercise.muscle_group.strip().lower() | |
| scale: float | None | |
| if muscle in ("chest", "shoulders", "triceps", "forearms", "biceps", "abs", "lower back", "neck"): | |
| scale = bench_scale | |
| elif muscle in ("quads", "hamstrings", "glutes", "calves"): | |
| scale = squat_scale | |
| elif muscle in ("lats", "middle back", "traps"): | |
| if bench_scale is not None and squat_scale is not None: | |
| scale = (bench_scale + squat_scale) / 2.0 | |
| elif bench_scale is not None: | |
| scale = bench_scale | |
| else: | |
| scale = squat_scale | |
| else: | |
| if bench_scale is not None and squat_scale is not None: | |
| scale = (bench_scale + squat_scale) / 2.0 | |
| else: | |
| scale = bench_scale if bench_scale is not None else squat_scale | |
| if scale is None: | |
| return research_kg | |
| return research_kg * scale | |
| def resolve_research_starting_weight_kg( | |
| exercise: Exercise, | |
| body_weight_kg: float | None, | |
| *, | |
| calibration_bench_lb_per_hand: float | None = None, | |
| calibration_squat_lb_per_hand: float | None = None, | |
| ) -> tuple[float | None, str]: | |
| """Return (quantized kg for first session, reference blurb).""" | |
| ref = exercise.starting_load_reference.strip() if exercise.starting_load_reference else REFERENCE_SHORT | |
| if exercise.starting_load_kg is not None and exercise.starting_load_kg > 0: | |
| return _quantize_half_kg(exercise.starting_load_kg), ref | |
| fraction: float | None | |
| if exercise.starting_bodyweight_fraction is not None: | |
| fraction = exercise.starting_bodyweight_fraction | |
| else: | |
| fraction, ref = default_bodyweight_fraction_total_load(exercise) | |
| if fraction is None or fraction <= 0: | |
| return None, ref | |
| if body_weight_kg is None or body_weight_kg <= 0: | |
| return None, ref | |
| raw_kg = fraction * body_weight_kg | |
| if calibration_bench_lb_per_hand is not None or calibration_squat_lb_per_hand is not None: | |
| raw_kg = apply_anchor_calibration_to_research_kg( | |
| raw_kg, | |
| exercise, | |
| body_weight_kg, | |
| calibration_bench_lb_per_hand, | |
| calibration_squat_lb_per_hand, | |
| ) | |
| return _quantize_half_kg(raw_kg), ref | |