From 8b1b807ef35ac0143babddefa22f869eae9060ff Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 14 Feb 2024 18:58:16 +0100 Subject: [PATCH] feat: compute scores from ratings via management command --- pretalx_musicrate/__init__.py | 2 +- pretalx_musicrate/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/computescores.py | 65 +++++++++++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 pretalx_musicrate/management/__init__.py create mode 100644 pretalx_musicrate/management/commands/__init__.py create mode 100644 pretalx_musicrate/management/commands/computescores.py diff --git a/pretalx_musicrate/__init__.py b/pretalx_musicrate/__init__.py index 7e0dc0e..9e78220 100644 --- a/pretalx_musicrate/__init__.py +++ b/pretalx_musicrate/__init__.py @@ -1 +1 @@ -__version__ = "0.13.1" +__version__ = "0.14.0" diff --git a/pretalx_musicrate/management/__init__.py b/pretalx_musicrate/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pretalx_musicrate/management/commands/__init__.py b/pretalx_musicrate/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pretalx_musicrate/management/commands/computescores.py b/pretalx_musicrate/management/commands/computescores.py new file mode 100644 index 0000000..56c74e9 --- /dev/null +++ b/pretalx_musicrate/management/commands/computescores.py @@ -0,0 +1,65 @@ +import math +import statistics +from operator import itemgetter + +from django.core.management.base import BaseCommand, CommandError +from django_scopes import scope +from pretalx.event.models import Event +from pretalx.submission.models import Submission + +from ...models import Rating, Score + +RATINGS_MIN = 30 +SCALE = list(map(float, map(itemgetter(0), Rating.RATING_CHOICES))) + +MEAN = statistics.mean(SCALE) +STD = math.sqrt(statistics.mean([(i - MEAN) ** 2 for i in SCALE])) + + +class Command(BaseCommand): + help = "Compute submission scores from ratings" + + def add_arguments(self, parser): + parser.add_argument("event") + + def handle(self, *args, **kwargs): + try: + event = Event.objects.get(slug=kwargs["event"]) + except Event.DoesNotExist: + raise CommandError(f"no event found with slug '{kwargs['event']}'") + + with scope(event=event): + submissions = {} + + for juror in event.jurors.prefetch_related("ratings"): + ratings = { + r.submission.code: int(r.rating) + for r in juror.ratings.exclude(rating="") + } + values = list(ratings.values()) + + if len(values) < RATINGS_MIN: + mean = MEAN + std = STD + else: + mean = sum(values) / len(values) + std = math.sqrt( + sum([(i - mean) ** 2 for i in values]) / len(values) + ) + + for code in ratings: + if code not in submissions: + submissions[code] = [] + submissions[code].append((ratings[code] - mean) / std * STD + MEAN) + + for submission in Submission.objects.prefetch_related("score").filter( + code__in=submissions.keys() + ): + try: + score = submission.score + except Submission.score.RelatedObjectDoesNotExist: + score = Score(submission=submission) + + ratings = submissions[submission.code] + score.value = sum(ratings) / len(ratings) + score.save()