Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Freeze results fix #444

Merged
merged 2 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions competition/exceptions.py

This file was deleted.

26 changes: 6 additions & 20 deletions competition/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import datetime
import json
from typing import Optional

from django.conf import settings
Expand All @@ -18,7 +17,6 @@
from base.managers import UnspecifiedValueManager
from base.models import RestrictedFileField
from base.validators import school_year_validator
from competition.exceptions import FreezingNotClosedResults
from competition.querysets import ActiveQuerySet
from competition.utils.school_year_manipulation import \
get_school_year_end_by_date
Expand Down Expand Up @@ -249,11 +247,6 @@ def get_first_series(self) -> 'Series':
def get_second_series(self) -> 'Series':
return self.series_set.get(order=2)

def freeze_results(self, results):
if any(not series.complete for series in self.series_set.all()):
raise FreezingNotClosedResults()
self.frozen_results = json.dumps(results)

@property
def complete(self) -> bool:
return self.frozen_results is not None
Expand Down Expand Up @@ -363,14 +356,6 @@ def get_actual_late_flag(self) -> Optional[LateTag]:
.order_by('upper_bound')\
.first()

def freeze_results(self, results):
if any(
problem.num_solutions != problem.num_corrected_solutions
for problem in self.problems.all()
):
raise FreezingNotClosedResults()
self.frozen_results = json.dumps(results)

@property
def num_problems(self) -> int:
return self.problems.count()
Expand Down Expand Up @@ -418,7 +403,8 @@ class Meta:

def __str__(self):
return f'{self.series.semester.competition.name}-{self.series.semester.year}' \
f'-{self.series.semester.season[0]}S-S{self.series.order} - {self.order}. úloha'
f'-{self.series.semester.season[0]}S-S{self.series.order}'\
f' - {self.order}. úloha'

def get_stats(self):
stats = {}
Expand Down Expand Up @@ -607,7 +593,7 @@ def get_registration_by_profile_and_event(profile, event):
return registration

def __str__(self):
return f'{ self.profile.user.get_full_name() } @ { self.event }'
return f'{self.profile.user.get_full_name()} @ {self.event}'

def can_user_modify(self, user):
return self.event.can_user_modify(user)
Expand Down Expand Up @@ -674,18 +660,18 @@ class Meta:
verbose_name='internetové riešenie', default=False)

def __str__(self):
return f'Riešiteľ: { self.semester_registration } - úloha { self.problem }'
return f'Riešiteľ: {self.semester_registration} - úloha {self.problem}'

def get_solution_file_name(self):
return f'{self.semester_registration.profile.user.get_full_name_camel_case()}'\
f'-{self.problem.id}-{self.semester_registration.id}.pdf'
f'-{self.problem.id}-{self.semester_registration.id}.pdf'

def get_solution_file_path(self):
return f'solutions/user_solutions/{self.get_solution_file_name()}'

def get_corrected_solution_file_name(self):
return f'{self.semester_registration.profile.user.get_full_name_camel_case()}'\
f'-{self.problem.id}-{self.semester_registration.id}_corrected.pdf'
f'-{self.problem.id}-{self.semester_registration.id}_corrected.pdf'

def get_corrected_solution_file_path(self):
return f'solutions/corrected/{self.get_corrected_solution_file_name()}'
Expand Down
156 changes: 156 additions & 0 deletions competition/results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from json import loads as json_loads, dumps as json_dumps
from competition.utils import sum_methods
from competition.serializers import EventRegistrationReadSerializer
from competition.models import Series, Semester, EventRegistration
from operator import itemgetter


class FreezingNotClosedResults(Exception):
"""Snažíš sa zamraziť výsledky série, ktorá nemá opravené všetky riešenia"""


def semester_results(self: Semester) -> dict:
"""Vyrobí výsledky semestra"""
if self.frozen_results is not None:
return json_loads(self.frozen_results)
results = []
for registration in self.eventregistration_set.all():
results.append(_generate_result_row(registration, self))

results.sort(key=itemgetter('total'), reverse=True)
results = _rank_results(results)
return results


def freeze_semester_results(semester: Semester):
if any(not series.complete for series in semester.series_set.all()):
raise FreezingNotClosedResults()

semester.frozen_results = json_dumps(semester_results(semester))


def series_results(series: Series):
results = [
_generate_result_row(registration, only_series=series)
for registration in series.semester.eventregistration_set.all()
]

results.sort(key=itemgetter('total'), reverse=True)

return _rank_results(results)


def freeze_series_results(series: Series):
if any(
problem.num_solutions != problem.num_corrected_solutions
for problem in series.problems.all()
):
raise FreezingNotClosedResults()

series.frozen_results = json_dumps(series.results)


