fix conversion for round parameter so it is converted to integer if isnumeric is true. Added some extra comments to make it more clear to the user how to interact with the Gradio UI
Browse files- app.py +22 -17
- fastf1_tools.py +15 -3
- todo.txt +1 -3
app.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import gradio as gr
|
|
|
2 |
|
3 |
# Local modules
|
4 |
import fastf1_tools
|
@@ -17,8 +18,8 @@ iface_driver_championship_standings = gr.Interface(
|
|
17 |
gr.Dropdown(label="Driver", choices=DRIVER_NAMES)
|
18 |
],
|
19 |
outputs="text",
|
20 |
-
title="Driver Championship Standings",
|
21 |
-
description="Get the driver championship standings"
|
22 |
)
|
23 |
|
24 |
iface_constructor_championship_standings = gr.Interface(
|
@@ -28,20 +29,20 @@ iface_constructor_championship_standings = gr.Interface(
|
|
28 |
gr.Dropdown(label="Constructor", choices=CONSTRUCTOR_NAMES)
|
29 |
],
|
30 |
outputs="text",
|
31 |
-
title="Constructor Championship Standings",
|
32 |
-
description="Get the constructor championship standings"
|
33 |
)
|
34 |
|
35 |
iface_event_info = gr.Interface(
|
36 |
fn=fastf1_tools.get_event_info,
|
37 |
inputs=[
|
38 |
gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
|
39 |
-
gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported)"),
|
40 |
gr.Radio(["human", "LLM"], label="Display format", value="human")
|
41 |
],
|
42 |
outputs="text",
|
43 |
title="Event Info",
|
44 |
-
description="Get
|
45 |
)
|
46 |
|
47 |
iface_season_calendar = gr.Interface(
|
@@ -51,32 +52,36 @@ iface_season_calendar = gr.Interface(
|
|
51 |
],
|
52 |
outputs="text",
|
53 |
title="Season Calendar",
|
54 |
-
description="Get the season calendar
|
55 |
)
|
56 |
|
57 |
iface_track_visualization = gr.Interface(
|
58 |
fn=fastf1_tools.track_visualization,
|
59 |
inputs=[
|
60 |
gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
|
61 |
-
gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported)"),
|
62 |
-
gr.Radio(["speed", "corners", "gear"], label="Visualization type", value="speed"),
|
63 |
-
gr.Dropdown(DRIVER_NAMES)
|
64 |
],
|
65 |
outputs="image",
|
66 |
title="Track Visualizations",
|
67 |
-
description="Get the track visualization for the given Grand Prix"
|
68 |
)
|
69 |
|
70 |
iface_session_results = gr.Interface(
|
71 |
fn=fastf1_tools.get_session_results,
|
72 |
inputs=[
|
73 |
gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
|
74 |
-
gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported)"),
|
75 |
-
gr.Dropdown([session_type for session_type in DROPDOWN_SESSION_TYPES if "practice" not in session_type], label="Session type", value="race")
|
76 |
],
|
77 |
-
outputs=gr.
|
|
|
|
|
|
|
|
|
78 |
title="Session Results",
|
79 |
-
description="Get the session results for the given Grand Prix"
|
80 |
)
|
81 |
|
82 |
iface_driver_info = gr.Interface(
|
@@ -86,7 +91,7 @@ iface_driver_info = gr.Interface(
|
|
86 |
],
|
87 |
outputs="text",
|
88 |
title="Driver Info",
|
89 |
-
description="Get information about a specific driver"
|
90 |
)
|
91 |
|
92 |
iface_constructor_info = gr.Interface(
|
@@ -96,7 +101,7 @@ iface_constructor_info = gr.Interface(
|
|
96 |
],
|
97 |
outputs="text",
|
98 |
title="Constructor Info",
|
99 |
-
description="Get information about a specific constructor"
|
100 |
)
|
101 |
|
102 |
|
|
|
1 |
import gradio as gr
|
2 |
+
import pandas as pd
|
3 |
|
4 |
# Local modules
|
5 |
import fastf1_tools
|
|
|
18 |
gr.Dropdown(label="Driver", choices=DRIVER_NAMES)
|
19 |
],
|
20 |
outputs="text",
|
21 |
+
title="World Driver Championship Standings",
|
22 |
+
description="Get the world driver championship standings for a specific driver"
|
23 |
)
|
24 |
|
25 |
iface_constructor_championship_standings = gr.Interface(
|
|
|
29 |
gr.Dropdown(label="Constructor", choices=CONSTRUCTOR_NAMES)
|
30 |
],
|
31 |
outputs="text",
|
32 |
+
title="World Constructor Championship Standings",
|
33 |
+
description="Get the world constructor championship standings for a specific constructor"
|
34 |
)
|
35 |
|
36 |
iface_event_info = gr.Interface(
|
37 |
fn=fastf1_tools.get_event_info,
|
38 |
inputs=[
|
39 |
gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
|
40 |
+
gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported) or round number"),
|
41 |
gr.Radio(["human", "LLM"], label="Display format", value="human")
|
42 |
],
|
43 |
outputs="text",
|
44 |
title="Event Info",
|
45 |
+
description="Get information about a specific Grand Prix event. Example: (2025,Monaco,human)"
|
46 |
)
|
47 |
|
48 |
iface_season_calendar = gr.Interface(
|
|
|
52 |
],
|
53 |
outputs="text",
|
54 |
title="Season Calendar",
|
55 |
+
description="Get the season calendar for the given year"
|
56 |
)
|
57 |
|
58 |
iface_track_visualization = gr.Interface(
|
59 |
fn=fastf1_tools.track_visualization,
|
60 |
inputs=[
|
61 |
gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
|
62 |
+
gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported) or round number"),
|
63 |
+
gr.Radio(["speed", "corners", "gear"], label="Visualization type", value="speed", info="What type of track visualization to generate"),
|
64 |
+
gr.Dropdown(label="Driver", choices=DRIVER_NAMES, info="Only applied for speed visualization. gear uses fastest lap during race.")
|
65 |
],
|
66 |
outputs="image",
|
67 |
title="Track Visualizations",
|
68 |
+
description="Get the track visualization for the given Grand Prix. Example: (2025,Monaco,speed,Leclerc)"
|
69 |
)
|
70 |
|
71 |
iface_session_results = gr.Interface(
|
72 |
fn=fastf1_tools.get_session_results,
|
73 |
inputs=[
|
74 |
gr.Number(label="Calendar year", value=CURRENT_YEAR, minimum=1950, maximum=CURRENT_YEAR),
|
75 |
+
gr.Textbox(label="Grand Prix", placeholder="Ex: Monaco", info="The name of the GP/country/location (Fuzzy matching supported) or round number"),
|
76 |
+
gr.Dropdown([session_type for session_type in DROPDOWN_SESSION_TYPES if "practice" not in session_type], label="Session type", value="race", info="The session type to get results for. Dataframe's columns vary depending on session type.")
|
77 |
],
|
78 |
+
outputs=gr.Dataframe(
|
79 |
+
headers=None, # Let it infer from returned DataFrame
|
80 |
+
row_count=(0, "dynamic"), # Start empty, allow it to grow
|
81 |
+
col_count=(0, "dynamic") # Let columns adjust too
|
82 |
+
),
|
83 |
title="Session Results",
|
84 |
+
description="Get the session results for the given Grand Prix. Example: (2025,Monaco,qualifying)"
|
85 |
)
|
86 |
|
87 |
iface_driver_info = gr.Interface(
|
|
|
91 |
],
|
92 |
outputs="text",
|
93 |
title="Driver Info",
|
94 |
+
description="Get background information about a specific driver"
|
95 |
)
|
96 |
|
97 |
iface_constructor_info = gr.Interface(
|
|
|
101 |
],
|
102 |
outputs="text",
|
103 |
title="Constructor Info",
|
104 |
+
description="Get background information about a specific constructor"
|
105 |
)
|
106 |
|
107 |
|
fastf1_tools.py
CHANGED
@@ -48,6 +48,9 @@ def get_session(year: int, round: gp, session_type: session_type) -> Session:
|
|
48 |
if isinstance(session_type, str):
|
49 |
if session_type.lower() not in AVAILABLE_SESSION_TYPES:
|
50 |
return f"Session type {session_type} is not available. Supported session types: {AVAILABLE_SESSION_TYPES}"
|
|
|
|
|
|
|
51 |
|
52 |
return fastf1.get_session(year, round, session_type)
|
53 |
|
@@ -75,6 +78,9 @@ def get_event_info(year: int, round: gp, format: str) -> str:
|
|
75 |
str: Formatted event information based on the specified format
|
76 |
"""
|
77 |
|
|
|
|
|
|
|
78 |
event = fastf1.get_session(year, round, "race").event # Event object is the same for all sessions, so hardcode "race"
|
79 |
if format == "human":
|
80 |
data_interval = f"{event['Session1DateUtc'].date()} - {event['Session5DateUtc'].date()}"
|
@@ -173,6 +179,8 @@ def track_visualization(year: int, round: gp, visualization_type: str, driver_na
|
|
173 |
Returns:
|
174 |
Image.Image: A PIL Image object containing the visualization
|
175 |
"""
|
|
|
|
|
176 |
|
177 |
session = get_session(year, round, "race")
|
178 |
session.load()
|
@@ -196,17 +204,21 @@ def get_session_results(year: int, round: gp, session_type: session_type) -> pd.
|
|
196 |
pd.DataFrame: DataFrame containing the session results
|
197 |
|
198 |
Raises:
|
199 |
-
|
200 |
"""
|
|
|
|
|
201 |
|
202 |
try:
|
203 |
session = get_session(year, round, session_type)
|
204 |
session.load(telemetry=False)
|
205 |
-
|
|
|
|
|
206 |
except ValueError as e:
|
207 |
raise gr.Error(f"Session type {session_type} is not supported for the specified round. This Grand Prix most likely did not include a sprint race/quali.")
|
208 |
|
209 |
-
|
210 |
df["Name"] = df.apply(lambda row: f"{row['FullName']} ({row['Abbreviation']} • {row['DriverNumber']})", axis=1)
|
211 |
df = df.drop(columns=["FullName", "Abbreviation", "DriverNumber"])
|
212 |
df = df.rename(columns={"Position": "Pos", "GridPosition": "Grid Pos"})
|
|
|
48 |
if isinstance(session_type, str):
|
49 |
if session_type.lower() not in AVAILABLE_SESSION_TYPES:
|
50 |
return f"Session type {session_type} is not available. Supported session types: {AVAILABLE_SESSION_TYPES}"
|
51 |
+
|
52 |
+
if isinstance(round, str) and round.isnumeric():
|
53 |
+
round = int(round)
|
54 |
|
55 |
return fastf1.get_session(year, round, session_type)
|
56 |
|
|
|
78 |
str: Formatted event information based on the specified format
|
79 |
"""
|
80 |
|
81 |
+
if isinstance(round, str) and round.isnumeric():
|
82 |
+
round = int(round)
|
83 |
+
|
84 |
event = fastf1.get_session(year, round, "race").event # Event object is the same for all sessions, so hardcode "race"
|
85 |
if format == "human":
|
86 |
data_interval = f"{event['Session1DateUtc'].date()} - {event['Session5DateUtc'].date()}"
|
|
|
179 |
Returns:
|
180 |
Image.Image: A PIL Image object containing the visualization
|
181 |
"""
|
182 |
+
if isinstance(round, str) and round.isnumeric():
|
183 |
+
round = int(round)
|
184 |
|
185 |
session = get_session(year, round, "race")
|
186 |
session.load()
|
|
|
204 |
pd.DataFrame: DataFrame containing the session results
|
205 |
|
206 |
Raises:
|
207 |
+
ValueError: If the session type is invalid
|
208 |
"""
|
209 |
+
if isinstance(round, str) and round.isnumeric():
|
210 |
+
round = int(round)
|
211 |
|
212 |
try:
|
213 |
session = get_session(year, round, session_type)
|
214 |
session.load(telemetry=False)
|
215 |
+
# Create a proper copy of the results DataFrame
|
216 |
+
df = session.results[['DriverNumber', 'Abbreviation', 'FullName', 'Position',
|
217 |
+
'GridPosition', 'Points', 'Status', 'Q1', 'Q2', 'Q3']].copy()
|
218 |
except ValueError as e:
|
219 |
raise gr.Error(f"Session type {session_type} is not supported for the specified round. This Grand Prix most likely did not include a sprint race/quali.")
|
220 |
|
221 |
+
# Now we can safely modify the DataFrame
|
222 |
df["Name"] = df.apply(lambda row: f"{row['FullName']} ({row['Abbreviation']} • {row['DriverNumber']})", axis=1)
|
223 |
df = df.drop(columns=["FullName", "Abbreviation", "DriverNumber"])
|
224 |
df = df.rename(columns={"Position": "Pos", "GridPosition": "Grid Pos"})
|
todo.txt
CHANGED
@@ -2,6 +2,4 @@
|
|
2 |
* Solution is to have a static json file that maps drivers for each season/year
|
3 |
- Same applies for constructor championship standings but instead Constructor dropdown
|
4 |
* Similar solution to above
|
5 |
-
- Implement a tab for the OpenF1 tools but it might not be that user friendly :/
|
6 |
-
- Add some helpful info for each Gradio Tab to help user understand what they are able to do
|
7 |
-
- for session result remove the default placeholder value
|
|
|
2 |
* Solution is to have a static json file that maps drivers for each season/year
|
3 |
- Same applies for constructor championship standings but instead Constructor dropdown
|
4 |
* Similar solution to above
|
5 |
+
- Implement a tab for the OpenF1 tools but it might not be that user friendly :/
|
|
|
|