refactor score

This commit is contained in:
2026-03-30 05:56:47 -06:00
parent d05c05757d
commit 4cb37b0c95
5 changed files with 87 additions and 6 deletions

View File

@@ -86,6 +86,8 @@ def list_submissions(request, filters: SubmissionFilter = Query(...)):
submissions = submissions.filter(flag__isnull=False) submissions = submissions.filter(flag__isnull=False)
else: else:
submissions = submissions.filter(flag=filters.flag) submissions = submissions.filter(flag=filters.flag)
if filters.zone:
submissions = submissions.filter(zone=filters.zone)
if filters.score_lt_threshold is not None: if filters.score_lt_threshold is not None:
submissions = submissions.filter(score__lt=filters.score_lt_threshold) submissions = submissions.filter(score__lt=filters.score_lt_threshold)

View File

@@ -0,0 +1,27 @@
from django.core.management.base import BaseCommand
from submission.models import Submission
class Command(BaseCommand):
help = "Recalculate score, raw_score, and zone for all rated submissions"
def add_arguments(self, parser):
parser.add_argument("--dry-run", action="store_true", help="Show what would change without saving")
def handle(self, *args, **options):
dry_run = options["dry_run"]
qs = Submission.objects.filter(ratings__isnull=False).distinct()
total = qs.count()
self.stdout.write(f"Found {total} rated submission(s).")
if dry_run:
self.stdout.write("Dry run — no changes made.")
return
for i, s in enumerate(qs, 1):
old_score = s.score
s.update_score()
self.stdout.write(f"[{i}/{total}] {s.user.username}/{s.task.title}: {old_score:.3f}{s.score:.3f} zone={s.zone}")
self.stdout.write(self.style.SUCCESS("Done."))

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-03-30 11:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('submission', '0007_remove_nominated'),
]
operations = [
migrations.AddField(
model_name='submission',
name='zone',
field=models.CharField(blank=True, choices=[('featured', '精选'), ('low', '待改进'), ('pending', '待评')], db_index=True, default=None, max_length=10, null=True, verbose_name='分区'),
),
]

View File

@@ -19,6 +19,12 @@ class FlagChoices(models.TextChoices):
YELLOW = "yellow", "需要改进" YELLOW = "yellow", "需要改进"
class ZoneChoices(models.TextChoices):
FEATURED = "featured", "精选"
LOW = "low", "待改进"
PENDING = "pending", "待评"
class Submission(TimeStampedModel): class Submission(TimeStampedModel):
id = models.UUIDField( id = models.UUIDField(
primary_key=True, primary_key=True,
@@ -49,6 +55,15 @@ class Submission(TimeStampedModel):
verbose_name="标记", verbose_name="标记",
) )
raw_score = models.FloatField(default=0.0, verbose_name="原始加权分") raw_score = models.FloatField(default=0.0, verbose_name="原始加权分")
zone = models.CharField(
max_length=10,
choices=ZoneChoices.choices,
null=True,
blank=True,
default=None,
db_index=True,
verbose_name="分区",
)
class Meta: class Meta:
ordering = ("-created",) ordering = ("-created",)
@@ -62,6 +77,19 @@ class Submission(TimeStampedModel):
""" """
return self.task.task_type return self.task.task_type
def _update_zone(self, n: int):
if n < 5:
new_zone = ZoneChoices.PENDING
elif self.score >= 4.0:
new_zone = ZoneChoices.FEATURED
elif self.score < 3.0:
new_zone = ZoneChoices.LOW
else:
new_zone = None
if self.zone != new_zone:
self.zone = new_zone
self.save(update_fields=["zone"])
def update_score(self): def update_score(self):
ratings = list(self.ratings.select_related("user").all()) ratings = list(self.ratings.select_related("user").all())
n = len(ratings) n = len(ratings)
@@ -70,15 +98,18 @@ class Submission(TimeStampedModel):
self.raw_score = 0.0 self.raw_score = 0.0
self.score = 0.0 self.score = 0.0
self.save(update_fields=["raw_score", "score"]) self.save(update_fields=["raw_score", "score"])
self._update_zone(n)
return return
weighted_sum = sum( role_weights = [
r.score * (0.5 if r.user.role == RoleChoices.SUPER 0.5 if r.user.role == RoleChoices.SUPER
else 0.3 if r.user.role == RoleChoices.ADMIN else 0.3 if r.user.role == RoleChoices.ADMIN
else 0.2) else 0.2
for r in ratings for r in ratings
) ]
self.raw_score = weighted_sum / n weighted_sum = sum(r.score * w for r, w in zip(ratings, role_weights))
weight_total = sum(role_weights)
self.raw_score = weighted_sum / weight_total
C = 3 C = 3
global_mean = ( global_mean = (
@@ -89,6 +120,7 @@ class Submission(TimeStampedModel):
self.score = (C * global_mean + n * self.raw_score) / (C + n) self.score = (C * global_mean + n * self.raw_score) / (C + n)
self.save(update_fields=["raw_score", "score"]) self.save(update_fields=["raw_score", "score"])
self._update_zone(n)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super().save(*args, **kwargs) super().save(*args, **kwargs)

View File

@@ -26,6 +26,7 @@ class SubmissionOut(Schema):
js: Optional[str] = None js: Optional[str] = None
conversation_id: Optional[UUID] = None conversation_id: Optional[UUID] = None
flag: Optional[str] = None flag: Optional[str] = None
zone: Optional[str] = None
submit_count: int = 0 submit_count: int = 0
created: str created: str
modified: str modified: str
@@ -107,6 +108,7 @@ class SubmissionFilter(Schema):
username: Optional[str] = None username: Optional[str] = None
user_id: Optional[int] = None user_id: Optional[int] = None
flag: Optional[Literal["red", "blue", "green", "yellow", "any"]] = None flag: Optional[Literal["red", "blue", "green", "yellow", "any"]] = None
zone: Optional[Literal["featured", "low", "pending"]] = None
score_min: Optional[float] = None score_min: Optional[float] = None
score_max_exclusive: Optional[float] = None score_max_exclusive: Optional[float] = None
score_lt_threshold: Optional[float] = None score_lt_threshold: Optional[float] = None