Files
WerkoutAPI/generator/migrations/0001_initial.py
Trey t 1c61b80731 workout generator audit: rules engine, structure rules, split patterns, injury UX, metadata cleanup
- 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>
2026-02-22 20:07:40 -06:00

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'],
},
),
]