Update race result standings handling
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 14s

This commit is contained in:
2024-02-20 20:33:29 +01:00
parent ea1cf7ec59
commit 2d912bdc73
5 changed files with 95 additions and 71 deletions

View File

@ -154,40 +154,51 @@ def find_or_create_race_result(race_name: str) -> RaceResult:
def update_race_result(race_name: str, pxx_driver_names_list: List[str], dnf_driver_names_list: List[str], excluded_driver_names_list: List[str]) -> Response:
# Use strings as keys, as these dicts will be serialized to json
pxx_driver_names: Dict[str, str] = {str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list)}
dnf_driver_names: Dict[str, str] = {str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list) if driver in dnf_driver_names_list}
# The pxx_driver_names_list contains all 20 drivers, so use that one to determine positions for dnf_driver_names and excluded_driver_names
pxx_driver_names: Dict[str, str] = {
str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list)
if driver not in dnf_driver_names_list and driver not in excluded_driver_names_list
}
dnf_driver_names: Dict[str, str] = {
str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list)
if driver in dnf_driver_names_list and driver not in excluded_driver_names_list
}
excluded_driver_names: Dict[str, str] = {
str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list)
if driver in excluded_driver_names_list
}
# This one is only used for validation
excluded_driver_names: Dict[str, str] = {str(position + 1): driver for position, driver in enumerate(pxx_driver_names_list) if driver in excluded_driver_names_list}
best_excluded_driver_position: int = sorted(list(map(int, list(excluded_driver_names.keys()))))[0] if len(excluded_driver_names) > 0 else 21
worst_dnf_driver_position: int = sorted(list(map(int, list(dnf_driver_names.keys()))), reverse=True)[0] if len(dnf_driver_names) > 0 else best_excluded_driver_position - 1
if not positions_are_contiguous(list(dnf_driver_names.keys())):
# All dictionaries should be disjunct and result in a complete list from P1-P20 if merged
union: Dict[str, str] = pxx_driver_names | dnf_driver_names | excluded_driver_names
if len(union) != 20 or not positions_are_contiguous(list(union.keys())) or "1" not in union or "20" not in union:
return redirect(f"/result/{quote(race_name)}")
if not positions_are_contiguous(list(excluded_driver_names.keys())):
return redirect(f"/result/{quote(race_name)}")
# dnf_drivers have positions above excluded_drivers
if len(excluded_driver_names) > 0:
best_excluded_driver_position: int = min(map(int, excluded_driver_names.keys()))
for position in dnf_driver_names.keys():
if int(position) >= best_excluded_driver_position:
return redirect(f"/result/{quote(race_name)}")
# DNF + exclude is exclusive
for driver_name in dnf_driver_names_list:
if driver_name in excluded_driver_names_list:
return redirect(f"/result/{quote(race_name)}")
# pxx_drivers have positions above dnf_drivers
if len(dnf_driver_names) > 0:
best_dnf_driver_position: int = min(map(int, dnf_driver_names.keys()))
for position in pxx_driver_names.keys():
if int(position) >= best_dnf_driver_position:
return redirect(f"/result/{quote(race_name)}")
# Excluded drivers occupy the last positions, if any are excluded
if len(excluded_driver_names_list) > 0 and not "20" in excluded_driver_names:
return redirect(f"/result/{quote(race_name)}")
# DNF drivers occupy the positions between finishing and excluded drivers
if len(dnf_driver_names_list) > 0 and worst_dnf_driver_position + 1 != best_excluded_driver_position:
print(dnf_driver_names, worst_dnf_driver_position)
print(excluded_driver_names, best_excluded_driver_position)
return redirect(f"/result/{quote(race_name)}")
# pxx_drivers have positions above excluded_drivers
if len(excluded_driver_names) > 0:
best_excluded_driver_position: int = min(map(int, excluded_driver_names.keys()))
for position in pxx_driver_names.keys():
if int(position) >= best_excluded_driver_position:
return redirect(f"/result/{quote(race_name)}")
race_result: RaceResult = find_or_create_race_result(race_name)
race_result.pxx_driver_names = pxx_driver_names
race_result.dnf_driver_names = dnf_driver_names if len(dnf_driver_names_list) > 0 else {"20": "NONE"}
race_result.excluded_driver_names = excluded_driver_names_list
race_result.dnf_driver_names = dnf_driver_names
race_result.excluded_driver_names = excluded_driver_names
db.session.commit()

