""" 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 found.' ) # 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.WARNING( f'Missing from DB ({len(missing_in_db)}):' )) for name in missing_in_db: self.stdout.write(f' - {name}') self.stdout.write('') if mismatches: 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.' )) sys.exit(1) else: self.stdout.write(self.style.SUCCESS( 'No drift detected. DB values match research calibration.' ))