Skip to content

Commit 8503f9c

Browse files
committed
Freeze results fix
1 parent 8425318 commit 8503f9c

File tree

7 files changed

+190
-179
lines changed

7 files changed

+190
-179
lines changed

competition/exceptions.py

Lines changed: 0 additions & 3 deletions
This file was deleted.

competition/models.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import datetime
2-
import json
32
from typing import Optional
43

54
from django.conf import settings
@@ -18,7 +17,6 @@
1817
from base.managers import UnspecifiedValueManager
1918
from base.models import RestrictedFileField
2019
from base.validators import school_year_validator
21-
from competition.exceptions import FreezingNotClosedResults
2220
from competition.querysets import ActiveQuerySet
2321
from competition.utils.school_year_manipulation import \
2422
get_school_year_end_by_date
@@ -249,11 +247,6 @@ def get_first_series(self) -> 'Series':
249247
def get_second_series(self) -> 'Series':
250248
return self.series_set.get(order=2)
251249

252-
def freeze_results(self, results):
253-
if any(not series.complete for series in self.series_set.all()):
254-
raise FreezingNotClosedResults()
255-
self.frozen_results = json.dumps(results)
256-
257250
@property
258251
def complete(self) -> bool:
259252
return self.frozen_results is not None
@@ -363,14 +356,6 @@ def get_actual_late_flag(self) -> Optional[LateTag]:
363356
.order_by('upper_bound')\
364357
.first()
365358

366-
def freeze_results(self, results):
367-
if any(
368-
problem.num_solutions != problem.num_corrected_solutions
369-
for problem in self.problems.all()
370-
):
371-
raise FreezingNotClosedResults()
372-
self.frozen_results = json.dumps(results)
373-
374359
@property
375360
def num_problems(self) -> int:
376361
return self.problems.count()
@@ -418,7 +403,8 @@ class Meta:
418403

419404
def __str__(self):
420405
return f'{self.series.semester.competition.name}-{self.series.semester.year}' \
421-
f'-{self.series.semester.season[0]}S-S{self.series.order} - {self.order}. úloha'
406+
f'-{self.series.semester.season[0]
407+
}S-S{self.series.order} - {self.order}. úloha'
422408

423409
def get_stats(self):
424410
stats = {}
@@ -607,7 +593,7 @@ def get_registration_by_profile_and_event(profile, event):
607593
return registration
608594

609595
def __str__(self):
610-
return f'{ self.profile.user.get_full_name() } @ { self.event }'
596+
return f'{self.profile.user.get_full_name()} @ {self.event}'
611597

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

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

679665
def get_solution_file_name(self):
680666
return f'{self.semester_registration.profile.user.get_full_name_camel_case()}'\
681-
f'-{self.problem.id}-{self.semester_registration.id}.pdf'
667+
f'-{self.problem.id}-{self.semester_registration.id}.pdf'
682668

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

686672
def get_corrected_solution_file_name(self):
687673
return f'{self.semester_registration.profile.user.get_full_name_camel_case()}'\
688-
f'-{self.problem.id}-{self.semester_registration.id}_corrected.pdf'
674+
f'-{self.problem.id}-{self.semester_registration.id}_corrected.pdf'
689675

690676
def get_corrected_solution_file_path(self):
691677
return f'solutions/corrected/{self.get_corrected_solution_file_name()}'

