cache for result api

This commit is contained in:
Loïc Gremaud 2026-05-22 17:21:53 +02:00
parent 9e5ab8539a
commit 25072e8eb4
Signed by: Legrems
GPG Key ID: D4620E6DF3E0121D
3 changed files with 61 additions and 24 deletions

View File

@ -61,8 +61,14 @@ def results(request: HttpRequest) -> dict:
@router.get("top-submissions", response=TournamentSubmissionsOut) @router.get("top-submissions", response=TournamentSubmissionsOut)
def top_submissions(request: HttpRequest, limit: int = 5) -> TournamentSubmissionsOut: def top_submissions(request: HttpRequest, limit: int = 5) -> dict:
"""Get tournament top submissions for each puzzle. Only available when tournament is closed.""" """Get tournament top submissions for each puzzle. Only available when tournament is closed."""
cache_key = f"api:results:top_submissions:{limit}"
cached_data = cache.get(cache_key)
if cached_data is not None:
return cached_data
collection = get_object_or_404(SteamCollection, is_active=True) collection = get_object_or_404(SteamCollection, is_active=True)
# Only allow access when tournament is closed # Only allow access when tournament is closed
@ -70,15 +76,16 @@ def top_submissions(request: HttpRequest, limit: int = 5) -> TournamentSubmissio
raise HttpError(403, "Tournament is still accepting submissions") raise HttpError(403, "Tournament is still accepting submissions")
# Get all puzzles # Get all puzzles
puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by("order_index") puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by(
"order_index"
)
# Build response # Build response
submissions_list = [] submissions_list = []
for puzzle in puzzles: for puzzle in puzzles:
# Get the top N responses for this puzzle (ranked by points, highest first) # Get the top N responses for this puzzle (ranked by points, highest first)
top_responses = ( top_responses = (
PuzzleResponse.objects PuzzleResponse.objects.filter(puzzle=puzzle, needs_manual_validation=False)
.filter(puzzle=puzzle, needs_manual_validation=False)
.filter_user_best_response() .filter_user_best_response()
.annotate_rank_points() .annotate_rank_points()
.order_by("-rank_points")[:limit] .order_by("-rank_points")[:limit]
@ -92,23 +99,30 @@ def top_submissions(request: HttpRequest, limit: int = 5) -> TournamentSubmissio
response_files = [ response_files = [
WinnerFileOut( WinnerFileOut(
file_url=file.file_url or "", file_url=file.file_url or "",
original_filename=file.original_filename original_filename=file.original_filename,
) )
for file in files for file in files
] ]
# Calculate total coefficient # Calculate total coefficient
total_coef = None total_coef = None
if puzzle.points_factor and response.final_cost is not None and response.final_cycles is not None and response.final_area is not None: if (
puzzle.points_factor
and response.final_cost is not None
and response.final_cycles is not None
and response.final_area is not None
):
total_coef = ( total_coef = (
puzzle.points_factor.cost * response.final_cost + puzzle.points_factor.cost * response.final_cost
puzzle.points_factor.cycles * response.final_cycles + + puzzle.points_factor.cycles * response.final_cycles
puzzle.points_factor.area * response.final_area + puzzle.points_factor.area * response.final_area
) )
submission_data = WinnerResponseOut( submission_data = WinnerResponseOut(
user_id=response.submission.user.id if response.submission.user else 0, user_id=response.submission.user.id if response.submission.user else 0,
username=response.submission.user.username if response.submission.user else "Anonymous", username=response.submission.user.username
if response.submission.user
else "Anonymous",
final_cost=response.final_cost, final_cost=response.final_cost,
final_cycles=response.final_cycles, final_cycles=response.final_cycles,
final_area=response.final_area, final_area=response.final_area,
@ -126,12 +140,20 @@ def top_submissions(request: HttpRequest, limit: int = 5) -> TournamentSubmissio
) )
) )
return TournamentSubmissionsOut(submissions=submissions_list) data = {"submissions": submissions_list}
cache.set(f"api:results:top_submissions:{limit}", data, 300)
return data
@router.get("puzzle-results", response=TournamentPuzzleResultsOut) @router.get("puzzle-results", response=TournamentPuzzleResultsOut)
def puzzle_results(request: HttpRequest, limit: int = 5) -> TournamentPuzzleResultsOut: def puzzle_results(request: HttpRequest, limit: int = 5) -> dict:
"""Get tournament results organized by puzzle with coefficients. Only available when tournament is closed.""" """Get tournament results organized by puzzle with coefficients. Only available when tournament is closed."""
cache_key = f"api:results:puzzle_results:{limit}"
cached_data = cache.get(cache_key)
if cached_data is not None:
return cached_data
collection = get_object_or_404(SteamCollection, is_active=True) collection = get_object_or_404(SteamCollection, is_active=True)
# Only allow access when tournament is closed # Only allow access when tournament is closed
@ -139,15 +161,16 @@ def puzzle_results(request: HttpRequest, limit: int = 5) -> TournamentPuzzleResu
raise HttpError(403, "Tournament is still accepting submissions") raise HttpError(403, "Tournament is still accepting submissions")
# Get all puzzles # Get all puzzles
puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by("order_index") puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by(
"order_index"
)
# Build response # Build response
results_list = [] results_list = []
for puzzle in puzzles: for puzzle in puzzles:
# Get the top N responses for this puzzle (ranked by points) # Get the top N responses for this puzzle (ranked by points)
top_responses = ( top_responses = (
PuzzleResponse.objects PuzzleResponse.objects.filter(puzzle=puzzle, needs_manual_validation=False)
.filter(puzzle=puzzle, needs_manual_validation=False)
.filter_user_best_response() .filter_user_best_response()
.annotate_rank_points() .annotate_rank_points()
.order_by("-rank_points")[:limit] .order_by("-rank_points")[:limit]
@ -161,24 +184,31 @@ def puzzle_results(request: HttpRequest, limit: int = 5) -> TournamentPuzzleResu
response_files = [ response_files = [
WinnerFileOut( WinnerFileOut(
file_url=file.file_url or "", file_url=file.file_url or "",
original_filename=file.original_filename original_filename=file.original_filename,
) )
for file in files for file in files
] ]
# Calculate total coefficient # Calculate total coefficient
total_coef = None total_coef = None
if puzzle.points_factor and response.final_cost is not None and response.final_cycles is not None and response.final_area is not None: if (
puzzle.points_factor
and response.final_cost is not None
and response.final_cycles is not None
and response.final_area is not None
):
total_coef = ( total_coef = (
puzzle.points_factor.cost * response.final_cost + puzzle.points_factor.cost * response.final_cost
puzzle.points_factor.cycles * response.final_cycles + + puzzle.points_factor.cycles * response.final_cycles
puzzle.points_factor.area * response.final_area + puzzle.points_factor.area * response.final_area
) )
submission_data = PuzzleSubmissionWithRankOut( submission_data = PuzzleSubmissionWithRankOut(
rank=rank, rank=rank,
user_id=response.submission.user.id if response.submission.user else 0, user_id=response.submission.user.id if response.submission.user else 0,
username=response.submission.user.username if response.submission.user else "Anonymous", username=response.submission.user.username
if response.submission.user
else "Anonymous",
final_cost=response.final_cost, final_cost=response.final_cost,
final_cycles=response.final_cycles, final_cycles=response.final_cycles,
final_area=response.final_area, final_area=response.final_area,
@ -206,4 +236,6 @@ def puzzle_results(request: HttpRequest, limit: int = 5) -> TournamentPuzzleResu
) )
) )
return TournamentPuzzleResultsOut(results=results_list) data = {"results": results_list}
cache.set(f"api:results:puzzle_results:{limit}", data, 300)
return data

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("submissions", "0013_steamcollectionitem_points_value"), ("submissions", "0013_steamcollectionitem_points_value"),
] ]

View File

@ -3,7 +3,13 @@ from typing import List, Optional
from datetime import datetime from datetime import datetime
from uuid import UUID from uuid import UUID
from .models import Submission, PuzzleResponse, SubmissionFile, SteamCollectionItem, SteamCollection from .models import (
Submission,
PuzzleResponse,
SubmissionFile,
SteamCollectionItem,
SteamCollection,
)
# Input Schemas # Input Schemas