Skip to content

Commit

Permalink
Cleanup get_level_info (#295)
Browse files Browse the repository at this point in the history
* split get_level_info

* call get_level_info less

* lint

* remove hint reference

* lint
  • Loading branch information
Incompleteusern committed Sep 3, 2023
1 parent 6ff81e1 commit 081366f
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 47 deletions.
2 changes: 1 addition & 1 deletion dashboard/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def portal(request: AuthHttpRequest, student_pk: int) -> HttpResponse:

level_info = get_level_info(student)
if request.user == student.user:
result = check_level_up(student)
result = check_level_up(student, level_info)
if result is True and profile.show_bars is True:
lvl = level_info["level_number"]
messages.success(request, f"You leveled up! You're now level {lvl}.")
Expand Down
121 changes: 76 additions & 45 deletions rpg/levelsys.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,44 @@ def get_level_info(student: Student) -> LevelInfoDict:
"""Uses a bunch of expensive database queries to compute a student's levels and data,
returning the findings as a typed dictionary."""

level_data = LevelInfoDict() # type: ignore

total_clubs, total_hearts = get_clubs_hearts_stats(student, level_data)

total_diamonds = get_diamond_stats(student)

total_spades = get_spade_stats(student, level_data)

try:
dynamic_progress = (UserProfile.objects.get(user=student.user)).dynamic_progress
except UserProfile.DoesNotExist:
dynamic_progress = False

meters: FourMetersDict = {
"clubs": Meter.ClubMeter(int(total_clubs), dynamic_progress),
"hearts": Meter.HeartMeter(round(total_hearts, 2), dynamic_progress),
"diamonds": Meter.DiamondMeter(int(total_diamonds), dynamic_progress),
"spades": Meter.SpadeMeter(round(total_spades, 1), dynamic_progress),
}
level_number = sum(meter.level for meter in meters.values()) # type: ignore
level = (
Level.objects.filter(threshold__lte=level_number).order_by("-threshold").first()
)
level_name = level.name if level is not None else "No Level"
max_level = Level.objects.all().aggregate(max=Max("threshold"))["max"] or 0

level_data["meters"] = meters
level_data["level_number"] = level_number
level_data["level_name"] = level_name
level_data["is_maxed"] = level_number >= max_level
level_data["bonus_levels"] = BonusLevel.objects.filter(level__lte=level_number)

return level_data


def get_clubs_hearts_stats(
student: Student, leveldict: LevelInfoDict = None
) -> Tuple[float, int]:
psets = PSet.objects.filter(student__user=student.user, status="A", eligible=True)
psets = psets.order_by("upload__created_at")
pset_data = psets.aggregate(
Expand All @@ -183,24 +221,44 @@ def get_level_info(student: Student) -> LevelInfoDict:
clubs_Z=Sum("clubs", filter=Q(unit__code__startswith="Z")),
hearts=Sum("hours"),
)
total_clubs = (
total_clubs: float = (
(pset_data["clubs_any"] or 0)
+ (pset_data["clubs_D"] or 0) * BONUS_D_UNIT
+ (pset_data["clubs_Z"] or 0) * BONUS_Z_UNIT
)
total_hearts = pset_data["hearts"] or 0
total_hearts: int = pset_data["hearts"] or 0

if leveldict is not None:
leveldict["psets"] = psets
leveldict["pset_data"] = pset_data

return total_clubs, total_hearts


def get_diamond_stats(student: Student) -> int:
diamond_qset = AchievementUnlock.objects.filter(user=student.user)
total_diamonds = diamond_qset.aggregate(s=Sum("achievement__diamonds"))["s"] or 0

return total_diamonds


def get_spade_stats(student: Student, leveldict: LevelInfoDict = None) -> int:
total_spades = 0

# a billion unrelated spades items lol
quiz_attempts = ExamAttempt.objects.filter(student__user=student.user)
quiz_attempts = quiz_attempts.order_by("quiz__family", "quiz__number")
total_spades = (quiz_attempts.aggregate(total=Sum("score"))["total"] or 0) * 2

quest_completes = QuestComplete.objects.filter(student__user=student.user)
quest_completes = quest_completes.order_by("-timestamp")
total_spades += quest_completes.aggregate(total=Sum("spades"))["total"] or 0

mock_completes = MockCompleted.objects.filter(student__user=student.user)
mock_completes = mock_completes.select_related("exam")
mock_completes = mock_completes.order_by("exam__family", "exam__number")
total_spades += mock_completes.count() * 3

market_guesses = (
Guess.objects.filter(
user=student.user,
Expand All @@ -209,6 +267,8 @@ def get_level_info(student: Student) -> LevelInfoDict:
.order_by("-market__end_date")
.select_related("market")
)
total_spades += market_guesses.aggregate(total=Sum("score"))["total"] or 0

suggested_units_queryset = ProblemSuggestion.objects.filter(
user=student.user,
status__in=("SUGG_NOK", "SUGG_OK"),
Expand All @@ -219,57 +279,29 @@ def get_level_info(student: Student) -> LevelInfoDict:
"unit__code",
)
suggest_units_set: SuggestUnitSet = set(suggested_units_queryset)
total_spades += len(suggest_units_set)

completed_jobs = Job.objects.filter(
assignee__user=student.user, progress="JOB_VFD"
).select_related("folder")
total_spades += completed_jobs.aggregate(total=Sum("spades_bounty"))["total"] or 0

hanabi_replays = HanabiReplay.objects.filter(
contest__processed=True,
hanabiparticipation__player__user=student.user,
)

total_spades = (quiz_attempts.aggregate(total=Sum("score"))["total"] or 0) * 2
total_spades += quest_completes.aggregate(total=Sum("spades"))["total"] or 0
total_spades += market_guesses.aggregate(total=Sum("score"))["total"] or 0
total_spades += completed_jobs.aggregate(total=Sum("spades_bounty"))["total"] or 0
total_spades += mock_completes.count() * 3
total_spades += len(suggest_units_set)
total_spades += hanabi_replays.aggregate(total=Sum("spades_score"))["total"] or 0

try:
dynamic_progress = (UserProfile.objects.get(user=student.user)).dynamic_progress
except UserProfile.DoesNotExist:
dynamic_progress = False
if leveldict is not None:
leveldict["quiz_attempts"] = quiz_attempts
leveldict["quest_completes"] = quest_completes
leveldict["market_guesses"] = market_guesses
leveldict["mock_completes"] = mock_completes
leveldict["suggest_unit_set"] = suggest_units_set
leveldict["completed_jobs"] = completed_jobs
leveldict["hanabi_replays"] = hanabi_replays

meters: FourMetersDict = {
"clubs": Meter.ClubMeter(int(total_clubs), dynamic_progress),
"hearts": Meter.HeartMeter(round(total_hearts, 2), dynamic_progress),
"diamonds": Meter.DiamondMeter(int(total_diamonds), dynamic_progress),
"spades": Meter.SpadeMeter(round(total_spades, 1), dynamic_progress),
}
level_number = sum(meter.level for meter in meters.values()) # type: ignore
level = (
Level.objects.filter(threshold__lte=level_number).order_by("-threshold").first()
)
level_name = level.name if level is not None else "No Level"
max_level = Level.objects.all().aggregate(max=Max("threshold"))["max"] or 0
level_data: LevelInfoDict = {
"psets": psets,
"pset_data": pset_data,
"meters": meters,
"level_number": level_number,
"level_name": level_name,
"is_maxed": (level_number >= max_level),
"bonus_levels": BonusLevel.objects.filter(level__lte=level_number),
# spade properties
"quiz_attempts": quiz_attempts,
"quest_completes": quest_completes,
"market_guesses": market_guesses,
"mock_completes": mock_completes,
"suggest_unit_set": suggest_units_set,
"completed_jobs": completed_jobs,
"hanabi_replays": hanabi_replays,
}
return level_data
return total_spades


def annotate_student_queryset_with_scores(
Expand Down Expand Up @@ -398,10 +430,9 @@ def get_student_rows(queryset: QuerySet[Student]) -> list[dict[str, Any]]:
return rows


def check_level_up(student: Student) -> bool:
def check_level_up(student: Student, level_info: LevelInfoDict) -> bool:
if not student.semester.active:
return False
level_info = get_level_info(student)
level_number = level_info["level_number"]
if level_number <= student.last_level_seen:
return False
Expand Down
1 change: 0 additions & 1 deletion rpg/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,6 @@ def get_object(self, *args: Any, **kwargs: Any) -> Achievement:
return achievement

def form_valid(self, form: BaseModelForm[Achievement]):
assert_maxed_out_level_info(self.student)
form.instance.diamonds = RUBY_PALACE_DIAMOND_VALUE
form.instance.creator = self.student.user
messages.success(
Expand Down

0 comments on commit 081366f

Please sign in to comment.