Compare commits
10 Commits
34434cc7cc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b485791d25 | |||
| e8c8e35e05 | |||
| 15305b2f3e | |||
| 0dc4b22c72 | |||
| cef40a9e8b | |||
| c509746688 | |||
| 9cdd7267db | |||
| 61d247508f | |||
| f38a5f2e6d | |||
| 95760baebf |
@ -111,6 +111,8 @@
|
|||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
myPython
|
myPython
|
||||||
|
|
||||||
|
sqlitebrowser
|
||||||
|
|
||||||
nodejs_21
|
nodejs_21
|
||||||
nodePackages.sass
|
nodePackages.sass
|
||||||
nodePackages.postcss-cli
|
nodePackages.postcss-cli
|
||||||
|
|||||||
@ -29,7 +29,7 @@ cache.init_app(app)
|
|||||||
# app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=("/formula10/*",), sort_by=("cumtime",))
|
# app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=("/formula10/*",), sort_by=("cumtime",))
|
||||||
|
|
||||||
# NOTE: These imports are required to register the routes. They need to be imported after "app" is declared
|
# NOTE: These imports are required to register the routes. They need to be imported after "app" is declared
|
||||||
import formula10.controller.race_controller # type: ignore
|
import formula10.controller.race_controller
|
||||||
import formula10.controller.season_controller
|
import formula10.controller.season_controller
|
||||||
import formula10.controller.leaderboard_controller
|
import formula10.controller.leaderboard_controller
|
||||||
import formula10.controller.statistics_controller
|
import formula10.controller.statistics_controller
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
from typing import List
|
from typing import Callable, List
|
||||||
|
|
||||||
from formula10 import cache
|
from formula10 import cache
|
||||||
|
from formula10.domain.domain_model import Model
|
||||||
|
from formula10.domain.points_model import PointsModel
|
||||||
|
|
||||||
|
|
||||||
def cache_invalidate_user_updated() -> None:
|
def cache_invalidate_user_updated() -> None:
|
||||||
@ -12,10 +14,10 @@ def cache_invalidate_user_updated() -> None:
|
|||||||
"points_user_standing",
|
"points_user_standing",
|
||||||
]
|
]
|
||||||
|
|
||||||
memoized_caches: List[str] = [
|
memoized_caches: List[Callable] = [
|
||||||
"points_by",
|
PointsModel.points_by,
|
||||||
"race_guesses_by",
|
PointsModel.race_guesses_by,
|
||||||
"season_guesses_by",
|
PointsModel.season_guesses_by,
|
||||||
]
|
]
|
||||||
|
|
||||||
for c in caches:
|
for c in caches:
|
||||||
@ -45,16 +47,16 @@ def cache_invalidate_race_result_updated() -> None:
|
|||||||
"template_first_race_without_result",
|
"template_first_race_without_result",
|
||||||
]
|
]
|
||||||
|
|
||||||
memoized_caches: List[str] = [
|
memoized_caches: List[Callable] = [
|
||||||
"driver_points_per_step",
|
PointsModel.driver_points_per_step,
|
||||||
"driver_points_by",
|
PointsModel.driver_points_by,
|
||||||
"total_driver_points_by",
|
PointsModel.total_driver_points_by,
|
||||||
"drivers_sorted_by_points",
|
PointsModel.drivers_sorted_by_points,
|
||||||
"total_team_points_by",
|
PointsModel.total_team_points_by,
|
||||||
"points_by",
|
PointsModel.points_by,
|
||||||
"is_team_winner",
|
PointsModel.is_team_winner,
|
||||||
"has_podium",
|
PointsModel.has_podium,
|
||||||
"picks_with_points_count",
|
PointsModel.picks_with_points_count,
|
||||||
]
|
]
|
||||||
|
|
||||||
for c in caches:
|
for c in caches:
|
||||||
@ -69,8 +71,8 @@ def cache_invalidate_race_guess_updated() -> None:
|
|||||||
"domain_all_race_guesses",
|
"domain_all_race_guesses",
|
||||||
]
|
]
|
||||||
|
|
||||||
memoized_caches: List[str] = [
|
memoized_caches: List[Callable] = [
|
||||||
"race_guesses_by",
|
Model.race_guesses_by,
|
||||||
]
|
]
|
||||||
|
|
||||||
for c in caches:
|
for c in caches:
|
||||||
@ -82,11 +84,11 @@ def cache_invalidate_race_guess_updated() -> None:
|
|||||||
|
|
||||||
def cache_invalidate_season_guess_updated() -> None:
|
def cache_invalidate_season_guess_updated() -> None:
|
||||||
caches: List[str] = [
|
caches: List[str] = [
|
||||||
"domain_all_season_guesses"
|
"domain_all_season_guesses",
|
||||||
]
|
]
|
||||||
|
|
||||||
memoized_caches: List[str] = [
|
memoized_caches: List[Callable] = [
|
||||||
"season_guesses_by"
|
Model.season_guesses_by,
|
||||||
]
|
]
|
||||||
|
|
||||||
for c in caches:
|
for c in caches:
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Any, Callable, Dict, List, overload
|
from typing import Any, Callable, Dict, List, overload, Tuple
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from formula10 import cache
|
from formula10 import cache
|
||||||
@ -11,15 +11,11 @@ from formula10.domain.model.season_guess import SeasonGuess
|
|||||||
from formula10.domain.model.season_guess_result import SeasonGuessResult
|
from formula10.domain.model.season_guess_result import SeasonGuessResult
|
||||||
from formula10.domain.model.team import Team
|
from formula10.domain.model.team import Team
|
||||||
from formula10.domain.model.user import User
|
from formula10.domain.model.user import User
|
||||||
|
from formula10.database.validation import find_single_or_none_strict
|
||||||
|
|
||||||
# Guess points
|
# Guess points
|
||||||
|
|
||||||
RACE_GUESS_OFFSET_POINTS: Dict[int, int] = {
|
RACE_GUESS_OFFSET_POINTS: Dict[int, int] = {3: 1, 2: 3, 1: 6, 0: 10}
|
||||||
3: 1,
|
|
||||||
2: 3,
|
|
||||||
1: 6,
|
|
||||||
0: 10
|
|
||||||
}
|
|
||||||
RACE_GUESS_DNF_POINTS: int = 10
|
RACE_GUESS_DNF_POINTS: int = 10
|
||||||
SEASON_GUESS_HOT_TAKE_POINTS: int = 10
|
SEASON_GUESS_HOT_TAKE_POINTS: int = 10
|
||||||
SEASON_GUESS_P2_POINTS: int = 10
|
SEASON_GUESS_P2_POINTS: int = 10
|
||||||
@ -44,18 +40,9 @@ DRIVER_RACE_POINTS: Dict[int, int] = {
|
|||||||
7: 6,
|
7: 6,
|
||||||
8: 4,
|
8: 4,
|
||||||
9: 2,
|
9: 2,
|
||||||
10: 1
|
10: 1,
|
||||||
}
|
|
||||||
DRIVER_SPRINT_POINTS: Dict[int, int] = {
|
|
||||||
1: 8,
|
|
||||||
2: 7,
|
|
||||||
3: 6,
|
|
||||||
4: 5,
|
|
||||||
5: 4,
|
|
||||||
6: 3,
|
|
||||||
7: 2,
|
|
||||||
8: 1
|
|
||||||
}
|
}
|
||||||
|
DRIVER_SPRINT_POINTS: Dict[int, int] = {1: 8, 2: 7, 3: 6, 4: 5, 5: 4, 6: 3, 7: 2, 8: 1}
|
||||||
DRIVER_FASTEST_LAP_POINTS: int = 1
|
DRIVER_FASTEST_LAP_POINTS: int = 1
|
||||||
|
|
||||||
# Last season results
|
# Last season results
|
||||||
@ -80,8 +67,9 @@ WDC_STANDING_2023: Dict[str, int] = {
|
|||||||
"Daniel Ricciardo": 17,
|
"Daniel Ricciardo": 17,
|
||||||
"Zhou Guanyu": 18,
|
"Zhou Guanyu": 18,
|
||||||
"Kevin Magnussen": 19,
|
"Kevin Magnussen": 19,
|
||||||
"Logan Sargeant": 21
|
"Logan Sargeant": 21,
|
||||||
}
|
}
|
||||||
|
|
||||||
WCC_STANDING_2023: Dict[str, int] = {
|
WCC_STANDING_2023: Dict[str, int] = {
|
||||||
"Red Bull": 1,
|
"Red Bull": 1,
|
||||||
"Mercedes": 2,
|
"Mercedes": 2,
|
||||||
@ -92,11 +80,47 @@ WCC_STANDING_2023: Dict[str, int] = {
|
|||||||
"Williams": 7,
|
"Williams": 7,
|
||||||
"VCARB": 8,
|
"VCARB": 8,
|
||||||
"Sauber": 9,
|
"Sauber": 9,
|
||||||
"Haas": 10
|
"Haas": 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
# In case a substitute driver is driving, those points have to be subtracted from the actual driver
|
||||||
|
# (Driver_ID, Race_ID, Points)
|
||||||
|
WDC_SUBSTITUTE_POINTS: List[Tuple[int, int, int]] = [
|
||||||
|
(15, 2, 6), # Bearman raced for Sainz in Saudi Arabia
|
||||||
|
(8, 17, 1), # Bearman raced for Magnussen in Azerbaijan
|
||||||
|
]
|
||||||
|
|
||||||
|
WDC_STANDING_2024: Dict[str, int] = {
|
||||||
|
"Max Verstappen": 1,
|
||||||
|
"Lando Norris": 2,
|
||||||
|
"Charles Leclerc": 3,
|
||||||
|
"Oscar Piastri": 4,
|
||||||
|
"Carlos Sainz": 5,
|
||||||
|
"George Russell": 6,
|
||||||
|
"Lewis Hamilton": 7,
|
||||||
|
"Sergio Perez": 8,
|
||||||
|
"Fernando Alonso": 9,
|
||||||
|
"Pierre Gasly": 10,
|
||||||
|
"Nico Hulkenberg": 11,
|
||||||
|
"Yuki Tsunoda": 12,
|
||||||
|
"Lance Stroll": 13,
|
||||||
|
"Esteban Ocon": 14,
|
||||||
|
"Kevin Magnussen": 15,
|
||||||
|
"Alexander Albon": 16,
|
||||||
|
"Daniel Ricciardo": 17,
|
||||||
|
"Oliver Bearman": 18,
|
||||||
|
"Franco Colapinto": 19,
|
||||||
|
"Zhou Guanyu": 20,
|
||||||
|
"Liam Lawson": 21,
|
||||||
|
"Valtteri Bottas": 22,
|
||||||
|
"Logan Sargeant": 23,
|
||||||
|
"Jack Doohan": 24
|
||||||
}
|
}
|
||||||
|
|
||||||
def standing_points(race_guess: RaceGuess, race_result: RaceResult) -> int:
|
def standing_points(race_guess: RaceGuess, race_result: RaceResult) -> int:
|
||||||
guessed_driver_position: int | None = race_result.driver_standing_position(driver=race_guess.pxx_guess)
|
guessed_driver_position: int | None = race_result.driver_standing_position(
|
||||||
|
driver=race_guess.pxx_guess
|
||||||
|
)
|
||||||
if guessed_driver_position is None:
|
if guessed_driver_position is None:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -106,6 +130,7 @@ def standing_points(race_guess: RaceGuess, race_result: RaceResult) -> int:
|
|||||||
|
|
||||||
return RACE_GUESS_OFFSET_POINTS[position_offset]
|
return RACE_GUESS_OFFSET_POINTS[position_offset]
|
||||||
|
|
||||||
|
|
||||||
def dnf_points(race_guess: RaceGuess, race_result: RaceResult) -> int:
|
def dnf_points(race_guess: RaceGuess, race_result: RaceResult) -> int:
|
||||||
if race_guess.dnf_guess in race_result.initial_dnf:
|
if race_guess.dnf_guess in race_result.initial_dnf:
|
||||||
return RACE_GUESS_DNF_POINTS
|
return RACE_GUESS_DNF_POINTS
|
||||||
@ -115,6 +140,17 @@ def dnf_points(race_guess: RaceGuess, race_result: RaceResult) -> int:
|
|||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def substitute_points(driver: Driver, race_number: int) -> int:
|
||||||
|
predicate: Callable[[Tuple[int, int, int]], bool] = lambda substitution: driver.id == substitution[0] and race_number == substitution[1]
|
||||||
|
substitution: Tuple[int, int, int] = find_single_or_none_strict(predicate, WDC_SUBSTITUTE_POINTS)
|
||||||
|
|
||||||
|
if substitution is not None:
|
||||||
|
return substitution[2]
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class PointsModel(Model):
|
class PointsModel(Model):
|
||||||
"""
|
"""
|
||||||
This class bundles all data + functionality required to do points calculations.
|
This class bundles all data + functionality required to do points calculations.
|
||||||
@ -123,51 +159,78 @@ class PointsModel(Model):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
Model.__init__(self)
|
Model.__init__(self)
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_points_per_step") # Clear when adding/updating race results or users
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_points_per_step"
|
||||||
|
) # Clear when adding/updating race results or users
|
||||||
def points_per_step(self) -> Dict[str, List[int]]:
|
def points_per_step(self) -> Dict[str, List[int]]:
|
||||||
"""
|
"""
|
||||||
Returns a dictionary of lists, containing points per race for each user.
|
Returns a dictionary of lists, containing points per race for each user.
|
||||||
"""
|
"""
|
||||||
points_per_step = dict()
|
points_per_step = dict()
|
||||||
for user in self.all_users():
|
for user in self.all_users():
|
||||||
points_per_step[user.name] = [0] * (len(self.all_races()) + 1) # Start at index 1, like the race numbers
|
points_per_step[user.name] = [0] * (
|
||||||
|
len(self.all_races()) + 1
|
||||||
|
) # Start at index 1, like the race numbers
|
||||||
|
|
||||||
for race_guess in self.all_race_guesses():
|
for race_guess in self.all_race_guesses():
|
||||||
user_name: str = race_guess.user.name
|
user_name: str = race_guess.user.name
|
||||||
race_number: int = race_guess.race.number
|
race_number: int = race_guess.race.number
|
||||||
race_result: RaceResult | None = self.race_result_by(race_name=race_guess.race.name)
|
race_result: RaceResult | None = self.race_result_by(
|
||||||
|
race_name=race_guess.race.name
|
||||||
|
)
|
||||||
|
|
||||||
if race_result is None:
|
if race_result is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
points_per_step[user_name][race_number] = standing_points(race_guess, race_result) + dnf_points(race_guess, race_result)
|
points_per_step[user_name][race_number] = standing_points(
|
||||||
|
race_guess, race_result
|
||||||
|
) + dnf_points(race_guess, race_result)
|
||||||
|
|
||||||
return points_per_step
|
return points_per_step
|
||||||
|
|
||||||
@cache.memoize(timeout=None, args_to_ignore=["self"]) # Clear when adding/updating race results
|
@cache.memoize(
|
||||||
|
timeout=None, args_to_ignore=["self"]
|
||||||
|
) # Clear when adding/updating race results
|
||||||
def driver_points_per_step(self, *, include_inactive: bool) -> Dict[str, List[int]]:
|
def driver_points_per_step(self, *, include_inactive: bool) -> Dict[str, List[int]]:
|
||||||
"""
|
"""
|
||||||
Returns a dictionary of lists, containing points per race for each driver.
|
Returns a dictionary of lists, containing points per race for each driver.
|
||||||
"""
|
"""
|
||||||
driver_points_per_step = dict()
|
driver_points_per_step = dict()
|
||||||
for driver in self.all_drivers(include_none=False, include_inactive=include_inactive):
|
for driver in self.all_drivers(
|
||||||
driver_points_per_step[driver.name] = [0] * (len(self.all_races()) + 1) # Start at index 1, like the race numbers
|
include_none=False, include_inactive=include_inactive
|
||||||
|
):
|
||||||
|
driver_points_per_step[driver.name] = [0] * (
|
||||||
|
len(self.all_races()) + 1
|
||||||
|
) # Start at index 1, like the race numbers
|
||||||
|
|
||||||
for race_result in self.all_race_results():
|
for race_result in self.all_race_results():
|
||||||
race_number: int = race_result.race.number
|
race_number: int = race_result.race.number
|
||||||
|
|
||||||
for position, driver in race_result.standing.items():
|
for position, driver in race_result.standing.items():
|
||||||
driver_points_per_step[driver.name][race_number] = DRIVER_RACE_POINTS[int(position)] if int(position) in DRIVER_RACE_POINTS else 0
|
driver_points_per_step[driver.name][race_number] = (
|
||||||
driver_points_per_step[driver.name][race_number] += DRIVER_FASTEST_LAP_POINTS if race_result.fastest_lap_driver == driver else 0
|
DRIVER_RACE_POINTS[int(position)]
|
||||||
|
if int(position) in DRIVER_RACE_POINTS
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
driver_points_per_step[driver.name][race_number] += (
|
||||||
|
DRIVER_FASTEST_LAP_POINTS
|
||||||
|
if race_result.fastest_lap_driver == driver
|
||||||
|
and int(position) <= 10
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
driver_points_per_step[driver.name][race_number] -= substitute_points(driver, race_number)
|
||||||
|
|
||||||
for position, driver in race_result.sprint_standing.items():
|
for position, driver in race_result.sprint_standing.items():
|
||||||
driver_name: str = driver.name
|
driver_name: str = driver.name
|
||||||
|
|
||||||
driver_points_per_step[driver_name][race_number] += DRIVER_SPRINT_POINTS[int(position)] if int(position) in DRIVER_SPRINT_POINTS else 0
|
driver_points_per_step[driver_name][race_number] += (
|
||||||
|
DRIVER_SPRINT_POINTS[int(position)]
|
||||||
|
if int(position) in DRIVER_SPRINT_POINTS
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
return driver_points_per_step
|
return driver_points_per_step
|
||||||
|
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_team_points_per_step")
|
@cache.cached(timeout=None, key_prefix="points_team_points_per_step")
|
||||||
def team_points_per_step(self) -> Dict[str, List[int]]:
|
def team_points_per_step(self) -> Dict[str, List[int]]:
|
||||||
"""
|
"""
|
||||||
@ -175,14 +238,20 @@ class PointsModel(Model):
|
|||||||
"""
|
"""
|
||||||
team_points_per_step = dict()
|
team_points_per_step = dict()
|
||||||
for team in self.all_teams(include_none=False):
|
for team in self.all_teams(include_none=False):
|
||||||
team_points_per_step[team.name] = [0] * (len(self.all_races()) + 1) # Start at index 1, like the race numbers
|
team_points_per_step[team.name] = [0] * (
|
||||||
|
len(self.all_races()) + 1
|
||||||
|
) # Start at index 1, like the race numbers
|
||||||
|
|
||||||
for race_result in self.all_race_results():
|
for race_result in self.all_race_results():
|
||||||
for driver in race_result.standing.values():
|
for driver in race_result.standing.values():
|
||||||
team_name: str = driver.team.name
|
team_name: str = driver.team.name
|
||||||
race_number: int = race_result.race.number
|
race_number: int = race_result.race.number
|
||||||
|
|
||||||
team_points_per_step[team_name][race_number] += self.driver_points_per_step(include_inactive=True)[driver.name][race_number]
|
team_points_per_step[team_name][
|
||||||
|
race_number
|
||||||
|
] += self.driver_points_per_step(include_inactive=True)[driver.name][
|
||||||
|
race_number
|
||||||
|
]
|
||||||
|
|
||||||
return team_points_per_step
|
return team_points_per_step
|
||||||
|
|
||||||
@ -206,48 +275,78 @@ class PointsModel(Model):
|
|||||||
# Driver stats
|
# Driver stats
|
||||||
#
|
#
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_driver_points_per_step_cumulative") # Cleanup when adding/updating race results
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_driver_points_per_step_cumulative"
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def driver_points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
def driver_points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
||||||
"""
|
"""
|
||||||
Returns a dictionary of lists, containing cumulative points per race for each driver.
|
Returns a dictionary of lists, containing cumulative points per race for each driver.
|
||||||
"""
|
"""
|
||||||
points_per_step_cumulative: Dict[str, List[int]] = dict()
|
points_per_step_cumulative: Dict[str, List[int]] = dict()
|
||||||
for driver_name, points in self.driver_points_per_step(include_inactive=True).items():
|
for driver_name, points in self.driver_points_per_step(
|
||||||
|
include_inactive=True
|
||||||
|
).items():
|
||||||
points_per_step_cumulative[driver_name] = np.cumsum(points).tolist()
|
points_per_step_cumulative[driver_name] = np.cumsum(points).tolist()
|
||||||
|
|
||||||
return points_per_step_cumulative
|
return points_per_step_cumulative
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def driver_points_by(self, *, driver_name: str, include_inactive: bool) -> List[int]:
|
def driver_points_by(
|
||||||
|
self, *, driver_name: str, include_inactive: bool
|
||||||
|
) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Returns a list of points per race for a specific driver.
|
Returns a list of points per race for a specific driver.
|
||||||
"""
|
"""
|
||||||
return self.driver_points_by(driver_name=driver_name, include_inactive=include_inactive)
|
return self.driver_points_by(
|
||||||
|
driver_name=driver_name, include_inactive=include_inactive
|
||||||
|
)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def driver_points_by(self, *, race_name: str, include_inactive: bool) -> Dict[str, int]:
|
def driver_points_by(
|
||||||
|
self, *, race_name: str, include_inactive: bool
|
||||||
|
) -> Dict[str, int]:
|
||||||
"""
|
"""
|
||||||
Returns a dictionary of points per driver for a specific race.
|
Returns a dictionary of points per driver for a specific race.
|
||||||
"""
|
"""
|
||||||
return self.driver_points_by(race_name=race_name, include_inactive=include_inactive)
|
return self.driver_points_by(
|
||||||
|
race_name=race_name, include_inactive=include_inactive
|
||||||
|
)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def driver_points_by(self, *, driver_name: str, race_name: str, include_inactive: bool) -> int:
|
def driver_points_by(
|
||||||
|
self, *, driver_name: str, race_name: str, include_inactive: bool
|
||||||
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Returns the points for a specific race for a specific driver.
|
Returns the points for a specific race for a specific driver.
|
||||||
"""
|
"""
|
||||||
return self.driver_points_by(driver_name=driver_name, race_name=race_name, include_inactive=include_inactive)
|
return self.driver_points_by(
|
||||||
|
driver_name=driver_name,
|
||||||
|
race_name=race_name,
|
||||||
|
include_inactive=include_inactive,
|
||||||
|
)
|
||||||
|
|
||||||
@cache.memoize(timeout=None, args_to_ignore=["self"]) # Cleanup when adding/updating race results
|
@cache.memoize(
|
||||||
def driver_points_by(self, *, driver_name: str | None = None, race_name: str | None = None, include_inactive: bool) -> List[int] | Dict[str, int] | int:
|
timeout=None, args_to_ignore=["self"]
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
|
def driver_points_by(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
driver_name: str | None = None,
|
||||||
|
race_name: str | None = None,
|
||||||
|
include_inactive: bool
|
||||||
|
) -> List[int] | Dict[str, int] | int:
|
||||||
if driver_name is not None and race_name is None:
|
if driver_name is not None and race_name is None:
|
||||||
return self.driver_points_per_step(include_inactive=include_inactive)[driver_name]
|
return self.driver_points_per_step(include_inactive=include_inactive)[
|
||||||
|
driver_name
|
||||||
|
]
|
||||||
|
|
||||||
if driver_name is None and race_name is not None:
|
if driver_name is None and race_name is not None:
|
||||||
race_number: int = self.race_by(race_name=race_name).number
|
race_number: int = self.race_by(race_name=race_name).number
|
||||||
points_by_race: Dict[str, int] = dict()
|
points_by_race: Dict[str, int] = dict()
|
||||||
|
|
||||||
for _driver_name, points in self.driver_points_per_step(include_inactive=include_inactive).items():
|
for _driver_name, points in self.driver_points_per_step(
|
||||||
|
include_inactive=include_inactive
|
||||||
|
).items():
|
||||||
points_by_race[_driver_name] = points[race_number]
|
points_by_race[_driver_name] = points[race_number]
|
||||||
|
|
||||||
return points_by_race
|
return points_by_race
|
||||||
@ -255,24 +354,43 @@ class PointsModel(Model):
|
|||||||
if driver_name is not None and race_name is not None:
|
if driver_name is not None and race_name is not None:
|
||||||
race_number: int = self.race_by(race_name=race_name).number
|
race_number: int = self.race_by(race_name=race_name).number
|
||||||
|
|
||||||
return self.driver_points_per_step(include_inactive=include_inactive)[driver_name][race_number]
|
return self.driver_points_per_step(include_inactive=include_inactive)[
|
||||||
|
driver_name
|
||||||
|
][race_number]
|
||||||
|
|
||||||
raise Exception("driver_points_by received an illegal combination of arguments")
|
raise Exception("driver_points_by received an illegal combination of arguments")
|
||||||
|
|
||||||
@cache.memoize(timeout=None, args_to_ignore=["self"]) # Cleanup when adding/updating race results
|
@cache.memoize(
|
||||||
|
timeout=None, args_to_ignore=["self"]
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def total_driver_points_by(self, driver_name: str) -> int:
|
def total_driver_points_by(self, driver_name: str) -> int:
|
||||||
return sum(self.driver_points_by(driver_name=driver_name, include_inactive=True))
|
return sum(
|
||||||
|
self.driver_points_by(driver_name=driver_name, include_inactive=True)
|
||||||
|
)
|
||||||
|
|
||||||
@cache.memoize(timeout=None, args_to_ignore=["self"]) # Cleanup when adding/updating race results
|
@cache.memoize(
|
||||||
|
timeout=None, args_to_ignore=["self"]
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def drivers_sorted_by_points(self, *, include_inactive: bool) -> List[Driver]:
|
def drivers_sorted_by_points(self, *, include_inactive: bool) -> List[Driver]:
|
||||||
comparator: Callable[[Driver], int] = lambda driver: self.total_driver_points_by(driver.name)
|
comparator: Callable[[Driver], int] = (
|
||||||
return sorted(self.all_drivers(include_none=False, include_inactive=include_inactive), key=comparator, reverse=True)
|
lambda driver: self.total_driver_points_by(driver.name)
|
||||||
|
)
|
||||||
|
return sorted(
|
||||||
|
self.all_drivers(include_none=False, include_inactive=include_inactive),
|
||||||
|
key=comparator,
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_wdc_standing_by_position") # Cleanup when adding/updating race results
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_wdc_standing_by_position"
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def wdc_standing_by_position(self) -> Dict[int, List[str]]:
|
def wdc_standing_by_position(self) -> Dict[int, List[str]]:
|
||||||
standing: Dict[int, List[str]] = dict()
|
standing: Dict[int, List[str]] = dict()
|
||||||
|
|
||||||
for position in range(1, len(self.all_drivers(include_none=False, include_inactive=True)) + 1):
|
if WDC_STANDING_2024 is None:
|
||||||
|
for position in range(
|
||||||
|
1, len(self.all_drivers(include_none=False, include_inactive=True)) + 1
|
||||||
|
):
|
||||||
standing[position] = list()
|
standing[position] = list()
|
||||||
|
|
||||||
position: int = 1
|
position: int = 1
|
||||||
@ -281,37 +399,63 @@ class PointsModel(Model):
|
|||||||
for driver in self.drivers_sorted_by_points(include_inactive=True):
|
for driver in self.drivers_sorted_by_points(include_inactive=True):
|
||||||
points: int = self.total_driver_points_by(driver.name)
|
points: int = self.total_driver_points_by(driver.name)
|
||||||
if points < last_points:
|
if points < last_points:
|
||||||
position += 1
|
# If multiple drivers have equal points, a place is shared.
|
||||||
|
# In this case, the next driver does not occupy the immediate next position.
|
||||||
|
position += len(standing[position])
|
||||||
|
|
||||||
standing[position].append(driver.name)
|
standing[position].append(driver.name)
|
||||||
last_points = points
|
last_points = points
|
||||||
|
|
||||||
|
if WDC_STANDING_2024 is not None:
|
||||||
|
for position in range(1, len(WDC_STANDING_2024) + 1):
|
||||||
|
standing[position] = list()
|
||||||
|
|
||||||
|
for driver, position in WDC_STANDING_2024.items():
|
||||||
|
standing[position] += [driver]
|
||||||
|
|
||||||
return standing
|
return standing
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_wdc_standing_by_driver") # Cleanup when adding/updating race results
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_wdc_standing_by_driver"
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def wdc_standing_by_driver(self) -> Dict[str, int]:
|
def wdc_standing_by_driver(self) -> Dict[str, int]:
|
||||||
standing: Dict[str, int] = dict()
|
standing: Dict[str, int] = dict()
|
||||||
|
|
||||||
|
if WDC_STANDING_2024 is None:
|
||||||
position: int = 1
|
position: int = 1
|
||||||
last_points: int = 0
|
last_points: int = 0
|
||||||
|
|
||||||
for driver in self.drivers_sorted_by_points(include_inactive=True):
|
for driver in self.drivers_sorted_by_points(include_inactive=True):
|
||||||
points: int = self.total_driver_points_by(driver.name)
|
points: int = self.total_driver_points_by(driver.name)
|
||||||
if points < last_points:
|
if points < last_points:
|
||||||
position += 1
|
drivers_with_this_position = 0
|
||||||
|
for _driver, _position in standing.items():
|
||||||
|
if _position == position:
|
||||||
|
drivers_with_this_position += 1
|
||||||
|
|
||||||
|
# If multiple drivers have equal points, a place is shared.
|
||||||
|
# In this case, the next driver does not occupy the immediate next position.
|
||||||
|
position += drivers_with_this_position
|
||||||
|
|
||||||
standing[driver.name] = position
|
standing[driver.name] = position
|
||||||
last_points = points
|
last_points = points
|
||||||
|
|
||||||
return standing
|
return standing
|
||||||
|
|
||||||
|
if WDC_STANDING_2024 is not None:
|
||||||
|
return WDC_STANDING_2024
|
||||||
|
|
||||||
def wdc_diff_2023_by(self, driver_name: str) -> int:
|
def wdc_diff_2023_by(self, driver_name: str) -> int:
|
||||||
if not driver_name in WDC_STANDING_2023:
|
if not driver_name in WDC_STANDING_2023:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return WDC_STANDING_2023[driver_name] - self.wdc_standing_by_driver()[driver_name]
|
return (
|
||||||
|
WDC_STANDING_2023[driver_name] - self.wdc_standing_by_driver()[driver_name]
|
||||||
|
)
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_most_dnf_names") # Cleanup when adding/updating race results
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_most_dnf_names"
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def most_dnf_names(self) -> List[str]:
|
def most_dnf_names(self) -> List[str]:
|
||||||
dnf_names: List[str] = list()
|
dnf_names: List[str] = list()
|
||||||
most_dnfs: int = 0
|
most_dnfs: int = 0
|
||||||
@ -326,7 +470,9 @@ class PointsModel(Model):
|
|||||||
|
|
||||||
return dnf_names
|
return dnf_names
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_most_gained_names") # Cleanup when adding/updating race results
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_most_gained_names"
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def most_gained_names(self) -> List[str]:
|
def most_gained_names(self) -> List[str]:
|
||||||
most_gained_names: List[str] = list()
|
most_gained_names: List[str] = list()
|
||||||
most_gained: int = 0
|
most_gained: int = 0
|
||||||
@ -345,7 +491,9 @@ class PointsModel(Model):
|
|||||||
|
|
||||||
return most_gained_names
|
return most_gained_names
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_most_lost_names") # Cleanup when adding/updating race results
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_most_lost_names"
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def most_lost_names(self) -> List[str]:
|
def most_lost_names(self) -> List[str]:
|
||||||
most_lost_names: List[str] = list()
|
most_lost_names: List[str] = list()
|
||||||
most_lost: int = 100
|
most_lost: int = 100
|
||||||
@ -368,7 +516,9 @@ class PointsModel(Model):
|
|||||||
# Team points
|
# Team points
|
||||||
#
|
#
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_team_points_per_step_cumulative") # Cleanup when adding/updating race results
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_team_points_per_step_cumulative"
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def team_points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
def team_points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
||||||
"""
|
"""
|
||||||
Returns a dictionary of lists, containing cumulative points per race for each team.
|
Returns a dictionary of lists, containing cumulative points per race for each team.
|
||||||
@ -379,17 +529,30 @@ class PointsModel(Model):
|
|||||||
|
|
||||||
return points_per_step_cumulative
|
return points_per_step_cumulative
|
||||||
|
|
||||||
@cache.memoize(timeout=None, args_to_ignore=["self"]) # Cleanup when adding/updating race results
|
@cache.memoize(
|
||||||
|
timeout=None, args_to_ignore=["self"]
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def total_team_points_by(self, team_name: str) -> int:
|
def total_team_points_by(self, team_name: str) -> int:
|
||||||
teammates: List[Driver] = self.drivers_by(team_name=team_name, include_inactive=True)
|
teammates: List[Driver] = self.drivers_by(
|
||||||
return sum(sum(self.driver_points_by(driver_name=teammate.name, include_inactive=True)) for teammate in teammates)
|
team_name=team_name, include_inactive=True
|
||||||
|
)
|
||||||
|
return sum(
|
||||||
|
sum(self.driver_points_by(driver_name=teammate.name, include_inactive=True))
|
||||||
|
for teammate in teammates
|
||||||
|
)
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_teams_sorted_by_points") # Cleanup when adding/updating race results
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_teams_sorted_by_points"
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def teams_sorted_by_points(self) -> List[Team]:
|
def teams_sorted_by_points(self) -> List[Team]:
|
||||||
comparator: Callable[[Team], int] = lambda team: self.total_team_points_by(team.name)
|
comparator: Callable[[Team], int] = lambda team: self.total_team_points_by(
|
||||||
|
team.name
|
||||||
|
)
|
||||||
return sorted(self.all_teams(include_none=False), key=comparator, reverse=True)
|
return sorted(self.all_teams(include_none=False), key=comparator, reverse=True)
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_wcc_standing_by_position") # Cleanup when adding/updating race results
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_wcc_standing_by_position"
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def wcc_standing_by_position(self) -> Dict[int, List[str]]:
|
def wcc_standing_by_position(self) -> Dict[int, List[str]]:
|
||||||
standing: Dict[int, List[str]] = dict()
|
standing: Dict[int, List[str]] = dict()
|
||||||
|
|
||||||
@ -402,14 +565,18 @@ class PointsModel(Model):
|
|||||||
for team in self.teams_sorted_by_points():
|
for team in self.teams_sorted_by_points():
|
||||||
points: int = self.total_team_points_by(team.name)
|
points: int = self.total_team_points_by(team.name)
|
||||||
if points < last_points:
|
if points < last_points:
|
||||||
position += 1
|
# If multiple teams have equal points, a place is shared.
|
||||||
|
# In this case, the next team does not occupy the immediate next position.
|
||||||
|
position += len(standing[position])
|
||||||
|
|
||||||
standing[position].append(team.name)
|
standing[position].append(team.name)
|
||||||
last_points = points
|
last_points = points
|
||||||
|
|
||||||
return standing
|
return standing
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_wcc_standing_by_team") # Cleanup when adding/updating race results
|
@cache.cached(
|
||||||
|
timeout=None, key_prefix="points_wcc_standing_by_team"
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def wcc_standing_by_team(self) -> Dict[str, int]:
|
def wcc_standing_by_team(self) -> Dict[str, int]:
|
||||||
standing: Dict[str, int] = dict()
|
standing: Dict[str, int] = dict()
|
||||||
|
|
||||||
@ -419,7 +586,14 @@ class PointsModel(Model):
|
|||||||
for team in self.teams_sorted_by_points():
|
for team in self.teams_sorted_by_points():
|
||||||
points: int = self.total_team_points_by(team.name)
|
points: int = self.total_team_points_by(team.name)
|
||||||
if points < last_points:
|
if points < last_points:
|
||||||
position += 1
|
teams_with_this_position = 0
|
||||||
|
for _team, _position in standing.items():
|
||||||
|
if _position == position:
|
||||||
|
teams_with_this_position += 1
|
||||||
|
|
||||||
|
# If multiple teams have equal points, a place is shared.
|
||||||
|
# In this case, the next team does not occupy the immediate next position.
|
||||||
|
position += teams_with_this_position
|
||||||
|
|
||||||
standing[team.name] = position
|
standing[team.name] = position
|
||||||
last_points = points
|
last_points = points
|
||||||
@ -464,8 +638,12 @@ class PointsModel(Model):
|
|||||||
"""
|
"""
|
||||||
return self.points_by(user_name=user_name, race_name=race_name)
|
return self.points_by(user_name=user_name, race_name=race_name)
|
||||||
|
|
||||||
@cache.memoize(timeout=None, args_to_ignore=["self"]) # Cleanup when adding/updating race results or users
|
@cache.memoize(
|
||||||
def points_by(self, *, user_name: str | None = None, race_name: str | None = None) -> List[int] | Dict[str, int] | int:
|
timeout=None, args_to_ignore=["self"]
|
||||||
|
) # Cleanup when adding/updating race results or users
|
||||||
|
def points_by(
|
||||||
|
self, *, user_name: str | None = None, race_name: str | None = None
|
||||||
|
) -> List[int] | Dict[str, int] | int:
|
||||||
if user_name is not None and race_name is None:
|
if user_name is not None and race_name is None:
|
||||||
return self.points_per_step()[user_name]
|
return self.points_per_step()[user_name]
|
||||||
|
|
||||||
@ -485,32 +663,76 @@ class PointsModel(Model):
|
|||||||
|
|
||||||
raise Exception("points_by received an illegal combination of arguments")
|
raise Exception("points_by received an illegal combination of arguments")
|
||||||
|
|
||||||
def total_points_by(self, user_name: str) -> int:
|
def season_points_by(self, *, user_name: str) -> int:
|
||||||
|
"""
|
||||||
|
Returns the number of points from seasonguesses for a specific user.
|
||||||
|
"""
|
||||||
|
big_picks = (int(self.hot_take_correct(user_name=user_name)) * 10
|
||||||
|
+ int(self.p2_constructor_correct(user_name=user_name)) * 10
|
||||||
|
+ int(self.overtakes_correct(user_name=user_name)) * 10
|
||||||
|
+ int(self.dnfs_correct(user_name=user_name)) * 10
|
||||||
|
+ int(self.most_gained_correct(user_name=user_name)) * 10
|
||||||
|
+ int(self.most_lost_correct(user_name=user_name)) * 10)
|
||||||
|
|
||||||
|
small_picks = 0
|
||||||
|
guess: SeasonGuess = self.season_guesses_by(user_name=user_name)
|
||||||
|
|
||||||
|
for driver in guess.team_winners:
|
||||||
|
if self.is_team_winner(driver):
|
||||||
|
small_picks += 3
|
||||||
|
else:
|
||||||
|
small_picks -= 3
|
||||||
|
|
||||||
|
# NOTE: Not picked drivers that had a podium are also wrong
|
||||||
|
for driver in self.all_drivers(include_none=False, include_inactive=True):
|
||||||
|
if driver in guess.podiums and self.has_podium(driver):
|
||||||
|
small_picks += 3
|
||||||
|
elif driver in guess.podiums and not self.has_podium(driver):
|
||||||
|
small_picks -=2
|
||||||
|
elif driver not in guess.podiums and self.has_podium(driver):
|
||||||
|
small_picks -=2
|
||||||
|
|
||||||
|
return big_picks + small_picks
|
||||||
|
|
||||||
|
def total_points_by(self, *, user_name: str, include_season: bool) -> int:
|
||||||
"""
|
"""
|
||||||
Returns the total number of points for a specific user.
|
Returns the total number of points for a specific user.
|
||||||
"""
|
"""
|
||||||
|
if include_season:
|
||||||
|
return sum(self.points_by(user_name=user_name)) + self.season_points_by(user_name=user_name)
|
||||||
|
else:
|
||||||
return sum(self.points_by(user_name=user_name))
|
return sum(self.points_by(user_name=user_name))
|
||||||
|
|
||||||
def users_sorted_by_points(self) -> List[User]:
|
def users_sorted_by_points(self, *, include_season: bool) -> List[User]:
|
||||||
"""
|
"""
|
||||||
Returns the list of users, sorted by their points from race guesses (in descending order).
|
Returns the list of users, sorted by their points from race guesses (in descending order).
|
||||||
"""
|
"""
|
||||||
comparator: Callable[[User], int] = lambda user: self.total_points_by(user.name)
|
comparator: Callable[[User], int] = lambda user: self.total_points_by(user_name=user.name, include_season=include_season)
|
||||||
return sorted(self.all_users(), key=comparator, reverse=True)
|
return sorted(self.all_users(), key=comparator, reverse=True)
|
||||||
|
|
||||||
@cache.cached(timeout=None, key_prefix="points_user_standing") # Cleanup when adding/updating race results or users
|
@cache.cached(
|
||||||
def user_standing(self) -> Dict[str, int]:
|
timeout=None, key_prefix="points_user_standing"
|
||||||
|
) # Cleanup when adding/updating race results or users
|
||||||
|
def user_standing(self, *, include_season: bool) -> Dict[str, int]:
|
||||||
standing: Dict[str, int] = dict()
|
standing: Dict[str, int] = dict()
|
||||||
|
|
||||||
position: int = 1
|
position: int = 1
|
||||||
last_points: int = 0
|
last_points: int = 0
|
||||||
for user in self.users_sorted_by_points():
|
|
||||||
if self.total_points_by(user.name) < last_points:
|
for user in self.users_sorted_by_points(include_season=include_season):
|
||||||
position += 1
|
if self.total_points_by(user_name=user.name, include_season=include_season) < last_points:
|
||||||
|
users_with_this_position = 0
|
||||||
|
for _user, _position in standing.items():
|
||||||
|
if _position == position:
|
||||||
|
users_with_this_position += 1
|
||||||
|
|
||||||
|
# If multiple users have equal points, a place is shared.
|
||||||
|
# In this case, the next user does not occupy the immediate next position.
|
||||||
|
position += users_with_this_position
|
||||||
|
|
||||||
standing[user.name] = position
|
standing[user.name] = position
|
||||||
|
|
||||||
last_points = self.total_points_by(user.name)
|
last_points = self.total_points_by(user_name=user.name, include_season=include_season)
|
||||||
|
|
||||||
return standing
|
return standing
|
||||||
|
|
||||||
@ -518,12 +740,16 @@ class PointsModel(Model):
|
|||||||
# Treat standing + dnf picks separately
|
# Treat standing + dnf picks separately
|
||||||
return len(self.race_guesses_by(user_name=user_name)) * 2
|
return len(self.race_guesses_by(user_name=user_name)) * 2
|
||||||
|
|
||||||
@cache.memoize(timeout=None, args_to_ignore=["self"]) # Cleanup when adding/updating race results
|
@cache.memoize(
|
||||||
|
timeout=None, args_to_ignore=["self"]
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def picks_with_points_count(self, user_name: str) -> int:
|
def picks_with_points_count(self, user_name: str) -> int:
|
||||||
count: int = 0
|
count: int = 0
|
||||||
|
|
||||||
for race_guess in self.race_guesses_by(user_name=user_name):
|
for race_guess in self.race_guesses_by(user_name=user_name):
|
||||||
race_result: RaceResult | None = self.race_result_by(race_name=race_guess.race.name)
|
race_result: RaceResult | None = self.race_result_by(
|
||||||
|
race_name=race_guess.race.name
|
||||||
|
)
|
||||||
if race_result is None:
|
if race_result is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -538,16 +764,22 @@ class PointsModel(Model):
|
|||||||
if self.picks_count(user_name) == 0:
|
if self.picks_count(user_name) == 0:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
return self.total_points_by(user_name) / self.picks_count(user_name)
|
return self.total_points_by(user_name=user_name, include_season=False) / self.picks_count(user_name)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Season guess evaluation
|
# Season guess evaluation
|
||||||
#
|
#
|
||||||
|
|
||||||
def hot_take_correct(self, user_name: str) -> bool:
|
def hot_take_correct(self, user_name: str) -> bool:
|
||||||
season_guess_result: SeasonGuessResult | None = self.season_guess_result_by(user_name=user_name)
|
season_guess_result: SeasonGuessResult | None = self.season_guess_result_by(
|
||||||
|
user_name=user_name
|
||||||
|
)
|
||||||
|
|
||||||
return season_guess_result.hot_take_correct if season_guess_result is not None else False
|
return (
|
||||||
|
season_guess_result.hot_take_correct
|
||||||
|
if season_guess_result is not None
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
def p2_constructor_correct(self, user_name: str) -> bool:
|
def p2_constructor_correct(self, user_name: str) -> bool:
|
||||||
season_guess: SeasonGuess | None = self.season_guesses_by(user_name=user_name)
|
season_guess: SeasonGuess | None = self.season_guesses_by(user_name=user_name)
|
||||||
@ -558,9 +790,15 @@ class PointsModel(Model):
|
|||||||
return season_guess.p2_wcc.name in self.wcc_standing_by_position()[2]
|
return season_guess.p2_wcc.name in self.wcc_standing_by_position()[2]
|
||||||
|
|
||||||
def overtakes_correct(self, user_name: str) -> bool:
|
def overtakes_correct(self, user_name: str) -> bool:
|
||||||
season_guess_result: SeasonGuessResult | None = self.season_guess_result_by(user_name=user_name)
|
season_guess_result: SeasonGuessResult | None = self.season_guess_result_by(
|
||||||
|
user_name=user_name
|
||||||
|
)
|
||||||
|
|
||||||
return season_guess_result.overtakes_correct if season_guess_result is not None else False
|
return (
|
||||||
|
season_guess_result.overtakes_correct
|
||||||
|
if season_guess_result is not None
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
def dnfs_correct(self, user_name: str) -> bool:
|
def dnfs_correct(self, user_name: str) -> bool:
|
||||||
season_guess: SeasonGuess | None = self.season_guesses_by(user_name=user_name)
|
season_guess: SeasonGuess | None = self.season_guesses_by(user_name=user_name)
|
||||||
@ -586,14 +824,22 @@ class PointsModel(Model):
|
|||||||
|
|
||||||
return season_guess.most_wdc_lost.name in self.most_lost_names()
|
return season_guess.most_wdc_lost.name in self.most_lost_names()
|
||||||
|
|
||||||
@cache.memoize(timeout=None, args_to_ignore=["self"]) # Cleanup when adding/updating race results
|
@cache.memoize(
|
||||||
|
timeout=None, args_to_ignore=["self"]
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def is_team_winner(self, driver: Driver) -> bool:
|
def is_team_winner(self, driver: Driver) -> bool:
|
||||||
teammates: List[Driver] = self.drivers_by(team_name=driver.team.name, include_inactive=True)
|
teammates: List[Driver] = self.drivers_by(
|
||||||
teammate: Driver = teammates[0] if teammates[1] == driver else teammates[1]
|
team_name=driver.team.name, include_inactive=True
|
||||||
|
)
|
||||||
|
|
||||||
return self.wdc_standing_by_driver()[driver.name] <= self.wdc_standing_by_driver()[teammate.name]
|
# Min - Highest position is the lowest place number
|
||||||
|
winner: Driver = min(teammates, key=lambda driver: self.wdc_standing_by_driver()[driver.name])
|
||||||
|
|
||||||
@cache.memoize(timeout=None, args_to_ignore=["self"]) # Cleanup when adding/updating race results
|
return driver == winner
|
||||||
|
|
||||||
|
@cache.memoize(
|
||||||
|
timeout=None, args_to_ignore=["self"]
|
||||||
|
) # Cleanup when adding/updating race results
|
||||||
def has_podium(self, driver: Driver) -> bool:
|
def has_podium(self, driver: Driver) -> bool:
|
||||||
for race_result in self.all_race_results():
|
for race_result in self.all_race_results():
|
||||||
position: int | None = race_result.driver_standing_position(driver)
|
position: int | None = race_result.driver_standing_position(driver)
|
||||||
@ -611,13 +857,13 @@ class PointsModel(Model):
|
|||||||
|
|
||||||
data["labels"] = [0] + [
|
data["labels"] = [0] + [
|
||||||
race.name for race in sorted(self.all_races(), key=lambda race: race.number)
|
race.name for race in sorted(self.all_races(), key=lambda race: race.number)
|
||||||
]
|
] # + ["Season"]
|
||||||
|
|
||||||
data["datasets"] = [
|
data["datasets"] = [
|
||||||
{
|
{
|
||||||
"data": self.points_per_step_cumulative()[user.name],
|
"data": self.points_per_step_cumulative()[user.name], # + [self.total_points_by(user_name=user.name, include_season=True)],
|
||||||
"label": user.name,
|
"label": user.name,
|
||||||
"fill": False
|
"fill": False,
|
||||||
}
|
}
|
||||||
for user in self.all_users()
|
for user in self.all_users()
|
||||||
]
|
]
|
||||||
@ -635,7 +881,7 @@ class PointsModel(Model):
|
|||||||
{
|
{
|
||||||
"data": self.driver_points_per_step_cumulative()[driver.name],
|
"data": self.driver_points_per_step_cumulative()[driver.name],
|
||||||
"label": driver.abbr,
|
"label": driver.abbr,
|
||||||
"fill": False
|
"fill": False,
|
||||||
}
|
}
|
||||||
for driver in self.all_drivers(include_none=False, include_inactive=True)
|
for driver in self.all_drivers(include_none=False, include_inactive=True)
|
||||||
]
|
]
|
||||||
@ -653,7 +899,7 @@ class PointsModel(Model):
|
|||||||
{
|
{
|
||||||
"data": self.team_points_per_step_cumulative()[team.name],
|
"data": self.team_points_per_step_cumulative()[team.name],
|
||||||
"label": team.name,
|
"label": team.name,
|
||||||
"fill": False
|
"fill": False,
|
||||||
}
|
}
|
||||||
for team in self.all_teams(include_none=False)
|
for team in self.all_teams(include_none=False)
|
||||||
]
|
]
|
||||||
|
|||||||
@ -104,4 +104,4 @@ class TemplateModel(Model):
|
|||||||
|
|
||||||
def active_drivers_for_wdc_gained(self) -> List[Driver]:
|
def active_drivers_for_wdc_gained(self) -> List[Driver]:
|
||||||
predicate: Callable[[Driver], bool] = lambda driver: driver.abbr not in self._wdc_gained_excluded_abbrs
|
predicate: Callable[[Driver], bool] = lambda driver: driver.abbr not in self._wdc_gained_excluded_abbrs
|
||||||
return find_multiple_strict(predicate, self.all_drivers(include_none=False, include_inactive=False))
|
return find_multiple_strict(predicate, self.all_drivers(include_none=False, include_inactive=True))
|
||||||
|
|||||||
@ -24,13 +24,13 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{# Simple driver select for forms #}
|
{# Simple driver select for forms #}
|
||||||
{% macro driver_select(name, label, include_none, drivers=none, disabled=false, border="") %}
|
{% macro driver_select(name, label, include_none, include_inactive=false, drivers=none, disabled=false, border="") %}
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}" {% if disabled %}disabled="disabled"{% endif %}>
|
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}" {% if disabled %}disabled="disabled"{% endif %}>
|
||||||
<option value="" selected disabled hidden></option>
|
<option value="" selected disabled hidden></option>
|
||||||
|
|
||||||
{% if drivers == none %}
|
{% if drivers == none %}
|
||||||
{% set drivers = model.all_drivers(include_none=include_none, include_inactive=False) %}
|
{% set drivers = model.all_drivers(include_none=include_none, include_inactive=include_inactive) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for driver in drivers %}
|
{% for driver in drivers %}
|
||||||
@ -42,14 +42,14 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{# Driver select for forms where a value might be preselected #}
|
{# Driver select for forms where a value might be preselected #}
|
||||||
{% macro driver_select_with_preselect(driver_match, name, label, include_none, drivers=none, disabled=false, border="") %}
|
{% macro driver_select_with_preselect(driver_match, name, label, include_none, include_inactive=false, drivers=none, disabled=false, border="") %}
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}" {% if disabled %}disabled="disabled"{% endif %}>
|
<select name="{{ name }}" id="{{ name }}" class="form-select {{ border }}" aria-label="{{ name }}" {% if disabled %}disabled="disabled"{% endif %}>
|
||||||
{# Use namespace wrapper to persist scope between loop iterations #}
|
{# Use namespace wrapper to persist scope between loop iterations #}
|
||||||
{% set user_has_chosen = namespace(driverpre=false) %}
|
{% set user_has_chosen = namespace(driverpre=false) %}
|
||||||
|
|
||||||
{% if drivers == none %}
|
{% if drivers == none %}
|
||||||
{% set drivers = model.all_drivers(include_none=include_none, include_inactive=False) %}
|
{% set drivers = model.all_drivers(include_none=include_none, include_inactive=include_inactive) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for driver in drivers %}
|
{% for driver in drivers %}
|
||||||
|
|||||||
@ -6,15 +6,15 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div class="card shadow-sm mb-2">
|
{# <div class="card shadow-sm mb-2">#}
|
||||||
<div class="card-header">
|
{# <div class="card-header">#}
|
||||||
Note
|
{# Note#}
|
||||||
</div>
|
{# </div>#}
|
||||||
|
{##}
|
||||||
<div class="card-body">
|
{# <div class="card-body">#}
|
||||||
Points only include race picks.
|
{# Points only include race picks.#}
|
||||||
</div>
|
{# </div>#}
|
||||||
</div>
|
{# </div>#}
|
||||||
|
|
||||||
<div class="card shadow-sm mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@ -29,6 +29,8 @@
|
|||||||
<th scope="col" class="text-center" style="min-width: 50px;">Place</th>
|
<th scope="col" class="text-center" style="min-width: 50px;">Place</th>
|
||||||
<th scope="col" class="text-center" style="min-width: 50px;">User</th>
|
<th scope="col" class="text-center" style="min-width: 50px;">User</th>
|
||||||
<th scope="col" class="text-center" style="min-width: 100px;">Points</th>
|
<th scope="col" class="text-center" style="min-width: 100px;">Points</th>
|
||||||
|
<th scope="col" class="text-center" style="min-width: 100px;">Points (Race)</th>
|
||||||
|
<th scope="col" class="text-center" style="min-width: 100px;">Points (Season)</th>
|
||||||
<th scope="col" class="text-center" style="min-width: 100px;">Total picks</th>
|
<th scope="col" class="text-center" style="min-width: 100px;">Total picks</th>
|
||||||
<th scope="col" class="text-center" style="min-width: 100px;" data-bs-toggle="tooltip"
|
<th scope="col" class="text-center" style="min-width: 100px;" data-bs-toggle="tooltip"
|
||||||
title="Any points count as correct">Correct picks
|
title="Any points count as correct">Correct picks
|
||||||
@ -38,12 +40,14 @@
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for user in points.users_sorted_by_points() %}
|
{% for user in points.users_sorted_by_points(include_season=True) %}
|
||||||
{% set user_standing = points.user_standing()[user.name] %}
|
{% set user_standing = points.user_standing(include_season=True)[user.name] %}
|
||||||
<tr class="{% if user_standing == 1 %}table-danger{% endif %}">
|
<tr class="{% if user_standing == 1 %}table-danger{% endif %}">
|
||||||
<td class="text-center text-nowrap">{{ user_standing }}</td>
|
<td class="text-center text-nowrap">{{ user_standing }}</td>
|
||||||
<td class="text-center text-nowrap">{{ user.name }}</td>
|
<td class="text-center text-nowrap">{{ user.name }}</td>
|
||||||
<td class="text-center text-nowrap">{{ points.total_points_by(user.name) }}</td>
|
<td class="text-center text-nowrap">{{ points.total_points_by(user_name=user.name, include_season=True) }}</td>
|
||||||
|
<td class="text-center text-nowrap">{{ points.total_points_by(user_name=user.name, include_season=False) }}</td>
|
||||||
|
<td class="text-center text-nowrap">{{ points.season_points_by(user_name=user.name) }}</td>
|
||||||
<td class="text-center text-nowrap">{{ points.picks_count(user.name) }}</td>
|
<td class="text-center text-nowrap">{{ points.picks_count(user.name) }}</td>
|
||||||
<td class="text-center text-nowrap">{{ points.picks_with_points_count(user.name) }}</td>
|
<td class="text-center text-nowrap">{{ points.picks_with_points_count(user.name) }}</td>
|
||||||
<td class="text-center text-nowrap">{{ "%0.2f" % points.points_per_pick(user.name) }}</td>
|
<td class="text-center text-nowrap">{{ "%0.2f" % points.points_per_pick(user.name) }}</td>
|
||||||
@ -57,7 +61,7 @@
|
|||||||
|
|
||||||
<div class="card shadow-sm mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
History
|
History (Race)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
@ -39,13 +39,13 @@
|
|||||||
{# Link should only be visible if all users are visible #}
|
{# Link should only be visible if all users are visible #}
|
||||||
{% if model.active_user is not none %}
|
{% if model.active_user is not none %}
|
||||||
<td class="text-center text-nowrap" style="min-width: 100px;">{{ model.active_user.name }}
|
<td class="text-center text-nowrap" style="min-width: 100px;">{{ model.active_user.name }}
|
||||||
({{ points.total_points_by(model.active_user.name) }})
|
({{ points.total_points_by(user_name=model.active_user.name, include_season=False) }})
|
||||||
</td>
|
</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for user in model.all_users() %}
|
{% for user in model.all_users() %}
|
||||||
<td class="text-center text-nowrap" style="min-width: 100px;">
|
<td class="text-center text-nowrap" style="min-width: 100px;">
|
||||||
<a href="/race/{{ user.name_sanitized }}" class="link-dark">{{ user.name }}
|
<a href="/race/{{ user.name_sanitized }}" class="link-dark">{{ user.name }}
|
||||||
({{ points.total_points_by(user.name) }})</a>
|
({{ points.total_points_by(user_name=user.name, include_season=False) }})</a>
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -10,16 +10,16 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<div class="card shadow-sm mb-2">
|
{# <div class="card shadow-sm mb-2">#}
|
||||||
<div class="card-header">
|
{# <div class="card-header">#}
|
||||||
Note
|
{# Note#}
|
||||||
</div>
|
{# </div>#}
|
||||||
|
{##}
|
||||||
<div class="card-body">
|
{# <div class="card-body">#}
|
||||||
Picks that match the current standings are marked in green, except for the hot-take and overtake picks, as
|
{# Picks that match the current standings are marked in green, except for the hot-take and overtake picks, as#}
|
||||||
those are not evaluated automatically.<br>
|
{# those are not evaluated automatically.<br>#}
|
||||||
</div>
|
{# </div>#}
|
||||||
</div>
|
{# </div>#}
|
||||||
|
|
||||||
<div class="grid card-grid">
|
<div class="grid card-grid">
|
||||||
|
|
||||||
@ -50,7 +50,7 @@
|
|||||||
{# Hot Take #}
|
{# Hot Take #}
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<textarea
|
<textarea
|
||||||
class="form-control {% if points.hot_take_correct(user.name) %}border-success{% endif %}"
|
class="form-control {% if points.hot_take_correct(user.name) %}border-success{% else %}border-danger{% endif %}"
|
||||||
id="hot-take-input-{{ user.name }}" name="hottakeselect"
|
id="hot-take-input-{{ user.name }}" name="hottakeselect"
|
||||||
style="height: 150px"
|
style="height: 150px"
|
||||||
{% if season_guess_open == false %}disabled="disabled"{% endif %}>
|
{% if season_guess_open == false %}disabled="disabled"{% endif %}>
|
||||||
@ -64,29 +64,29 @@
|
|||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
{{ team_select_with_preselect(team_match=user_guess.p2_wcc, name="p2select", label="P2 in WCC:",
|
{{ team_select_with_preselect(team_match=user_guess.p2_wcc, name="p2select", label="P2 in WCC:",
|
||||||
include_none=false, disabled=not season_guess_open,
|
include_none=false, disabled=not season_guess_open,
|
||||||
border=("border-success" if points.p2_constructor_correct(user.name) else "")) }}
|
border=("border-success" if points.p2_constructor_correct(user.name) else "border-danger")) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Most Overtakes + DNFs #}
|
{# Most Overtakes + DNFs #}
|
||||||
<div class="input-group mt-2">
|
<div class="input-group mt-2">
|
||||||
{{ driver_select_with_preselect(driver_match=user_guess.most_overtakes, name="overtakeselect",
|
{{ driver_select_with_preselect(driver_match=user_guess.most_overtakes, name="overtakeselect",
|
||||||
label="Most overtakes:", include_none=false, disabled=not season_guess_open,
|
label="Most overtakes:", include_none=false, include_inactive=true, disabled=not season_guess_open,
|
||||||
border=("border-success" if points.overtakes_correct(user.name) else "")) }}
|
border=("border-success" if points.overtakes_correct(user.name) else "border-danger")) }}
|
||||||
{{ driver_select_with_preselect(driver_match=user_guess.most_dnfs, name="dnfselect", label="Most DNFs:",
|
{{ driver_select_with_preselect(driver_match=user_guess.most_dnfs, name="dnfselect", label="Most DNFs:",
|
||||||
include_none=false, disabled=not season_guess_open,
|
include_none=false, include_inactive=true, disabled=not season_guess_open,
|
||||||
border=("border-success" if points.dnfs_correct(user.name) else "")) }}
|
border=("border-success" if points.dnfs_correct(user.name) else "border-danger")) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Most Gained + Lost #}
|
{# Most Gained + Lost #}
|
||||||
<div class="input-group mt-2" data-bs-toggle="tooltip"
|
<div class="input-group mt-2" data-bs-toggle="tooltip"
|
||||||
title="Which driver will gain/lose the most places in comparison to last season's results?">
|
title="Which driver will gain/lose the most places in comparison to last season's results?">
|
||||||
{{ driver_select_with_preselect(driver_match=user_guess.most_wdc_gained, name="gainedselect",
|
{{ driver_select_with_preselect(driver_match=user_guess.most_wdc_gained, name="gainedselect",
|
||||||
label="Most WDC pl. gained:", include_none=false, drivers=model.active_drivers_for_wdc_gained(),
|
label="Most WDC pl. gained:", include_none=false, include_inactive=true,
|
||||||
disabled=not season_guess_open,
|
drivers=model.active_drivers_for_wdc_gained(), disabled=not season_guess_open,
|
||||||
border=("border-success" if points.most_gained_correct(user.name) else "")) }}
|
border=("border-success" if points.most_gained_correct(user.name) else "border-danger")) }}
|
||||||
{{ driver_select_with_preselect(driver_match=user_guess.most_wdc_lost, name="lostselect",
|
{{ driver_select_with_preselect(driver_match=user_guess.most_wdc_lost, name="lostselect",
|
||||||
label="Most WDC pl. lost:", include_none=false, disabled=not season_guess_open,
|
label="Most WDC pl. lost:", include_none=false, include_inactive=true, disabled=not season_guess_open,
|
||||||
border=("border-success" if points.most_lost_correct(user.name) else "")) }}
|
border=("border-success" if points.most_lost_correct(user.name) else "border-danger")) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Team-internal Winners #}
|
{# Team-internal Winners #}
|
||||||
@ -95,8 +95,10 @@
|
|||||||
winners:</h6>
|
winners:</h6>
|
||||||
<div class="grid mt-2 container" style="row-gap: 0;">
|
<div class="grid mt-2 container" style="row-gap: 0;">
|
||||||
{% for team in model.all_teams(include_none=false) %}
|
{% for team in model.all_teams(include_none=false) %}
|
||||||
{% set driver_a = model.drivers_by(team_name=team.name, include_inactive=False)[0] %}
|
{# HACK: Choosing 0 and 1 will chose the drivers with the lowest IDs (although there could be others). #}
|
||||||
{% set driver_b = model.drivers_by(team_name=team.name, include_inactive=False)[1] %}
|
{# This means the drivers chosen from at the start of the season will be visible. #}
|
||||||
|
{% set driver_a = model.drivers_by(team_name=team.name, include_inactive=True)[0] %}
|
||||||
|
{% set driver_b = model.drivers_by(team_name=team.name, include_inactive=True)[1] %}
|
||||||
|
|
||||||
<div class="g-col-6">
|
<div class="g-col-6">
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
@ -106,7 +108,8 @@
|
|||||||
value="{{ driver_a.id }}"
|
value="{{ driver_a.id }}"
|
||||||
{% if (user_guess is not none) and (driver_a in user_guess.team_winners) %}checked="checked"{% endif %}
|
{% if (user_guess is not none) and (driver_a in user_guess.team_winners) %}checked="checked"{% endif %}
|
||||||
{% if season_guess_open == false %}disabled="disabled"{% endif %}>
|
{% if season_guess_open == false %}disabled="disabled"{% endif %}>
|
||||||
<label class="form-check-label {% if (user_guess is not none) and (driver_a in user_guess.team_winners) and points.is_team_winner(driver_a) %}text-success{% endif %}"
|
<label class="form-check-label {% if (user_guess is not none) and (driver_a in user_guess.team_winners) and points.is_team_winner(driver_a) %}text-success
|
||||||
|
{% elif (user_guess is not none) and (driver_a in user_guess.team_winners) and (not points.is_team_winner(driver_a)) %}text-danger{% endif %}"
|
||||||
for="teamwinner-{{ team.id }}-1-{{ user.id }}">{{ driver_a.name }}</label>
|
for="teamwinner-{{ team.id }}-1-{{ user.id }}">{{ driver_a.name }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -119,7 +122,8 @@
|
|||||||
value="{{ driver_b.id }}"
|
value="{{ driver_b.id }}"
|
||||||
{% if (user_guess is not none) and (driver_b in user_guess.team_winners) %}checked="checked"{% endif %}
|
{% if (user_guess is not none) and (driver_b in user_guess.team_winners) %}checked="checked"{% endif %}
|
||||||
{% if season_guess_open == false %}disabled="disabled"{% endif %}>
|
{% if season_guess_open == false %}disabled="disabled"{% endif %}>
|
||||||
<label class="form-check-label {% if (user_guess is not none) and (driver_b in user_guess.team_winners) and points.is_team_winner(driver_b) %}text-success{% endif %}"
|
<label class="form-check-label {% if (user_guess is not none) and (driver_b in user_guess.team_winners) and points.is_team_winner(driver_b) %}text-success
|
||||||
|
{% elif (user_guess is not none) and (driver_b in user_guess.team_winners) and (not points.is_team_winner(driver_b)) %}text-danger{% endif %}"
|
||||||
for="teamwinner-{{ team.id }}-2-{{ user.id }}">{{ driver_b.name }}</label>
|
for="teamwinner-{{ team.id }}-2-{{ user.id }}">{{ driver_b.name }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -131,8 +135,10 @@
|
|||||||
title="Which driver will reach at least a single podium?">Drivers with podium(s):</h6>
|
title="Which driver will reach at least a single podium?">Drivers with podium(s):</h6>
|
||||||
<div class="grid mt-2 container" style="row-gap: 0;">
|
<div class="grid mt-2 container" style="row-gap: 0;">
|
||||||
{% for team in model.all_teams(include_none=false) %}
|
{% for team in model.all_teams(include_none=false) %}
|
||||||
{% set driver_a = model.drivers_by(team_name=team.name, include_inactive=False)[0] %}
|
{# HACK: Choosing 0 and 1 will chose the drivers with the lowest IDs (although there could be others). #}
|
||||||
{% set driver_b = model.drivers_by(team_name=team.name, include_inactive=False)[1] %}
|
{# This means the drivers chosen from at the start of the season will be visible. #}
|
||||||
|
{% set driver_a = model.drivers_by(team_name=team.name, include_inactive=True)[0] %}
|
||||||
|
{% set driver_b = model.drivers_by(team_name=team.name, include_inactive=True)[1] %}
|
||||||
|
|
||||||
<div class="g-col-6">
|
<div class="g-col-6">
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
@ -142,7 +148,9 @@
|
|||||||
value="{{ driver_a.id }}"
|
value="{{ driver_a.id }}"
|
||||||
{% if (user_guess is not none) and (driver_a in user_guess.podiums) %}checked="checked"{% endif %}
|
{% if (user_guess is not none) and (driver_a in user_guess.podiums) %}checked="checked"{% endif %}
|
||||||
{% if season_guess_open == false %}disabled="disabled"{% endif %}>
|
{% if season_guess_open == false %}disabled="disabled"{% endif %}>
|
||||||
<label class="form-check-label {% if (user_guess is not none) and (driver_a in user_guess.podiums) and points.has_podium(driver_a) %}text-success{% endif %}"
|
<label class="form-check-label {% if (user_guess is not none) and (driver_a in user_guess.podiums) and points.has_podium(driver_a) %}text-success
|
||||||
|
{% elif (user_guess is not none) and (driver_a in user_guess.podiums) and (not points.has_podium(driver_a)) %}text-danger
|
||||||
|
{% elif (user_guess is not none) and (driver_a not in user_guess.podiums) and (points.has_podium(driver_a)) %}text-danger{% endif %}"
|
||||||
for="podium-{{ driver_a.id }}-{{ user.id }}">{{ driver_a.name }}</label>
|
for="podium-{{ driver_a.id }}-{{ user.id }}">{{ driver_a.name }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -155,7 +163,9 @@
|
|||||||
value="{{ driver_b.id }}"
|
value="{{ driver_b.id }}"
|
||||||
{% if (user_guess is not none) and (driver_b in user_guess.podiums) %}checked="checked"{% endif %}
|
{% if (user_guess is not none) and (driver_b in user_guess.podiums) %}checked="checked"{% endif %}
|
||||||
{% if season_guess_open == false %}disabled="disabled"{% endif %}>
|
{% if season_guess_open == false %}disabled="disabled"{% endif %}>
|
||||||
<label class="form-check-label {% if (user_guess is not none) and (driver_b in user_guess.podiums) and points.has_podium(driver_b) %}text-success{% endif %}"
|
<label class="form-check-label {% if (user_guess is not none) and (driver_b in user_guess.podiums) and points.has_podium(driver_b) %}text-success
|
||||||
|
{% elif (user_guess is not none) and (driver_b in user_guess.podiums) and (not points.has_podium(driver_b)) %}text-danger
|
||||||
|
{% elif (user_guess is not none) and (driver_b not in user_guess.podiums) and (points.has_podium(driver_b)) %}text-danger{% endif %}"
|
||||||
for="podium-{{ driver_b.id }}-{{ user.id }}">{{ driver_b.name }}</label>
|
for="podium-{{ driver_b.id }}-{{ user.id }}">{{ driver_b.name }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user