refactor score
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
27
submission/management/commands/recalculate_scores.py
Normal file
27
submission/management/commands/recalculate_scores.py
Normal 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."))
|
||||||
18
submission/migrations/0008_add_zone.py
Normal file
18
submission/migrations/0008_add_zone.py
Normal 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='分区'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user