From 5250f40c659826620e83928aca6aba1ce08c4e93 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpov Date: Wed, 15 Nov 2023 03:17:52 +0700 Subject: [PATCH 1/9] feat: marked route with penalty laps --- languages/ru_RU/LC_MESSAGES/sportorg.po | 36 ++++ sportorg/gui/dialogs/result_edit.py | 1 + .../gui/dialogs/timekeeping_properties.py | 44 +++- sportorg/gui/menu/actions.py | 1 + sportorg/libs/template/template.py | 14 ++ sportorg/models/memory.py | 1 + sportorg/models/result/result_checker.py | 61 +++++- sportorg/modules/printing/printout_split.py | 79 ++++++- .../modules/sportident/result_generation.py | 2 + templates/split/1_split_printout.html | 5 + .../split/2_marked_route_penalty_A5.html | 48 +++++ .../split/3_marked_route_penalty_80mm.html | 54 +++++ tests/test_penalty_checking.py | 195 ++++++++---------- 13 files changed, 419 insertions(+), 122 deletions(-) create mode 100644 templates/split/2_marked_route_penalty_A5.html create mode 100644 templates/split/3_marked_route_penalty_80mm.html diff --git a/languages/ru_RU/LC_MESSAGES/sportorg.po b/languages/ru_RU/LC_MESSAGES/sportorg.po index 1d830def..0bbf2819 100644 --- a/languages/ru_RU/LC_MESSAGES/sportorg.po +++ b/languages/ru_RU/LC_MESSAGES/sportorg.po @@ -354,6 +354,9 @@ msgstr "Финиш" msgid "Penalty" msgstr "Штраф" +msgid "Penalty laps" +msgstr "Штрафные круги" + msgid "Penalty legs" msgstr "Штраф, круги" @@ -648,18 +651,45 @@ msgstr "Штраф за каждую просроченную минуту" msgid "no penalty" msgstr "штраф не начисляется" +msgid "No penalty" +msgstr "Штраф не начисляется" + msgid "penalty time" msgstr "штрафное время" +msgid "Penalty calculation mode: penalty time" +msgstr "Режим начисления штрафа: штрафное время" + msgid "penalty laps" msgstr "штрафные круги" +msgid "Penalty calculation mode: penalty laps" +msgstr "Режим начисления штрафа: штрафные круги" + msgid "counting lap" msgstr "оценочный круг" +msgid "Operating mode: evaluation point" +msgstr "Режим работы: оценочный круг" + +msgid "Print number of penalty laps instead of splits" +msgstr "При считывании печатается распечатка" + +msgid "when competitor reads out his card" +msgstr "с количеством штрафных кругов" + msgid "lap station" msgstr "станция на штрафном круге" +msgid "Station number on the penalty lap" +msgstr "Номер станции на штрафном круге" + +msgid "Competitor must punch at station" +msgstr "Спортсмен должен отметиться на станции" + +msgid "every time he/she passes a penalty lap" +msgstr "при каждом прохождении штрафного круга" + msgid "scores off" msgstr "очки не рассчитываются" @@ -1515,6 +1545,12 @@ msgstr "СОШЕЛ" msgid "Disqualified" msgstr "ДИСКВ." +msgid "Missing penalty lap" +msgstr "Пропущен штрафной круг" + +msgid "laps" +msgstr "кр." + msgid "contain" msgstr "содержит" diff --git a/sportorg/gui/dialogs/result_edit.py b/sportorg/gui/dialogs/result_edit.py index 23fb4f71..3bd329ca 100644 --- a/sportorg/gui/dialogs/result_edit.py +++ b/sportorg/gui/dialogs/result_edit.py @@ -289,6 +289,7 @@ def apply_changes_impl(self): try: ResultChecker.checking(result) ResultChecker.calculate_penalty(result) + ResultChecker.checking(result) if result.person and result.person.group: GroupSplits(race(), result.person.group).generate(True) except ResultCheckerException as e: diff --git a/sportorg/gui/dialogs/timekeeping_properties.py b/sportorg/gui/dialogs/timekeeping_properties.py index 2e430108..d1782820 100644 --- a/sportorg/gui/dialogs/timekeeping_properties.py +++ b/sportorg/gui/dialogs/timekeeping_properties.py @@ -183,15 +183,25 @@ def init_ui(self): self.result_proc_tab.setLayout(self.result_proc_layout) - # marked route settings + # marked route penalty calculation settings self.marked_route_tab = QWidget() self.mr_layout = QFormLayout() self.mr_off_radio = QRadioButton(translate('no penalty')) + self.mr_off_radio.setToolTip(translate('No penalty')) + self.mr_off_radio.toggled.connect(self.penalty_calculation_mode) self.mr_layout.addRow(self.mr_off_radio) self.mr_time_radio = QRadioButton(translate('penalty time')) + self.mr_time_radio.setToolTip( + translate('Penalty calculation mode: penalty time') + ) + self.mr_time_radio.toggled.connect(self.penalty_calculation_mode) self.mr_time_edit = AdvTimeEdit(display_format=self.time_format) self.mr_layout.addRow(self.mr_time_radio, self.mr_time_edit) self.mr_laps_radio = QRadioButton(translate('penalty laps')) + self.mr_laps_radio.setToolTip( + translate('Penalty calculation mode: penalty laps') + ) + self.mr_laps_radio.toggled.connect(self.penalty_calculation_mode) self.mr_layout.addRow(self.mr_laps_radio) self.mr_counting_lap_check = QCheckBox(translate('counting lap')) self.mr_layout.addRow(self.mr_counting_lap_check) @@ -199,8 +209,10 @@ def init_ui(self): self.mr_lap_station_edit = AdvSpinBox(max_width=50) self.mr_layout.addRow(self.mr_lap_station_check, self.mr_lap_station_edit) self.mr_dont_dqs_check = QCheckBox(translate("Don't disqualify")) + self.mr_dont_dqs_check.setToolTip(translate("Don't disqualify")) self.mr_layout.addRow(self.mr_dont_dqs_check) self.mr_max_penalty_by_cp = QCheckBox(translate('Max penalty = quantity of cp')) + self.mr_max_penalty_by_cp.setToolTip(translate('Max penalty = quantity of cp')) self.mr_layout.addRow(self.mr_max_penalty_by_cp) self.marked_route_tab.setLayout(self.mr_layout) @@ -305,6 +317,32 @@ def on_assignment_mode(self): self.chip_reading_box.setDisabled(mode) self.chip_duplicate_box.setDisabled(mode) + def penalty_calculation_mode(self): + self.mr_time_edit.setDisabled(not self.mr_time_radio.isChecked()) + self.mr_counting_lap_check.setDisabled( + not ( + self.mr_laps_radio.isChecked() + and not self.mr_lap_station_check.isChecked() + ) + ) + self.mr_lap_station_check.setDisabled( + not ( + self.mr_laps_radio.isChecked() + and not self.mr_counting_lap_check.isChecked() + ) + ) + self.mr_lap_station_edit.setDisabled( + not ( + self.mr_laps_radio.isChecked() and self.mr_lap_station_check.isChecked() + ) + ) + self.mr_dont_dqs_check.setDisabled( + not (self.mr_laps_radio.isChecked() or self.mr_time_radio.isChecked()) + ) + self.mr_max_penalty_by_cp.setDisabled( + not (self.mr_laps_radio.isChecked() or self.mr_time_radio.isChecked()) + ) + def set_values_from_model(self): cur_race = race() zero_time = cur_race.get_setting('system_zero_time', (8, 0, 0)) @@ -417,7 +455,7 @@ def set_values_from_model(self): mr_penalty_time = OTime( msec=obj.get_setting('marked_route_penalty_time', 60000) ) - mr_if_counting_lap = obj.get_setting('marked_route_if_counting_lap', True) + mr_if_counting_lap = obj.get_setting('marked_route_if_counting_lap', False) mr_if_station_check = obj.get_setting('marked_route_if_station_check', False) mr_station_code = obj.get_setting('marked_route_station_code', 80) mr_if_dont_dsq_check = obj.get_setting('marked_route_dont_dsq', False) @@ -596,7 +634,7 @@ def apply_changes_impl(self): obj.set_setting('marked_route_penalty_time', mr_penalty_time) obj.set_setting('marked_route_if_counting_lap', mr_if_counting_lap) obj.set_setting('marked_route_if_station_check', mr_if_station_check) - obj.set_setting('marked_route_station_code', mr_station_code) + obj.set_setting('marked_route_penalty_lap_station_code', mr_station_code) obj.set_setting('marked_route_dont_dsq', mr_if_dont_dsq) obj.set_setting('marked_route_max_penalty_by_cp', mr_if_max_penalty_by_cp) diff --git a/sportorg/gui/menu/actions.py b/sportorg/gui/menu/actions.py index 024fa560..f394f3c4 100644 --- a/sportorg/gui/menu/actions.py +++ b/sportorg/gui/menu/actions.py @@ -594,6 +594,7 @@ def execute(self): for result in race().results: if result.person: ResultChecker.calculate_penalty(result) + ResultChecker.checking(result) logging.debug('Penalty calculation finish') ResultCalculation(race()).process_results() self.app.refresh() diff --git a/sportorg/libs/template/template.py b/sportorg/libs/template/template.py index 5ab6e18c..47f3f8fb 100644 --- a/sportorg/libs/template/template.py +++ b/sportorg/libs/template/template.py @@ -31,6 +31,19 @@ def date(value, fmt=None): return dateutil.parser.parse(value).strftime(fmt) +def plural(value, fmt=None): + if value is None: + return '' + if 5 <= abs(value) <= 19: + return 'ов' + elif abs(value) % 10 == 1: + return '' + elif abs(value) % 10 in (2, 3, 4): + return 'а' + else: + return 'ов' + + def finalize(thing): return thing if thing else '' @@ -50,6 +63,7 @@ def get_text_from_template(searchpath: str, path: str, **kwargs): env.filters['tohhmmss'] = to_hhmmss env.filters['date'] = date env.policies['json.dumps_kwargs']['ensure_ascii'] = False + env.filters['plural'] = plural template = env.get_template(path) return template.render(**kwargs) diff --git a/sportorg/models/memory.py b/sportorg/models/memory.py index c7e20cee..813bdf3a 100644 --- a/sportorg/models/memory.py +++ b/sportorg/models/memory.py @@ -93,6 +93,7 @@ class ResultStatus(_TitleType): DID_NOT_ENTER = 14 CANCELLED = 15 RESTORED = 16 + MISS_PENALTY_LAP = 17 class Organization(Model): diff --git a/sportorg/models/result/result_checker.py b/sportorg/models/result/result_checker.py index dcb46d96..c7e228ca 100644 --- a/sportorg/models/result/result_checker.py +++ b/sportorg/models/result/result_checker.py @@ -49,12 +49,15 @@ def checking(cls, result): ResultStatus.OK, ResultStatus.MISSING_PUNCH, ResultStatus.OVERTIME, + ResultStatus.MISS_PENALTY_LAP, ]: result.status = ResultStatus.OK if not o.check_result(result): result.status = ResultStatus.MISSING_PUNCH result.status_comment = 'п.п.3.13.12.2' + elif not cls.check_penalty_laps(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': @@ -72,7 +75,7 @@ def check_all(cls): ResultChecker.calculate_penalty(result) @staticmethod - def calculate_penalty(result): + def calculate_penalty(result: Result): mode = race().get_setting('marked_route_mode', 'off') if mode == 'off': return @@ -89,16 +92,19 @@ def calculate_penalty(result): return controls = course.controls + splits = result.splits + + if mode == 'laps' and race().get_setting('marked_route_if_station_check'): + lap_station = race().get_setting('marked_route_penalty_lap_station_code') + splits, _ = ResultChecker.detach_penalty_laps2(splits, lap_station) if race().get_setting('marked_route_dont_dsq', False): # free order, don't penalty for extra cp - penalty = ResultChecker.penalty_calculation_free_order( - result.splits, controls - ) + penalty = ResultChecker.penalty_calculation_free_order(splits, controls) else: # marked route with penalty penalty = ResultChecker.penalty_calculation( - result.splits, controls, check_existence=True + splits, controls, check_existence=True ) if race().get_setting('marked_route_max_penalty_by_cp', False): @@ -167,9 +173,12 @@ def penalty_calculation(splits, controls, check_existence=False): // returns 1 if check_existence=True ``` """ + user_array = [i.code for i in splits] origin_array = [i.get_number_code() for i in controls] res = 0 + + # может дать 0 штрафа при мусоре в чипе if check_existence and len(user_array) < len(origin_array): # add 1 penalty score for missing points res = len(origin_array) - len(user_array) @@ -232,6 +241,48 @@ def penalty_calculation_free_order(splits, controls): return res + @staticmethod + def detach_penalty_laps(splits, lap_station): + if not splits: + return [], [] + for idx, punch in enumerate(reversed(splits)): + if int(punch.code) != lap_station: + break + else: + idx = len(splits) + idx = len(splits) - idx + return splits[:idx], splits[idx:] + + @staticmethod + def detach_penalty_laps2(splits, lap_station): + """Walkaround: извлекает отметки на штрафной станции. + Наивный метод, надо учитывать, что штрафные КП должны относиться + к пункту оценки, а не появляться из неочищенного чипа""" + if not splits: + return [], [] + regular = [punch for punch in splits if int(punch.code) != lap_station] + penalty = [punch for punch in splits if int(punch.code) == lap_station] + return regular, penalty + + @staticmethod + def check_penalty_laps(result): + assert isinstance(result, Result) + + mode = race().get_setting('marked_route_mode', 'off') + check_laps = race().get_setting('marked_route_if_station_check') + + if mode == 'laps' and check_laps: + lap_station = race().get_setting('marked_route_penalty_lap_station_code') + _, penalty_laps = ResultChecker.detach_penalty_laps2( + result.splits, lap_station + ) + num_penalty_laps = len(penalty_laps) + + if num_penalty_laps < result.penalty_laps: + return False + + return True + @staticmethod def get_control_score(code): obj = race() diff --git a/sportorg/modules/printing/printout_split.py b/sportorg/modules/printing/printout_split.py index ceb5c093..14247f2e 100644 --- a/sportorg/modules/printing/printout_split.py +++ b/sportorg/modules/printing/printout_split.py @@ -1,7 +1,7 @@ import platform from sportorg.language import translate -from sportorg.models.memory import Group, ResultStatus, race +from sportorg.models.memory import Group, Result, ResultStatus, race from sportorg.models.result.result_calculation import ResultCalculation if platform.system() == 'Windows': # current realisation works on Windows only @@ -56,6 +56,83 @@ def print_line(self, text, font_name, font_size, font_weight=400): self.move_cursor(font_size * 1.3) def print_split(self, result): + if not race().get_setting('marked_route_if_counting_lap', False): + # Обычный сплит + self.print_split_normal(result) + else: + # Печать штрафа на пункте оценки: сверху номер, снизу штраф + self.print_penalty_laps(result) + + def print_penalty_laps(self, result: Result): + person = result.person + if person is None: + return + + for _ in range(20): + self.print_line('.', 'Arial', 1) # empty vertical space + self.print_bib_line(result) + for _ in range(7): + self.print_line('.', 'Arial', 1) # empty vertical space + self.print_penalty_line(result) + + def print_bib_line(self, result: Result): + text = str(result.person.bib) + + font_name = 'Arial Black' + font_size = 50 + font_weight = 400 + + font = win32ui.CreateFont( + { + 'name': font_name, + 'height': int(self.scale_factor * font_size), + 'weight': font_weight, + } + ) + self.dc.SelectObject(font) + self.dc.TextOut(self.x, self.y, str(text)) + + self.move_cursor(font_size * 1.3) + + def print_penalty_line(self, result: Result): + text = str(result.penalty_laps).rjust(2) + + font_name = 'Arial Black' + font_size = 50 + font_weight = 400 + + font = win32ui.CreateFont( + { + 'name': font_name, + 'height': int(self.scale_factor * font_size), + 'weight': font_weight, + } + ) + self.dc.SelectObject(font) + self.dc.TextOut(self.x, self.y, str(text)) + + dx1, dy1 = self.dc.GetTextExtent(str(text)) + + text_small = ' ' + translate('laps') + font_name_small = 'Arial' + font_size_small = 15 + font_weight = 400 + + font = win32ui.CreateFont( + { + 'name': font_name_small, + 'height': int(self.scale_factor * font_size_small), + 'weight': font_weight, + } + ) + self.dc.SelectObject(font) + _, dy2 = self.dc.GetTextExtent(str(text_small)) + dy = int(0.8 * (dy1 - dy2)) # calculate font baseline position + self.dc.TextOut(self.x + dx1, self.y - dy, str(text_small)) + + self.move_cursor(font_size * 1.3) + + def print_split_normal(self, result): obj = race() person = result.person diff --git a/sportorg/modules/sportident/result_generation.py b/sportorg/modules/sportident/result_generation.py index 270b4561..3d63599b 100644 --- a/sportorg/modules/sportident/result_generation.py +++ b/sportorg/modules/sportident/result_generation.py @@ -151,6 +151,7 @@ def _merge_punches(self): if existing_res.merge_with(self._result): # existing result changed, recalculate group results and printout self._result = existing_res + ResultChecker.checking(self._result) ResultChecker.calculate_penalty(self._result) ResultChecker.checking(self._result) self.popup_result(self._result) @@ -202,6 +203,7 @@ def _add_result(self): try: ResultChecker.checking(self._result) ResultChecker.calculate_penalty(self._result) + ResultChecker.checking(self._result) except ResultCheckerException as e: logging.error(str(e)) diff --git a/templates/split/1_split_printout.html b/templates/split/1_split_printout.html index fc10a5d8..e37f6281 100644 --- a/templates/split/1_split_printout.html +++ b/templates/split/1_split_printout.html @@ -175,6 +175,11 @@

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