View File

@ -19,9 +19,7 @@ db.init_app(app)
# TODO
# General
# - A lot of validation (esp. in the model), each input should be checked (e.g. DNF, excluded order)...
# - NONE driver handling: If PXX = NONE is selected, excluded drivers have to be taken into account
# - Show place when entering race result
# - Show place when entering race result (would require updating the drag'n'drop code...)
# - Show cards of previous race results, like with season guesses?
# - Make the season card grid left-aligned? So e.g. 2 cards are not spread over the whole screen with large gaps?

View File

@ -161,18 +161,18 @@ class RaceResult(db.Model):
self.dnf_driver_names_json = json.dumps(new_dnf_driver_names)
@property
def excluded_driver_names(self) -> List[str]:
def excluded_driver_names(self) -> Dict[str, str]:
return json.loads(self.excluded_driver_names_json)
@excluded_driver_names.setter
def excluded_driver_names(self, new_excluded_driver_names: List[str]):
def excluded_driver_names(self, new_excluded_driver_names: Dict[str, str]):
self.excluded_driver_names_json = json.dumps(new_excluded_driver_names)
# Relationships
race: Mapped["Race"] = relationship("Race", foreign_keys=[race_name])
_pxx_drivers: Dict[str, Driver] | None = None
_dnf_drivers: Dict[str, Driver] | None = None
_excluded_drivers: List[Driver] | None = None
_excluded_drivers: Dict[str, Driver] | None = None
@property
def pxx_drivers(self) -> Dict[str, Driver]:
@ -187,16 +187,6 @@ class RaceResult(db.Model):
return self._pxx_drivers
@property
def pxx_drivers_values(self) -> List[Driver]:
drivers: List[Driver] = list()
# I don't know what order dict.values() etc. will return...
for position in range(1, 21):
drivers.append(self.pxx_drivers[str(position)])
return drivers
@property
def dnf_drivers(self) -> Dict[str, Driver]:
if self._dnf_drivers is None:
@ -211,28 +201,55 @@ class RaceResult(db.Model):
return self._dnf_drivers
@property
def excluded_drivers(self) -> List[Driver]:
def excluded_drivers(self) -> Dict[str, Driver]:
if self._excluded_drivers is None:
self._excluded_drivers = list()
for driver_name in self.excluded_driver_names:
self._excluded_drivers = dict()
for position, driver_name in self.excluded_driver_names.items():
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
if driver is None:
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
self._excluded_drivers.append(driver)
self._excluded_drivers[position] = driver
return self._excluded_drivers
def pxx(self, offset: int = 0) -> Driver:
def pxx(self, offset: int = 0) -> Driver | None:
pxx_num: str = str(self.race.pxx + offset)
if pxx_num not in self.pxx_drivers:
raise Exception(f"Error: Position {self.race.pxx} not contained in race result")
none_driver: Driver | None = db.session.query(Driver).filter_by(name="NONE").first()
if none_driver is None:
raise Exception("NONE-driver not found in database")
return none_driver
return self.pxx_drivers[pxx_num]
@property
def dnf(self) -> Driver:
return sorted(self.dnf_drivers.items(), reverse=True)[0][1] # SortedList[FirstElement][KeyPairValue]
none_driver: Driver | None = db.session.query(Driver).filter_by(name="NONE").first()
if none_driver is None:
raise Exception("NONE-driver not found in database")
return sorted(self.dnf_drivers.items(), reverse=True)[0][1] if len(self.dnf_drivers) > 0 else none_driver
def single_position(self, position: str) -> Driver:
if position in self.pxx_drivers:
return self.pxx_drivers[position]
if position in self.dnf_drivers:
return self.dnf_drivers[position]
if position in self.excluded_drivers:
return self.excluded_drivers[position]
raise Exception(f"Driver for position {position} not found in pxx/dnf/excluded")
@property
def all_positions(self) -> List[Driver]:
return [
self.single_position(str(position)) for position in range(1, 21)
]
class RaceGuess(db.Model):

View File