competition/results.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
from json import loads as json_loads, dumps as json_dumps
2+
from competition.utils import sum_methods
3+
from competition.serializers import EventRegistrationReadSerializer
4+
from competition.models import Series, Semester, EventRegistration
5+
from operator import itemgetter
6+
7+
8+
class FreezingNotClosedResults(Exception):
9+
"""Snažíš sa zamraziť výsledky série, ktorá nemá opravené všetky riešenia"""
10+
11+
12+
def semester_results(self: Semester) -> dict:
13+
"""Vyrobí výsledky semestra"""
14+
if self.frozen_results is not None:
15+
return json_loads(self.frozen_results)
16+
results = []
17+
for registration in self.eventregistration_set.all():
18+
results.append(_generate_result_row(registration, self))
19+
20+
results.sort(key=itemgetter('total'), reverse=True)
21+
results = _rank_results(results)
22+
return results
23+
24+
25+
def freeze_semester_results(semester: Semester):
26+
if any(not series.complete for series in semester.series_set.all()):
27+
raise FreezingNotClosedResults()
28+
29+
semester.frozen_results = json_dumps(semester_results(semester))
30+
31+
32+
def series_results(series: Series):
33+
results = [
34+
_generate_result_row(registration, only_series=series)
35+
for registration in series.semester.eventregistration_set.all()
36+
]
37+
38+
results.sort(key=itemgetter('total'), reverse=True)
39+
40+
return _rank_results(results)
41+
42+
43+
def freeze_series_results(series: Series):
44+
if any(
45+
problem.num_solutions != problem.num_corrected_solutions
46+
for problem in series.problems.all()
47+
):
48+
raise FreezingNotClosedResults()
49+
50+
series.frozen_results = json_dumps(series.results)
51+
52+
53+
def generate_praticipant_invitations(
54+
results_with_ranking: list[dict],
55+
number_of_participants: int,
56+
number_of_substitues: int,
57+
) -> list[dict]:
58+
invited_users = []
59+
for i, result_row in enumerate(results_with_ranking):
60+
if i < number_of_participants:
61+
invited_users.append({
62+
'first_name': result_row['registration']['profile']['first_name'],
63+
'last_name': result_row['registration']['profile']['last_name'],
64+
'school': result_row['registration']['school'],
65+
'is_participant': True
66+
})
67+
elif i < number_of_participants+number_of_substitues:
68+
invited_users.append({
69+
'first_name': result_row['registration']['profile']['first_name'],
70+
'last_name': result_row['registration']['profile']['last_name'],
71+
'school': result_row['registration']['school'],
72+
'is_participant': False
73+
})
74+
return invited_users
75+
76+
77+
def _generate_result_row(
78+
semester_registration: EventRegistration,
79+
semester: Semester | None = None,
80+
only_series: Series | None = None,
81+
):
82+
"""
83+
Vygeneruje riadok výsledku pre používateľa.
84+
Ak je uvedený only_semester vygenerujú sa výsledky iba sa daný semester
85+
"""
86+
user_solutions = semester_registration.solution_set
87+
series_set = semester.series_set.order_by(
88+
'order') if semester is not None else [only_series]
89+
solutions = []
90+
subtotal = []
91+
for series in series_set:
92+
series_solutions = []
93+
solution_points = []
94+
for problem in series.problems.order_by('order'):
95+
sol = user_solutions.filter(problem=problem).first()
96+
97+
solution_points.append(sol.score or 0 if sol is not None else 0)
98+
series_solutions.append(
99+
{
100+
'points': (str(sol.score if sol.score is not None else '?')
101+
if sol is not None else '-'),
102+
'solution_pk': sol.pk if sol is not None else None,
103+
'problem_pk': problem.pk,
104+
'votes': 0 # TODO: Implement votes sol.vote
105+
}
106+
)
107+
series_sum_func = getattr(sum_methods, series.sum_method or '',
108+
sum_methods.series_simple_sum)
109+
solutions.append(series_solutions)
110+
subtotal.append(
111+
series_sum_func(solution_points, semester_registration)
112+
)
113+
return {
114+
# Poradie - horná hranica, v prípade deleného miesto(napr. 1.-3.) ide o nižšie miesto(1)
115+
'rank_start': 0,
116+
# Poradie - dolná hranica, v prípade deleného miesto(napr. 1.-3.) ide o vyššie miesto(3)
117+
'rank_end': 0,
118+
# Indikuje či sa zmenilo poradie od minulej priečky, slúži na delené miesta
119+
'rank_changed': True,
120+
# primary key riešiteľovej registrácie do semestra
121+
'registration': EventRegistrationReadSerializer(semester_registration).data,
122+
# Súčty bodov po sériách
123+
'subtotal': subtotal,
124+
# Celkový súčet za danú entitu
125+
'total': sum(subtotal),
126+
# Zoznam riešení,
127+
'solutions': solutions
128+
}
129+
130+
131+
def _rank_results(results: list[dict]) -> list[dict]:
132+
# Spodná hranica
133+
current_rank = 1
134+
n_teams = 1
135+
last_points = None
136+
for res in results:
137+
if last_points != res['total']:
138+
current_rank = n_teams
139+
last_points = res['total']
140+
res['rank_changed'] = True
141+
else:
142+
res['rank_changed'] = False
143+
res['rank_start'] = current_rank
144+
n_teams += 1
145+
146+
# Horná hranica
147+
current_rank = len(results)
148+
n_teams = len(results)
149+
last_points = None
150+
for res in reversed(results):
151+
if last_points != res['total']:
152+
current_rank = n_teams
153+
last_points = res['total']
154+
res['rank_end'] = current_rank
155+
n_teams -= 1
156+
return results

competition/serializers.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from rest_framework import serializers
44

55
from competition import models
6-
from competition.models import Event, Problem, RegistrationLink
76
from personal.serializers import ProfileShortSerializer, SchoolShortSerializer
87

98

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

7372
if registration_link is not None:
74-
registration_link = RegistrationLink.objects.create(
73+
registration_link = models.RegistrationLink.objects.create(
7574
**registration_link,
7675
)
7776

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

302-
def get_tex_header(self, obj: Problem) -> str:
301+
def get_tex_header(self, obj: models.Problem) -> str:
303302
"""Generuje tex hlavicku vzoraku do casaku"""
304303
try:
305304
corrected_by = [user.get_full_name()
@@ -309,7 +308,7 @@ def get_tex_header(self, obj: Problem) -> str:
309308
best_solutions = [user.get_full_name()
310309
for user in obj.correction.corrected_by.all()]
311310
best_solution_suffix = 'e' if len(best_solutions) > 1 else 'a'
312-
except Problem.correction.RelatedObjectDoesNotExist: # pylint: disable=no-member
311+
except models.Problem.correction.RelatedObjectDoesNotExist: # pylint: disable=no-member
313312
corrected_by = None
314313
corrected_suffix = ''
315314
best_solutions = None

competition/utils/results.py

Lines changed: 0 additions & 49 deletions
This file was deleted.

0 commit comments

Comments
 (0)