rework noita objectives + deathcounter

This commit is contained in:
2026-05-15 01:50:34 +02:00
parent 754b0b0803
commit 7cfab20826
14 changed files with 481 additions and 167 deletions
+69 -61
View File
@@ -5,18 +5,15 @@ from django.db.models import (
F,
Case,
When,
Sum,
Count,
IntegerField,
Subquery,
OuterRef,
Window,
)
from django.db.models.functions import Rank
from ninja import Router, File
from ninja.files import UploadedFile
from noita.schemas import ObjectivOut, ResultsOut, LeaderboardOut
from noita.schemas import ResultsOut, LeaderboardOut
from noita.services.objectives import parse_objectives_and_store
from .models import LogfileSubmission, Objectiv, ObjectivPoint
@@ -26,11 +23,6 @@ from .schemas import NoitaSubmissionOut
router = Router()
@router.get("objectives", response=list[ObjectivOut])
def get_my_objectives(request: HttpRequest):
return Objectiv.objects.order_by("-count").filter(user=request.user)
@router.get("results", response=ResultsOut)
def get_results(request: HttpRequest):
cache_key = f"api:noita:results:{request.user.id}"
@@ -44,8 +36,15 @@ def get_results(request: HttpRequest):
Calculates points as: ObjectivPoint.point * min(max_count, count) for each objective
Uses Django ORM annotate for efficient queryset computation.
"""
# Group objectives by objectiv_id and count occurrences
user_objectives = (
Objectiv.objects.filter(user=request.user)
.values("objectiv_id")
.annotate(count=Count("id"))
)
# Fetch points from ObjectivPoint using Subquery
user_objectives = Objectiv.objects.filter(user=request.user).annotate(
user_objectives = user_objectives.annotate(
# Get points per objective from ObjectivPoint
points_per_objectiv=Subquery(
ObjectivPoint.objects.filter(objectiv_id=OuterRef("objectiv_id")).values(
@@ -81,22 +80,28 @@ def get_results(request: HttpRequest):
total_points=F("points_per_objectiv") * F("capped_count"),
)
# Get total score
total_score_result = user_objectives.aggregate(Sum("total_points"))[
"total_points__sum"
]
total_score = total_score_result or 0
# Annotate seed + first-seen-at
user_objectives = user_objectives.annotate(
seed=F("seed"),
first_seen_at=F("first_seen_at"),
)
# Build response with all objectives
objectives_with_points = [
{
"objectiv_id": obj.objectiv_id,
"count": obj.count,
"points_per_objectiv": obj.points_per_objectiv or 0,
"total_points": obj.total_points or 0,
}
for obj in user_objectives.order_by("-total_points")
]
# Build response with all objectives and compute total score
objectives_with_points = []
total_score = 0
for obj in user_objectives.order_by("-total_points"):
points = obj["total_points"] or 0
objectives_with_points.append(
{
"objectiv_id": obj["objectiv_id"],
"count": obj["count"],
"points_per_objectiv": obj["points_per_objectiv"] or 0,
"total_points": points,
"first_seen_at": obj["first_seen_at"],
"seed": obj["seed"],
}
)
total_score += points
data = {
"total_score": total_score,
@@ -124,9 +129,11 @@ def get_leaderboard(request: HttpRequest):
User = get_user_model()
# Get all objectives with calculated points
# Get all objectives with calculated points (grouped by objectiv_id and user)
all_objectives = (
Objectiv.objects.annotate(
Objectiv.objects.values("user", "objectiv_id")
.annotate(count=Count("id"))
.annotate(
# Fetch points from ObjectivPoint using Subquery
points_per_objectiv=Subquery(
ObjectivPoint.objects.filter(
@@ -161,46 +168,47 @@ def get_leaderboard(request: HttpRequest):
)
)
# Get user totals using subquery
user_totals = (
all_objectives.values("user")
.annotate(total_score=Sum("total_points"))
.values("user", "total_score")
)
# Build user totals by iterating through objectives
user_totals_dict = {}
for obj in all_objectives:
user_id = obj["user"]
points = obj["total_points"] or 0
if user_id not in user_totals_dict:
user_totals_dict[user_id] = 0
user_totals_dict[user_id] += points
# Get unique users and their scores, then apply ranking
leaderboard = (
User.objects.filter(objectiv__isnull=False)
.distinct()
.annotate(
total_score=Subquery(
user_totals.filter(user=OuterRef("id")).values("total_score")[:1],
output_field=IntegerField(),
)
users_with_scores = []
for user_id, total_score in user_totals_dict.items():
user = User.objects.get(id=user_id)
objectives_count = (
Objectiv.objects.filter(user_id=user_id)
.values("objectiv_id")
.distinct()
.count()
)
.annotate(objectives_count=Count("objectiv", distinct=True))
.annotate(
rank=Window(
expression=Rank(),
order_by=F("total_score").desc(),
)
)
.values("rank", "username", "total_score", "objectives_count")
.order_by("rank")
)
data = {
"leaderboard": [
users_with_scores.append(
{
"rank": entry["rank"],
"username": entry["username"],
"total_score": entry["total_score"] or 0,
"objectives_count": entry["objectives_count"],
"user_id": user_id,
"username": user.username,
"total_score": total_score,
"objectives_count": objectives_count,
}
for entry in leaderboard
]
}
)
# Sort by score and add rank
users_with_scores.sort(key=lambda x: x["total_score"], reverse=True)
leaderboard = [
{
"rank": idx + 1,
"username": entry["username"],
"total_score": entry["total_score"],
"objectives_count": entry["objectives_count"],
}
for idx, entry in enumerate(users_with_scores)
]
data = {"leaderboard": leaderboard}
cache.set("api:noita:leaderboard", data, 300)
return data