import logging from workout.models import Workout from superset.models import Superset, SupersetExercise logger = logging.getLogger(__name__) class PlanBuilder: """ Creates Django ORM objects (Workout, Superset, SupersetExercise) from a workout specification dict. Follows the exact same creation pattern used by the existing ``add_workout`` view. """ def __init__(self, registered_user): self.registered_user = registered_user def create_workout_from_spec(self, workout_spec): """ Create a full Workout with Supersets and SupersetExercises. Parameters ---------- workout_spec : dict Expected shape:: { 'name': 'Upper Push + Core', 'description': 'Generated workout targeting chest ...', 'supersets': [ { 'name': 'Warm Up', 'rounds': 1, 'exercises': [ { 'exercise': , 'duration': 30, 'order': 1, }, { 'exercise': , 'reps': 10, 'weight': 50, 'order': 2, }, ], }, ... ], } Returns ------- Workout The fully-persisted Workout instance with all child objects. """ # ---- 1. Create the Workout ---- workout = Workout.objects.create( name=workout_spec.get('name', 'Generated Workout'), description=workout_spec.get('description', ''), registered_user=self.registered_user, ) workout.save() workout_total_time = 0 superset_order = 1 # ---- 2. Create each Superset ---- for ss_spec in workout_spec.get('supersets', []): ss_name = ss_spec.get('name', f'Set {superset_order}') rounds = ss_spec.get('rounds', 1) exercises = ss_spec.get('exercises', []) superset = Superset.objects.create( workout=workout, name=ss_name, rounds=rounds, order=superset_order, rest_between_rounds=ss_spec.get('rest_between_rounds', 45), ) superset.save() superset_total_time = 0 # ---- 3. Create each SupersetExercise ---- for ex_spec in exercises: exercise_obj = ex_spec.get('exercise') if exercise_obj is None: logger.warning( "Skipping exercise entry with no exercise object in " "superset '%s'", ss_name, ) continue order = ex_spec.get('order', 1) superset_exercise = SupersetExercise.objects.create( superset=superset, exercise=exercise_obj, order=order, ) # Assign optional fields exactly like add_workout does if ex_spec.get('weight') is not None: superset_exercise.weight = ex_spec['weight'] if ex_spec.get('reps') is not None: superset_exercise.reps = ex_spec['reps'] rep_duration = exercise_obj.estimated_rep_duration or 3.0 superset_total_time += ex_spec['reps'] * rep_duration if ex_spec.get('duration') is not None: superset_exercise.duration = ex_spec['duration'] superset_total_time += ex_spec['duration'] superset_exercise.save() # ---- 4. Update superset estimated_time ---- # Store total time including all rounds and rest between rounds rest_between_rounds = ss_spec.get('rest_between_rounds', 45) rest_time = rest_between_rounds * max(0, rounds - 1) superset.estimated_time = (superset_total_time * rounds) + rest_time superset.save() # Accumulate into workout total (use the already-calculated superset time) workout_total_time += superset.estimated_time superset_order += 1 # Add transition time between supersets # (matches GENERATION_RULES['rest_between_supersets'] in workout_generator) superset_count = superset_order - 1 if superset_count > 1: rest_between_supersets = 30 workout_total_time += rest_between_supersets * (superset_count - 1) # ---- 5. Update workout estimated_time ---- workout.estimated_time = workout_total_time workout.save() logger.info( "Created workout '%s' (id=%s) with %d supersets, est. %ds", workout.name, workout.pk, superset_order - 1, workout_total_time, ) return workout