Compare commits

...

7 Commits

Author SHA1 Message Date
b485791d25 Bug: Update method signature in single-user raceguess page
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 15s
2024-12-08 20:01:30 +01:00
e8c8e35e05 Disable info cards regarding season picks
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 11s
2024-12-08 19:57:24 +01:00
15305b2f3e Bug: Fix statistics issues caused by driver substitutions
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 16s
2024-12-08 19:52:02 +01:00
0dc4b22c72 Commented out leaderboard diagram extension for season points
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 16s
2024-12-08 19:38:31 +01:00
cef40a9e8b Implement season guess evaluation
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 15s
2024-12-08 19:25:06 +01:00
c509746688 Template: Mark wrong season guesses with red border
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 27s
2024-12-08 17:22:35 +01:00
9cdd7267db Bug: Fix missing inactive drivers in season guesses 2024-12-08 17:22:14 +01:00
7 changed files with 218 additions and 112 deletions

View File

@ -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

View File

@ -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,6 +11,7 @@ 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
@ -68,6 +69,7 @@ WDC_STANDING_2023: Dict[str, int] = {
"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,
@ -81,6 +83,39 @@ WCC_STANDING_2023: Dict[str, int] = {
"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( guessed_driver_position: int | None = race_result.driver_standing_position(
@ -106,6 +141,16 @@ 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.
@ -170,8 +215,10 @@ class PointsModel(Model):
driver_points_per_step[driver.name][race_number] += ( driver_points_per_step[driver.name][race_number] += (
DRIVER_FASTEST_LAP_POINTS DRIVER_FASTEST_LAP_POINTS
if race_result.fastest_lap_driver == driver if race_result.fastest_lap_driver == driver
and int(position) <= 10
else 0 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
@ -340,6 +387,7 @@ class PointsModel(Model):
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()
if WDC_STANDING_2024 is None:
for position in range( for position in range(
1, len(self.all_drivers(include_none=False, include_inactive=True)) + 1 1, len(self.all_drivers(include_none=False, include_inactive=True)) + 1
): ):
@ -358,6 +406,13 @@ class PointsModel(Model):
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( @cache.cached(
@ -366,6 +421,7 @@ class PointsModel(Model):
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
@ -386,6 +442,9 @@ class PointsModel(Model):
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
@ -604,30 +663,64 @@ 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( @cache.cached(
timeout=None, key_prefix="points_user_standing" timeout=None, key_prefix="points_user_standing"
) # Cleanup when adding/updating race results or users ) # Cleanup when adding/updating race results or users
def user_standing(self) -> Dict[str, int]: 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(): for user in self.users_sorted_by_points(include_season=include_season):
if self.total_points_by(user.name) < last_points: if self.total_points_by(user_name=user.name, include_season=include_season) < last_points:
users_with_this_position = 0 users_with_this_position = 0
for _user, _position in standing.items(): for _user, _position in standing.items():
if _position == position: if _position == position:
@ -639,7 +732,7 @@ class PointsModel(Model):
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
@ -671,7 +764,7 @@ 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
@ -738,12 +831,11 @@ class PointsModel(Model):
teammates: List[Driver] = self.drivers_by( teammates: List[Driver] = self.drivers_by(
team_name=driver.team.name, include_inactive=True team_name=driver.team.name, include_inactive=True
) )
teammate: Driver = teammates[0] if teammates[1] == driver else teammates[1]
return ( # Min - Highest position is the lowest place number
self.wdc_standing_by_driver()[driver.name] winner: Driver = min(teammates, key=lambda driver: self.wdc_standing_by_driver()[driver.name])
<= self.wdc_standing_by_driver()[teammate.name]
) return driver == winner
@cache.memoize( @cache.memoize(
timeout=None, args_to_ignore=["self"] timeout=None, args_to_ignore=["self"]
@ -765,11 +857,11 @@ 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,
} }

View File

@ -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))

View File

@ -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 %}

View File

@ -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">

View File

@ -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 %}

View File

@ -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>