feat: add community contribution ranks

This commit is contained in:
2026-09-29 22:18:57 +03:30
parent 0f6220f72c
commit 77567178ad
46 changed files with 2206 additions and 23 deletions

View File

@@ -0,0 +1,98 @@
"""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": "انتشار یا هم‌نویسندگی ده نوشته"},
),
"comments": (
{"threshold": 5, "label": "اهل گفت‌وگو", "description": "پنج دیدگاه یا پاسخ تأییدشده"},
{"threshold": 20, "label": "همراه گفت‌وگو", "description": "بیست دیدگاه یا پاسخ تأییدشده"},
{"threshold": 50, "label": "صدای اثرگذار", "description": "پنجاه دیدگاه یا پاسخ تأییدشده"},
),
"likes": (
{"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 = "post" if category == "posts" else 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