"""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