@ -98,23 +98,23 @@
{#@formatter:off#}
{% macro pxx_guess_colorization(driver_abbr='', result=none) -%}
{% if driver_abbr == result.pxx(-3).abbr %}fw-bold
{% elif driver_abbr == result.pxx(-2).abbr %}text-danger fw-bold
{% elif driver_abbr == result.pxx(-1).abbr %}text-warning fw-bold
{% elif driver_abbr == result.pxx(0).abbr %}text-success fw-bold
{% elif driver_abbr == result.pxx(1).abbr %}text-warning fw-bold
{% elif driver_abbr == result.pxx(2).abbr %}text-danger fw-bold
{% elif driver_abbr == result.pxx(3).abbr %}fw-bold{% endif %}
{% if (driver_abbr == result.pxx(-3).abbr) and (driver_abbr != "NON") %}fw-bold
{% elif (driver_abbr == result.pxx(-2).abbr) and (driver_abbr != "NON") %}text-danger fw-bold
{% elif (driver_abbr == result.pxx(-1).abbr) and (driver_abbr != "NON") %}text-warning fw-bold
{% elif (driver_abbr == result.pxx(0).abbr) %}text-success fw-bold
{% elif (driver_abbr == result.pxx(1).abbr) and (driver_abbr != "NON") %}text-warning fw-bold
{% elif (driver_abbr == result.pxx(2).abbr) and (driver_abbr != "NON") %}text-danger fw-bold
{% elif (driver_abbr == result.pxx(3).abbr) and (driver_abbr != "NON") %}fw-bold{% endif %}
{% endmacro %}
{% macro pxx_points_tooltip_text(driver_abbr='', result=none) -%}
{% if driver_abbr == result.pxx(-3).abbr %}1 Point
{% elif driver_abbr == result.pxx(-2).abbr %}3 Points
{% elif driver_abbr == result.pxx(-1).abbr %}6 Points
{% elif driver_abbr == result.pxx(0).abbr %}10 Points
{% elif driver_abbr == result.pxx(1).abbr %}6 Points
{% elif driver_abbr == result.pxx(2).abbr %}3 Points
{% elif driver_abbr == result.pxx(3).abbr %}1 Point
{% if (driver_abbr == result.pxx(-3).abbr) and (driver_abbr != "NON") %}1 Point
{% elif (driver_abbr == result.pxx(-2).abbr) and (driver_abbr != "NON") %}3 Points
{% elif (driver_abbr == result.pxx(-1).abbr) and (driver_abbr != "NON") %}6 Points
{% elif (driver_abbr == result.pxx(0).abbr) %}10 Points
{% elif (driver_abbr == result.pxx(1).abbr) and (driver_abbr != "NON") %}6 Points
{% elif (driver_abbr == result.pxx(2).abbr) and (driver_abbr != "NON") %}3 Points
{% elif (driver_abbr == result.pxx(3).abbr) and (driver_abbr != "NON") %}1 Point
{% else %}0 Points{% endif %}
{%- endmacro %}

View File

@ -65,17 +65,14 @@
{% endif %}
</h5>
<form action="/result-enter/
{%- if active_result is not none %}{{ active_result.race.name }}{% else %}{{ current_race.name }}{% endif %}"
{# @formatter:off #}
<form action="/result-enter/{%- if active_result is not none %}{{ active_result.race.name }}{% else %}{{ current_race.name }}{% endif %}"
method="post">
{# @formatter:on #}
<ul id="columns" class="list-group list-group-flush">
{% if active_result is not none %}
{% set drivers = active_result.pxx_drivers_values %}
{% set drivers = active_result.all_positions %}
{% else %}
{% set drivers = model.all_drivers_except_none() %}
{% endif %}
@ -98,9 +95,10 @@
<div class="form-check form-check-reverse d-inline-block mx-2">
<input type="checkbox" class="form-check-input" value="{{ driver.name }}"
id="exclude-{{ driver.name }}" name="exclude-drivers"
{% if (active_result is not none) and (driver in active_result.excluded_drivers) %}checked{% endif %}>
{% if (active_result is not none) and (driver in active_result.excluded_drivers.values()) %}checked{% endif %}>
<label for="exclude-{{ driver.name }}"
class="form-check-label text-muted" data-bs-toggle="tooltip" title="Exclude driver from points calculation">Exclude</label>
class="form-check-label text-muted" data-bs-toggle="tooltip"
title="Exclude driver from points calculation">Exclude</label>
</div>
</div>