Files
WerkoutAPI/generator/management/commands/check_rules_drift.py
Trey t 03681c532d Unraid deployment fixes and generator improvements
- Add Next.js rewrites to proxy API calls through same origin (fixes login/media on werkout.treytartt.com)
- Fix mediaUrl() in DayCard and ExerciseRow to use relative paths in production
- Add proxyTimeout for long-running workout generation endpoints
- Add CSRF trusted origin for treytartt.com
- Split docker-compose into production (Unraid) and dev configs
- Show display_name and descriptions on workout type cards
- Generator: rules engine improvements, movement enforcement, exercise selector updates
- Add new test files for rules drift, workout research generation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:25:45 -06:00

126 lines
4.0 KiB
Python

"""
CI management command: check for drift between workout_research.md
calibration values and WorkoutType DB records.
Usage:
python manage.py check_rules_drift
python manage.py check_rules_drift --verbosity 2
"""
import sys
from django.core.management.base import BaseCommand
from generator.models import WorkoutType
from generator.rules_engine import DB_CALIBRATION
class Command(BaseCommand):
help = (
'Check for drift between research doc calibration values '
'and WorkoutType DB records. Exits 1 if mismatches, missing '
'types, or zero fields checked.'
)
# Fields to compare between DB_CALIBRATION and WorkoutType model
FIELDS_TO_CHECK = [
'duration_bias',
'typical_rest_between_sets',
'typical_intensity',
'rep_range_min',
'rep_range_max',
'round_range_min',
'round_range_max',
'superset_size_min',
'superset_size_max',
]
def handle(self, *args, **options):
verbosity = options.get('verbosity', 1)
mismatches = []
missing_in_db = []
checked = 0
for type_name, expected_values in DB_CALIBRATION.items():
try:
wt = WorkoutType.objects.get(name=type_name)
except WorkoutType.DoesNotExist:
missing_in_db.append(type_name)
continue
for field_name in self.FIELDS_TO_CHECK:
if field_name not in expected_values:
continue
expected = expected_values[field_name]
actual = getattr(wt, field_name, None)
checked += 1
if actual != expected:
mismatches.append({
'type': type_name,
'field': field_name,
'expected': expected,
'actual': actual,
})
elif verbosity >= 2:
self.stdout.write(
f" OK {type_name}.{field_name} = {actual}"
)
# Report results
self.stdout.write('')
self.stdout.write(f'Checked {checked} field(s) across {len(DB_CALIBRATION)} workout types.')
self.stdout.write('')
if missing_in_db:
self.stdout.write(self.style.ERROR(
f'Missing from DB ({len(missing_in_db)}):'
))
for name in missing_in_db:
self.stdout.write(f' - {name}')
self.stdout.write('')
has_errors = False
if checked == 0:
has_errors = True
self.stdout.write(self.style.ERROR(
'No calibration fields were checked. '
'DB_CALIBRATION keys likely do not match WorkoutType.name values.'
))
self.stdout.write('')
if missing_in_db:
has_errors = True
self.stdout.write(self.style.ERROR(
'Missing workout types prevent full drift validation.'
))
self.stdout.write('')
if mismatches:
has_errors = True
self.stdout.write(self.style.ERROR(
f'DRIFT DETECTED: {len(mismatches)} mismatch(es)'
))
self.stdout.write('')
header = f'{"Workout Type":<35} {"Field":<30} {"Expected":<15} {"Actual":<15}'
self.stdout.write(header)
self.stdout.write('-' * len(header))
for m in mismatches:
self.stdout.write(
f'{m["type"]:<35} {m["field"]:<30} '
f'{str(m["expected"]):<15} {str(m["actual"]):<15}'
)
self.stdout.write('')
self.stdout.write(self.style.ERROR(
'To fix: update WorkoutType records in the DB or '
'update DB_CALIBRATION in generator/rules_engine.py.'
))
if has_errors:
sys.exit(1)
self.stdout.write(self.style.SUCCESS(
'No drift detected. DB values match research calibration.'
))