feat(gamification): expand badge tiers and count only outgoing likes
This commit is contained in:
@@ -24,19 +24,22 @@ ACTIVITY_BADGES = {
|
|||||||
{"threshold": 25, "label": "چهره آشنای رویدادها", "description": "حضور تأییدشده در بیستوپنج رویداد"},
|
{"threshold": 25, "label": "چهره آشنای رویدادها", "description": "حضور تأییدشده در بیستوپنج رویداد"},
|
||||||
),
|
),
|
||||||
"posts": (
|
"posts": (
|
||||||
{"threshold": 1, "label": "نویسنده انجمن", "description": "انتشار یا همنویسندگی یک نوشته"},
|
{"threshold": 1, "label": "نویسنده انجمن", "description": "۱ نوشته منتشرشده"},
|
||||||
{"threshold": 5, "label": "نویسنده فعال", "description": "انتشار یا همنویسندگی پنج نوشته"},
|
{"threshold": 5, "label": "نویسنده فعال", "description": "۵ نوشته منتشرشده"},
|
||||||
{"threshold": 10, "label": "نویسنده برجسته", "description": "انتشار یا همنویسندگی ده نوشته"},
|
{"threshold": 10, "label": "نویسنده برجسته", "description": "۱۰ نوشته منتشرشده"},
|
||||||
|
{"threshold": 25, "label": "نویسنده پیشرو", "description": "۲۵ نوشته منتشرشده"},
|
||||||
),
|
),
|
||||||
"comments": (
|
"comments": (
|
||||||
|
{"threshold": 1, "label": "آغاز گفتوگو", "description": "۱ دیدگاه یا پاسخ تأییدشده"},
|
||||||
{"threshold": 5, "label": "اهل گفتوگو", "description": "پنج دیدگاه یا پاسخ تأییدشده"},
|
{"threshold": 5, "label": "اهل گفتوگو", "description": "پنج دیدگاه یا پاسخ تأییدشده"},
|
||||||
{"threshold": 20, "label": "همراه گفتوگو", "description": "بیست دیدگاه یا پاسخ تأییدشده"},
|
{"threshold": 20, "label": "همراه گفتوگو", "description": "بیست دیدگاه یا پاسخ تأییدشده"},
|
||||||
{"threshold": 50, "label": "صدای اثرگذار", "description": "پنجاه دیدگاه یا پاسخ تأییدشده"},
|
{"threshold": 50, "label": "صدای اثرگذار", "description": "پنجاه دیدگاه یا پاسخ تأییدشده"},
|
||||||
),
|
),
|
||||||
"likes": (
|
"likes": (
|
||||||
{"threshold": 10, "label": "دستگرم", "description": "ده پسند دادهشده یا دریافتشده"},
|
{"threshold": 1, "label": "نخستین لایک", "description": "نخستین لایک روی یک نوشته"},
|
||||||
{"threshold": 50, "label": "محبوب انجمن", "description": "پنجاه پسند دادهشده یا دریافتشده"},
|
{"threshold": 10, "label": "دستگرم", "description": "ده لایک روی نوشتهها"},
|
||||||
{"threshold": 100, "label": "الهامبخش", "description": "صد پسند دادهشده یا دریافتشده"},
|
{"threshold": 50, "label": "محبوب انجمن", "description": "پنجاه لایک روی نوشتهها"},
|
||||||
|
{"threshold": 100, "label": "الهامبخش", "description": "صد لایک روی نوشتهها"},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +89,7 @@ def earned_badges(total_points: int, activity_counts: dict[str, int]) -> list[di
|
|||||||
|
|
||||||
for category, levels in ACTIVITY_BADGES.items():
|
for category, levels in ACTIVITY_BADGES.items():
|
||||||
count = max(0, int(activity_counts.get(category, 0)))
|
count = max(0, int(activity_counts.get(category, 0)))
|
||||||
badge_prefix = "post" if category == "posts" else category
|
badge_prefix = {"posts": "post", "comments": "comment"}.get(category, category)
|
||||||
for badge_level in levels:
|
for badge_level in levels:
|
||||||
threshold = badge_level["threshold"]
|
threshold = badge_level["threshold"]
|
||||||
if count >= threshold:
|
if count >= threshold:
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ def summary_for_user(user):
|
|||||||
)
|
)
|
||||||
activity_counts = Counter()
|
activity_counts = Counter()
|
||||||
active_effects = (
|
active_effects = (
|
||||||
rows.values("effect_key", "activity")
|
rows.values("effect_key", "activity", "role")
|
||||||
.annotate(net_points=Sum("points"))
|
.annotate(net_points=Sum("points"))
|
||||||
.filter(net_points__gt=0)
|
.filter(net_points__gt=0)
|
||||||
)
|
)
|
||||||
@@ -166,7 +166,7 @@ def summary_for_user(user):
|
|||||||
activity_counts["posts"] += 1
|
activity_counts["posts"] += 1
|
||||||
elif effect["activity"] == "comment":
|
elif effect["activity"] == "comment":
|
||||||
activity_counts["comments"] += 1
|
activity_counts["comments"] += 1
|
||||||
elif effect["activity"] == "like":
|
elif effect["activity"] == "like" and effect["role"] == "actor":
|
||||||
activity_counts["likes"] += 1
|
activity_counts["likes"] += 1
|
||||||
|
|
||||||
points_total = totals["points_total"] or 0
|
points_total = totals["points_total"] or 0
|
||||||
|
|||||||
@@ -26,7 +26,12 @@ def test_authenticated_summary_returns_progress_and_activity_breakdown(client, u
|
|||||||
assert summary["activity_counts"]["attendance"] == 1
|
assert summary["activity_counts"]["attendance"] == 1
|
||||||
assert "attendance-1" in {badge["id"] for badge in summary["badges"]}
|
assert "attendance-1" in {badge["id"] for badge in summary["badges"]}
|
||||||
assert summary["points_per_activity"] == {"attendance": 30, "post": 20, "comment": 5, "like": 1, "save": 1}
|
assert summary["points_per_activity"] == {"attendance": 30, "post": 20, "comment": 5, "like": 1, "save": 1}
|
||||||
assert summary["badge_thresholds"]["likes"] == [10, 50, 100]
|
assert summary["badge_thresholds"] == {
|
||||||
|
"attendance": [1, 5, 10, 25],
|
||||||
|
"posts": [1, 5, 10, 25],
|
||||||
|
"comments": [1, 5, 20, 50],
|
||||||
|
"likes": [1, 10, 50, 100],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_public_profile_exposes_badges_but_never_points_even_when_details_are_hidden(
|
def test_public_profile_exposes_badges_but_never_points_even_when_details_are_hidden(
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ def test_publish_coauthor_like_and_save_award_expected_contributions(user_factor
|
|||||||
assert summary_for_user(coauthor)["points_total"] == 22
|
assert summary_for_user(coauthor)["points_total"] == 22
|
||||||
assert summary_for_user(actor)["points_total"] == 2
|
assert summary_for_user(actor)["points_total"] == 2
|
||||||
assert summary_for_user(actor)["activity_counts"]["likes"] == 1
|
assert summary_for_user(actor)["activity_counts"]["likes"] == 1
|
||||||
assert summary_for_user(coauthor)["activity_counts"]["likes"] == 1
|
assert summary_for_user(author)["activity_counts"]["likes"] == 0
|
||||||
|
assert summary_for_user(coauthor)["activity_counts"]["likes"] == 0
|
||||||
|
|
||||||
post.writers.remove(coauthor)
|
post.writers.remove(coauthor)
|
||||||
post.writers.remove(author)
|
post.writers.remove(author)
|
||||||
|
|||||||
@@ -44,11 +44,22 @@ def test_highest_level_has_no_next_level_or_remaining_progress():
|
|||||||
def test_earned_badges_include_every_reached_global_and_activity_milestone():
|
def test_earned_badges_include_every_reached_global_and_activity_milestone():
|
||||||
badges = earned_badges(
|
badges = earned_badges(
|
||||||
total_points=300,
|
total_points=300,
|
||||||
activity_counts={"attendance": 5, "posts": 1, "comments": 4, "likes": 50},
|
activity_counts={"attendance": 5, "posts": 25, "comments": 4, "likes": 50},
|
||||||
)
|
)
|
||||||
badge_ids = {badge["id"] for badge in badges}
|
badge_ids = {badge["id"] for badge in badges}
|
||||||
|
|
||||||
assert {"level-companion", "level-contributor", "level-ally"} <= badge_ids
|
assert {"level-companion", "level-contributor", "level-ally"} <= badge_ids
|
||||||
assert {"attendance-1", "attendance-5", "post-1", "likes-10", "likes-50"} <= badge_ids
|
assert {
|
||||||
|
"attendance-1",
|
||||||
|
"attendance-5",
|
||||||
|
"post-1",
|
||||||
|
"post-5",
|
||||||
|
"post-10",
|
||||||
|
"post-25",
|
||||||
|
"comment-1",
|
||||||
|
"likes-1",
|
||||||
|
"likes-10",
|
||||||
|
"likes-50",
|
||||||
|
} <= badge_ids
|
||||||
assert "comment-5" not in badge_ids
|
assert "comment-5" not in badge_ids
|
||||||
assert not any(badge_id.startswith("save-") for badge_id in badge_ids)
|
assert not any(badge_id.startswith("save-") for badge_id in badge_ids)
|
||||||
|
|||||||
Reference in New Issue
Block a user