{% endfor %}
Финиш: {{result.finish_msec|tohhmmss}} {{result.speed}} + {% if race['settings'].get('marked_route_mode', 'off') == 'laps' %} +
Штраф: {{result.penalty_laps}} круг{{result.penalty_laps|plural}} + {% elif race['settings'].get('marked_route_mode', 'off') == 'time' %} +
Штраф: {{result.penalty_time|tohhmmss}} + {% endif %}
Результат: {{result.result}} {% if result.place > 0 %}
Место: {{result.place}} из {{group.count_finished}} (всего {{group.count_person}}) diff --git a/templates/split/2_marked_route_penalty_A5.html b/templates/split/2_marked_route_penalty_A5.html new file mode 100644 index 00000000..f11a35c7 --- /dev/null +++ b/templates/split/2_marked_route_penalty_A5.html @@ -0,0 +1,48 @@ + + + + + + {{name}} Split + {% raw %} + + {% endraw %} + + + + +

 

+

+ + {{person.bib}} +

+

 

+
+

 

+

+ {{result.penalty_laps}} + кр. +

+
+ + + \ No newline at end of file diff --git a/templates/split/3_marked_route_penalty_80mm.html b/templates/split/3_marked_route_penalty_80mm.html new file mode 100644 index 00000000..04fe7a47 --- /dev/null +++ b/templates/split/3_marked_route_penalty_80mm.html @@ -0,0 +1,54 @@ + + + + + + {{name}} Split + {% raw %} + + {% endraw %} + + + + +
+

