mutiple fixes
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.utils import timezone
|
||||
from .models import (
|
||||
from submissions.models import (
|
||||
SteamAPIKey,
|
||||
SteamCollection,
|
||||
SteamCollectionItem,
|
||||
@@ -148,6 +148,7 @@ class SteamCollectionItemAdmin(admin.ModelAdmin):
|
||||
("Author Information", {"fields": ("author_name", "author_steam_id")}),
|
||||
("Metadata", {"fields": ("tags",)}),
|
||||
("Timestamps", {"fields": ("created_at", "updated_at")}),
|
||||
("Points factor", {"fields": ("points_factor", "points_value")}),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -112,6 +112,14 @@ def create_submission(
|
||||
ocr_confidence_cost=response_data.ocr_confidence_cost,
|
||||
ocr_confidence_cycles=response_data.ocr_confidence_cycles,
|
||||
ocr_confidence_area=response_data.ocr_confidence_area,
|
||||
**{
|
||||
# Put validated if not manual validation is needed
|
||||
"validated_cost": response_data.cost,
|
||||
"validated_cycles": response_data.cycles,
|
||||
"validated_area": response_data.area,
|
||||
}
|
||||
if not data.manual_validation_requested
|
||||
else {},
|
||||
)
|
||||
|
||||
# Process files for this response
|
||||
@@ -197,7 +205,7 @@ def validate_response(request, response_id: int, data: ValidationIn):
|
||||
|
||||
|
||||
@router.put("/responses/{response_id}/validate/auto", response=PuzzleResponseOut)
|
||||
def validate_response(request, response_id: int):
|
||||
def validate_auto(request, response_id: int):
|
||||
"""Try to auto validate a puzzle response"""
|
||||
|
||||
if not request.user.is_authenticated or not request.user.is_staff:
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-23 22:33
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('animations', '0001_initial'),
|
||||
('submissions', '0008_alter_puzzleresponse_unique_together'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='steamcollectionitem',
|
||||
name='points_factor',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='animations.puzzlepointsfactor'),
|
||||
),
|
||||
]
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-23 23:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('submissions', '0009_steamcollectionitem_points_factor'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='puzzleresponse',
|
||||
name='validated_area',
|
||||
field=models.IntegerField(blank=True, help_text='Manually validated area value'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='puzzleresponse',
|
||||
name='validated_cost',
|
||||
field=models.IntegerField(blank=True, help_text='Manually validated cost value'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='puzzleresponse',
|
||||
name='validated_cycles',
|
||||
field=models.IntegerField(blank=True, help_text='Manually validated cycles value'),
|
||||
),
|
||||
]
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-23 23:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('submissions', '0010_alter_puzzleresponse_validated_area_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='puzzleresponse',
|
||||
name='area',
|
||||
field=models.IntegerField(blank=True, help_text='Area value from OCR'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='puzzleresponse',
|
||||
name='cost',
|
||||
field=models.IntegerField(blank=True, help_text='Cost value from OCR'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='puzzleresponse',
|
||||
name='cycles',
|
||||
field=models.IntegerField(blank=True, help_text='Cycles value from OCR'),
|
||||
),
|
||||
]
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-23 23:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('submissions', '0011_alter_puzzleresponse_area_alter_puzzleresponse_cost_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='puzzleresponse',
|
||||
name='validated_area',
|
||||
field=models.IntegerField(blank=True, help_text='Manually validated area value', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='puzzleresponse',
|
||||
name='validated_cost',
|
||||
field=models.IntegerField(blank=True, help_text='Manually validated cost value', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='puzzleresponse',
|
||||
name='validated_cycles',
|
||||
field=models.IntegerField(blank=True, help_text='Manually validated cycles value', null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-24 01:00
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('animations', '0002_puzzlepointsvalue'),
|
||||
('submissions', '0012_alter_puzzleresponse_validated_area_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='steamcollectionitem',
|
||||
name='points_value',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='animations.puzzlepointsvalue'),
|
||||
),
|
||||
]
|
||||
@@ -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}"
|
||||
|
||||
@@ -20,9 +20,9 @@ class PuzzleResponseIn(Schema):
|
||||
|
||||
puzzle_id: int
|
||||
puzzle_name: str
|
||||
cost: Optional[str] = None
|
||||
cycles: Optional[str] = None
|
||||
area: Optional[str] = None
|
||||
cost: Optional[int] = None
|
||||
cycles: Optional[int] = None
|
||||
area: Optional[int] = None
|
||||
needs_manual_validation: bool = False
|
||||
ocr_confidence_cost: Optional[float] = None
|
||||
ocr_confidence_cycles: Optional[float] = None
|
||||
@@ -61,9 +61,9 @@ class PuzzleResponseOut(ModelSchema):
|
||||
"""Schema for puzzle response output"""
|
||||
|
||||
files: List[SubmissionFileOut]
|
||||
final_cost: Optional[str]
|
||||
final_cycles: Optional[str]
|
||||
final_area: Optional[str]
|
||||
final_cost: Optional[int]
|
||||
final_cycles: Optional[int]
|
||||
final_area: Optional[int]
|
||||
|
||||
class Meta:
|
||||
model = PuzzleResponse
|
||||
@@ -126,9 +126,9 @@ class ValidationIn(Schema):
|
||||
"""Schema for manual validation input"""
|
||||
|
||||
puzzle: Optional[int] = None
|
||||
validated_cost: Optional[str] = None
|
||||
validated_cycles: Optional[str] = None
|
||||
validated_area: Optional[str] = None
|
||||
validated_cost: Optional[int] = None
|
||||
validated_cycles: Optional[int] = None
|
||||
validated_area: Optional[int] = None
|
||||
|
||||
|
||||
# Collection Schemas
|
||||
|
||||
@@ -4,7 +4,7 @@ Utilities for fetching Steam Workshop collection data using Steam Web API
|
||||
|
||||
import re
|
||||
import requests
|
||||
from submissions.models import SteamCollection, SubmissionFile
|
||||
from submissions.models import SteamCollection, SteamCollectionItem, SubmissionFile
|
||||
from datetime import datetime
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
@@ -485,7 +485,7 @@ def verify_ocr_data_for_file(file: str) -> tuple[str, int, int, int]:
|
||||
# cv2.imwrite("output_debug.jpg", output)
|
||||
|
||||
os.remove("temp.jpg")
|
||||
return puzzle, cost, cycles, area
|
||||
return puzzle, int(cost), int(cycles), int(area)
|
||||
|
||||
|
||||
def verify_and_validate_ocr_date_for_submission(file: SubmissionFile):
|
||||
@@ -496,10 +496,15 @@ def verify_and_validate_ocr_date_for_submission(file: SubmissionFile):
|
||||
f"{r.submission.user}: ({r.cost: >4} {r.cycles: >4} {r.area: >4}) -> ({ocr_data[1]: >4} {ocr_data[2]: >4} {ocr_data[3]: >4})"
|
||||
)
|
||||
|
||||
if puzzle := SteamCollectionItem.objects.filter(title=ocr_data[0]).first():
|
||||
# print(f"{r.puzzle.title} -> {ocr_data[0]}")
|
||||
r.puzzle = puzzle
|
||||
|
||||
valid_count = 0
|
||||
for index, field in enumerate(["cost", "cycles", "area"]):
|
||||
value = getattr(r, field, -1)
|
||||
|
||||
# print(f"{value} -> {ocr_data[index + 1]}")
|
||||
if value == ocr_data[index + 1]:
|
||||
setattr(r, f"validated_{field}", value)
|
||||
valid_count += 1
|
||||
|
||||
Reference in New Issue
Block a user