mutiple fixes

This commit is contained in:
2025-11-28 14:05:26 +01:00
parent 9ee45463a8
commit fa76fbce92
36 changed files with 694 additions and 242 deletions
+99 -11
View File
@@ -1,11 +1,43 @@
from typing import Self
from django.db import models
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
import uuid
from django.db.models.expressions import Window
from django.db.models.functions import Cast, Rank, RowNumber
from django.db.models.query import F
User = get_user_model()
class JsonIndex(models.Func):
function = ""
template = "%(json_field)s -> (%(index)s::int)"
def __init__(self, json_field, index_expression, **extra):
super().__init__(json_field, index_expression, **extra)
self.output_field = models.IntegerField()
def resolve_expression(
self, query, allow_joins=True, reuse=None, summarize=False, for_save=False
):
# Resolve both expressions in the query context to ensure joins are set up correctly
clone = self.copy()
clone.source_expressions = [
expr.resolve_expression(query, allow_joins, reuse, summarize, for_save)
for expr in self.source_expressions
]
return clone
def as_sql(self, compiler, connection):
json_sql, json_params = compiler.compile(self.source_expressions[0])
idx_sql, idx_params = compiler.compile(self.source_expressions[1])
sql = self.template % {"json_field": json_sql, "index": idx_sql}
params = json_params + idx_params
return sql, params
class SteamAPIKey(models.Model):
"""Model to store Steam API key configuration - Admin only"""
@@ -178,6 +210,19 @@ class SteamCollectionItem(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# Puzzle points
points_factor = models.ForeignKey(
"animations.PuzzlePointsFactor",
null=True,
on_delete=models.SET_NULL,
)
points_value = models.ForeignKey(
"animations.PuzzlePointsValue",
null=True,
on_delete=models.SET_NULL,
)
class Meta:
ordering = ["collection", "order_index"]
unique_together = ["collection", "steam_item_id"]
@@ -271,6 +316,48 @@ class Submission(models.Model):
return self.responses.filter(needs_manual_validation=True).exists()
class PuzzleResponseQuerySet(models.QuerySet):
def annotate_rank_points(self) -> Self:
return (
self.annotate(
points=F("puzzle__points_factor__cost") * F("validated_cost")
+ F("puzzle__points_factor__cycles") * F("validated_cycles")
+ F("puzzle__points_factor__area") * F("validated_area")
)
.annotate(
user_response_rank=Window(
expression=RowNumber(),
partition_by=[F("puzzle"), F("submission__user")],
order_by=F("points").asc(),
)
)
# .filter(user_response_rank=1)
.annotate(
puzzle_user_rank=Window(
expression=Rank(),
partition_by=[F("puzzle")],
order_by=F("points").asc(),
)
)
.annotate(
rank_points=Cast(
JsonIndex(
F("puzzle__points_value__points"),
Cast(F("puzzle_user_rank") - 1, models.IntegerField()),
),
models.IntegerField(),
)
)
)
def filter_user_best_response(self) -> Self:
return self.annotate_rank_points().filter(user_response_rank=1)
class PuzzleResponseManager(models.Manager.from_queryset(PuzzleResponseQuerySet)):
pass
class PuzzleResponse(models.Model):
"""Model representing a response/solution for a specific puzzle"""
@@ -289,11 +376,9 @@ class PuzzleResponse(models.Model):
puzzle_name = models.CharField(
max_length=255, help_text="Puzzle name as detected by OCR"
)
cost = models.CharField(max_length=20, blank=True, help_text="Cost value from OCR")
cycles = models.CharField(
max_length=20, blank=True, help_text="Cycles value from OCR"
)
area = models.CharField(max_length=20, blank=True, help_text="Area value from OCR")
cost = models.IntegerField(blank=True, help_text="Cost value from OCR")
cycles = models.IntegerField(blank=True, help_text="Cycles value from OCR")
area = models.IntegerField(blank=True, help_text="Area value from OCR")
# Validation flags
needs_manual_validation = models.BooleanField(
@@ -311,24 +396,27 @@ class PuzzleResponse(models.Model):
)
# Manual validation overrides
validated_cost = models.CharField(
max_length=20, blank=True, help_text="Manually validated cost value"
validated_cost = models.IntegerField(
null=True, blank=True, help_text="Manually validated cost value"
)
validated_cycles = models.CharField(
max_length=20, blank=True, help_text="Manually validated cycles value"
validated_cycles = models.IntegerField(
null=True, blank=True, help_text="Manually validated cycles value"
)
validated_area = models.CharField(
max_length=20, blank=True, help_text="Manually validated area value"
validated_area = models.IntegerField(
null=True, blank=True, help_text="Manually validated area value"
)
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = PuzzleResponseManager()
class Meta:
ordering = ["submission", "puzzle__order_index"]
verbose_name = "Puzzle Response"
verbose_name_plural = "Puzzle Responses"
_base_manager_name = "objects"
def __str__(self):
return f"Response for {self.puzzle_name} in {self.submission}"