Codebase hardening: 102 fixes across 35+ files

Deep audit identified 106 findings; 102 fixed, 4 deferred. Covers 8 areas:

- Settings & deploy: env-gated DEBUG/SECRET_KEY, HTTPS headers, gunicorn, celery worker
- Auth (registered_user): password write_only, request.data fixes, transaction safety, proper HTTP status codes
- Workout app: IDOR protection, get_object_or_404, prefetch_related N+1 fixes, transaction.atomic
- Video/scripts: path traversal sanitization, HLS trigger guard, auth on cache wipe
- Models (exercise/equipment/muscle/superset): null-safe __str__, stable IDs, prefetch support
- Generator views: helper for registered_user lookup, logger.exception, bulk_update, transaction wrapping
- Generator core (rules/selector/generator): push-pull ratio, type affinity normalization, modality checks, side-pair exact match, word-boundary regex, equipment cache clearing
- Generator services (plan_builder/analyzer/normalizer): transaction.atomic, muscle cache, bulk_update, glutes classification fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-27 22:29:14 -06:00
parent 63b57a83ab
commit c80c66c2e5
58 changed files with 3363 additions and 1049 deletions

View File

@@ -6,6 +6,7 @@ workout_research.md. Used by the quality gates in WorkoutGenerator
and the check_rules_drift management command.
"""
import re
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any, Tuple
@@ -428,6 +429,9 @@ def _get_working_supersets(supersets: list) -> list:
def _count_push_pull(supersets: list) -> Tuple[int, int]:
"""Count push and pull exercises across working supersets.
Exercises with BOTH push AND pull patterns are counted as neutral
(neither push nor pull) to avoid double-counting.
Returns (push_count, pull_count).
"""
push_count = 0
@@ -439,9 +443,14 @@ def _count_push_pull(supersets: list) -> Tuple[int, int]:
continue
patterns = getattr(ex, 'movement_patterns', '') or ''
patterns_lower = patterns.lower()
if 'push' in patterns_lower:
is_push = 'push' in patterns_lower
is_pull = 'pull' in patterns_lower
if is_push and is_pull:
# Dual pattern — count as neutral to avoid double-counting
pass
elif is_push:
push_count += 1
if 'pull' in patterns_lower:
elif is_pull:
pull_count += 1
return push_count, pull_count
@@ -485,6 +494,31 @@ def _focus_key_for_entry(entry: dict) -> Optional[str]:
return None
def _is_recovery_entry(entry: dict) -> bool:
"""Return True when an entry is a recovery/stretch movement."""
ex = entry.get('exercise')
if ex is None:
return False
name = (getattr(ex, 'name', '') or '').lower()
# Use word boundary check to avoid over-matching (e.g. "Stretch Band Row"
# should not be flagged as recovery).
if re.search(r'\bstretch(ing|es|ed)?\b', name):
return True
patterns = (getattr(ex, 'movement_patterns', '') or '').lower()
recovery_tokens = (
'mobility - static',
'static stretch',
'cool down',
'cooldown',
'yoga',
'breathing',
'massage',
)
return any(token in patterns for token in recovery_tokens)
# ======================================================================
# Main validation function
# ======================================================================
@@ -608,7 +642,7 @@ def validate_workout(
for ss in working:
ex_count = len(ss.get('exercises', []))
# Allow 1 extra for sided pairs
if ex_count > high + 2:
if ex_count > high + 1:
violations.append(RuleViolation(
rule_id='superset_size',
severity='warning',
@@ -638,7 +672,7 @@ def validate_workout(
actual_value=ratio,
expected_range=(min_ratio, None),
))
elif push_count > 2 and pull_count == 0:
elif pull_count == 0 and push_count > 0:
violations.append(RuleViolation(
rule_id='push_pull_ratio',
severity='warning',
@@ -651,7 +685,41 @@ def validate_workout(
))
# ------------------------------------------------------------------
# 5. Focus spread across working supersets
# 5. Working-set guardrails (no recovery movements, non-zero rest)
# ------------------------------------------------------------------
for ss in working:
ss_name = ss.get('name') or 'Working set'
rest_between_rounds = ss.get('rest_between_rounds')
if rest_between_rounds is None or rest_between_rounds <= 0:
violations.append(RuleViolation(
rule_id='working_rest_missing',
severity='warning',
message=(
f"{ss_name} is missing rest_between_rounds "
"(expected a positive value)."
),
actual_value=rest_between_rounds,
expected_range=(15, None),
))
recovery_names = []
for entry in ss.get('exercises', []):
if _is_recovery_entry(entry):
ex = entry.get('exercise')
recovery_names.append(getattr(ex, 'name', 'Unknown Exercise'))
if recovery_names:
violations.append(RuleViolation(
rule_id='working_contains_recovery',
severity='error',
message=(
f"{ss_name} contains recovery/stretch movement(s): "
f"{', '.join(sorted(set(recovery_names)))}."
),
actual_value=sorted(set(recovery_names)),
))
# ------------------------------------------------------------------
# 6. Focus spread across working supersets
# ------------------------------------------------------------------
if working:
for ss in working:
@@ -697,7 +765,7 @@ def validate_workout(
previous_focus = focus_keys
# ------------------------------------------------------------------
# 6. Compound before isolation ordering
# 7. Compound before isolation ordering
# ------------------------------------------------------------------
if UNIVERSAL_RULES['compound_before_isolation']:
if not _check_compound_before_isolation(supersets):
@@ -708,7 +776,7 @@ def validate_workout(
))
# ------------------------------------------------------------------
# 7. Warmup check
# 8. Warmup check
# ------------------------------------------------------------------
if UNIVERSAL_RULES['warmup_mandatory']:
if not _has_warmup(supersets):
@@ -719,7 +787,7 @@ def validate_workout(
))
# ------------------------------------------------------------------
# 8. Cooldown check
# 9. Cooldown check
# ------------------------------------------------------------------
if not _has_cooldown(supersets):
violations.append(RuleViolation(
@@ -729,7 +797,7 @@ def validate_workout(
))
# ------------------------------------------------------------------
# 9. HIIT duration cap
# 10. HIIT duration cap
# ------------------------------------------------------------------
if wt_key == 'high_intensity_interval_training':
max_hiit_min = UNIVERSAL_RULES.get('max_hiit_duration_min', 30)
@@ -757,7 +825,7 @@ def validate_workout(
))
# ------------------------------------------------------------------
# 10. Total exercise count cap
# 11. Total exercise count cap
# ------------------------------------------------------------------
max_exercises = wt_rules.get(
'max_exercises_per_session',
@@ -780,13 +848,23 @@ def validate_workout(
))
# ------------------------------------------------------------------
# 11. Workout type match percentage (refactored from _validate_workout_type_match)
# 12. Workout type match percentage (refactored from _validate_workout_type_match)
# ------------------------------------------------------------------
_STRENGTH_TYPES = {
'traditional_strength_training', 'functional_strength_training',
'hypertrophy',
}
_HIIT_TYPES = {'high_intensity_interval_training'}
_CARDIO_TYPES = {'cardio'}
_CORE_TYPES = {'core_training'}
_FLEXIBILITY_TYPES = {'flexibility'}
is_strength = wt_key in _STRENGTH_TYPES
is_hiit = wt_key in _HIIT_TYPES
is_cardio = wt_key in _CARDIO_TYPES
is_core = wt_key in _CORE_TYPES
is_flexibility = wt_key in _FLEXIBILITY_TYPES
if working:
total_ex = 0
matching_ex = 0
@@ -799,7 +877,33 @@ def validate_workout(
if is_strength:
if getattr(ex, 'is_weight', False) or getattr(ex, 'is_compound', False):
matching_ex += 1
elif is_hiit:
# HIIT: favor high HR, compound, or duration-capable exercises
hr = getattr(ex, 'hr_elevation_rating', None) or 0
if hr >= 5 or getattr(ex, 'is_compound', False) or getattr(ex, 'is_duration', False):
matching_ex += 1
elif is_cardio:
# Cardio: favor duration-capable or high-HR exercises
hr = getattr(ex, 'hr_elevation_rating', None) or 0
if getattr(ex, 'is_duration', False) or hr >= 5:
matching_ex += 1
elif is_core:
# Core: check if exercise targets core muscles
muscles = (getattr(ex, 'muscle_groups', '') or '').lower()
patterns = (getattr(ex, 'movement_patterns', '') or '').lower()
if any(tok in muscles for tok in ('core', 'abs', 'oblique')):
matching_ex += 1
elif 'core' in patterns or 'anti' in patterns:
matching_ex += 1
elif is_flexibility:
# Flexibility: favor duration-based, stretch/mobility exercises
patterns = (getattr(ex, 'movement_patterns', '') or '').lower()
if getattr(ex, 'is_duration', False) or any(
tok in patterns for tok in ('stretch', 'mobility', 'yoga', 'flexibility')
):
matching_ex += 1
else:
# Unknown type — count all as matching (no false negatives)
matching_ex += 1
if total_ex > 0:
match_pct = matching_ex / total_ex