+ + {{person.bib}} +

+
+
+

 

+

+ {% if result.penalty_laps %}{{result.penalty_laps}}{% else %}0{% endif %} + кр. +

+
+ + + + \ No newline at end of file diff --git a/tests/test_penalty_checking.py b/tests/test_penalty_checking.py index 1131a470..b1fa4d4f 100644 --- a/tests/test_penalty_checking.py +++ b/tests/test_penalty_checking.py @@ -63,106 +63,64 @@ def test_marked_route_yes_no(): отметиться в станции НЕТ. За неправильную отметку спортсмен получает штраф. За отсутствующую отметку спортсмен должен быть дисквалифицирован. """ + # fmt: off create_race() race().set_setting('marked_route_mode', 'time') # Отметка ок, без штрафа - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[31, 41, 51, 100], - penalty=0, - ) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 31, 41, 51, 100], penalty=0) # Отметка ок, 1 штраф - неверный выбор на одном КП - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[31, 42, 51, 100], - penalty=1, - ) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 31, 42, 51, 100], penalty=1) # Отметка ок, 3 штрафа - неверный выбор на трёх КП - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[32, 42, 52, 100], - penalty=3, - ) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 32, 42, 52, 100], penalty=3) # Отметка ок, 0 штрафа - лишняя отметка на одном истинном КП - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[31, 31, 41, 51, 100], - penalty=0, - ) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 31, 31, 41, 51, 100], penalty=0) # Отметка ок, 1 штраф - лишняя отметка на одном ложном КП - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[31, 41, 52, 52, 100], - penalty=1, - ) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 31, 41, 52, 52, 100], penalty=1) # Отметка ок, 1 штраф - отметка на обоих станциях на одном КП - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[32, 31, 41, 51, 100], - penalty=1, - ) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 32, 31, 41, 51, 100], penalty=1) # Дисквалифицирован, 1 штраф - отсутствует отметка на одном КП - assert dsq( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[31, 51, 100], - penalty=1, - ) + assert dsq(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 31, 51, 100], penalty=1) # Дисквалифицирован, 1 штраф - отсутствует отметка на последнем КП100 - assert dsq( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[ - 31, - 41, - 51, - ], - penalty=1, - ) + assert dsq(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 31, 41, 51, ], penalty=1) # Дисквалифицирован, 4 штрафа - пустой чип - assert dsq( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], splits=[], penalty=4 - ) + assert dsq(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ ], penalty=4) # Отметка ок, 0 штрафа - отметка на КП, которого нет на карте - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[31, 75, 41, 51, 100], - penalty=0, - ) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 31, 75, 41, 51, 100], penalty=0) # Отметка ок, 0 штрафа - отметка на КП, которого нет на карте - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[31, 75, 41, 51, 100], - penalty=0, - ) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 31, 75, 41, 51, 100], penalty=0) # Отметка ок, 0 штрафа - неочищенный чип НЕ содержит часть дистанции - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[70, 71, 72, 73, 31, 41, 51, 100], - penalty=0, - ) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 70, 71, 72, 73, 31, 41, 51, 100], penalty=0) # Отметка ок, 1 штраф - неочищенный чип содержит часть дистанции - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[40, 41, 42, 43, 31, 41, 51, 100], - penalty=1, - ) - assert ok( - course=['31(31,32)', '41(41,42)', '51(51,52)', 100], - splits=[40, 41, 42, 43, 31, 42, 51, 100], - penalty=1, - ) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 40, 41, 42, 43, 31, 41, 51, 100], penalty=1) + assert ok(course=['31(31,32)', '41(41,42)', '51(51,52)', 100], + splits=[ 40, 41, 42, 43, 31, 42, 51, 100], penalty=1) + # fmt: on def test_penalty_calculation_function(): @@ -172,118 +130,124 @@ def test_penalty_calculation_function(): так как с параметрами penalty_calculation(<...>, check_existence=False) эта функция нигде не вызывается. """ + # fmt: off create_race() race().set_setting('marked_route_mode', 'time') # return quantity of incorrect or duplicated punches, order is ignored # origin: 31,41,51; athlete: 31,41,51; result:0 - assert ok(course=[31, 41, 51], splits=[31, 41, 51], penalty=0) + assert ok(course=[31, 41, 51], + splits=[31, 41, 51], penalty=0) # origin: 31,41,51; athlete: 31; result:0 # check_existence=False assert 0 == ResultChecker.penalty_calculation( splits=make_splits([31]), - controls=make_course_controls([31, 41, 51]), - check_existence=False, - ) + controls=make_course_controls([31, 41, 51]), check_existence=False) # origin: 31,41,51; athlete: 31; result:2 # check_existence=True - assert dsq( - course=[31, 41, 51], - splits=[ - 31, - ], - penalty=2, - ) + assert dsq(course=[31, 41, 51], + splits=[31, ], penalty=2) # origin: 31,41,51; athlete: 41,31,51; result:0 - assert dsq(course=[31, 41, 51], splits=[41, 31, 51], penalty=0) + assert dsq(course=[31, 41, 51], + splits=[41, 31, 51], penalty=0) # origin: 31,41,51; athlete: 41,31,51; result:0 - assert dsq(course=[31, 41, 51], splits=[41, 31, 51], penalty=0) + assert dsq(course=[31, 41, 51], + splits=[41, 31, 51], penalty=0) # origin: 31,41,51; athlete: 31,42,51; result:1 - assert dsq(course=[31, 41, 51], splits=[31, 42, 51], penalty=1) + assert dsq(course=[31, 41, 51], + splits=[31, 42, 51], penalty=1) # origin: 31,41,51; athlete: 31,41,51,52; result:1 - assert ok(course=[31, 41, 51], splits=[31, 41, 51, 52], penalty=1) + assert ok(course=[31, 41, 51], + splits=[31, 41, 51, 52], penalty=1) # origin: 31,41,51; athlete: 31,42,51,52; result:2 - assert dsq(course=[31, 41, 51], splits=[31, 42, 51, 52], penalty=2) + assert dsq(course=[31, 41, 51], + splits=[31, 42, 51, 52], penalty=2) # origin: 31,41,51; athlete: 31,31,41,51; result:1 - assert ok(course=[31, 41, 51], splits=[31, 31, 41, 51], penalty=1) + assert ok(course=[31, 41, 51], + splits=[31, 31, 41, 51], penalty=1) # origin: 31,41,51; athlete: 31,41,51,51; result:1 - assert ok(course=[31, 41, 51], splits=[31, 41, 51, 51], penalty=1) + assert ok(course=[31, 41, 51], + splits=[31, 41, 51, 51], penalty=1) # origin: 31,41,51; athlete: 32,42,52; result:3 - assert dsq(course=[31, 41, 51], splits=[32, 42, 52], penalty=3) + assert dsq(course=[31, 41, 51], + splits=[32, 42, 52], penalty=3) # origin: 31,41,51; athlete: 31,41,51,61,71,81,91; result:4 - assert ok(course=[31, 41, 51], splits=[31, 41, 51, 61, 71, 81, 91], penalty=4) + assert ok(course=[31, 41, 51], + splits=[31, 41, 51, 61, 71, 81, 91], penalty=4) # origin: 31,41,51; athlete: 31,41,52,61,71,81,91; result:5 - assert dsq(course=[31, 41, 51], splits=[31, 41, 52, 61, 71, 81, 91], penalty=5) + assert dsq(course=[31, 41, 51], + splits=[31, 41, 52, 61, 71, 81, 91], penalty=5) # origin: 31,41,51; athlete: 51,61,71,81,91,31,41; result:4 - assert dsq(course=[31, 41, 51], splits=[51, 61, 71, 81, 91, 31, 41], penalty=4) + assert dsq(course=[31, 41, 51], + splits=[51, 61, 71, 81, 91, 31, 41], penalty=4) # origin: 31,41,51; athlete: 51,61,71,81,91,32,41; result:5 - assert dsq(course=[31, 41, 51], splits=[51, 61, 71, 81, 91, 32, 41], penalty=5) + assert dsq(course=[31, 41, 51], + splits=[51, 61, 71, 81, 91, 32, 41], penalty=5) # origin: 31,41,51; athlete: 51,61,71,81,91,32,42; result:6 - assert dsq(course=[31, 41, 51], splits=[51, 61, 71, 81, 91, 32, 42], penalty=6) + assert dsq(course=[31, 41, 51], + splits=[51, 61, 71, 81, 91, 32, 42], penalty=6) # origin: 31,41,51; athlete: 52,61,71,81,91,32,42; result:7 - assert dsq(course=[31, 41, 51], splits=[52, 61, 71, 81, 91, 32, 42], penalty=7) + assert dsq(course=[31, 41, 51], + splits=[52, 61, 71, 81, 91, 32, 42], penalty=7) # origin: 31,41,51; athlete: no punches; result:0 # check_existence=False assert 0 == ResultChecker.penalty_calculation( splits=make_splits([]), - controls=make_course_controls([31, 41, 51]), - check_existence=False, - ) + controls=make_course_controls([31, 41, 51]), check_existence=False) # origin: 31,41,51; athlete: no punches; result:3 # check_existence=True - assert dsq(course=[31, 41, 51], splits=[], penalty=3) + assert dsq(course=[31, 41, 51], + splits=[], penalty=3) # wildcard support for free order # origin: *,*,* athlete: 31; result:2 # check_existence=False assert 0 == ResultChecker.penalty_calculation( splits=make_splits([31]), - controls=make_course_controls(['*', '*', '*']), - check_existence=False, - ) + controls=make_course_controls(['*', '*', '*']), check_existence=False) # check_existence=True - assert dsq(course=['*', '*', '*'], splits=[31], penalty=2) + assert dsq(course=['*', '*', '*'], + splits=[31], penalty=2) # origin: *,*,* athlete: 31,31; result:2 //wrong # check_existence=False assert 0 == ResultChecker.penalty_calculation( splits=make_splits([31, 31]), - controls=make_course_controls(['*', '*', '*']), - check_existence=False, - ) + controls=make_course_controls(['*', '*', '*']), check_existence=False) # check_existence=True - assert dsq(course=['*', '*', '*'], splits=[31, 31], penalty=1) + assert dsq(course=['*', '*', '*'], + splits=[31, 31], penalty=1) # origin: *,*,* athlete: 31,31,31,31; result:3 //wrong # check_existence=False assert 1 == ResultChecker.penalty_calculation( splits=make_splits([31, 31, 31, 31]), - controls=make_course_controls(['*', '*', '*']), - check_existence=False, - ) + controls=make_course_controls(['*', '*', '*']), check_existence=False) # check_existence=True - assert dsq(course=['*', '*', '*'], splits=[31, 31, 31, 31], penalty=1) + assert dsq(course=['*', '*', '*'], + splits=[31, 31, 31, 31], penalty=1) + # fmt: on def test_non_obvious_behavior(): @@ -305,6 +269,11 @@ def test_non_obvious_behavior(): assert dsq(course=['31', '41(41,42)'], splits=[39, 41], penalty=0) assert dsq(course=['31', '41(41,42)'], splits=[31, 49], penalty=0) + # Различные способы задания дистанции приводят к различному + # начислению штрафа за лишние отметки + assert ok(course=['31', '41'], splits=[31, 77, 41], penalty=1) + assert ok(course=['31', '41(41,42)'], splits=[31, 77, 41], penalty=0) + def ok( course: List[Union[int, str]], From 0553f0654aebd1eff5152161045fd0b1a016b663 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpov Date: Tue, 28 Nov 2023 16:35:04 +0700 Subject: [PATCH 2/9] fix: remove unused 'missing penalty laps' string --- languages/ru_RU/LC_MESSAGES/sportorg.po | 3 --- 1 file changed, 3 deletions(-) diff --git a/languages/ru_RU/LC_MESSAGES/sportorg.po b/languages/ru_RU/LC_MESSAGES/sportorg.po index 0bbf2819..f118b155 100644 --- a/languages/ru_RU/LC_MESSAGES/sportorg.po +++ b/languages/ru_RU/LC_MESSAGES/sportorg.po @@ -1545,9 +1545,6 @@ msgstr "СОШЕЛ" msgid "Disqualified" msgstr "ДИСКВ." -msgid "Missing penalty lap" -msgstr "Пропущен штрафной круг" - msgid "laps" msgstr "кр." From 84460378c16e342b65a2e95bd94a13c647c60c95 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpov Date: Tue, 28 Nov 2023 16:36:11 +0700 Subject: [PATCH 3/9] fix: add missing checking() --- sportorg/gui/menu/actions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sportorg/gui/menu/actions.py b/sportorg/gui/menu/actions.py index f394f3c4..4a65c95d 100644 --- a/sportorg/gui/menu/actions.py +++ b/sportorg/gui/menu/actions.py @@ -593,6 +593,7 @@ def execute(self): logging.debug('Penalty calculation start') for result in race().results: if result.person: + ResultChecker.checking(result) ResultChecker.calculate_penalty(result) ResultChecker.checking(result) logging.debug('Penalty calculation finish') From 782eff673bab27d9ebaff1b218f7eed593ffe3d5 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpov Date: Tue, 28 Nov 2023 16:38:08 +0700 Subject: [PATCH 4/9] fix: rename setting penalty lap station code --- sportorg/gui/dialogs/timekeeping_properties.py | 2 +- sportorg/models/result/result_checker.py | 2 +- tests/test_penalty_checking.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sportorg/gui/dialogs/timekeeping_properties.py b/sportorg/gui/dialogs/timekeeping_properties.py index d1782820..d2a09f0f 100644 --- a/sportorg/gui/dialogs/timekeeping_properties.py +++ b/sportorg/gui/dialogs/timekeeping_properties.py @@ -457,7 +457,7 @@ def set_values_from_model(self): ) mr_if_counting_lap = obj.get_setting('marked_route_if_counting_lap', False) mr_if_station_check = obj.get_setting('marked_route_if_station_check', False) - mr_station_code = obj.get_setting('marked_route_station_code', 80) + mr_station_code = obj.get_setting('marked_route_penalty_lap_station_code', 80) mr_if_dont_dsq_check = obj.get_setting('marked_route_dont_dsq', False) mr_if_max_penalty_by_cp = obj.get_setting( 'marked_route_max_penalty_by_cp', False diff --git a/sportorg/models/result/result_checker.py b/sportorg/models/result/result_checker.py index c7e228ca..02035144 100644 --- a/sportorg/models/result/result_checker.py +++ b/sportorg/models/result/result_checker.py @@ -332,7 +332,7 @@ def marked_route_check_penalty_laps(result: Result): mr_if_counting_lap = obj.get_setting('marked_route_if_counting_lap', False) mr_if_station_check = obj.get_setting('marked_route_if_station_check', False) - mr_station_code = obj.get_setting('marked_route_station_code', 0) + mr_station_code = obj.get_setting('marked_route_penalty_lap_station_code', 0) if mr_if_station_check and int(mr_station_code) > 0: count_laps = 0 diff --git a/tests/test_penalty_checking.py b/tests/test_penalty_checking.py index b1fa4d4f..ac3f2a2c 100644 --- a/tests/test_penalty_checking.py +++ b/tests/test_penalty_checking.py @@ -28,8 +28,8 @@ # default value: marked_route_if_counting_lap = True # marked_route_if_station_check [bool] - режим проверки количества пройденных штрафных кругов # default value: marked_route_if_station_check = False -# marked_route_station_code [int] - номер станции на штрафном круге -# default value: marked_route_station_code = 80 +# marked_route_penalty_lap_station_code [int] - номер станции на штрафном круге +# default value: marked_route_penalty_lap_station_code = 80 # marked_route_dont_dsq [bool] - дисквалифицировать ли спортсмена за пропущенные КП # True - проверяет функцией penalty_calculation_free_order() # False - проверяет функцией penalty_calculation(<...>, check_existence=True) From e87b8d8a60d413f6fbdc5a6160c0769d28369035 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpov Date: Tue, 28 Nov 2023 16:47:10 +0700 Subject: [PATCH 5/9] fix: fix enable/disable widgets in penalty properties window --- .../gui/dialogs/timekeeping_properties.py | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/sportorg/gui/dialogs/timekeeping_properties.py b/sportorg/gui/dialogs/timekeeping_properties.py index d2a09f0f..dfe34fde 100644 --- a/sportorg/gui/dialogs/timekeeping_properties.py +++ b/sportorg/gui/dialogs/timekeeping_properties.py @@ -204,8 +204,18 @@ def init_ui(self): self.mr_laps_radio.toggled.connect(self.penalty_calculation_mode) self.mr_layout.addRow(self.mr_laps_radio) self.mr_counting_lap_check = QCheckBox(translate('counting lap')) + self.mr_counting_lap_check.setToolTip( + translate('Operating mode: evaluation point') + '\n' + + translate('Print number of penalty laps instead of splits') + '\n' + + translate('when competitor reads out his card')) + self.mr_counting_lap_check.stateChanged.connect(self.penalty_calculation_mode) self.mr_layout.addRow(self.mr_counting_lap_check) self.mr_lap_station_check = QCheckBox(translate('lap station')) + self.mr_lap_station_check.setToolTip( + translate('Station number on the penalty lap') + '\n' + + translate('Competitor must punch at station') + '\n' + + translate('every time he/she passes a penalty lap')) + self.mr_lap_station_check.stateChanged.connect(self.penalty_calculation_mode) self.mr_lap_station_edit = AdvSpinBox(max_width=50) self.mr_layout.addRow(self.mr_lap_station_check, self.mr_lap_station_edit) self.mr_dont_dqs_check = QCheckBox(translate("Don't disqualify")) @@ -318,29 +328,28 @@ def on_assignment_mode(self): self.chip_duplicate_box.setDisabled(mode) def penalty_calculation_mode(self): - self.mr_time_edit.setDisabled(not self.mr_time_radio.isChecked()) + self.mr_time_edit.setDisabled( + not self.mr_time_radio.isChecked() + ) self.mr_counting_lap_check.setDisabled( - not ( - self.mr_laps_radio.isChecked() - and not self.mr_lap_station_check.isChecked() - ) + not (self.mr_laps_radio.isChecked() and + not self.mr_lap_station_check.isChecked()) ) self.mr_lap_station_check.setDisabled( - not ( - self.mr_laps_radio.isChecked() - and not self.mr_counting_lap_check.isChecked() - ) + not (self.mr_laps_radio.isChecked() and + not self.mr_counting_lap_check.isChecked()) ) self.mr_lap_station_edit.setDisabled( - not ( - self.mr_laps_radio.isChecked() and self.mr_lap_station_check.isChecked() - ) + not (self.mr_laps_radio.isChecked() and + self.mr_lap_station_check.isChecked()) ) self.mr_dont_dqs_check.setDisabled( - not (self.mr_laps_radio.isChecked() or self.mr_time_radio.isChecked()) + not (self.mr_laps_radio.isChecked() or + self.mr_time_radio.isChecked()) ) self.mr_max_penalty_by_cp.setDisabled( - not (self.mr_laps_radio.isChecked() or self.mr_time_radio.isChecked()) + not (self.mr_laps_radio.isChecked() or + self.mr_time_radio.isChecked()) ) def set_values_from_model(self): From 78aa7dbb3ffd4b00445295ca7ed8ecd6b96b08fa Mon Sep 17 00:00:00 2001 From: Aleksandr Karpov Date: Tue, 28 Nov 2023 16:57:44 +0700 Subject: [PATCH 6/9] style: fix formatting --- .../gui/dialogs/timekeeping_properties.py | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/sportorg/gui/dialogs/timekeeping_properties.py b/sportorg/gui/dialogs/timekeeping_properties.py index dfe34fde..78a6cfbd 100644 --- a/sportorg/gui/dialogs/timekeeping_properties.py +++ b/sportorg/gui/dialogs/timekeeping_properties.py @@ -205,16 +205,22 @@ def init_ui(self): self.mr_layout.addRow(self.mr_laps_radio) self.mr_counting_lap_check = QCheckBox(translate('counting lap')) self.mr_counting_lap_check.setToolTip( - translate('Operating mode: evaluation point') + '\n' + - translate('Print number of penalty laps instead of splits') + '\n' + - translate('when competitor reads out his card')) + translate('Operating mode: evaluation point') + + '\n' + + translate('Print number of penalty laps instead of splits') + + '\n' + + translate('when competitor reads out his card') + ) self.mr_counting_lap_check.stateChanged.connect(self.penalty_calculation_mode) self.mr_layout.addRow(self.mr_counting_lap_check) self.mr_lap_station_check = QCheckBox(translate('lap station')) self.mr_lap_station_check.setToolTip( - translate('Station number on the penalty lap') + '\n' + - translate('Competitor must punch at station') + '\n' + - translate('every time he/she passes a penalty lap')) + translate('Station number on the penalty lap') + + '\n' + + translate('Competitor must punch at station') + + '\n' + + translate('every time he/she passes a penalty lap') + ) self.mr_lap_station_check.stateChanged.connect(self.penalty_calculation_mode) self.mr_lap_station_edit = AdvSpinBox(max_width=50) self.mr_layout.addRow(self.mr_lap_station_check, self.mr_lap_station_edit) @@ -328,28 +334,29 @@ def on_assignment_mode(self): self.chip_duplicate_box.setDisabled(mode) def penalty_calculation_mode(self): - self.mr_time_edit.setDisabled( - not self.mr_time_radio.isChecked() - ) + self.mr_time_edit.setDisabled(not self.mr_time_radio.isChecked()) self.mr_counting_lap_check.setDisabled( - not (self.mr_laps_radio.isChecked() and - not self.mr_lap_station_check.isChecked()) + not ( + self.mr_laps_radio.isChecked() + and not self.mr_lap_station_check.isChecked() + ) ) self.mr_lap_station_check.setDisabled( - not (self.mr_laps_radio.isChecked() and - not self.mr_counting_lap_check.isChecked()) + not ( + self.mr_laps_radio.isChecked() + and not self.mr_counting_lap_check.isChecked() + ) ) self.mr_lap_station_edit.setDisabled( - not (self.mr_laps_radio.isChecked() and - self.mr_lap_station_check.isChecked()) + not ( + self.mr_laps_radio.isChecked() and self.mr_lap_station_check.isChecked() + ) ) self.mr_dont_dqs_check.setDisabled( - not (self.mr_laps_radio.isChecked() or - self.mr_time_radio.isChecked()) + not (self.mr_laps_radio.isChecked() or self.mr_time_radio.isChecked()) ) self.mr_max_penalty_by_cp.setDisabled( - not (self.mr_laps_radio.isChecked() or - self.mr_time_radio.isChecked()) + not (self.mr_laps_radio.isChecked() or self.mr_time_radio.isChecked()) ) def set_values_from_model(self): From ae5d289013d0751849ae814190fadeb8a6075755 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpov Date: Mon, 18 Dec 2023 23:13:03 +0700 Subject: [PATCH 7/9] fix: update tool tips to fix multiline strings --- languages/ru_RU/LC_MESSAGES/sportorg.po | 32 +++++++++---------- .../gui/dialogs/timekeeping_properties.py | 20 ++++++------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/languages/ru_RU/LC_MESSAGES/sportorg.po b/languages/ru_RU/LC_MESSAGES/sportorg.po index f118b155..7d96a281 100644 --- a/languages/ru_RU/LC_MESSAGES/sportorg.po +++ b/languages/ru_RU/LC_MESSAGES/sportorg.po @@ -669,26 +669,26 @@ msgstr "Режим начисления штрафа: штрафные круг msgid "counting lap" msgstr "оценочный круг" -msgid "Operating mode: evaluation point" -msgstr "Режим работы: оценочный круг" - -msgid "Print number of penalty laps instead of splits" -msgstr "При считывании печатается распечатка" - -msgid "when competitor reads out his card" -msgstr "с количеством штрафных кругов" +msgid "" +"Operating mode: evaluation point\n" +"Print the number of penalty laps instead of splits\n" +"when a competitor reads out his card" +msgstr "" +"Режим работы: оценочный круг\n" +"При считывании печатать распечатку\n" +"с количеством штрафных кругов" msgid "lap station" msgstr "станция на штрафном круге" -msgid "Station number on the penalty lap" -msgstr "Номер станции на штрафном круге" - -msgid "Competitor must punch at station" -msgstr "Спортсмен должен отметиться на станции" - -msgid "every time he/she passes a penalty lap" -msgstr "при каждом прохождении штрафного круга" +msgid "" +"Station number on the penalty lap\n" +"A competitor must punch at the station\n" +"each time they pass the penalty lap" +msgstr "" +"Номер станции на штрафном круге\n" +"Спортсмен должен отметиться на станции\n" +"при каждом прохождении штрафного круга" msgid "scores off" msgstr "очки не рассчитываются" diff --git a/sportorg/gui/dialogs/timekeeping_properties.py b/sportorg/gui/dialogs/timekeeping_properties.py index 78a6cfbd..6fa5fcc1 100644 --- a/sportorg/gui/dialogs/timekeeping_properties.py +++ b/sportorg/gui/dialogs/timekeeping_properties.py @@ -205,21 +205,21 @@ def init_ui(self): self.mr_layout.addRow(self.mr_laps_radio) self.mr_counting_lap_check = QCheckBox(translate('counting lap')) self.mr_counting_lap_check.setToolTip( - translate('Operating mode: evaluation point') - + '\n' - + translate('Print number of penalty laps instead of splits') - + '\n' - + translate('when competitor reads out his card') + translate( + 'Operating mode: evaluation point\n' + 'Print the number of penalty laps instead of splits\n' + 'when a competitor reads out his card' + ) ) self.mr_counting_lap_check.stateChanged.connect(self.penalty_calculation_mode) self.mr_layout.addRow(self.mr_counting_lap_check) self.mr_lap_station_check = QCheckBox(translate('lap station')) self.mr_lap_station_check.setToolTip( - translate('Station number on the penalty lap') - + '\n' - + translate('Competitor must punch at station') - + '\n' - + translate('every time he/she passes a penalty lap') + translate( + 'Station number on the penalty lap\n' + 'A competitor must punch at the station\n' + 'each time they pass the penalty lap' + ) ) self.mr_lap_station_check.stateChanged.connect(self.penalty_calculation_mode) self.mr_lap_station_edit = AdvSpinBox(max_width=50) From 9749c0eb5e6095d89b07182bbc424613561c37ee Mon Sep 17 00:00:00 2001 From: Aleksandr Karpov Date: Mon, 18 Dec 2023 23:17:53 +0700 Subject: [PATCH 8/9] fix: remove plural() function --- sportorg/libs/template/template.py | 14 -------------- templates/split/1_split_printout.html | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/sportorg/libs/template/template.py b/sportorg/libs/template/template.py index 47f3f8fb..5ab6e18c 100644 --- a/sportorg/libs/template/template.py +++ b/sportorg/libs/template/template.py @@ -31,19 +31,6 @@ def date(value, fmt=None): return dateutil.parser.parse(value).strftime(fmt) -def plural(value, fmt=None): - if value is None: - return '' - if 5 <= abs(value) <= 19: - return 'ов' - elif abs(value) % 10 == 1: - return '' - elif abs(value) % 10 in (2, 3, 4): - return 'а' - else: - return 'ов' - - def finalize(thing): return thing if thing else '' @@ -63,7 +50,6 @@ def get_text_from_template(searchpath: str, path: str, **kwargs): env.filters['tohhmmss'] = to_hhmmss env.filters['date'] = date env.policies['json.dumps_kwargs']['ensure_ascii'] = False - env.filters['plural'] = plural template = env.get_template(path) return template.render(**kwargs) diff --git a/templates/split/1_split_printout.html b/templates/split/1_split_printout.html index e37f6281..8f21c1d3 100644 --- a/templates/split/1_split_printout.html +++ b/templates/split/1_split_printout.html @@ -176,7 +176,7 @@

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


