Skip to content
Snippets Groups Projects

Vylepšení výsledkové listiny

Merged Martin Mareš requested to merge mj/vysledky-p into devel
1 unresolved thread
1 file
+ 9
8
Compare changes
  • Side-by-side
  • Inline
+ 44
26
@@ -3,10 +3,11 @@ from sqlalchemy import and_
from sqlalchemy.orm import joinedload
from sqlalchemy.orm.query import Query
from sqlalchemy.sql.expression import select
from typing import Any, List, Tuple, Optional, Dict
from typing import Any, List, Tuple, Optional, Dict, Union
import mo.db as db
from mo.util import normalize_grade
from mo.util_format import inflect_with_number
class ScoreOrder:
@@ -36,15 +37,15 @@ class ScoreResult:
# vzestupně (pro sestupné třídění podle čísla je potřeba klíč vynásobit -1)
_order_key: List[Any]
_null_score_order = ScoreOrder(0)
def __init__(self, user: db.User, pant: db.Participant, pion: db.Participation):
self.user = user
self.pant = pant
self.pion = pion
self._sols = {}
self.order = 1
self.place_span = 1
self.place_continuation = False
self.order = ScoreResult._null_score_order
self.winner = False
self.successful = False
self._order_key = []
@@ -75,14 +76,19 @@ class ScoreTask:
def get_difficulty(self) -> Fraction:
if self.num_solutions == 0:
return 0
return Fraction(0)
return Fraction(self.sum_points, self.num_solutions)
def get_difficulty_str(self) -> str:
return f'{self.sum_points}/{self.num_solutions}'
class Score:
round: db.Round
contest: Optional[db.Contest]
part_states: List[db.PartState]
want_successful: bool
want_winners: bool
# Řádky výsledkovky
_results: Dict[int, ScoreResult]
@@ -104,10 +110,13 @@ class Score:
self.round = round
self.contest = contest
self.part_states = part_states
self.want_successful = round.score_successful_limit is not None
self.want_winners = round.score_winner_limit is not None
# Příprava subquery na účastníky (contest_subq obsahuje master_contest_id)
sess = db.get_session()
if contest:
assert contest.master_contest_id is not None
contest_subq = [contest.master_contest_id]
else:
contest_subq = sess.query(db.Contest.master_contest_id).filter_by(round=round)
@@ -120,8 +129,7 @@ class Score:
.join(db.Participant, and_(
db.Participant.user_id == db.Participation.user_id,
db.Participant.year == round.year
)
).filter(
)).filter(
db.User.is_test == False,
db.Participation.state.in_(part_states),
db.Participation.contest_id.in_(contest_subq)
@@ -141,7 +149,19 @@ class Score:
self._load_tasks_and_sols(0, round, contest_subq)
self._mark_winners()
def _load_tasks_and_sols(self, step: int, round: db.Round, contest_subq: Query):
# Vynecháme účastníky, kteří nic neodevzdali
to_remove = []
for user_id, results in self._results.items():
if not results._sols[0]:
to_remove.append(user_id)
if to_remove:
self._add_message('info',
inflect_with_number(len(to_remove), 'Vynechán %s soutěžící', 'Vynecháni %s soutěžící', 'Vynecháno %s soutěžících')
+ ' bez odevzdaných řešení.')
for user_id in to_remove:
self._results.pop(user_id)
def _load_tasks_and_sols(self, step: int, round: db.Round, contest_subq: Union[Query, List[int]]):
"""Obecná funkce na načtení úloh a řešení tohoto nebo předchozího kola"""
if step in self._tasks:
return
@@ -186,14 +206,8 @@ class Score:
def _mark_winners(self):
for result in self._results.values():
total_points = result.get_total_points()
result.winner = (
self.round.score_winner_limit is not None
and total_points >= self.round.score_winner_limit
)
result.successful = (
self.round.score_successful_limit is not None
and total_points >= self.round.score_successful_limit
)
result.winner = self.want_winners and total_points >= self.round.score_winner_limit
result.successful = self.want_successful and total_points >= self.round.score_successful_limit
def _load_prev_round(self, step: int) -> bool:
"""Načtení úloh a řešení předchozího kola, pokud takové existuje."""
@@ -253,7 +267,7 @@ class Score:
results: List[ScoreResult] = sorted(self._results.values(), key=lambda result: (
result._order_key, result.user.last_name, result.user.first_name, result.user.user_id
))
last: ScoreResult = None
last: Optional[ScoreResult] = None
# Spočítáme pořadí - v prvním kroku prolinkujeme opakující se ScoreOrder na první,
# ve druhém kroku je pak správně rozkopírujeme s nastaveným continuation na True
for result in results:
@@ -266,7 +280,7 @@ class Score:
else:
result.order = ScoreOrder(last.order.place + last.order.span)
last = result
lastOrder: ScoreOrder = None
lastOrder: Optional[ScoreOrder] = None
for result in results:
if result.order == lastOrder:
result.order = ScoreOrder(lastOrder.place, lastOrder.span, True)
@@ -316,7 +330,7 @@ class Score:
f"Úlohy {last_task.task.code} a {task.task.code} mají stejnou vypočtenou obtížnost"
+ f" {difficulty}, pro výpočet obtížnosti je řadím podle kódu úlohy"
)
difficulty_report.append(f"{task.task.code} ({round(float(difficulty), 2)} b)")
difficulty_report.append(f"{task.task.code} ({task.get_difficulty_str()}={float(difficulty):.2f})")
last_task, last_difficulty = task, difficulty
self._add_message(
@@ -337,8 +351,12 @@ class Score:
points_from_max = list(sorted(sol_points.values()))
points_from_difficult = [sol_points[task_id] for task_id in tasks_by_difficulty]
if result.successful or not self.want_successful:
# Primárně podle počtu získaných bodů, sekundárně podle bodů od maxima, terciárně podle bodů od nejobtížnější
result._order_key.extend((total_points, points_from_max, points_from_difficult))
else:
# Neúspěšné řešitele třídíme podle počtu získaných bodů, sekundárně podle jména
result._order_key.extend((total_points, result.user.name_sort_key()))
# Otestujeme, jestli teď existují sdílená místa
if not self._exists_same_order_key():
@@ -373,12 +391,12 @@ class Score:
winners += 1
if result.successful:
successfulls += 1
if successfulls > participants/2:
if successfulls > participants // 2:
self._add_message(
"error",
f"Počet úspěšných řešitelů ({successfulls}) převyšuje polovinu celkového počtu účastníků ({participants})"
)
if winners > successfulls/2:
if winners > successfulls // 2:
self._add_message(
"error",
f"Počet vítězů ({winners}) převyšuje polovinu počtu úspěšných řešitelů ({successfulls})"
Loading