Files
thrillwiki_django_no_react/backend/apps/api/v1/serializers_rankings.py
pacnpal 540f40e689 Revert "update"
This reverts commit 75cc618c2b.
2025-09-21 20:11:00 -04:00

269 lines
8.3 KiB
Python

"""
API serializers for the ride ranking system.
"""
from rest_framework import serializers
from drf_spectacular.utils import (
extend_schema_serializer,
extend_schema_field,
OpenApiExample,
)
from apps.rides.models import RideRanking, RankingSnapshot
@extend_schema_serializer(
examples=[
OpenApiExample(
"Ride Ranking Example",
summary="Example ranking response",
description="A ride ranking with all metrics",
value={
"id": 1,
"rank": 1,
"ride": {
"id": 123,
"name": "Steel Vengeance",
"slug": "steel-vengeance",
"park": {"id": 45, "name": "Cedar Point", "slug": "cedar-point"},
"category": "RC",
},
"wins": 523,
"losses": 87,
"ties": 45,
"winning_percentage": 0.8234,
"mutual_riders_count": 1250,
"comparison_count": 655,
"average_rating": 9.2,
"last_calculated": "2024-01-15T02:00:00Z",
"rank_change": 2,
"previous_rank": 3,
},
)
]
)
class RideRankingSerializer(serializers.ModelSerializer):
"""Serializer for ride rankings."""
ride = serializers.SerializerMethodField()
rank_change = serializers.SerializerMethodField()
previous_rank = serializers.SerializerMethodField()
class Meta:
model = RideRanking
fields = [
"id",
"rank",
"ride",
"wins",
"losses",
"ties",
"winning_percentage",
"mutual_riders_count",
"comparison_count",
"average_rating",
"last_calculated",
"rank_change",
"previous_rank",
]
@extend_schema_field(serializers.DictField())
def get_ride(self, obj):
"""Get ride details."""
return {
"id": obj.ride.id,
"name": obj.ride.name,
"slug": obj.ride.slug,
"park": {
"id": obj.ride.park.id,
"name": obj.ride.park.name,
"slug": obj.ride.park.slug,
},
"category": obj.ride.category,
}
@extend_schema_field(serializers.IntegerField(allow_null=True))
def get_rank_change(self, obj):
"""Calculate rank change from previous snapshot."""
from apps.rides.models import RankingSnapshot
latest_snapshots = RankingSnapshot.objects.filter(ride=obj.ride).order_by(
"-snapshot_date"
)[:2]
if len(latest_snapshots) >= 2:
return latest_snapshots[0].rank - latest_snapshots[1].rank
return None
@extend_schema_field(serializers.IntegerField(allow_null=True))
def get_previous_rank(self, obj):
"""Get previous rank."""
from apps.rides.models import RankingSnapshot
latest_snapshots = RankingSnapshot.objects.filter(ride=obj.ride).order_by(
"-snapshot_date"
)[:2]
if len(latest_snapshots) >= 2:
return latest_snapshots[1].rank
return None
class RideRankingDetailSerializer(serializers.ModelSerializer):
"""Detailed serializer for a specific ride's ranking."""
ride = serializers.SerializerMethodField()
head_to_head_comparisons = serializers.SerializerMethodField()
ranking_history = serializers.SerializerMethodField()
class Meta:
model = RideRanking
fields = [
"id",
"rank",
"ride",
"wins",
"losses",
"ties",
"winning_percentage",
"mutual_riders_count",
"comparison_count",
"average_rating",
"last_calculated",
"calculation_version",
"head_to_head_comparisons",
"ranking_history",
]
@extend_schema_field(serializers.DictField())
def get_ride(self, obj):
"""Get detailed ride information."""
ride = obj.ride
return {
"id": ride.id,
"name": ride.name,
"slug": ride.slug,
"description": ride.description,
"park": {
"id": ride.park.id,
"name": ride.park.name,
"slug": ride.park.slug,
"location": {
"city": (
ride.park.location.city
if hasattr(ride.park, "location")
else None
),
"state": (
ride.park.location.state
if hasattr(ride.park, "location")
else None
),
"country": (
ride.park.location.country
if hasattr(ride.park, "location")
else None
),
},
},
"category": ride.category,
"manufacturer": (
{"id": ride.manufacturer.id, "name": ride.manufacturer.name}
if ride.manufacturer
else None
),
"opening_date": ride.opening_date,
"status": ride.status,
}
@extend_schema_field(serializers.ListField(child=serializers.DictField()))
def get_head_to_head_comparisons(self, obj):
"""Get top head-to-head comparisons."""
from django.db.models import Q
from apps.rides.models import RidePairComparison
comparisons = (
RidePairComparison.objects.filter(Q(ride_a=obj.ride) | Q(ride_b=obj.ride))
.select_related("ride_a", "ride_b")
.order_by("-mutual_riders_count")[:10]
)
results = []
for comp in comparisons:
if comp.ride_a == obj.ride:
opponent = comp.ride_b
wins = comp.ride_a_wins
losses = comp.ride_b_wins
else:
opponent = comp.ride_a
wins = comp.ride_b_wins
losses = comp.ride_a_wins
result = "win" if wins > losses else "loss" if losses > wins else "tie"
results.append(
{
"opponent": {
"id": opponent.id,
"name": opponent.name,
"slug": opponent.slug,
"park": opponent.park.name,
},
"wins": wins,
"losses": losses,
"ties": comp.ties,
"result": result,
"mutual_riders": comp.mutual_riders_count,
}
)
return results
@extend_schema_field(serializers.ListField(child=serializers.DictField()))
def get_ranking_history(self, obj):
"""Get recent ranking history."""
from apps.rides.models import RankingSnapshot
history = RankingSnapshot.objects.filter(ride=obj.ride).order_by(
"-snapshot_date"
)[:30]
return [
{
"date": snapshot.snapshot_date,
"rank": snapshot.rank,
"winning_percentage": float(snapshot.winning_percentage),
}
for snapshot in history
]
class RankingSnapshotSerializer(serializers.ModelSerializer):
"""Serializer for ranking history snapshots."""
ride_name = serializers.CharField(source="ride.name", read_only=True)
park_name = serializers.CharField(source="ride.park.name", read_only=True)
class Meta:
model = RankingSnapshot
fields = [
"id",
"ride",
"ride_name",
"park_name",
"rank",
"winning_percentage",
"snapshot_date",
]
class RankingStatsSerializer(serializers.Serializer):
"""Serializer for ranking system statistics."""
total_ranked_rides = serializers.IntegerField()
total_comparisons = serializers.IntegerField()
last_calculation_time = serializers.DateTimeField()
calculation_duration = serializers.FloatField()
top_rated_ride = serializers.DictField()
most_compared_ride = serializers.DictField()
biggest_rank_change = serializers.DictField()