from collections import Counter from django.core.management.base import BaseCommand from django.db import transaction from django.utils import timezone from apps.events.models import Registration from apps.gamification.rules import ACTIVITY_BADGES, LEVELS, POINTS, level_for_points from apps.gamification.services import sync_registration_attendance class Command(BaseCommand): help = "Report or backfill lifetime points from existing attended event registrations." def add_arguments(self, parser): parser.add_argument( "--apply", action="store_true", help="Write historical attendance provenance and ledger awards; default is read-only.", ) def handle(self, *args, **options): registrations = list( Registration.objects.filter(status=Registration.StatusChoices.ATTENDED) .select_related("user") .order_by("user_id", "id") ) attendance_counts = Counter(registration.user_id for registration in registrations) level_counts = Counter() for count in attendance_counts.values(): level_counts[level_for_points(count * POINTS["attendance"])["id"]] += 1 self.stdout.write(f"Attended registrations eligible: {len(registrations)}") self.stdout.write(f"Users with attended registrations: {len(attendance_counts)}") self.stdout.write("Projected level distribution (event history only):") for level in LEVELS: self.stdout.write(f" {level['id']}: {level_counts[level['id']]}") self.stdout.write("Projected attendance badges (threshold -> users):") for badge in ACTIVITY_BADGES["attendance"]: members = sum(count >= badge["threshold"] for count in attendance_counts.values()) self.stdout.write(f" {badge['threshold']}: {members}") if not options["apply"]: self.stdout.write(self.style.WARNING("Dry run only; no records were changed. Pass --apply to backfill.")) return verified_at = timezone.now() with transaction.atomic(): for registration in registrations: if not registration.attendance_verified_at: registration.attendance_verified_at = verified_at registration.attendance_verification_source = Registration.AttendanceVerificationSource.HISTORICAL registration.save(update_fields=[ "attendance_verified_at", "attendance_verification_source", "updated_at", ]) sync_registration_attendance(registration, trust_legacy=True) self.stdout.write(self.style.SUCCESS(f"Backfilled {len(registrations)} attended registrations."))