Skip to content
Snippets Groups Projects
Commit e980a029 authored by Jiří Setnička's avatar Jiří Setnička
Browse files

DB: Body mohou být desetinné, nastavení podle points_step v rounds

Přesnost v databázi je nastavena na jedno desetinné místo - numeric(5,1).
V rozhraní se nastavuje v rámci nastavení kola.

Issue #189
parent d8419df1
Branches
No related tags found
1 merge request!51Desetinné body
......@@ -107,6 +107,7 @@ CREATE TABLE rounds (
score_mode score_mode NOT NULL DEFAULT 'basic', -- mód výsledkovky
score_winner_limit int DEFAULT NULL, -- bodový limit na označení za vítěze
score_successful_limit int DEFAULT NULL, -- bodový limit na označení za úspěšného řešitele
points_step numeric(2,1) NOT NULL DEFAULT 1, -- s jakou přesností jsou přidělovány body (celé aneb 1, 0.5, 0.1)
has_messages boolean NOT NULL DEFAULT false, -- má zprávičky
UNIQUE (year, category, seq, part)
);
......@@ -162,7 +163,7 @@ CREATE TABLE tasks (
round_id int NOT NULL REFERENCES rounds(round_id),
code varchar(255) NOT NULL, -- např. "P-I-1"
name varchar(255) NOT NULL,
max_points int DEFAULT NULL, -- maximální počet bodů, pokud je nastaven, nelze zadat více bodů
max_points numeric(5,1) DEFAULT NULL, -- maximální počet bodů, pokud je nastaven, nelze zadat více bodů
UNIQUE (round_id, code)
);
......@@ -200,7 +201,7 @@ CREATE TABLE solutions (
user_id int NOT NULL REFERENCES users(user_id),
final_submit int DEFAULT NULL REFERENCES papers(paper_id), -- verze odevzdání, která se má hodnotit
final_feedback int DEFAULT NULL REFERENCES papers(paper_id), -- verze komentáře opravovatelů, kterou má vidět účastník
points int DEFAULT NULL,
points numeric(5,1) DEFAULT NULL,
note text NOT NULL DEFAULT '', -- komentář pro řešitele
org_note text NOT NULL DEFAULT '', -- komentář viditelný jen organizátorům
PRIMARY KEY (task_id, user_id)
......@@ -213,7 +214,7 @@ CREATE TABLE points_history (
points_history_id serial PRIMARY KEY,
task_id int NOT NULL REFERENCES tasks(task_id),
participant_id int NOT NULL REFERENCES users(user_id),
points int DEFAULT NULL,
points numeric(5,1) DEFAULT NULL,
points_by int NOT NULL REFERENCES users(user_id), -- kdo přidělil body
points_at timestamp with time zone NOT NULL -- a kdy
);
......
SET ROLE 'mo_osmo';
ALTER TABLE rounds
ADD COLUMN points_step numeric(2,1) NOT NULL DEFAULT 1; -- s jakou přesností jsou přidělovány body (celé aneb 1, 0.5, 0.1)
ALTER TABLE solutions
ALTER COLUMN points SET DATA TYPE numeric(5,1);
ALTER TABLE points_history
ALTER COLUMN points SET DATA TYPE numeric(5,1);
ALTER TABLE tasks
ALTER COLUMN max_points SET DATA TYPE numeric(5,1);
......@@ -15,6 +15,7 @@ from sqlalchemy.orm.attributes import get_history
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.expression import CTE
from sqlalchemy.sql.sqltypes import Numeric
from typing import Optional, List, Tuple
import mo
......@@ -186,6 +187,15 @@ round_score_mode_names = {
}
# V DB jako numeric(2,1), používá se tak snadněji, než enum
round_points_step_names = {
1: "Celé body",
0.5: "Půlbody",
0.1: "Desetinné body",
}
round_points_step_choices = round_points_step_names.items()
class Round(Base):
__tablename__ = 'rounds'
__table_args__ = (
......@@ -209,6 +219,7 @@ class Round(Base):
score_mode = Column(Enum(RoundScoreMode, name='score_mode'), nullable=False, server_default=text("'basic'::score_mode"))
score_winner_limit = Column(Integer)
score_successful_limit = Column(Integer)
points_step = Column(Numeric, nullable=False)
has_messages = Column(Boolean, nullable=False, server_default=text("false"))
master = relationship('Round', primaryjoin='Round.master_round_id == Round.round_id', remote_side='Round.round_id', post_update=True)
......@@ -246,6 +257,11 @@ class Round(Base):
else:
return self.state
def points_step_name(self) -> str:
if float(self.points_step) in round_points_step_names:
return round_points_step_names[float(self.points_step)]
return str(self.points_step)
class User(Base):
__tablename__ = 'users'
......@@ -415,7 +431,7 @@ class Task(Base):
round_id = Column(Integer, ForeignKey('rounds.round_id'), nullable=False)
code = Column(String(255), nullable=False)
name = Column(String(255), nullable=False)
max_points = Column(Integer)
max_points = Column(Numeric)
round = relationship('Round')
......@@ -524,7 +540,7 @@ class PointsHistory(Base):
points_history_id = Column(Integer, primary_key=True, server_default=text("nextval('points_history_points_history_id_seq'::regclass)"))
task_id = Column(Integer, ForeignKey('tasks.task_id'), nullable=False)
participant_id = Column(Integer, ForeignKey('users.user_id'), nullable=False)
points = Column(Integer)
points = Column(Numeric)
points_by = Column(Integer, ForeignKey('users.user_id'), nullable=False)
points_at = Column(DateTime(True), nullable=False)
......@@ -540,7 +556,7 @@ class Solution(Base):
user_id = Column(Integer, ForeignKey('users.user_id'), primary_key=True, nullable=False)
final_submit = Column(Integer, ForeignKey('papers.paper_id'))
final_feedback = Column(Integer, ForeignKey('papers.paper_id'))
points = Column(Integer)
points = Column(Numeric)
note = Column(Text, nullable=False, server_default=text("''::text"))
org_note = Column(Text, nullable=False, server_default=text("''::text"))
......
import decimal
from fractions import Fraction
from sqlalchemy import and_
from sqlalchemy.orm import joinedload
......@@ -56,8 +57,8 @@ class ScoreResult:
def get_sols_map(self) -> Dict[int, db.Solution]:
return self._sols[0]
def get_total_points(self) -> int:
sum = 0
def get_total_points(self) -> decimal.Decimal:
sum = decimal.Decimal(0)
for sol in self.get_sols():
if sol.points:
sum += sol.points
......@@ -67,17 +68,17 @@ class ScoreResult:
class ScoreTask:
task: db.Task
num_solutions: int
sum_points: int
sum_points: decimal.Decimal
def __init__(self, task: db.Task):
self.task = task
self.num_solutions = 0
self.sum_points = 0
self.sum_points = decimal.Decimal(0)
def get_difficulty(self) -> Fraction:
if self.num_solutions == 0:
return Fraction(0)
return Fraction(self.sum_points, self.num_solutions)
return Fraction(Fraction(self.sum_points), self.num_solutions)
def get_difficulty_str(self) -> str:
return f'{self.sum_points}/{self.num_solutions}'
......
......@@ -431,6 +431,10 @@ class RoundEditForm(FlaskForm):
"Hranice bodů pro úspěšné řešitele", validators=[validators.Optional()],
description="Řešitelé s alespoň tolika body budou označeni za úspěšné řešitele, prázdná hodnota = žádné neoznačovat",
)
points_step = wtforms.SelectField(
"Přesnost bodování", choices=db.round_points_step_choices,
description="Ovlivňuje možnost zadávání nových bodů, již uložené body nezmění"
)
has_messages = wtforms.BooleanField("Zprávičky pro účastníky (aktivuje možnost vytvářet novinky zobrazované účastníkům)")
submit = wtforms.SubmitField('Uložit')
......@@ -450,6 +454,7 @@ def org_round_edit(id: int):
del form.score_mode
del form.score_winner_limit
del form.score_successful_limit
del form.points_step
if form.validate_on_submit():
form.populate_obj(round)
......
......@@ -61,6 +61,7 @@
<tr><td>Výsledková listina<td>{{ round.master.score_mode.friendly_name() }}
<tr><td>Hranice bodů pro vítěze<td>{{ round.master.score_winner_limit|none_value(Markup('<i>nenastaveno</i>')) }}
<tr><td>Hranice bodů pro úspěšné řešitele<td>{{ round.master.score_successful_limit|none_value(Markup('<i>nenastaveno</i>')) }}
<tr><td>Přesnost bodování<td>{{ round.points_step_name() }}
</table>
<div style="clear: both;"></div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment