diff --git a/languages/ru_RU/LC_MESSAGES/sportorg.po b/languages/ru_RU/LC_MESSAGES/sportorg.po index cf095672..35e0a321 100644 --- a/languages/ru_RU/LC_MESSAGES/sportorg.po +++ b/languages/ru_RU/LC_MESSAGES/sportorg.po @@ -636,8 +636,8 @@ msgstr "Режим присвоения" msgid "by time" msgstr "по времени" -msgid "by scores" -msgstr "по баллам" +msgid "by scores (rogaine)" +msgstr "по баллам (рогейн)" msgid "ardf" msgstr "ADRF" @@ -651,6 +651,9 @@ msgstr "фиксированные баллы за КП" msgid "minute penalty" msgstr "Штраф за каждую просроченную минуту" +msgid "maximum overrun time" +msgstr "Макс. превышение контрольного времени" + msgid "no penalty" msgstr "штраф не начисляется" @@ -756,6 +759,12 @@ msgstr "Командные результаты" msgid "Scores" msgstr "Очки" +msgid "Points gained" +msgstr "Набрано очков" + +msgid "Penalty for finishing late" +msgstr "Штраф за превышение" + msgid "Penalty calculation" msgstr "Штраф" diff --git a/sportorg/gui/dialogs/timekeeping_properties.py b/sportorg/gui/dialogs/timekeeping_properties.py index 5a1b4705..1e080ac1 100644 --- a/sportorg/gui/dialogs/timekeeping_properties.py +++ b/sportorg/gui/dialogs/timekeeping_properties.py @@ -109,12 +109,19 @@ def init_ui(self): # result processing tab self.result_proc_tab = QWidget() self.result_proc_layout = QFormLayout() + self.result_processing_group = QGroupBox(translate('Result processing')) + self.result_processing_layout = QFormLayout() self.rp_time_radio = QRadioButton(translate('by time')) - self.result_proc_layout.addRow(self.rp_time_radio) + self.rp_time_radio.toggled.connect(self.rp_result_calculation_mode) + self.result_processing_layout.addRow(self.rp_time_radio) self.rp_ardf_radio = QRadioButton(translate('ardf')) - self.result_proc_layout.addRow(self.rp_ardf_radio) - self.rp_scores_radio = QRadioButton(translate('by scores')) - self.result_proc_layout.addRow(self.rp_scores_radio) + self.rp_ardf_radio.toggled.connect(self.rp_result_calculation_mode) + self.result_processing_layout.addRow(self.rp_ardf_radio) + self.rp_scores_radio = QRadioButton(translate('by scores (rogaine)')) + self.rp_scores_radio.toggled.connect(self.rp_result_calculation_mode) + self.result_processing_layout.addRow(self.rp_scores_radio) + self.result_processing_group.setLayout(self.result_processing_layout) + self.result_proc_layout.addRow(self.result_processing_group) self.rp_scores_group = QGroupBox() self.rp_scores_layout = QFormLayout(self.rp_scores_group) @@ -130,6 +137,15 @@ def init_ui(self): self.rp_scores_layout.addRow( self.rp_scores_minute_penalty_label, self.rp_scores_minute_penalty_edit ) + self.rp_scores_max_overrun_time_label = QLabel( + translate('maximum overrun time') + ) + self.rp_scores_max_overrun_time = AdvTimeEdit( + max_width=80, display_format='HH:mm:ss' + ) + self.rp_scores_layout.addRow( + self.rp_scores_max_overrun_time_label, self.rp_scores_max_overrun_time + ) self.rp_scores_allow_duplicates = QCheckBox(translate('allow duplicates')) self.rp_scores_allow_duplicates.setToolTip( translate( @@ -335,6 +351,12 @@ def on_assignment_mode(self): self.chip_reading_box.setDisabled(mode) self.chip_duplicate_box.setDisabled(mode) + def rp_result_calculation_mode(self): + if self.rp_scores_radio.isChecked(): + self.rp_scores_group.show() + else: + self.rp_scores_group.hide() + def penalty_calculation_mode(self): if ( self.mr_lap_station_check.isChecked() @@ -455,6 +477,12 @@ def set_values_from_model(self): rp_scores_minute_penalty = obj.get_setting( 'result_processing_scores_minute_penalty', 1 ) + rp_scores_max_overrun_time = OTime( + msec=obj.get_setting( + 'result_processing_scores_max_overrun_time', 30 * 60 * 1000 + ) + ) + rp_scores_allow_duplicates = obj.get_setting( 'result_processing_scores_allow_duplicates', False ) @@ -473,6 +501,7 @@ def set_values_from_model(self): self.rp_fixed_scores_edit.setValue(rp_fixed_scores_value) self.rp_scores_minute_penalty_edit.setValue(rp_scores_minute_penalty) + self.rp_scores_max_overrun_time.setTime(rp_scores_max_overrun_time.to_time()) self.rp_scores_allow_duplicates.setChecked(rp_scores_allow_duplicates) # penalty calculation @@ -631,6 +660,10 @@ def apply_changes_impl(self): rp_fixed_scores_value = self.rp_fixed_scores_edit.value() rp_scores_minute_penalty = self.rp_scores_minute_penalty_edit.value() + rp_scores_max_overrun_time = ( + self.rp_scores_max_overrun_time.getOTime().to_msec() + ) + rp_scores_allow_duplicates = self.rp_scores_allow_duplicates.isChecked() obj.set_setting('result_processing_mode', rp_mode) @@ -639,6 +672,9 @@ def apply_changes_impl(self): obj.set_setting( 'result_processing_scores_minute_penalty', rp_scores_minute_penalty ) + obj.set_setting( + 'result_processing_scores_max_overrun_time', rp_scores_max_overrun_time + ) obj.set_setting( 'result_processing_scores_allow_duplicates', rp_scores_allow_duplicates ) diff --git a/sportorg/models/memory.py b/sportorg/models/memory.py index 0a5524ff..927627cf 100644 --- a/sportorg/models/memory.py +++ b/sportorg/models/memory.py @@ -467,7 +467,8 @@ def __init__(self): self.penalty_laps = 0 # count of penalty legs (marked route) self.place = 0 self.scores = 0 - self.scores_rogain = 0 + self.rogaine_score = 0 + self.rogaine_penalty = 0 self.scores_ardf = 0 self.assigned_rank = Qualification.NOT_QUALIFIED self.diff: Optional[OTime] = None # readonly @@ -514,7 +515,7 @@ def __eq__(self, other) -> bool: else: return False else: # process by score (rogain) - eq = eq and self.scores_rogain == other.scores_rogain + eq = eq and self.rogaine_score == other.rogaine_score if eq and self.get_start_time() and other.get_start_time(): eq = eq and self.get_start_time() == other.get_start_time() if eq and self.get_finish_time() and other.get_finish_time(): @@ -548,10 +549,10 @@ def __gt__(self, other) -> bool: # greater is worse else: return self.scores_ardf < other.scores_ardf else: # process by score (rogain) - if self.scores_rogain == other.scores_rogain: + if self.rogaine_score == other.rogaine_score: return self.get_result_otime() > other.get_result_otime() else: - return self.scores_rogain < other.scores_rogain + return self.rogaine_score < other.rogaine_score @property @abstractmethod @@ -581,7 +582,8 @@ def to_dict(self): 'card_number': self.card_number, 'speed': self.speed, # readonly 'scores': self.scores, # readonly - 'scores_rogain': self.scores_rogain, # readonly + 'rogaine_score': self.rogaine_score, # readonly + 'rogaine_penalty': self.rogaine_penalty, # readonly 'scores_ardf': self.scores_ardf, # readonly 'created_at': self.created_at, # readonly 'result': self.get_result(), # readonly @@ -610,8 +612,10 @@ def update_data(self, data): self.scores = data['scores'] if 'scores_ardf' in data and data['scores_ardf'] is not None: self.scores_ardf = data['scores_ardf'] - if 'scores_rogain' in data and data['scores_rogain'] is not None: - self.scores_rogain = data['scores_rogain'] + if 'rogaine_score' in data and data['rogaine_score'] is not None: + self.rogaine_score = data['rogaine_score'] + if 'rogaine_penalty' in data and data['rogaine_penalty'] is not None: + self.rogaine_penalty = data['rogaine_penalty'] if str(data['place']).isdigit(): self.place = int(data['place']) self.assigned_rank = Qualification.get_qual_by_code(data['assigned_rank']) @@ -665,7 +669,7 @@ def get_result(self) -> str: if race().get_setting('result_processing_mode', 'time') == 'ardf': ret += f"{self.scores_ardf} {translate('points')} " elif race().get_setting('result_processing_mode', 'time') == 'scores': - ret += f"{self.scores_rogain} {translate('points')} " + ret += f"{self.rogaine_score} {translate('points')} " time_accuracy = race().get_setting('time_accuracy', 0) ret += self.get_result_otime().to_str(time_accuracy) @@ -684,7 +688,7 @@ def get_result_start_in_comment(self): if race().get_setting('result_processing_mode', 'time') == 'ardf': ret += f"{self.scores_ardf} {translate('points')} " elif race().get_setting('result_processing_mode', 'time') == 'scores': - ret += f"{self.scores_rogain} {translate('points')} " + ret += f"{self.rogaine_score} {translate('points')} " # time_accuracy = race().get_setting('time_accuracy', 0) start = hhmmss_to_time(self.person.comment) @@ -720,7 +724,7 @@ def get_result_relay(self) -> str: if race().get_setting('result_processing_mode', 'time') == 'ardf': ret += f"{self.scores_ardf} {translate('points')} " elif race().get_setting('result_processing_mode', 'time') == 'scores': - ret += f"{self.scores_rogain} {translate('points')} " + ret += f"{self.rogaine_score} {translate('points')} " time_accuracy = race().get_setting('time_accuracy', 0) ret += self.get_result_otime_relay().to_str(time_accuracy) diff --git a/sportorg/models/result/result_calculation.py b/sportorg/models/result/result_calculation.py index cfb533ba..70962a13 100644 --- a/sportorg/models/result/result_calculation.py +++ b/sportorg/models/result/result_calculation.py @@ -73,8 +73,9 @@ def get_group_persons(self, group): self._group_persons[group] = ret return ret - @staticmethod - def set_places(array): + def set_places(self, array): + is_rogaine = self.race.get_setting('result_processing_mode', 'time') == 'scores' + is_ardf = self.race.get_setting('result_processing_mode', 'time') == 'ardf' current_place = 1 last_place = 1 last_result = 0 @@ -86,7 +87,10 @@ def set_places(array): if res.is_status_ok(): current_result = res.get_result_otime() res.diff = current_result - array[0].get_result_otime() - res.diff_scores = array[0].scores - res.scores + if is_rogaine: + res.diff_scores = array[0].rogaine_score - res.rogaine_score + elif is_ardf: + res.diff_scores = array[0].scores_ardf - res.scores_ardf # skip if out of competition if res.person.is_out_of_competition: diff --git a/sportorg/models/result/result_checker.py b/sportorg/models/result/result_checker.py index 50ba05db..39c27909 100644 --- a/sportorg/models/result/result_checker.py +++ b/sportorg/models/result/result_checker.py @@ -30,8 +30,18 @@ def check_result(self, result: ResultSportident): result.scores_ardf = self.calculate_scores_ardf(result) return True elif race().get_setting('result_processing_mode', 'time') == 'scores': - # process by score (rogain) - result.scores_rogain = self.calculate_scores_rogain(result) + # process by score (rogaine) + allow_duplicates = race().get_setting( + 'result_processing_scores_allow_duplicates', False + ) + penalty_step = race().get_setting( + 'result_processing_scores_minute_penalty', 1 + ) + + score = self.calculate_rogaine_score(result, allow_duplicates) + penalty = self.calculate_rogaine_penalty(result, score, penalty_step) + result.rogaine_score = score - penalty + result.rogaine_penalty = penalty return True course = race().find_course(result) @@ -73,10 +83,22 @@ def checking(cls, result): result.status = ResultStatus.MISS_PENALTY_LAP elif result.person.group and result.person.group.max_time.to_msec(): - if result.get_result_otime() > result.person.group.max_time: - if race().get_setting('result_processing_mode', 'time') == 'time': + rp_mode = race().get_setting('result_processing_mode', 'time') + result_time = result.get_result_otime() + max_time = result.person.group.max_time + if rp_mode in ('time', 'ardf'): + if result_time > max_time: result.status = ResultStatus.OVERTIME - elif race().get_setting('result_processing_mode', 'time') == 'ardf': + elif rp_mode == 'scores': + max_overrun_time = OTime( + msec=race().get_setting( + 'result_processing_scores_max_overrun_time', 0 + ) + ) + if ( + max_overrun_time.to_msec() > 0 + and result_time > max_time + max_overrun_time + ): result.status = ResultStatus.OVERTIME result.status_comment = StatusComments().get_status_default_comment( @@ -332,19 +354,48 @@ def get_control_score(code): return int(code) // 10 # score = code / 10 @staticmethod - def calculate_scores_rogain(result): - user_array = [] - ret = 0 + def calculate_rogaine_score(result: Result, allow_duplicates: bool = False) -> int: + """ + Calculates the rogaine score for a given result. - allow_duplicates = race().get_setting( - 'result_processing_scores_allow_duplicates', False - ) + Parameters: + result (Result): The result for which the rogaine score needs to be calculated. + allow_duplicates (bool, optional): Whether to allow duplicate control points. Defaults to False. + + Returns: + int: The calculated rogaine score. + + If `allow_duplicates` flag is `True`, the function allows duplicate control points + to be included in the score calculation. + """ + user_array = [] + score = 0 for cur_split in result.splits: code = str(cur_split.code) if code not in user_array or allow_duplicates: user_array.append(code) - ret += ResultChecker.get_control_score(code) + score += ResultChecker.get_control_score(code) + + return score + + @staticmethod + def calculate_rogaine_penalty( + result: Result, score: int, penalty_step: int = 1 + ) -> int: + """ + Calculates the penalty for a given result based on the participant's excess of a race time. + + Parameters: + result (Result): The result for which the penalty needs to be calculated. + score (int): The competitor's score. + penalty_step (int, optional): The penalty points for each minute late. Defaults to 1. + + Returns: + int: The calculated penalty for the result. + + """ + penalty = 0 if result.person and result.person.group: user_time = result.get_result_otime() max_time = result.person.group.max_time @@ -352,13 +403,12 @@ def calculate_scores_rogain(result): time_diff = user_time - max_time seconds_diff = time_diff.to_sec() minutes_diff = (seconds_diff + 59) // 60 # note, 1:01 = 2 minutes - penalty_step = race().get_setting( - 'result_processing_scores_minute_penalty', 1.0 - ) - ret -= minutes_diff * penalty_step - if ret < 0: - ret = 0 - return ret + penalty = minutes_diff * penalty_step + + # result = score - penalty >= 0 + penalty = min(penalty, score) + + return penalty @staticmethod def calculate_scores_ardf(result): diff --git a/sportorg/modules/live/orgeo.py b/sportorg/modules/live/orgeo.py index a9820d2c..f1d20525 100644 --- a/sportorg/modules/live/orgeo.py +++ b/sportorg/modules/live/orgeo.py @@ -124,6 +124,10 @@ def _get_person_obj(data, race_data, result=None): if race_data['settings']['result_processing_mode'] == 'ardf': obj['score'] = result['scores_ardf'] + elif race_data['settings']['result_processing_mode'] == 'scores': + obj['score'] = result['rogaine_score'] + if result['rogaine_penalty'] > 0: + obj['penalty'] = str(result['rogaine_penalty']) obj['result_status'] = ( RESULT_STATUS[int(result['status'])] diff --git a/sportorg/modules/printing/printing.py b/sportorg/modules/printing/printing.py index 6299d5d8..9af87e9c 100644 --- a/sportorg/modules/printing/printing.py +++ b/sportorg/modules/printing/printing.py @@ -3,8 +3,8 @@ import time from multiprocessing import Process, Queue -from PySide6.QtCore import QSizeF -from PySide6.QtGui import QTextDocument +from PySide6.QtCore import QMarginsF, QSizeF +from PySide6.QtGui import QPageLayout, QTextDocument from PySide6.QtPrintSupport import QPrinter from PySide6.QtWidgets import QApplication @@ -63,11 +63,13 @@ def run(self): printer.setFullPage(True) printer.setPageMargins( - self.margin_left, - self.margin_top, - self.margin_right, - self.margin_bottom, - QPrinter.Millimeter, + QMarginsF( + self.margin_left, + self.margin_top, + self.margin_right, + self.margin_bottom, + ), + QPageLayout.Unit.Millimeter, ) page_size = QSizeF() diff --git a/sportorg/modules/printing/printout_split.py b/sportorg/modules/printing/printout_split.py index 6f636fba..dd73e8ff 100644 --- a/sportorg/modules/printing/printout_split.py +++ b/sportorg/modules/printing/printout_split.py @@ -141,7 +141,7 @@ def print_penalty_line(self, result: Result) -> None: self.move_cursor(font_size * 1.3) self.end_page() - def print_split_normal(self, result): + def print_split_normal(self, result: Result): obj = race() person = result.person @@ -164,6 +164,7 @@ def print_split_normal(self, result): is_relay = group.is_relay() fn = 'Lucida Console' + fs_small = 2.5 fs_main = 3 fs_large = 4 @@ -284,6 +285,21 @@ def print_split_normal(self, result): fs_main, ) + is_rogaine = race().get_setting('result_processing_mode', 'time') == 'scores' + if is_rogaine and result.rogaine_penalty > 0: + penalty = result.rogaine_penalty + total_score = result.rogaine_score + penalty + self.print_line( + translate('Points gained') + ': ' + str(total_score), + fn, + fs_main, + ) + self.print_line( + translate('Penalty for finishing late') + ': ' + str(penalty), + fn, + fs_main, + ) + if result.is_status_ok(): self.print_line( translate('Result') @@ -325,7 +341,12 @@ def print_split_normal(self, result): self.print_line(place, fn, fs_main) # Info about competitors, who can win current person - if result.is_status_ok() and not is_relay and is_group_existed: + if ( + result.is_status_ok() + and not is_relay + and not is_rogaine + and is_group_existed + ): if obj.get_setting('system_start_source', 'protocol') == 'protocol': if hasattr(result, 'can_win_count'): if result.can_win_count > 0: @@ -376,7 +397,7 @@ def print_split_normal(self, result): + ' ' + cur_res.get_result(), fn, - fs_main, + fs_main if not is_rogaine else fs_small, ) self.print_line(obj.data.url, fn, fs_main) diff --git "a/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262.html" "b/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262.html" index 5ff278c5..ba2b5e42 100644 --- "a/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262.html" +++ "b/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262.html" @@ -36,6 +36,9 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

var selected = {% endraw %}{{selected|tojson}}{%raw %}; racePreparation(race); + var isRogaine = race.settings.result_processing_mode === 'scores'; + var isArdf = race.settings.result_processing_mode === 'ardf'; + function getResultsByGroup(group, count) { count = +count || 0; var isRelay = group.__type ? group.__type === 3 : race.data.race_type === 3; @@ -53,10 +56,13 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

penalty_time: toHHMMSS(result.penalty_time), penalty_laps: result.penalty_laps, scores_ardf: result.scores_ardf, + rogaine_score: result.rogaine_score, + rogaine_penalty: result.rogaine_penalty, + rogaine_points_earned: result.rogaine_score + result.rogaine_penalty, result: result.result_current, result_relay: result.result_relay, result_msec: result.result_msec, - diff: race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf' ? result.diff_scores : toHHMMSS(result.diff), + diff: isRogaine || isArdf ? result.diff_scores : toHHMMSS(result.diff), place: result.place, status: result.status, scores: result.scores, @@ -73,6 +79,9 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

r.speed = ''; r.speed_kmh = ''; r.scores = ''; + r.rogaine_points_earned = ''; + r.rogaine_penalty = ''; + r.rogaine_score = ''; } // SPLITS @@ -164,8 +173,11 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

return -1 } if (a.is_out_of_competition || b.is_out_of_competition) { - if ((race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf') && a.scores !== b.scores) { - return a.scores - b.scores; + if (isRogaine && a.rogaine_score != b.rogaine_score) { + return -(a.rogaine_score - b.rogaine_score); + } + if (isArdf && a.scores_ardf != b.scores_ardf) { + return -(a.scores_ardf - b.scores_ardf); } return a.result_msec - b.result_msec; } @@ -224,9 +236,12 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

penalty_time: toHHMMSS(result.penalty_time), penalty_laps: result.penalty_laps, scores_ardf: result.scores_ardf, - result: result.result, + rogaine_score: result.rogaine_score, + rogaine_penalty: result.rogaine_penalty, + rogaine_points_earned: result.rogaine_score + result.rogaine_penalty, + result: result.result_current, result_msec: result.result_msec, - diff: race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf' ? result.diff_scores : toHHMMSS(result.diff), + diff: isRogaine || isArdf ? result.diff_scores : toHHMMSS(result.diff), place: result.place, status: result.status, scores: result.scores, @@ -243,6 +258,9 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

r.speed = ''; r.speed_kmh = ''; r.scores = ''; + r.rogaine_points_earned = ''; + r.rogaine_penalty = ''; + r.rogaine_score = ''; } // SPLITS @@ -309,8 +327,11 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

return -1 } if (a.is_out_of_competition || b.is_out_of_competition) { - if ((race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf') && a.scores !== b.scores) { - return a.scores - b.scores; + if (isRogaine && a.rogaine_score != b.rogaine_score) { + return -(a.rogaine_score - b.rogaine_score); + } + if (isArdf && a.scores_ardf != b.scores_ardf) { + return -(a.scores_ardf - b.scores_ardf); } return a.result_msec - b.result_msec; } @@ -320,6 +341,12 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

if (b.place < 1) { return -1 } + if (isRogaine && a.rogaine_score != b.rogaine_score) { + return -(a.rogaine_score - b.rogaine_score); + } + if (isArdf && a.scores_ardf != b.scores_ardf) { + return -(a.scores_ardf - b.scores_ardf); + } return a.result_msec - b.result_msec; } }); @@ -346,10 +373,16 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

results.push(r); } } else { + var get_score = function(res){ + if (isRogaine) return res.rogaine_score; + if (isArdf) return res.scores_ardf; + return 0 + } var newplace = 1; var doubleplace = -1; var best_result_msec = results[0] ? results[0].result_msec : 0; var last_result_msec = results[0] ? results[0].result_msec : 0; + var best_score = results[0] ? get_score(results[0]) : 0; var binary_search = function(arr,required){ if (!arr){ return 0; @@ -373,14 +406,14 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

index++; elem.index = index; if (!elem.data.person.is_out_of_competition && elem.place !== 0 && elem.place !== -1) { - if (last_result_msec < elem.result_msec){ + if (isRogaine || isArdf || last_result_msec < elem.result_msec){ newplace += 1 + doubleplace; doubleplace = 0; last_result_msec = elem.result_msec; } else { doubleplace++; } - elem.diff = race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf' ? elem.diff_scores : toHHMMSS(elem.result_msec - best_result_msec); + elem.diff = isRogaine || isArdf ? best_score - get_score(elem) : toHHMMSS(elem.result_msec - best_result_msec); elem.place = newplace; elem.place_show = newplace; } @@ -484,10 +517,13 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

{key: 'bib', title: 'Номер', size: 6}, {key: 'penalty_time', title: 'Штраф', size: 9, active: false}, {key: 'penalty_laps', title: 'Штраф', size: 9, active: false}, - {key: 'scores_ardf', title: 'КП', size: 3, active: race.settings.result_processing_mode === 'ardf'}, + {key: 'scores_ardf', title: 'КП', size: 3, active: isArdf}, + {key: 'rogaine_points_earned', title: 'Очки', size: 4, active: isRogaine}, + {key: 'rogaine_penalty', title: 'Штр.', size: 4, active: isRogaine}, + {key: 'rogaine_score', title: 'Итог', size: 4, active: isRogaine}, {key: 'result', title: 'Результат', size: 17}, {key: 'result_relay', title: 'Ком. рез-т', size: 14}, - {key: 'diff', title: 'Отставание', size: 11, active: race.settings.result_processing_mode !== 'ardf'}, + {key: 'diff', title: 'Отставание', size: 11, active: !isArdf && !isRogaine}, {key: 'speed', title: 'Темп', size: 8, active: false}, {key: 'speed_kmh', title: 'Скорость', size: 8, active: false}, {key: 'place_show', title: 'Место', size: 6}, @@ -727,7 +763,7 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

} }, { - title: 'КП', + title: 'ARDF КП', value: Fields.isActive('scores_ardf'), change: function (checked) { Fields.active('scores_ardf', checked); diff --git "a/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262_\321\201\320\277\320\273\320\270\321\202\321\213.html" "b/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262_\321\201\320\277\320\273\320\270\321\202\321\213.html" index f8b4b9c2..b08295d8 100644 --- "a/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262_\321\201\320\277\320\273\320\270\321\202\321\213.html" +++ "b/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262_\321\201\320\277\320\273\320\270\321\202\321\213.html" @@ -36,6 +36,9 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

var selected = {% endraw %}{{selected|tojson}}{%raw %}; racePreparation(race); + var isRogaine = race.settings.result_processing_mode === 'scores'; + var isArdf = race.settings.result_processing_mode === 'ardf'; + function getResultsByGroup(group, count) { count = +count || 0; var isRelay = group.__type ? group.__type === 3 : race.data.race_type === 3; @@ -52,10 +55,14 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

year: result.person.birth_date ? (new Date(result.person.birth_date)).getFullYear() : '', penalty_time: toHHMMSS(result.penalty_time), penalty_laps: result.penalty_laps, + scores_ardf: result.scores_ardf, + rogaine_score: result.rogaine_score, + rogaine_penalty: result.rogaine_penalty, + rogaine_points_earned: result.rogaine_score + result.rogaine_penalty, result: result.result_current, result_relay: result.result_relay, result_msec: result.result_msec, - diff: race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf' ? result.diff_scores : toHHMMSS(result.diff), + diff: isRogaine || isArdf ? result.diff_scores : toHHMMSS(result.diff), place: result.place, status: result.status, scores: result.scores, @@ -72,8 +79,9 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

r.speed = ''; r.speed_kmh = ''; r.scores = ''; - //r.penalty_time = ''; - //r.penalty_laps = ''; + r.rogaine_points_earned = ''; + r.rogaine_penalty = ''; + r.rogaine_score = ''; } // SPLITS @@ -165,8 +173,11 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

return -1 } if (a.is_out_of_competition || b.is_out_of_competition) { - if ((race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf') && a.scores !== b.scores) { - return a.scores - b.scores; + if (isRogaine && a.rogaine_score != b.rogaine_score) { + return -(a.rogaine_score - b.rogaine_score); + } + if (isArdf && a.scores_ardf != b.scores_ardf) { + return -(a.scores_ardf - b.scores_ardf); } return a.result_msec - b.result_msec; } @@ -224,9 +235,13 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

year: result.person.birth_date ? (new Date(result.person.birth_date)).getFullYear() : '', penalty_time: toHHMMSS(result.penalty_time), penalty_laps: result.penalty_laps, - result: result.result, + scores_ardf: result.scores_ardf, + rogaine_score: result.rogaine_score, + rogaine_penalty: result.rogaine_penalty, + rogaine_points_earned: result.rogaine_score + result.rogaine_penalty, + result: result.result_current, result_msec: result.result_msec, - diff: race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf' ? result.diff_scores : toHHMMSS(result.diff), + diff: isRogaine || isArdf ? result.diff_scores : toHHMMSS(result.diff), place: result.place, status: result.status, scores: result.scores, @@ -243,8 +258,9 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

r.speed = ''; r.speed_kmh = ''; r.scores = ''; - //r.penalty_time = ''; - //r.penalty_laps = ''; + r.rogaine_points_earned = ''; + r.rogaine_penalty = ''; + r.rogaine_score = ''; } // SPLITS @@ -311,8 +327,11 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

return -1 } if (a.is_out_of_competition || b.is_out_of_competition) { - if ((race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf') && a.scores !== b.scores) { - return a.scores - b.scores; + if (isRogaine && a.rogaine_score != b.rogaine_score) { + return -(a.rogaine_score - b.rogaine_score); + } + if (isArdf && a.scores_ardf != b.scores_ardf) { + return -(a.scores_ardf - b.scores_ardf); } return a.result_msec - b.result_msec; } @@ -322,6 +341,12 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

if (b.place < 1) { return -1 } + if (isRogaine && a.rogaine_score != b.rogaine_score) { + return -(a.rogaine_score - b.rogaine_score); + } + if (isArdf && a.scores_ardf != b.scores_ardf) { + return -(a.scores_ardf - b.scores_ardf); + } return a.result_msec - b.result_msec; } }); @@ -348,10 +373,16 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

results.push(r); } } else { + var get_score = function(res){ + if (isRogaine) return res.rogaine_score; + if (isArdf) return res.scores_ardf; + return 0 + } var newplace = 1; var doubleplace = -1; var best_result_msec = results[0] ? results[0].result_msec : 0; var last_result_msec = results[0] ? results[0].result_msec : 0; + var best_score = results[0] ? get_score(results[0]) : 0; var binary_search = function(arr,required){ if (!arr){ return 0; @@ -375,14 +406,14 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

index++; elem.index = index; if (!elem.data.person.is_out_of_competition && elem.place !== 0 && elem.place !== -1) { - if (last_result_msec < elem.result_msec){ + if (isRogaine || isArdf || last_result_msec < elem.result_msec){ newplace += 1 + doubleplace; doubleplace = 0; last_result_msec = elem.result_msec; } else { doubleplace++; } - elem.diff = race.settings.result_processing_mode === 'scores' ? elem.diff_scores : toHHMMSS(elem.result_msec - best_result_msec); + elem.diff = isRogaine || isArdf ? best_score - get_score(elem) : toHHMMSS(elem.result_msec - best_result_msec); elem.place = newplace; elem.place_show = newplace; } @@ -486,9 +517,13 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

{key: 'bib', title: 'Номер', size: 6}, {key: 'penalty_time', title: 'Штраф', size: 9, active: false}, {key: 'penalty_laps', title: 'Штраф', size: 9, active: false}, + {key: 'scores_ardf', title: 'КП', size: 3, active: isArdf}, + {key: 'rogaine_points_earned', title: 'Очки', size: 4, active: isRogaine}, + {key: 'rogaine_penalty', title: 'Штр.', size: 4, active: isRogaine}, + {key: 'rogaine_score', title: 'Итог', size: 4, active: isRogaine}, {key: 'result', title: 'Результат', size: 17}, {key: 'result_relay', title: 'Ком. рез-т', size: 14}, - {key: 'diff', title: 'Отставание', size: 11}, + {key: 'diff', title: 'Отставание', size: 11, active: !isArdf && !isRogaine}, {key: 'speed', title: 'Темп', size: 8, active: false}, {key: 'speed_kmh', title: 'Скорость', size: 8, active: false}, {key: 'place_show', title: 'Место', size: 6}, @@ -560,7 +595,6 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

} catch (e) {} function render() { - var resultBlock = document.getElementById('results-tables'); resultBlock.innerHTML = ''; Fields.active('group', store.showProtocolByCourse); @@ -635,7 +669,6 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

} }); queryString += 'sportorg=1'; - try { var href = location.href.split('?')[0]; history.pushState({}, null, href + queryString); @@ -733,6 +766,14 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

render() } }, + { + title: 'ARDF КП', + value: Fields.isActive('scores_ardf'), + change: function (checked) { + Fields.active('scores_ardf', checked); + render() + } + }, { title: 'Отставание', value: Fields.isActive('diff'), diff --git "a/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262_\321\202\320\265\321\200\320\274\320\276\320\277\321\200\320\270\320\275\321\202\320\265\321\200_80_\320\274\320\274.html" "b/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262_\321\202\320\265\321\200\320\274\320\276\320\277\321\200\320\270\320\275\321\202\320\265\321\200_80_\320\274\320\274.html" index 3d0b96de..663b9a46 100644 --- "a/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262_\321\202\320\265\321\200\320\274\320\276\320\277\321\200\320\270\320\275\321\202\320\265\321\200_80_\320\274\320\274.html" +++ "b/templates/reports/1_\320\277\321\200\320\276\321\202\320\276\320\272\320\276\320\273_\321\200\320\265\320\267\321\203\320\273\321\214\321\202\320\260\321\202\320\276\320\262_\321\202\320\265\321\200\320\274\320\276\320\277\321\200\320\270\320\275\321\202\320\265\321\200_80_\320\274\320\274.html" @@ -36,6 +36,9 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

var selected = {% endraw %}{{selected|tojson}}{%raw %}; racePreparation(race); + var isRogaine = race.settings.result_processing_mode === 'scores'; + var isArdf = race.settings.result_processing_mode === 'ardf'; + function getResultsByGroup(group, count) { count = +count || 0; var isRelay = group.__type ? group.__type === 3 : race.data.race_type === 3; @@ -52,9 +55,13 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

year: result.person.birth_date ? (new Date(result.person.birth_date)).getFullYear() : '', penalty_time: toHHMMSS(result.penalty_time), penalty_laps: result.penalty_laps, - result: result.result, + scores_ardf: result.scores_ardf, + rogaine_score: result.rogaine_score, + rogaine_penalty: result.rogaine_penalty, + rogaine_points_earned: result.rogaine_score + result.rogaine_penalty, + result: result.result_current, result_msec: result.result_msec, - diff: race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf' ? result.diff_scores : toHHMMSS(result.diff), + diff: isRogaine || isArdf ? result.diff_scores : toHHMMSS(result.diff), place: result.place, status: result.status, scores: result.scores, @@ -70,6 +77,9 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

r.scores = ''; r.penalty_time = ''; r.penalty_laps = ''; + r.rogaine_points_earned = ''; + r.rogaine_penalty = ''; + r.rogaine_score = ''; } // SPLITS @@ -166,8 +176,11 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

return -1 } if (a.is_out_of_competition || b.is_out_of_competition) { - if ((race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf') && a.scores !== b.scores) { - return a.scores - b.scores; + if (isRogaine && a.rogaine_score != b.rogaine_score) { + return -(a.rogaine_score - b.rogaine_score); + } + if (isArdf && a.scores_ardf != b.scores_ardf) { + return -(a.scores_ardf - b.scores_ardf); } return a.result_msec - b.result_msec; } @@ -242,7 +255,7 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

var Fields = { fields: [ - {key: 'index', title: '№', size: 4}, + {key: 'index', title: '№', size: 3}, {key: 'name', title: 'Фамилия, имя', size: 20}, {key: 'org', title: 'Коллектив', size: 20, active: false}, {key: 'year', title: 'ГР', size: 5, active: false}, @@ -250,6 +263,10 @@

ПРОТОКОЛ РЕЗУЛЬТАТОВ

{key: 'bib', title: 'Номер', size: 6, active: false}, {key: 'penalty_time', title: 'Штраф', size: 9, active: false}, {key: 'penalty_laps', title: 'Штраф', size: 9, active: false}, + {key: 'scores_ardf', title: 'КП', size: 3, active: isArdf}, + {key: 'rogaine_points_earned', title: 'Очки', size: 4, active: false}, + {key: 'rogaine_penalty', title: 'Штр.', size: 4, active: false}, + {key: 'rogaine_score', title: 'Итог', size: 4, active: isRogaine}, {key: 'result', title: 'Результат', size: 10}, {key: 'diff', title: 'Отставание', size: 11, active: false}, {key: 'speed', title: 'Темп', size: 12, active: false}, diff --git a/templates/reports/ENG_1_results.html b/templates/reports/ENG_1_results.html index 7209dc63..6b4942cb 100644 --- a/templates/reports/ENG_1_results.html +++ b/templates/reports/ENG_1_results.html @@ -38,6 +38,9 @@

RESULT LIST

var selected = {% endraw %}{{selected|tojson}}{%raw %}; racePreparation(race); + var isRogaine = race.settings.result_processing_mode === 'scores'; + var isArdf = race.settings.result_processing_mode === 'ardf'; + function getResultsByGroup(group, count) { count = +count || 0; var isRelay = group.__type ? group.__type === 3 : race.data.race_type === 3; @@ -54,10 +57,14 @@

RESULT LIST

year: result.person.birth_date ? (new Date(result.person.birth_date)).getFullYear() : '', penalty_time: toHHMMSS(result.penalty_time), penalty_laps: result.penalty_laps, - result: result.result, + scores_ardf: result.scores_ardf, + rogaine_score: result.rogaine_score, + rogaine_penalty: result.rogaine_penalty, + rogaine_points_earned: result.rogaine_score + result.rogaine_penalty, + result: result.result_current, result_relay: result.result_relay, result_msec: result.result_msec, - diff: race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf' ? result.diff_scores : toHHMMSS(result.diff), + diff: isRogaine || isArdf ? result.diff_scores : toHHMMSS(result.diff), place: result.place, status: result.status, scores: result.scores, @@ -71,8 +78,9 @@

RESULT LIST

r.place_show = ''; r.speed = ''; r.scores = ''; - r.penalty_time = ''; - r.penalty_laps = ''; + r.rogaine_points_earned = ''; + r.rogaine_penalty = ''; + r.rogaine_score = ''; } // SPLITS @@ -169,8 +177,11 @@

RESULT LIST

return -1 } if (a.is_out_of_competition || b.is_out_of_competition) { - if ((race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf') && a.scores !== b.scores) { - return a.scores - b.scores; + if (isRogaine && a.rogaine_score != b.rogaine_score) { + return -(a.rogaine_score - b.rogaine_score); + } + if (isArdf && a.scores_ardf != b.scores_ardf) { + return -(a.scores_ardf - b.scores_ardf); } return a.result_msec - b.result_msec; } @@ -228,9 +239,13 @@

RESULT LIST

year: result.person.birth_date ? (new Date(result.person.birth_date)).getFullYear() : '', penalty_time: toHHMMSS(result.penalty_time), penalty_laps: result.penalty_laps, - result: result.result, + scores_ardf: result.scores_ardf, + rogaine_score: result.rogaine_score, + rogaine_penalty: result.rogaine_penalty, + rogaine_points_earned: result.rogaine_score + result.rogaine_penalty, + result: result.result_current, result_msec: result.result_msec, - diff: race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf' ? result.diff_scores : toHHMMSS(result.diff), + diff: isRogaine || isArdf ? result.diff_scores : toHHMMSS(result.diff), place: result.place, status: result.status, scores: result.scores, @@ -245,8 +260,9 @@

RESULT LIST

r.place_show = ''; r.speed = ''; r.scores = ''; - r.penalty_time = ''; - r.penalty_laps = ''; + r.rogaine_points_earned = ''; + r.rogaine_penalty = ''; + r.rogaine_score = ''; } // SPLITS @@ -319,8 +335,11 @@

RESULT LIST

return -1 } if (a.is_out_of_competition || b.is_out_of_competition) { - if ((race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf') && a.scores !== b.scores) { - return a.scores - b.scores; + if (isRogaine && a.rogaine_score != b.rogaine_score) { + return -(a.rogaine_score - b.rogaine_score); + } + if (isArdf && a.scores_ardf != b.scores_ardf) { + return -(a.scores_ardf - b.scores_ardf); } return a.result_msec - b.result_msec; } @@ -330,6 +349,12 @@

RESULT LIST

if (b.place < 1) { return -1 } + if (isRogaine && a.rogaine_score != b.rogaine_score) { + return -(a.rogaine_score - b.rogaine_score); + } + if (isArdf && a.scores_ardf != b.scores_ardf) { + return -(a.scores_ardf - b.scores_ardf); + } return a.result_msec - b.result_msec; } }); @@ -356,10 +381,16 @@

RESULT LIST

results.push(r); } } else { - var newplace = 1; + var get_score = function(res){ + if (isRogaine) return res.rogaine_score; + if (isArdf) return res.scores_ardf; + return 0 + } + var newplace = 1; var doubleplace = -1; var best_result_msec = results[0] ? results[0].result_msec : 0; var last_result_msec = results[0] ? results[0].result_msec : 0; + var best_score = results[0] ? get_score(results[0]) : 0; var binary_search = function(arr,required){ if (!arr){ return 0; @@ -383,14 +414,14 @@

RESULT LIST

index++; elem.index = index; if (!elem.data.person.is_out_of_competition && elem.place !== 0 && elem.place !== -1) { - if (last_result_msec < elem.result_msec){ + if (isRogaine || isArdf || last_result_msec < elem.result_msec){ newplace += 1 + doubleplace; doubleplace = 0; last_result_msec = elem.result_msec; } else { doubleplace++; } - elem.diff = race.settings.result_processing_mode === 'scores' || race.settings.result_processing_mode === 'ardf' ? elem.diff_scores : toHHMMSS(elem.result_msec - best_result_msec); + elem.diff = isRogaine || isArdf ? best_score - get_score(elem) : toHHMMSS(elem.result_msec - best_result_msec); elem.place = newplace; elem.place_show = newplace; } @@ -483,9 +514,13 @@

RESULT LIST

{key: 'bib', title: 'Bib', size: 6}, {key: 'penalty_time', title: 'Penalty', size: 9, active: false}, {key: 'penalty_laps', title: 'Penalty,l', size: 9, active: false}, + {key: 'scores_ardf', title: 'ARDF', size: 4, active: isArdf}, + {key: 'rogaine_points_earned', title: 'Total', size: 5, active: isRogaine}, + {key: 'rogaine_penalty', title: 'Pen.', size: 4, active: isRogaine}, + {key: 'rogaine_score', title: 'Pts', size: 4, active: isRogaine}, {key: 'result', title: 'Result', size: 14}, {key: 'result_relay', title: 'Team result', size: 14}, - {key: 'diff', title: 'Diff', size: 11}, + {key: 'diff', title: 'Diff', size: 11, active: !isArdf && !isRogaine}, {key: 'speed', title: 'Pace', size: 12, active: false}, {key: 'place_show', title: 'Place', size: 6}, {key: 'scores', title: 'Scores', size: 5, active: false}, diff --git a/templates/split/1_split_printout.html b/templates/split/1_split_printout.html index 8f21c1d3..c1204db7 100644 --- a/templates/split/1_split_printout.html +++ b/templates/split/1_split_printout.html @@ -13,7 +13,7 @@ } .small-text { - font-size: 75%; + font-size: small; } p, @@ -175,6 +175,10 @@

{{ person.surname }} {{ person.name }} - {{ group.name }}

{% endfor %}
Финиш: {{result.finish_msec|tohhmmss}} {{result.speed}} + {% if race['settings'].get('result_processing_mode', 'time') == 'scores' and result.rogaine_penalty > 0 %} +
Набрано очков: {{result.rogaine_score + result.rogaine_penalty}} +
Штраф за превышение: {{result.rogaine_penalty}} + {% endif%} {% if race['settings'].get('marked_route_mode', 'off') == 'laps' %}
Штрафных кругов: {{result.penalty_laps}} {% elif race['settings'].get('marked_route_mode', 'off') == 'time' %} @@ -191,9 +195,7 @@

{{ person.surname }} {{ person.name }} - {{ group.name }}


Вас уже никто не обгонит! {% endif %} {% endif %} -
{% endif %} -
{% if result.status != 1 %} {% for control in course.controls %} {{control.code}} @@ -202,7 +204,7 @@

ПЛОХАЯ ОТМЕТКА

{% else %}

ОТМЕТКА - OK

{% endif %} - +
{% for item in items %} {% if item.result.place|int < 11 and item.result.place|int > 0 %} diff --git a/tests/test_result_checking.py b/tests/test_result_checking.py index f702a5b2..49e48b3c 100644 --- a/tests/test_result_checking.py +++ b/tests/test_result_checking.py @@ -1,13 +1,22 @@ from itertools import zip_longest from typing import List, Union +import pytest + +from sportorg.common.otime import OTime from sportorg.models.memory import ( Course, CourseControl, + Group, Person, + Race, ResultSportident, Split, + create, + new_event, + race, ) +from sportorg.models.result.result_checker import ResultChecker def test_controls_as_int(): @@ -241,14 +250,7 @@ def test_course_free_order(): assert dsq(c, [31, 32, 32, 70]) assert dsq(c, [32, 33, 70]) assert dsq(c, [31, 32, 70]) - assert dsq( - c, - [ - 31, - 32, - 33, - ], - ) + assert dsq(c, [31, 32, 33]) # Выбор + заданные КП c = ['*(31,32,33)', '55', '*(31,32,33)'] @@ -362,6 +364,92 @@ def test_course_butterfly(): assert dsq(c, [31, 32, 41, 42, 33, 32, 51, 33, 70]) +@pytest.mark.parametrize( + 'controls, expected', + [ + ([], 0), + ([31, 32, 33, 34], 12), + ([11, 22, 33, 44], 10), + ([31, 999, 34], 105), + ([31, 31, 33, 31, 33], 6), + ], +) +def test_calculate_rogaine_score(controls, expected): + create_race() + race().set_setting('result_processing_score_mode', 'rogain') # wrong spelling + res = make_result(controls) + assert ResultChecker.calculate_rogaine_score(res) == expected + + +@pytest.mark.parametrize( + 'fixed_value, controls, expected', + [ + (1, [], 0), + (1, [31, 32, 33, 34], 4), + (2, [31, 32, 33, 34], 8), + (1, [11, 22, 33, 44], 4), + (1, [31, 999, 34], 3), + (1, [31, 31, 33, 31, 33], 2), + ], +) +def test_calculate_fixed_score(fixed_value, controls, expected): + create_race() + race().set_setting('result_processing_score_mode', 'fixed') + race().set_setting('result_processing_fixed_score_value', fixed_value) + res = make_result(controls) + assert ResultChecker.calculate_rogaine_score(res) == expected + + +@pytest.mark.parametrize( + 'score_mode, controls, expected', + [ + ('rogain', [], 0), + ('rogain', [31, 31], 6), + ('rogain', [31, 32, 33, 34], 12), + ('rogain', [31, 31, 33, 31, 33], 15), + ('rogain', [11, 22, 33, 44], 10), + ('rogain', [11, 22, 33, 22, 44], 12), + ('fixed', [], 0), + ('fixed', [31, 31], 2), + ('fixed', [31, 32, 33, 34], 4), + ('fixed', [31, 31, 33, 31, 33], 5), + ('fixed', [11, 22, 33, 44], 4), + ('fixed', [11, 22, 33, 22, 44], 5), + ], +) +def test_calculate_score_allow_duplicates(score_mode, controls, expected): + create_race() + race().set_setting('result_processing_score_mode', score_mode) + race().set_setting('result_processing_fixed_score_value', 1) + res = make_result(controls) + assert ResultChecker.calculate_rogaine_score(res, allow_duplicates=True) == expected + + +# fmt: off +@pytest.mark.parametrize( + 'step, score, max_time, finish, expected', + [ + (1, 5, (0, 1, 0, 0), (0, 0, 59, 59), 0), + (1, 5, (0, 1, 0, 0), (0, 1, 0, 0), 0), + (1, 5, (0, 1, 0, 0), (0, 1, 0, 1), 1), + (1, 5, (0, 1, 0, 0), (0, 1, 0, 30), 1), + (1, 5, (0, 1, 0, 0), (0, 1, 1, 0), 1), + (1, 5, (0, 1, 0, 0), (0, 1, 1, 1), 2), + (1, 5, (0, 1, 0, 0), (0, 1, 5, 1), 5), + (1, 5, (0, 1, 0, 0), (0, 1, 6, 1), 5), # penalty does not exceed total score + (1, 5, (0, 1, 0, 0), (0, 1, 15, 1), 5), # penalty does not exceed total score + ], +) +# fmt: on +def test_calculate_rogaine_penalty(step, score, max_time, finish, expected): + create_race() + race().set_setting('result_processing_scores_minute_penalty', 1) + race().groups[0].max_time = OTime(*max_time) + result = race().results[0] + result.finish_time = OTime(*finish) + assert ResultChecker.calculate_rogaine_penalty(result, score, step) == expected + + def test_non_obvious_behavior(): """Неочевидное поведение при проверке дистанции. Не всегда это некорректная работа алгоритма, иногда может возникать из-за недочётов при составлении курсов. @@ -495,6 +583,19 @@ def check( return check_result +def create_race(): + course = create(Course) + group = create(Group, course=course) + person = create(Person, group=group) + result = ResultSportident() + result.person = person + new_event([create(Race)]) + race().courses.append(course) + race().groups.append(group) + race().persons.append(person) + race().results.append(result) + + def make_course(course: List[Union[int, str]]) -> Course: course_object = Course() course_object.controls = make_course_controls(course)