Skip to content

Commit

Permalink
feat: new features for a marked track with penalty loops (sportorg#421)
Browse files Browse the repository at this point in the history
Co-authored-by: Aleksandr Karpov <alex.nsk.karpov@gmail.com>
  • Loading branch information
alex-karpov and Aleksandr Karpov committed Dec 20, 2023
1 parent 4f36e83 commit 4ab5f11
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 126 deletions.
33 changes: 33 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\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\n"
"A competitor must punch at the station\n"
"each time they pass the penalty lap"
msgstr ""
"Номер станции на штрафном круге\n"
"Спортсмен должен отметиться на станции\n"
"при каждом прохождении штрафного круга"

msgid "scores off"
msgstr "очки не рассчитываются"

Expand Down Expand Up @@ -1515,6 +1545,9 @@ msgstr "СОШЕЛ"
msgid "Disqualified"
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
62 changes: 58 additions & 4 deletions sportorg/gui/dialogs/timekeeping_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,24 +183,52 @@ 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_counting_lap_check.setToolTip(
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'
'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)
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 +333,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,9 +471,9 @@ 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_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
Expand Down Expand Up @@ -596,7 +650,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
2 changes: 2 additions & 0 deletions sportorg/gui/menu/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,9 @@ 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')
ResultCalculation(race()).process_results()
self.app.refresh()
Expand Down
1 change: 1 addition & 0 deletions sportorg/models/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class ResultStatus(_TitleType):
DID_NOT_ENTER = 14
CANCELLED = 15
RESTORED = 16
MISS_PENALTY_LAP = 17


class Organization(Model):
Expand Down
63 changes: 57 additions & 6 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():
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):
'''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]
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 Expand Up @@ -281,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
Expand Down
Loading

0 comments on commit 4ab5f11

Please sign in to comment.