""" Recalculates estimated_time on all Workout and Superset records using the corrected estimated_rep_duration values + rest between rounds. Formula per superset: active_time = sum(reps * exercise.estimated_rep_duration) + sum(durations) rest_time = rest_between_rounds * (rounds - 1) superset.estimated_time = active_time (stores single-round active time) Formula per workout: workout.estimated_time = sum(superset_active_time * rounds + rest_time) Usage: python manage.py recalculate_workout_times python manage.py recalculate_workout_times --dry-run python manage.py recalculate_workout_times --rest=45 """ from django.core.management.base import BaseCommand from workout.models import Workout from superset.models import Superset, SupersetExercise DEFAULT_REST_BETWEEN_ROUNDS = 45 # seconds DEFAULT_REP_DURATION = 3.0 # fallback if null class Command(BaseCommand): help = 'Recalculate estimated_time on all Workouts and Supersets' def add_arguments(self, parser): parser.add_argument( '--dry-run', action='store_true', help='Show changes without writing to DB', ) parser.add_argument( '--rest', type=int, default=DEFAULT_REST_BETWEEN_ROUNDS, help=f'Rest between rounds in seconds (default: {DEFAULT_REST_BETWEEN_ROUNDS})', ) def handle(self, *args, **options): dry_run = options['dry_run'] rest_between_rounds = options['rest'] workouts = Workout.objects.prefetch_related( 'superset_workout__superset_exercises__exercise' ).all() total = workouts.count() updated = 0 for workout in workouts: supersets = workout.superset_workout.all().order_by('order') workout_total_time = 0 for ss in supersets: exercises = ss.superset_exercises.all() active_time = 0.0 for se in exercises: if se.reps and se.reps > 0: rep_dur = se.exercise.estimated_rep_duration or DEFAULT_REP_DURATION active_time += se.reps * rep_dur elif se.duration and se.duration > 0: active_time += se.duration # Rest between rounds (not after the last round) rest_time = rest_between_rounds * max(0, ss.rounds - 1) # Superset stores single-round active time old_ss_time = ss.estimated_time ss.estimated_time = active_time if not dry_run: ss.save(update_fields=['estimated_time']) # Workout accumulates: active per round * rounds + rest workout_total_time += (active_time * ss.rounds) + rest_time old_time = workout.estimated_time new_time = workout_total_time if not dry_run: workout.estimated_time = new_time workout.save(update_fields=['estimated_time']) updated += 1 self.stdout.write(self.style.SUCCESS( f'{"[DRY RUN] " if dry_run else ""}' f'Recalculated {updated}/{total} workouts ' f'(rest between rounds: {rest_between_rounds}s)' )) # Show some examples if not dry_run: self.stdout.write('\nSample workouts:') for w in Workout.objects.order_by('-id')[:5]: mins = w.estimated_time / 60 if w.estimated_time else 0 ss_count = Superset.objects.filter(workout=w).count() ex_count = SupersetExercise.objects.filter(superset__workout=w).count() self.stdout.write( f' #{w.id} "{w.name}": {mins:.0f}m ' f'({ss_count} supersets, {ex_count} exercises)' )