""" استریک: روزهای پیاپیِ فعالیت. تا ۲۰۲۶-۰۹-۲۴ هیچ‌جای کد StreakRecord یا ActiveDay نمی‌نوشت — فقط seed_test_data. یعنی «روز پیاپی» روی صفحه‌ی خانه برای هر کاربرِ واقعی برای همیشه صفر بود و فقط کاربرهای دمو عدد داشتند. «فعالیت» یعنی پاسخ به سوال یا تمام کردنِ درس/جزیره. هر روز (به وقتِ تهران، TIME_ZONE) یک بار شمرده می‌شود؛ صدا زدنِ مکرر در یک روز بی‌اثر است. """ from datetime import timedelta from django.db import transaction from django.utils import timezone from .models import ActiveDay, StreakRecord def record_activity(user_id, today=None): """امروز را روزِ فعال ثبت می‌کند و استریک را جلو می‌برد. idempotent در یک روز.""" today = today or timezone.localdate() with transaction.atomic(): StreakRecord.objects.get_or_create(user_id=user_id) streak = StreakRecord.objects.select_for_update().get(user_id=user_id) last = ( ActiveDay.objects.filter(streak=streak) .order_by('-date').values_list('date', flat=True).first() ) if last == today: return streak if last == today - timedelta(days=1): streak.current_streak += 1 else: streak.current_streak = 1 streak.longest_streak = max(streak.longest_streak, streak.current_streak) streak.save(update_fields=['current_streak', 'longest_streak']) ActiveDay.objects.get_or_create(streak=streak, date=today) return streak def effective_current_streak(streak, today=None): """ استریکی که الان باید نشان داده شود. current_streak فقط در لحظه‌ی فعالیت به‌روز می‌شود؛ کاربری که دو روز نیامده هنوز عددِ قبلی را در دیتابیس دارد. اگر آخرین روزِ فعال نه امروز است نه دیروز، زنجیره شکسته و عددِ واقعی صفر است. """ if streak is None: return 0 today = today or timezone.localdate() last = ( streak.active_days.order_by('-date').values_list('date', flat=True).first() ) if last is None or last < today - timedelta(days=1): return 0 return streak.current_streak def streak_state(streak, today=None): """ وضعیتِ زنجیره برای نمایش: (current, active_today, lost). lost طولِ زنجیره‌ای است که شکسته (آخرین روزِ فعال پیش از دیروز)؛ current_streakِ دیتابیس تا فعالیتِ بعدی همان عددِ قبلی را نگه می‌دارد (record_activity آن را به ۱ برمی‌گرداند)، پس همین عدد است. """ if streak is None: return 0, False, 0 today = today or timezone.localdate() last = streak.active_days.order_by('-date').values_list('date', flat=True).first() if last is None: return 0, False, 0 if last < today - timedelta(days=1): return 0, False, streak.current_streak return streak.current_streak, last == today, 0