Nessuna descrizione

benefit.py 2.2KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. from __future__ import annotations
  2. from typing import Optional
  3. from django.contrib.auth import get_user_model
  4. from django.db.models import Sum, F, Max, Q, Window
  5. from django.db.models.functions import Rank
  6. from django.utils import timezone
  7. from api.models import BenefitEvent
  8. User = get_user_model()
  9. # Default weights per action kind
  10. BENEFIT_WEIGHTS = {
  11. BenefitEvent.KIND_ACCEPTED_ANSWER: 10,
  12. BenefitEvent.KIND_REVIEW_HELPFUL: 3,
  13. BenefitEvent.KIND_REFERRAL: 20,
  14. BenefitEvent.KIND_RECOMMENDATION: 5,
  15. }
  16. def log_benefit_event(
  17. *,
  18. benefactor: User,
  19. beneficiary: User,
  20. kind: str,
  21. points: Optional[int] = None,
  22. meta: Optional[dict] = None,
  23. ) -> BenefitEvent:
  24. if points is None:
  25. points = BENEFIT_WEIGHTS.get(kind, 1)
  26. event = BenefitEvent(
  27. benefactor=benefactor,
  28. beneficiary=beneficiary,
  29. kind=kind,
  30. points=points,
  31. meta=meta or {},
  32. )
  33. event.save()
  34. return event
  35. def _period_start(period: str):
  36. period = (period or "").lower()
  37. if period in ("all", "all_time", "alltime", ""):
  38. return None
  39. now = timezone.now()
  40. if period in ("week", "weekly", "7d"):
  41. return now - timezone.timedelta(days=7)
  42. if period in ("month", "monthly", "30d"):
  43. return now - timezone.timedelta(days=30)
  44. # default fallback: weekly
  45. return now - timezone.timedelta(days=7)
  46. def build_leaderboard_queryset(period: str = "weekly"):
  47. start = _period_start(period)
  48. pf = Q()
  49. if start is not None:
  50. pf = Q(benefit_given__created_at__gte=start)
  51. qs = (
  52. User.objects
  53. .annotate(
  54. points_given=Sum('benefit_given__points', filter=pf),
  55. last_help_at=Max('benefit_given__created_at', filter=pf),
  56. )
  57. )
  58. if start is not None:
  59. qs = qs.filter(benefit_given__created_at__gte=start)
  60. qs = (
  61. qs.filter(points_given__gt=0)
  62. .annotate(
  63. rank=Window(
  64. expression=Rank(),
  65. order_by=(
  66. F('points_given').desc(nulls_last=True),
  67. F('last_help_at').asc(nulls_last=True),
  68. F('id').asc(),
  69. ),
  70. )
  71. )
  72. .order_by('rank')
  73. )
  74. return qs