def generate_praticipant_invitations(
results_with_ranking: list[dict],
number_of_participants: int,
number_of_substitues: int,
) -> list[dict]:
invited_users = []
for i, result_row in enumerate(results_with_ranking):
if i < number_of_participants:
invited_users.append({
'first_name': result_row['registration']['profile']['first_name'],
'last_name': result_row['registration']['profile']['last_name'],
'school': result_row['registration']['school'],
'is_participant': True
})
elif i < number_of_participants+number_of_substitues:
invited_users.append({
'first_name': result_row['registration']['profile']['first_name'],
'last_name': result_row['registration']['profile']['last_name'],
'school': result_row['registration']['school'],
'is_participant': False
})
return invited_users


def _generate_result_row(
semester_registration: EventRegistration,
semester: Semester | None = None,
only_series: Series | None = None,
):
"""
Vygeneruje riadok výsledku pre používateľa.
Ak je uvedený only_semester vygenerujú sa výsledky iba sa daný semester
"""
user_solutions = semester_registration.solution_set
series_set = semester.series_set.order_by(
'order') if semester is not None else [only_series]
solutions = []
subtotal = []
for series in series_set:
series_solutions = []
solution_points = []
for problem in series.problems.order_by('order'):
sol = user_solutions.filter(problem=problem).first()

solution_points.append(sol.score or 0 if sol is not None else 0)
series_solutions.append(
{
'points': (str(sol.score if sol.score is not None else '?')
if sol is not None else '-'),
'solution_pk': sol.pk if sol is not None else None,
'problem_pk': problem.pk,
'votes': 0 # TODO: Implement votes sol.vote
}
)
series_sum_func = getattr(sum_methods, series.sum_method or '',
sum_methods.series_simple_sum)
solutions.append(series_solutions)
subtotal.append(
series_sum_func(solution_points, semester_registration)
)
return {
# Poradie - horná hranica, v prípade deleného miesto(napr. 1.-3.) ide o nižšie miesto(1)
'rank_start': 0,
# Poradie - dolná hranica, v prípade deleného miesto(napr. 1.-3.) ide o vyššie miesto(3)
'rank_end': 0,
# Indikuje či sa zmenilo poradie od minulej priečky, slúži na delené miesta
'rank_changed': True,
# primary key riešiteľovej registrácie do semestra
'registration': EventRegistrationReadSerializer(semester_registration).data,
# Súčty bodov po sériách
'subtotal': subtotal,
# Celkový súčet za danú entitu
'total': sum(subtotal),
# Zoznam riešení,
'solutions': solutions
}


def _rank_results(results: list[dict]) -> list[dict]:
# Spodná hranica
current_rank = 1
n_teams = 1
last_points = None
for res in results:
if last_points != res['total']:
current_rank = n_teams
last_points = res['total']
res['rank_changed'] = True
else:
res['rank_changed'] = False
res['rank_start'] = current_rank
n_teams += 1

# Horná hranica
current_rank = len(results)
n_teams = len(results)
last_points = None
for res in reversed(results):
if last_points != res['total']:
current_rank = n_teams
last_points = res['total']
res['rank_end'] = current_rank
n_teams -= 1
return results
9 changes: 4 additions & 5 deletions competition/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from rest_framework import serializers

from competition import models
from competition.models import Event, Problem, RegistrationLink
from personal.serializers import ProfileShortSerializer, SchoolShortSerializer


Expand Down Expand Up @@ -71,11 +70,11 @@ def create(self, validated_data):
registration_link = validated_data.pop('registration_link', None)

if registration_link is not None:
registration_link = RegistrationLink.objects.create(
registration_link = models.RegistrationLink.objects.create(
**registration_link,
)

return Event.objects.create(
return models.Event.objects.create(
registration_link=registration_link,
**validated_data,
)
Expand Down Expand Up @@ -299,7 +298,7 @@ def format_list_of_names(self, names: list[str]) -> str:
def format_histogram(self, histogram: list[dict[str, int]]) -> str:
return ''.join([f'({item["score"]},{item["count"]})' for item in histogram])

def get_tex_header(self, obj: Problem) -> str:
def get_tex_header(self, obj: models.Problem) -> str:
"""Generuje tex hlavicku vzoraku do casaku"""
try:
corrected_by = [user.get_full_name()
Expand All @@ -309,7 +308,7 @@ def get_tex_header(self, obj: Problem) -> str:
best_solutions = [user.get_full_name()
for user in obj.correction.corrected_by.all()]
best_solution_suffix = 'e' if len(best_solutions) > 1 else 'a'
except Problem.correction.RelatedObjectDoesNotExist: # pylint: disable=no-member
except models.Problem.correction.RelatedObjectDoesNotExist: # pylint: disable=no-member
corrected_by = None
corrected_suffix = ''
best_solutions = None
Expand Down
49 changes: 0 additions & 49 deletions competition/utils/results.py

This file was deleted.

Loading