- Add rules_engine.py with quantitative rules for all 8 workout types - Add quality gate retry loop in generate_single_workout() - Expand calibrate_structure_rules to all 120 combinations (8 types × 5 goals × 3 sections) - Wire WeeklySplitPattern DB records into _pick_weekly_split() - Enforce movement patterns from WorkoutStructureRule in exercise selection - Add straight-set strength support (single main lift, 4-6 rounds) - Add modality consistency check for duration-dominant workout types - Add InjuryStep component to onboarding and preferences - Add sibling exercise exclusion in regenerate and preview_day endpoints - Display generator warnings on dashboard - Expand fix_rep_durations, fix_exercise_flags, fix_movement_pattern_typo - Add audit_exercise_data and check_rules_drift management commands - Add Next.js frontend with dashboard, onboarding, preferences, history pages - Add generator app with ML-powered workout generation pipeline - 96 new tests across 7 test modules Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
143 lines
9.8 KiB
Python
143 lines
9.8 KiB
Python
# Generated by Django 5.1.4 on 2026-02-11 16:54
|
|
|
|
import django.db.models.deletion
|
|
from django.db import migrations, models
|
|
|
|
|
|
class Migration(migrations.Migration):
|
|
|
|
initial = True
|
|
|
|
dependencies = [
|
|
('equipment', '0002_workoutequipment'),
|
|
('exercise', '0008_exercise_video_override'),
|
|
('muscle', '0002_exercisemuscle'),
|
|
('registered_user', '0003_registereduser_has_nsfw_toggle'),
|
|
('workout', '0015_alter_completedworkout_difficulty'),
|
|
]
|
|
|
|
operations = [
|
|
migrations.CreateModel(
|
|
name='MovementPatternOrder',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('position', models.CharField(choices=[('early', 'Early'), ('middle', 'Middle'), ('late', 'Late')], max_length=10)),
|
|
('movement_pattern', models.CharField(max_length=100)),
|
|
('frequency', models.IntegerField(default=0)),
|
|
('section_type', models.CharField(choices=[('warm_up', 'Warm Up'), ('working', 'Working'), ('cool_down', 'Cool Down')], default='working', max_length=10)),
|
|
],
|
|
options={
|
|
'ordering': ['position', '-frequency'],
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name='MuscleGroupSplit',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('muscle_names', models.JSONField(default=list, help_text='List of muscle group names')),
|
|
('frequency', models.IntegerField(default=0, help_text='How often this combo appeared')),
|
|
('label', models.CharField(blank=True, default='', max_length=100)),
|
|
('typical_exercise_count', models.IntegerField(default=6)),
|
|
('split_type', models.CharField(choices=[('push', 'Push'), ('pull', 'Pull'), ('legs', 'Legs'), ('upper', 'Upper'), ('lower', 'Lower'), ('full_body', 'Full Body'), ('core', 'Core'), ('cardio', 'Cardio')], default='full_body', max_length=20)),
|
|
],
|
|
),
|
|
migrations.CreateModel(
|
|
name='WeeklySplitPattern',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('days_per_week', models.IntegerField()),
|
|
('pattern', models.JSONField(default=list, help_text='Ordered list of MuscleGroupSplit IDs')),
|
|
('pattern_labels', models.JSONField(default=list, help_text='Ordered list of split labels')),
|
|
('frequency', models.IntegerField(default=0)),
|
|
('rest_day_positions', models.JSONField(default=list, help_text='Day indices that are rest days')),
|
|
],
|
|
),
|
|
migrations.CreateModel(
|
|
name='WorkoutType',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('name', models.CharField(max_length=100, unique=True)),
|
|
('description', models.TextField(blank=True, default='')),
|
|
('typical_rest_between_sets', models.IntegerField(default=60, help_text='Seconds')),
|
|
('typical_intensity', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=10)),
|
|
('rep_range_min', models.IntegerField(default=8)),
|
|
('rep_range_max', models.IntegerField(default=12)),
|
|
('round_range_min', models.IntegerField(default=3)),
|
|
('round_range_max', models.IntegerField(default=4)),
|
|
('duration_bias', models.FloatField(default=0.5, help_text='0.0=all rep-based, 1.0=all duration-based')),
|
|
('superset_size_min', models.IntegerField(default=2)),
|
|
('superset_size_max', models.IntegerField(default=4)),
|
|
],
|
|
),
|
|
migrations.CreateModel(
|
|
name='GeneratedWeeklyPlan',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
('week_start_date', models.DateField()),
|
|
('week_end_date', models.DateField()),
|
|
('status', models.CharField(choices=[('pending', 'Pending'), ('completed', 'Completed'), ('failed', 'Failed')], default='pending', max_length=10)),
|
|
('preferences_snapshot', models.JSONField(blank=True, default=dict)),
|
|
('generation_time_ms', models.IntegerField(blank=True, null=True)),
|
|
('registered_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='generated_plans', to='registered_user.registereduser')),
|
|
],
|
|
options={
|
|
'ordering': ['-created_at'],
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name='WorkoutStructureRule',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('section_type', models.CharField(choices=[('warm_up', 'Warm Up'), ('working', 'Working'), ('cool_down', 'Cool Down')], max_length=10)),
|
|
('movement_patterns', models.JSONField(default=list)),
|
|
('typical_rounds', models.IntegerField(default=3)),
|
|
('typical_exercises_per_superset', models.IntegerField(default=3)),
|
|
('typical_rep_range_min', models.IntegerField(default=8)),
|
|
('typical_rep_range_max', models.IntegerField(default=12)),
|
|
('typical_duration_range_min', models.IntegerField(default=30, help_text='Seconds')),
|
|
('typical_duration_range_max', models.IntegerField(default=45, help_text='Seconds')),
|
|
('goal_type', models.CharField(choices=[('strength', 'Strength'), ('hypertrophy', 'Hypertrophy'), ('endurance', 'Endurance'), ('weight_loss', 'Weight Loss'), ('general_fitness', 'General Fitness')], default='general_fitness', max_length=20)),
|
|
('workout_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='structure_rules', to='generator.workouttype')),
|
|
],
|
|
),
|
|
migrations.CreateModel(
|
|
name='UserPreference',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('fitness_level', models.IntegerField(choices=[(1, 'Beginner'), (2, 'Intermediate'), (3, 'Advanced'), (4, 'Elite')], default=2)),
|
|
('primary_goal', models.CharField(choices=[('strength', 'Strength'), ('hypertrophy', 'Hypertrophy'), ('endurance', 'Endurance'), ('weight_loss', 'Weight Loss'), ('general_fitness', 'General Fitness')], default='general_fitness', max_length=20)),
|
|
('secondary_goal', models.CharField(blank=True, choices=[('strength', 'Strength'), ('hypertrophy', 'Hypertrophy'), ('endurance', 'Endurance'), ('weight_loss', 'Weight Loss'), ('general_fitness', 'General Fitness')], default='', max_length=20)),
|
|
('days_per_week', models.IntegerField(default=4)),
|
|
('preferred_workout_duration', models.IntegerField(default=45, help_text='Minutes')),
|
|
('preferred_days', models.JSONField(blank=True, default=list, help_text='List of weekday ints (0=Mon, 6=Sun)')),
|
|
('injuries_limitations', models.TextField(blank=True, default='')),
|
|
('available_equipment', models.ManyToManyField(blank=True, related_name='user_preferences', to='equipment.equipment')),
|
|
('excluded_exercises', models.ManyToManyField(blank=True, related_name='excluded_by_users', to='exercise.exercise')),
|
|
('registered_user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='generator_preference', to='registered_user.registereduser')),
|
|
('target_muscle_groups', models.ManyToManyField(blank=True, related_name='user_preferences', to='muscle.muscle')),
|
|
('preferred_workout_types', models.ManyToManyField(blank=True, related_name='user_preferences', to='generator.workouttype')),
|
|
],
|
|
),
|
|
migrations.CreateModel(
|
|
name='GeneratedWorkout',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('scheduled_date', models.DateField()),
|
|
('day_of_week', models.IntegerField(help_text='0=Monday, 6=Sunday')),
|
|
('is_rest_day', models.BooleanField(default=False)),
|
|
('status', models.CharField(choices=[('pending', 'Pending'), ('accepted', 'Accepted'), ('rejected', 'Rejected'), ('completed', 'Completed')], default='pending', max_length=10)),
|
|
('focus_area', models.CharField(blank=True, default='', max_length=255)),
|
|
('target_muscles', models.JSONField(blank=True, default=list)),
|
|
('user_rating', models.IntegerField(blank=True, null=True)),
|
|
('user_feedback', models.TextField(blank=True, default='')),
|
|
('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='generated_workouts', to='generator.generatedweeklyplan')),
|
|
('workout', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='generated_from', to='workout.workout')),
|
|
('workout_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='generated_workouts', to='generator.workouttype')),
|
|
],
|
|
options={
|
|
'ordering': ['scheduled_date'],
|
|
},
|
|
),
|
|
]
|