Skip to content

Commit

Permalink
feat: marked route with penalty laps
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-karpov committed Nov 15, 2023
1 parent 0f25f75 commit 5250f40
Show file tree
Hide file tree
Showing 13 changed files with 419 additions and 122 deletions.
36 changes: 36 additions & 0 deletions languages/ru_RU/LC_MESSAGES/sportorg.po
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ msgstr "Финиш"
msgid "Penalty"
msgstr "Штраф"

msgid "Penalty laps"
msgstr "Штрафные круги"

msgid "Penalty legs"
msgstr "Штраф, круги"

Expand Down Expand Up @@ -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 "очки не рассчитываются"

Expand Down Expand Up @@ -1515,6 +1545,12 @@ msgstr "СОШЕЛ"
msgid "Disqualified"
msgstr "ДИСКВ."

msgid "Missing penalty lap"
msgstr "Пропущен штрафной круг"

msgid "laps"
msgstr "кр."

msgid "contain"
msgstr "содержит"

Expand Down
1 change: 1 addition & 0 deletions sportorg/gui/dialogs/result_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
44 changes: 41 additions & 3 deletions sportorg/gui/dialogs/timekeeping_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,24 +183,36 @@ 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)
self.mr_lap_station_check = QCheckBox(translate('lap station'))
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)

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
1 change: 1 addition & 0 deletions sportorg/gui/menu/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
14 changes: 14 additions & 0 deletions sportorg/libs/template/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ''

Expand All @@ -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)
1 change: 1 addition & 0 deletions sportorg/models/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class ResultStatus(_TitleType):
DID_NOT_ENTER = 14
CANCELLED = 15
RESTORED = 16
MISS_PENALTY_LAP = 17


class Organization(Model):
Expand Down
61 changes: 56 additions & 5 deletions sportorg/models/result/result_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand All @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
Loading

0 comments on commit 5250f40

Please sign in to comment.