Files
guilan-ace-backend/apps/gamification/rules.py

102 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Stable scoring weights and derived lifetime rank/badge rules."""
POINTS = {
"attendance": 30,
"post": 20,
"comment": 5,
"like": 1,
"save": 1,
}
LEVELS = (
{"id": "companion", "label": "همراه", "minimum_points": 0},
{"id": "contributor", "label": "مشارکت‌گر", "minimum_points": 30},
{"id": "ally", "label": "همیار", "minimum_points": 150},
{"id": "influencer", "label": "اثرگذار", "minimum_points": 750},
{"id": "pioneer", "label": "پیشرو", "minimum_points": 1500},
)
ACTIVITY_BADGES = {
"attendance": (
{"threshold": 1, "label": "حضور اول", "description": "حضور تأییدشده در یک رویداد"},
{"threshold": 5, "label": "همراه رویدادها", "description": "حضور تأییدشده در پنج رویداد"},
{"threshold": 10, "label": "همیار رویدادها", "description": "حضور تأییدشده در ده رویداد"},
{"threshold": 25, "label": "چهره آشنای رویدادها", "description": "حضور تأییدشده در بیست‌وپنج رویداد"},
),
"posts": (
{"threshold": 1, "label": "نویسنده انجمن", "description": "۱ نوشته منتشرشده"},
{"threshold": 5, "label": "نویسنده فعال", "description": "۵ نوشته منتشرشده"},
{"threshold": 10, "label": "نویسنده برجسته", "description": "۱۰ نوشته منتشرشده"},
{"threshold": 25, "label": "نویسنده پیشرو", "description": "۲۵ نوشته منتشرشده"},
),
"comments": (
{"threshold": 1, "label": "آغاز گفت‌وگو", "description": "۱ دیدگاه یا پاسخ تأییدشده"},
{"threshold": 5, "label": "اهل گفت‌وگو", "description": "پنج دیدگاه یا پاسخ تأییدشده"},
{"threshold": 20, "label": "همراه گفت‌وگو", "description": "بیست دیدگاه یا پاسخ تأییدشده"},
{"threshold": 50, "label": "صدای اثرگذار", "description": "پنجاه دیدگاه یا پاسخ تأییدشده"},
),
"likes": (
{"threshold": 1, "label": "نخستین لایک", "description": "نخستین لایک روی یک نوشته"},
{"threshold": 10, "label": "دست‌گرم", "description": "ده لایک روی نوشته‌ها"},
{"threshold": 50, "label": "محبوب انجمن", "description": "پنجاه لایک روی نوشته‌ها"},
{"threshold": 100, "label": "الهام‌بخش", "description": "صد لایک روی نوشته‌ها"},
),
}
def level_for_points(points: int) -> dict:
points = max(0, int(points))
return next(level for level in reversed(LEVELS) if points >= level["minimum_points"])
def level_progress(points: int) -> dict:
points = max(0, int(points))
current_index = max(
index for index, level in enumerate(LEVELS) if points >= level["minimum_points"]
)
current = LEVELS[current_index]
next_level = LEVELS[current_index + 1] if current_index + 1 < len(LEVELS) else None
if next_level is None:
return {
"current_level": current,
"next_level": None,
"points_to_next": 0,
"progress_percent": 100,
}
span = next_level["minimum_points"] - current["minimum_points"]
progress_points = points - current["minimum_points"]
return {
"current_level": current,
"next_level": next_level,
"points_to_next": next_level["minimum_points"] - points,
"progress_percent": min(100, int(progress_points * 100 / span)),
}
def earned_badges(total_points: int, activity_counts: dict[str, int]) -> list[dict]:
badges = []
for level in LEVELS:
if total_points >= level["minimum_points"]:
badges.append({
"id": f"level-{level['id']}",
"category": "level",
"label": level["label"],
"description": f"سطح کلی با {level['minimum_points']} امتیاز",
"threshold": level["minimum_points"],
})
for category, levels in ACTIVITY_BADGES.items():
count = max(0, int(activity_counts.get(category, 0)))
badge_prefix = {"posts": "post", "comments": "comment"}.get(category, category)
for badge_level in levels:
threshold = badge_level["threshold"]
if count >= threshold:
badges.append({
"id": f"{badge_prefix}-{threshold}",
"category": category,
**badge_level,
})
return badges