Skip to content

Commit

Permalink
Update achievement_score
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasdeluna committed Nov 13, 2024
1 parent dba3d4f commit 585c84e
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 51 deletions.
4 changes: 3 additions & 1 deletion lego/api/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@
from lego.utils.views import SiteMetaViewSet

router = routers.DefaultRouter()
router.register(r"achievements/leaderboard", LeaderBoardViewSet, basename="achievements")
router.register(
r"achievements/leaderboard", LeaderBoardViewSet, basename="achievements"
)
router.register(r"announcements", AnnouncementViewSet, basename="announcements")
router.register(r"articles", ArticlesViewSet)
router.register(r"bdb", AdminCompanyViewSet, basename="bdb")
Expand Down
7 changes: 5 additions & 2 deletions lego/apps/achievements/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ def percentage(self):

def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if hasattr(self.user, '_cached_properties') and 'achievement_score' in self.user.__dict__:
del self.user.__dict__['achievement_score']
if (
hasattr(self.user, "_cached_properties")
and "achievement_score" in self.user.__dict__
):
del self.user.__dict__["achievement_score"]

class Meta:
constraints = [
Expand Down
3 changes: 2 additions & 1 deletion lego/apps/achievements/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from rest_framework import serializers

from lego.apps.achievements.models import Achievement
from lego.apps.achievements.utils.calculation_utils import calculate_user_rank
from lego.utils.serializers import BasisModelSerializer
from rest_framework import serializers


class AchievementSerializer(BasisModelSerializer):
Expand Down
71 changes: 31 additions & 40 deletions lego/apps/achievements/utils/calculation_utils.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,43 @@

import math

from lego.apps.achievements.constants import EVENT_IDENTIFIER, EVENT_PRICE_IDENTIFIER, EVENT_RANK_IDENTIFIER, MEETING_IDENTIFIER, PENALTY_IDENTIFIER, POLL_IDENTIFIER, QUOTE_IDENTIFIER
from lego.apps.achievements.constants import (
EVENT_IDENTIFIER,
EVENT_PRICE_IDENTIFIER,
EVENT_RANK_IDENTIFIER,
MEETING_IDENTIFIER,
PENALTY_IDENTIFIER,
POLL_IDENTIFIER,
QUOTE_IDENTIFIER,
)

ACHIEVEMENT_DATA = {
EVENT_IDENTIFIER: [0, 1, 2, 3, 4, 6],
EVENT_RANK_IDENTIFIER: [7, 8, 9],
QUOTE_IDENTIFIER: [2],
EVENT_PRICE_IDENTIFIER: [2, 3, 5],
MEETING_IDENTIFIER: [2],
POLL_IDENTIFIER: [0, 2, 4],
PENALTY_IDENTIFIER: [0, 3, 5, 6],
EVENT_IDENTIFIER: [x + 1 for x in [0, 1, 2, 3, 4, 6]],
EVENT_RANK_IDENTIFIER: [x + 1 for x in [7, 8, 9]],
QUOTE_IDENTIFIER: [x + 1 for x in [1]],
EVENT_PRICE_IDENTIFIER: [x + 1 for x in [2, 3, 5]],
MEETING_IDENTIFIER: [x + 1 for x in [1]],
POLL_IDENTIFIER: [x + 1 for x in [0, 2, 4]],
PENALTY_IDENTIFIER: [x + 1 for x in [0, 3, 5, 6]],
}

delta = 0.1

def calculate_user_rank(user, alpha=0.68, beta=0.45, gamma=0.43, w=0.75):
N = len(ACHIEVEMENT_DATA)
highest_rarity = 0
weighted_rarities_product = 1
achievement_count = user.achievements.count()

if achievement_count == 0:
return 0

for achievement in user.achievements.all():
identifier = achievement.identifier
level = achievement.level

rarity_list = ACHIEVEMENT_DATA.get(identifier, [])
rarity = rarity_list[level] if level < len(rarity_list) else 0

highest_rarity = max(highest_rarity, rarity)

weighted_rarity = math.log(rarity + 2) ** 2
weighted_rarities_product *= weighted_rarity
max_possible_score = sum(
max(identifiers) + (identifiers.index(max(identifiers)) * delta)
for key, identifiers in ACHIEVEMENT_DATA.items()
if key != EVENT_RANK_IDENTIFIER
)

G = math.pow(weighted_rarities_product, 1 / achievement_count)

G_normalized = G / (math.log(9 + 2) ** 2)

baseline = (highest_rarity + 1) / 2
def calculate_user_rank(user):
score = 0.0

G_blended = w * G_normalized + (1 - w) * baseline / (math.log(9 + 2) ** 2)
user_achievements = user.achievements.all()
for achievement in user_achievements:
identifier_list = ACHIEVEMENT_DATA.get(achievement.identifier, [])

highest_rarity_component = alpha * (math.sqrt(highest_rarity + 1) / math.sqrt(10))
geometric_mean_component = beta * G_blended
achievement_count_component = gamma * (math.log(achievement_count + 1) / math.log(N + 1))
value = identifier_list[achievement.level]
score += value + (achievement.level * delta)

rank = (100 * (highest_rarity_component + geometric_mean_component + achievement_count_component)) / (alpha + beta + gamma)
rounded = round(rank, 2)
return rounded
# Normalize the score as a percentage of max_possible_score
return round((score / max_possible_score) * 100, 2) if max_possible_score else 0
6 changes: 3 additions & 3 deletions lego/apps/achievements/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from rest_framework import permissions, mixins, viewsets
from rest_framework import mixins, permissions, viewsets
from rest_framework.response import Response

from lego.apps.users.serializers.users import PublicUserWithGroupsSerializer
from lego.apps.users.models import User
from lego.apps.users.serializers.users import PublicUserWithGroupsSerializer


class LeaderBoardViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
Expand All @@ -20,4 +20,4 @@ def list(self, request, *args, **kwargs):
serializer.data, key=lambda x: x["achievement_score"], reverse=True
)[:50]

return Response(sorted_data)
return Response(sorted_data)
5 changes: 3 additions & 2 deletions lego/apps/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from django.db import models, transaction
from django.db.models import Q
from django.utils import timezone
from django.utils.timezone import datetime, timedelta
from django.utils.functional import cached_property
from django.utils.timezone import datetime, timedelta

from mptt.fields import TreeForeignKey
from mptt.models import MPTTModel
Expand Down Expand Up @@ -446,10 +446,11 @@ def email_address(self):
# Return the internal address if all requirements for a GSuite account are met.
return internal_address
return self.email

@cached_property
def achievement_score(self):
from lego.apps.achievements.utils.calculation_utils import calculate_user_rank

return calculate_user_rank(self)

@profile_picture.setter # type: ignore
Expand Down
4 changes: 2 additions & 2 deletions lego/apps/users/serializers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class Meta(PublicUserSerializer.Meta):
"past_memberships",
"memberships",
"achievements",
"achievement_score"
"achievement_score",
)


Expand Down Expand Up @@ -251,7 +251,7 @@ class Meta:
"github_username",
"linkedin_id",
"achievements",
"achievement_score"
"achievement_score",
)


Expand Down

0 comments on commit 585c84e

Please sign in to comment.