Финиш: {{result.finish_msec|tohhmmss}} {{result.speed}} {% if race['settings'].get('marked_route_mode', 'off') == 'laps' %} -
Штраф: {{result.penalty_laps}} круг{{result.penalty_laps|plural}} +
Штрафных кругов: {{result.penalty_laps}} {% elif race['settings'].get('marked_route_mode', 'off') == 'time' %}
Штраф: {{result.penalty_time|tohhmmss}} {% endif %} From 1f1f3001d3c7bfd11c65638e964d73a2576ffeb5 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpov Date: Mon, 18 Dec 2023 23:29:02 +0700 Subject: [PATCH 9/9] docs: translate docstring to english --- sportorg/models/result/result_checker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sportorg/models/result/result_checker.py b/sportorg/models/result/result_checker.py index 02035144..96fa55f7 100644 --- a/sportorg/models/result/result_checker.py +++ b/sportorg/models/result/result_checker.py @@ -255,9 +255,9 @@ def detach_penalty_laps(splits, lap_station): @staticmethod def detach_penalty_laps2(splits, lap_station): - """Walkaround: извлекает отметки на штрафной станции. - Наивный метод, надо учитывать, что штрафные КП должны относиться - к пункту оценки, а не появляться из неочищенного чипа""" + '''Detaches penalty laps from the given list of splits + based on the provided lap station code. + ''' if not splits: return [], [] regular = [punch for punch in splits if int(punch.code) != lap_station]