tracinginsights commited on
Commit
0377dae
1 Parent(s): 82528df

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +765 -6
main.py CHANGED
@@ -1,10 +1,769 @@
1
- from git import Repo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
- GITHUB_PAT = os.environ['GITHUB']
 
 
 
 
5
 
6
- if not os.path.exists('repo_directory'):
7
- # os.mkdir('repo_directory')
8
- Repo.clone_from(f'https://tracinginsights:{GITHUB_PAT}@github.com/TracingInsights/fastf1api.git', 'repo_directory' )
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- from repo_directory.main import *
 
1
+ # from git import Repo
2
+ # import os
3
+
4
+ # GITHUB_PAT = os.environ['GITHUB']
5
+
6
+ # if not os.path.exists('repo_directory'):
7
+ # # os.mkdir('repo_directory')
8
+ # Repo.clone_from(f'https://tracinginsights:{GITHUB_PAT}@github.com/TracingInsights/fastf1api.git', 'repo_directory' )
9
+
10
+ # from repo_directory.main import *
11
+
12
+
13
+ import concurrent.futures
14
+ import datetime
15
+ import functools
16
+ import math
17
  import os
18
+ from io import BytesIO
19
+
20
+ import fastf1
21
+ import numpy as np
22
+ import pandas as pd
23
+ import requests
24
+ import streamlit as st
25
+ from fastapi import Depends, FastAPI
26
+ from fastapi.middleware.cors import CORSMiddleware
27
+ from fastapi.responses import FileResponse, HTMLResponse
28
+ from fastf1.ergast import Ergast
29
+ from pydantic import BaseModel, Field
30
+ from sqlalchemy.orm import Session
31
+
32
+ # from . import accelerations, database, models, utils
33
+
34
+ import accelerations
35
+ import database
36
+ import models
37
+ import utils
38
+ FASTF1_CACHE_DIR = os.environ["FASTF1_CACHE_DIR"]
39
+
40
+ fastf1.Cache.enable_cache(FASTF1_CACHE_DIR)
41
+
42
+ app = FastAPI()
43
+
44
+ app.add_middleware(
45
+ CORSMiddleware,
46
+ allow_origins=["*"],
47
+ allow_credentials=True,
48
+ allow_methods=["*"],
49
+ allow_headers=["*"],
50
+ )
51
+
52
+
53
+ database.Base.metadata.create_all(bind=database.engine)
54
+
55
+
56
+ def get_db():
57
+ try:
58
+ db = database.SessionLocal()
59
+ yield db
60
+ finally:
61
+ db.close()
62
+
63
+
64
+ class RacePace(BaseModel):
65
+ year: int
66
+ event: str
67
+ session: str
68
+ Driver: str
69
+ LapTime: float
70
+ Diff: float
71
+ Team: str
72
+ fill: str
73
+
74
+
75
+ # @functools.cache
76
+ @app.get("/racepace/{year}/{event}/{session}", response_model=None)
77
+ async def average_race_pace(
78
+ year: int, event: str | int, session: str, db: Session = Depends(get_db)
79
+ ) -> any:
80
+ race_pace_data = (
81
+ db.query(models.RacePace)
82
+ .filter_by(year=year, event=event, session=session)
83
+ .all()
84
+ )
85
+
86
+ if race_pace_data:
87
+ print("Fetching from Database")
88
+
89
+ if not race_pace_data:
90
+ print("Writing to Database")
91
+ f1session = fastf1.get_session(
92
+ year,
93
+ event,
94
+ session,
95
+ # backend="fastf1",
96
+ # force_ergast=False,
97
+ )
98
+ f1session.load(telemetry=False, weather=False, messages=False)
99
+ laps = f1session.laps
100
+
101
+ laps = laps.loc[laps.LapNumber > 1]
102
+ laps = laps.pick_track_status(
103
+ "1",
104
+ )
105
+ laps["LapTime"] = laps.Sector1Time + laps.Sector2Time + laps.Sector3Time
106
+
107
+ # convert LapTime to seconds
108
+ laps["LapTime"] = laps["LapTime"].apply(lambda x: x.total_seconds())
109
+
110
+ laps = laps.loc[laps.LapTime < laps.LapTime.min() * 1.07]
111
+
112
+ df = (
113
+ laps[["LapTime", "Driver"]].groupby("Driver").mean().reset_index(drop=False)
114
+ )
115
+ df = df.sort_values(by="LapTime").reset_index(drop=True)
116
+ df["LapTime"] = df["LapTime"].round(3)
117
+ df["Diff"] = (df["LapTime"] - df["LapTime"].min()).round(3)
118
+ teams = laps[["Driver", "Team"]].drop_duplicates().reset_index(drop=True)
119
+ # join teams and df
120
+ df = df.merge(teams, on="Driver", how="left")
121
+
122
+ car_colors = utils.team_colors(year)
123
+
124
+ df["fill"] = df["Team"].map(car_colors)
125
+
126
+ df_json = df.to_dict("records")
127
+
128
+ # save the data to the database
129
+ for record in df.to_dict("records"):
130
+ race_pace = models.RacePace(**record)
131
+ db.add(race_pace)
132
+
133
+ db.commit()
134
+
135
+ return {"racePace": df_json}
136
+
137
+ return {"racePace": [dict(race_pace) for race_pace in race_pace_data]}
138
+
139
+
140
+ @functools.cache
141
+ @app.get("/topspeed/{year}/{event}/{session}", response_model=None)
142
+ async def top_speed(year: int, event: str | int, session: str) -> any:
143
+ f1session = fastf1.get_session(year, event, session)
144
+ f1session.load(telemetry=False, weather=False, messages=False)
145
+ laps = f1session.laps
146
+ team_colors = utils.team_colors(year)
147
+
148
+ fastest_speedtrap = (
149
+ laps[["SpeedI1", "SpeedI2", "SpeedST", "SpeedFL"]]
150
+ .idxmax(axis=1)
151
+ .value_counts()
152
+ .index[0]
153
+ )
154
+
155
+ speed_df = (
156
+ laps[[fastest_speedtrap, "Driver", "Compound", "Team"]]
157
+ .groupby("Driver")
158
+ .max()
159
+ .sort_values(fastest_speedtrap, ascending=False)
160
+ .reset_index()
161
+ )
162
+ # add team colors to dataframe
163
+ speed_df["fill"] = speed_df["Team"].apply(lambda x: team_colors[x])
164
+
165
+ # rename fastest speedtrap column to TopSpeed
166
+ speed_df.rename(columns={fastest_speedtrap: "TopSpeed"}, inplace=True)
167
+
168
+ # remove nan values in any column
169
+ speed_df = speed_df.dropna()
170
+
171
+ # Convert to int
172
+ speed_df["TopSpeed"] = speed_df["TopSpeed"].astype(int)
173
+
174
+ speed_dict = speed_df.to_dict(orient="records")
175
+
176
+ return {"topSpeed": speed_dict}
177
+
178
+
179
+ @functools.cache
180
+ @app.get("/overtakes/{year}/{event}", response_model=None)
181
+ def get_overtakes(year: int, event: str) -> any:
182
+ def get_overtakes_df(year, event):
183
+ if year == 2023:
184
+ url = "https://docs.google.com/spreadsheets/d/1M4aepPJaIfdqE9oU3L-2CQqKIyubLXG4Q4cqWnyqxp4/export?format=csv"
185
+ if year == 2022:
186
+ url = "https://docs.google.com/spreadsheets/d/1cuS3B6hk4iQmMaRQoMTcogIInJpavnV7rKuEsiJnEbU/export?format=csv"
187
+ if year == 2021:
188
+ url = "https://docs.google.com/spreadsheets/d/1ANQnPVkefRmvzrmGvEqXoqQ4dBfgcI_R9FPg-0BcM34/export?format=csv"
189
+ if year == 2020:
190
+ url = "https://docs.google.com/spreadsheets/d/1eG9WTkXKzFT4NMh-WqHOMs5G0UuPGnb6wP4CnFD8uzY/export?format=csv"
191
+ if year == 2019:
192
+ url = "https://docs.google.com/spreadsheets/d/10nHg7BIs5ySh_dE9uuIz2lq-gRWcg02tIMr0EPgPvJs/export?format=csv"
193
+ if year == 2018:
194
+ url = "https://docs.google.com/spreadsheets/d/1MyAwQdczccdca_FAIiZKkqZNauNh3ts99JZ278S2OKc/export?format=csv"
195
+
196
+ response = requests.get(url, timeout=10)
197
+ df = pd.read_csv(BytesIO(response.content))
198
+ df = df[["Driver", event]]
199
+ # replace NaNs with 0s
200
+ df = df.fillna(0)
201
+ # convert numbers to ints
202
+ df[event] = df[event].astype(int)
203
+ # replace event with "overtakes"
204
+ df = df.rename(columns={event: "overtakes"})
205
+ return df
206
+
207
+ def get_overtaken_df(year, event):
208
+ if year == 2023:
209
+ url = "https://docs.google.com/spreadsheets/d/1wszzx694Ot-mvA5YrFCpy3or37xMgnC0XpE8uNnJLWk/export?format=csv"
210
+ if year == 2022:
211
+ url = "https://docs.google.com/spreadsheets/d/19_XFDD3BZDIQVkNE4bG6dwuKvMaO4g5HNaUARGaJwhE/export?format=csv"
212
+ if year == 2021:
213
+ url = "https://docs.google.com/spreadsheets/d/1dQBHnd3AXEPNH5I75cjbzAAzi9ipqGk3v9eZT9eYKS4/export?format=csv"
214
+ if year == 2020:
215
+ url = "https://docs.google.com/spreadsheets/d/1snyntPMxYH4_KHSRI96AwBoJQrPbX6OanJAcqbYyW-Y/export?format=csv"
216
+ if year == 2019:
217
+ url = "https://docs.google.com/spreadsheets/d/11FfFkXErJg7F22iVwJo9XfLFAWucMBVlzL1qUGWxM3s/export?format=csv"
218
+ if year == 2018:
219
+ url = "https://docs.google.com/spreadsheets/d/1XJXAEyRpRS_UwLHzEtN2PdIaFJYGWSN6ypYN8Ecwp9A/export?format=csv"
220
+
221
+ response = requests.get(url, timeout=10)
222
+ df = pd.read_csv(BytesIO(response.content))
223
+ df = df[["Driver", event]]
224
+ # replace NaNs with 0s
225
+ df = df.fillna(0)
226
+ # convert numbers to ints
227
+ df[event] = df[event].astype(int)
228
+ df = df.rename(columns={event: "overtaken"})
229
+ return df
230
+
231
+ overtakes = get_overtakes_df(year, event)
232
+ overtaken = get_overtaken_df(year, event)
233
+ df = overtakes.merge(overtaken, on="Driver")
234
+
235
+ # remove drivers with 0 overtakes and 0 overtaken
236
+ df = df[(df["overtakes"] != 0) | (df["overtaken"] != 0)]
237
+
238
+ # sort in the decreasing order of overtakes
239
+ df = df.sort_values(
240
+ by=["overtakes", "overtaken"], ascending=[False, True]
241
+ ).reset_index(drop=True)
242
+ # convert to dictionary
243
+ df_dict = df.to_dict(orient="records")
244
+
245
+ return {"overtakes": df_dict}
246
+
247
+
248
+ @functools.cache
249
+ @app.get("/fastest/{year}/{event}/{session}", response_model=None)
250
+ async def fastest_lap(year: int, event: str | int, session: str) -> any:
251
+ f1session = fastf1.get_session(year, event, session)
252
+ f1session.load(telemetry=False, weather=False, messages=False)
253
+ laps = f1session.laps
254
+
255
+ drivers = pd.unique(laps["Driver"])
256
+
257
+ list_fastest_laps = list()
258
+
259
+ for drv in drivers:
260
+ drvs_fastest_lap = laps.pick_driver(drv).pick_fastest()
261
+ list_fastest_laps.append(drvs_fastest_lap)
262
+
263
+ df = (
264
+ fastf1.core.Laps(list_fastest_laps)
265
+ .sort_values(by="LapTime")
266
+ .reset_index(drop=True)
267
+ )
268
+
269
+ pole_lap = df.pick_fastest()
270
+ df["Diff"] = df["LapTime"] - pole_lap["LapTime"]
271
+
272
+ car_colors = utils.team_colors(year)
273
+
274
+ df["fill"] = df["Team"].map(car_colors)
275
+
276
+ # convert timedelta to float and round to 3 decimal places
277
+ df["Diff"] = df["Diff"].dt.total_seconds().round(3)
278
+ df = df[["Driver", "LapTime", "Diff", "Team", "fill"]]
279
+
280
+ # remove nan values in any column
281
+ df = df.dropna()
282
+
283
+ df_json = df.to_dict("records")
284
+
285
+ return {"fastest": df_json}
286
+
287
+
288
+ # @st.cache_data
289
+
290
+
291
+ @app.get("/wdc", response_model=None)
292
+ async def driver_standings() -> any:
293
+ YEAR = 2023 # datetime.datetime.now().year
294
+ df = pd.DataFrame(
295
+ pd.read_html(f"https://www.formula1.com/en/results.html/{YEAR}/drivers.html")[0]
296
+ )
297
+ df = df[["Driver", "PTS", "Car"]]
298
+ # reverse the order
299
+ df = df.sort_values(by="PTS", ascending=True)
300
+
301
+ # in Driver column only keep the last 3 characters
302
+ df["Driver"] = df["Driver"].str[:-5]
303
+
304
+ # add colors to the dataframe
305
+ car_colors = utils.team_colors(YEAR)
306
+ df["fill"] = df["Car"].map(car_colors)
307
+
308
+ # remove rows where points is 0
309
+ df = df[df["PTS"] != 0]
310
+ df.reset_index(inplace=True, drop=True)
311
+ df.rename(columns={"PTS": "Points"}, inplace=True)
312
+
313
+ return {"WDC": df.to_dict("records")}
314
+
315
+
316
+ # @st.cache_data
317
+
318
+
319
+ @app.get("/", response_model=None)
320
+ async def root():
321
+ return HTMLResponse(
322
+ content="""<iframe src="https://tracinginsights-f1-analysis.hf.space" frameborder="0" style="width:100%; height:100%;" scrolling="yes" allowfullscreen:"yes"></iframe>""",
323
+ status_code=200,
324
+ )
325
+
326
+
327
+ # @st.cache_data
328
+
329
+
330
+ @app.get("/years", response_model=None)
331
+ async def years_available() -> any:
332
+ # make a list from 2018 to current year
333
+ current_year = datetime.datetime.now().year
334
+ years = list(range(2018, current_year + 1))
335
+ # reverse the list to get the latest year first
336
+ years.reverse()
337
+ years = [{"label": str(year), "value": year} for year in years]
338
+ return {"years": years}
339
+
340
+
341
+ # format for events {"events":[{"label":"Saudi Arabian Grand Prix","value":2},{"label":"Bahrain Grand Prix","value":1},{"label":"Pre-Season Testing","value":"t1"}]}
342
+
343
+ # @st.cache_data
344
+
345
+
346
+ @app.get("/{year}", response_model=None)
347
+ async def events_available(year: int) -> any:
348
+ # get events available for a given year
349
+ data = utils.LatestData(year)
350
+ events = data.get_events()
351
+ events = [{"label": event, "value": event} for i, event in enumerate(events)]
352
+ events.reverse()
353
+
354
+ return {"events": events}
355
+
356
+
357
+ # format for sessions {"sessions":[{"label":"FP1","value":"FP1"},{"label":"FP2","value":"FP2"},{"label":"FP3","value":"FP3"},{"label":"Qualifying","value":"Q"},{"label":"Race","value":"R"}]}
358
+
359
+
360
+ # @st.cache_data
361
+ @functools.cache
362
+ @app.get("/{year}/{event}", response_model=None)
363
+ async def sessions_available(year: int, event: str | int) -> any:
364
+ # get sessions available for a given year and event
365
+ data = utils.LatestData(year)
366
+ sessions = data.get_sessions(event)
367
+ sessions = [{"label": session, "value": session} for session in sessions]
368
+
369
+ return {"sessions": sessions}
370
+
371
+
372
+ # format for drivers {"drivers":[{"color":"#fff500","label":"RIC","value":"RIC"},{"color":"#ff8700","label":"NOR","value":"NOR"},{"color":"#c00000","label":"VET","value":"VET"},{"color":"#0082fa","label":"LAT","value":"LAT"},{"color":"#787878","label":"GRO","value":"GRO"},{"color":"#ffffff","label":"GAS","value":"GAS"},{"color":"#f596c8","label":"STR","value":"STR"},{"color":"#787878","label":"MAG","value":"MAG"},{"color":"#0600ef","label":"ALB","value":"ALB"},{"color":"#ffffff","label":"KVY","value":"KVY"},{"color":"#fff500","label":"OCO","value":"OCO"},{"color":"#0600ef","label":"VER","value":"VER"},{"color":"#00d2be","label":"HAM","value":"HAM"},{"color":"#ff8700","label":"SAI","value":"SAI"},{"color":"#00d2be","label":"BOT","value":"BOT"},{"color":"#960000","label":"GIO","value":"GIO"}]}
373
+
374
+ # @st.cache_data
375
+
376
+
377
+ @functools.cache
378
+ @app.get("/strategy/{year}/{event}", response_model=None)
379
+ async def get_strategy(year: int, event: str | int) -> any:
380
+ f1session = fastf1.get_session(year, event, "R")
381
+ f1session.load(telemetry=False, weather=False, messages=False)
382
+ laps = f1session.laps
383
+
384
+ drivers_list = pd.unique(laps["Driver"])
385
+
386
+ drivers = pd.DataFrame(drivers_list, columns=["Driver"])
387
+ drivers["FinishOrder"] = drivers.index + 1
388
+
389
+ # Get the LapNumber of the first lap of each stint
390
+ first_lap = (
391
+ laps[["Driver", "Stint", "Compound", "LapNumber"]]
392
+ .groupby(["Driver", "Stint", "Compound"])
393
+ .first()
394
+ .reset_index()
395
+ )
396
+ # Add FinishOrder to first_lap
397
+ first_lap = pd.merge(first_lap, drivers, on="Driver")
398
+ # change LapNumber to LapStart
399
+ first_lap = first_lap.rename(columns={"LapNumber": "LapStart"})
400
+ # reduce the lapstart by 1
401
+ first_lap["LapStart"] = first_lap["LapStart"] - 1
402
+
403
+ # find the last lap of each stint
404
+ last_lap = (
405
+ laps[["Driver", "Stint", "Compound", "LapNumber"]]
406
+ .groupby(["Driver", "Stint", "Compound"])
407
+ .last()
408
+ .reset_index()
409
+ )
410
+ # change LapNumber to LapEnd
411
+ last_lap = last_lap.rename(columns={"LapNumber": "LapEnd"})
412
+
413
+ # combine first_lap and last_lap
414
+ stint_laps = pd.merge(first_lap, last_lap, on=["Driver", "Stint", "Compound"])
415
+ # to cover for outliers
416
+ stint_laps["fill"] = "white"
417
+
418
+ stint_laps["fill"] = stint_laps["Compound"].map(
419
+ {
420
+ "SOFT": "red",
421
+ "MEDIUM": "yellow",
422
+ "HARD": "white",
423
+ "INTERMEDIATE": "blue",
424
+ "WET": "green",
425
+ }
426
+ )
427
+
428
+ # sort by FinishOrder
429
+ stint_laps = stint_laps.sort_values(by=["FinishOrder"], ascending=[True])
430
+
431
+ stint_laps_dict = stint_laps.to_dict("records")
432
+
433
+ return {"strategy": stint_laps_dict}
434
+
435
+
436
+ @functools.cache
437
+ @app.get("/lapchart/{year}/{event}/{session}", response_model=None)
438
+ async def lap_chart(
439
+ year: int,
440
+ event: str | int,
441
+ session: str,
442
+ ) -> any:
443
+ ergast = Ergast()
444
+
445
+ race_names_df = ergast.get_race_schedule(season=year, result_type="pandas")
446
+ event_number = race_names_df[race_names_df["raceName"] == event]["round"].values[0]
447
+ drivers_df = ergast.get_driver_info(
448
+ season=year, round=event_number, result_type="pandas"
449
+ )
450
+ laptimes_df = ergast.get_lap_times(
451
+ season=year, round=event_number, result_type="pandas", limit=2000
452
+ ).content[0]
453
+ laptimes_df = pd.merge(laptimes_df, drivers_df, how="left", on="driverId")
454
+
455
+ results_df = ergast.get_race_results(
456
+ season=year, round=event_number, result_type="pandas"
457
+ ).content[0]
458
+ results_df = results_df[["driverCode", "constructorName"]]
459
+
460
+ # merge results_df on laptime_df
461
+ laptimes_df = pd.merge(laptimes_df, results_df, how="left", on="driverCode")
462
+
463
+ team_colors = utils.team_colors(year)
464
+ # add team_colors to laptimes_df
465
+ laptimes_df["fill"] = laptimes_df["constructorName"].map(team_colors)
466
+
467
+ # rename number as x and position as y
468
+ laptimes_df.rename(
469
+ columns={"number": "x", "position": "y", "driverCode": "id"}, inplace=True
470
+ )
471
+
472
+ lap_chart_data = []
473
+
474
+ for driver in laptimes_df["id"].unique():
475
+ data = laptimes_df[laptimes_df["id"] == driver]
476
+ fill = data["fill"].values[0]
477
+ data = data[["x", "y"]]
478
+ data_dict = data.to_dict(orient="records")
479
+ driver_dict = {"id": driver, "fill": fill, "data": data_dict}
480
+ # add this to all_data
481
+ lap_chart_data.append(driver_dict)
482
+
483
+ lap_chart_dict = {"lapChartData": lap_chart_data}
484
+
485
+ return lap_chart_dict
486
+
487
+
488
+ @functools.cache
489
+ @app.get("/{year}/{event}/{session}", response_model=None)
490
+ async def session_drivers(year: int, event: str | int, session: str) -> any:
491
+ # get drivers available for a given year, event and session
492
+ f1session = fastf1.get_session(year, event, session)
493
+ f1session.load(telemetry=False, weather=False, messages=False)
494
+
495
+ laps = f1session.laps
496
+ team_colors = utils.team_colors(year)
497
+ # add team_colors dict to laps on Team column
498
+ laps["color"] = laps["Team"].map(team_colors)
499
+
500
+ unique_drivers = laps["Driver"].unique()
501
+
502
+ drivers = [
503
+ {
504
+ "color": laps[laps.Driver == driver].color.iloc[0],
505
+ "label": driver,
506
+ "value": driver,
507
+ }
508
+ for driver in unique_drivers
509
+ ]
510
+
511
+ return {"drivers": drivers}
512
+
513
+
514
+ @functools.cache
515
+ @app.get("/laps/{year}/{event}/{session}", response_model=None)
516
+ async def get_driver_laps_data(year: int, event: str | int, session: str) -> any:
517
+ # get drivers available for a given year, event and session
518
+ f1session = fastf1.get_session(year, event, session)
519
+ f1session.load(telemetry=False, weather=False, messages=False)
520
+ laps = f1session.laps
521
+ team_colors = utils.team_colors(year)
522
+ # add team_colors dict to laps on Team column
523
+ laps["color"] = laps["Team"].map(team_colors)
524
+
525
+ # combine Driver and LapNumber as a new column
526
+ laps["label"] = (
527
+ laps["Driver"]
528
+ + "-"
529
+ + laps["LapNumber"].astype(int).astype(str)
530
+ + "-"
531
+ + str(year)
532
+ + "-"
533
+ + event
534
+ + "-"
535
+ + session
536
+ )
537
+ laps["value"] = (
538
+ laps["Driver"]
539
+ + "-"
540
+ + laps["LapNumber"].astype(int).astype(str)
541
+ + "-"
542
+ + str(year)
543
+ + "-"
544
+ + event
545
+ + "-"
546
+ + session
547
+ )
548
+
549
+ laps = laps[["value", "label", "color"]]
550
+
551
+ driver_laps_dict = laps.to_dict("records")
552
+
553
+ return {"laps": driver_laps_dict}
554
+
555
+
556
+ # format for chartData {"chartData":[{"lapnumber":1},{
557
+ # "VER":91.564,
558
+ # "VER_compound":"SOFT",
559
+ # "VER_compound_color":"#FF5733",
560
+ # "lapnumber":2
561
+ # },{"lapnumber":3},{"VER":90.494,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":4},{"lapnumber":5},{"VER":90.062,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":6},{"lapnumber":7},{"VER":89.815,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":8},{"VER":105.248,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":9},{"lapnumber":10},{"VER":89.79,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":11},{"VER":145.101,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":12},{"lapnumber":13},{"VER":89.662,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":14},{"lapnumber":15},{"VER":89.617,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":16},{"lapnumber":17},{"VER":140.717,"VER_compound":"SOFT","VER_compound_color":"#FF5733","lapnumber":18}]}
562
+
563
+ # @st.cache_data
564
+
565
+
566
+ @functools.cache
567
+ @app.get("/{year}/{event}/{session}/{driver}", response_model=None)
568
+ async def laps_data(year: int, event: str | int, session: str, driver: str) -> any:
569
+ # get drivers available for a given year, event and session
570
+ f1session = fastf1.get_session(year, event, session)
571
+ f1session.load(telemetry=False, weather=False, messages=False)
572
+ laps = f1session.laps
573
+ team_colors = utils.team_colors(year)
574
+ # add team_colors dict to laps on Team column
575
+
576
+ drivers = laps.Driver.unique()
577
+ # for each driver in drivers, get the Team column from laps and get the color from team_colors dict
578
+ drivers = [
579
+ {
580
+ "color": team_colors[laps[laps.Driver == driver].Team.iloc[0]],
581
+ "label": driver,
582
+ "value": driver,
583
+ }
584
+ for driver in drivers
585
+ ]
586
+
587
+ driver_laps = laps.pick_driver(driver)
588
+ driver_laps["LapTime"] = driver_laps["LapTime"].dt.total_seconds()
589
+ # remove rows where LapTime is null
590
+ driver_laps = driver_laps[driver_laps.LapTime.notnull()]
591
+ compound_colors = {
592
+ "SOFT": "#FF0000",
593
+ "MEDIUM": "#FFFF00",
594
+ "HARD": "#FFFFFF",
595
+ "INTERMEDIATE": "#00FF00",
596
+ "WET": "#088cd0",
597
+ }
598
+
599
+ driver_laps_data = []
600
+
601
+ for _, row in driver_laps.iterrows():
602
+ if row["LapTime"] > 0:
603
+ lap = {
604
+ f"{driver}": row["LapTime"],
605
+ f"{driver}_compound": row["Compound"],
606
+ f"{driver}_compound_color": compound_colors[row["Compound"]],
607
+ "lapnumber": row["LapNumber"],
608
+ }
609
+ else:
610
+ lap = {"lapnumber": row["LapNumber"]}
611
+
612
+ driver_laps_data.append(lap)
613
+
614
+ return {"chartData": driver_laps_data}
615
+
616
+
617
+ @functools.cache
618
+ @app.get("/laptimes/{year}/{event}/{session}/{driver}", response_model=None)
619
+ async def get_laps_data(year: int, event: str | int, session: str, driver: str) -> any:
620
+ # get drivers available for a given year, event and session
621
+ f1session = fastf1.get_session(year, event, session)
622
+ f1session.load(telemetry=False, weather=False, messages=False)
623
+ laps = f1session.laps
624
+ team_colors = utils.team_colors(year)
625
+ # add team_colors dict to laps on Team column
626
+
627
+ drivers = laps.Driver.unique()
628
+ # for each driver in drivers, get the Team column from laps and get the color from team_colors dict
629
+ drivers = [
630
+ {
631
+ "color": team_colors[laps[laps.Driver == driver].Team.iloc[0]],
632
+ "label": driver,
633
+ "value": driver,
634
+ }
635
+ for driver in drivers
636
+ ]
637
+
638
+ driver_laps = laps.pick_driver(driver)
639
+ driver_laps["LapTime"] = driver_laps["LapTime"].dt.total_seconds()
640
+ driver_laps = driver_laps[["Driver", "LapTime", "LapNumber", "Compound"]]
641
+
642
+ # remove rows where LapTime is null
643
+ driver_laps = driver_laps[driver_laps.LapTime.notnull()]
644
+
645
+ driver_laps_dict = driver_laps.to_dict("records")
646
+
647
+ return {"chartData": driver_laps_dict}
648
+
649
+
650
+ # @st.cache_data
651
+
652
+
653
+ @functools.cache
654
+ @app.get("/{year}/{event}/{session}/{driver}/{lap_number}", response_model=None)
655
+ async def telemetry_data(
656
+ year: int, event: str | int, session: str, driver: str, lap_number: int
657
+ ) -> any:
658
+ f1session = fastf1.get_session(year, event, session)
659
+ f1session.load(telemetry=True, weather=False, messages=False)
660
+ laps = f1session.laps
661
+
662
+ driver_laps = laps.pick_driver(driver)
663
+ driver_laps["LapTime"] = driver_laps["LapTime"].dt.total_seconds()
664
+
665
+ # get the telemetry for lap_number
666
+ selected_lap = driver_laps[driver_laps.LapNumber == lap_number]
667
+
668
+ telemetry = selected_lap.get_telemetry()
669
+
670
+ lon_acc, lat_acc = accelerations.compute_accelerations(telemetry)
671
+ telemetry["lon_acc"] = lon_acc
672
+ telemetry["lat_acc"] = lat_acc
673
+
674
+ telemetry["Time"] = telemetry["Time"].dt.total_seconds()
675
+
676
+ laptime = selected_lap.LapTime.values[0]
677
+ data_key = f"{driver} - Lap {int(lap_number)} - {year} {session} [laptime]"
678
+
679
+ telemetry["DRS"] = telemetry["DRS"].apply(lambda x: 1 if x in [10, 12, 14] else 0)
680
+
681
+ brake_tel = []
682
+ drs_tel = []
683
+ gear_tel = []
684
+ rpm_tel = []
685
+ speed_tel = []
686
+ throttle_tel = []
687
+ time_tel = []
688
+ track_map = []
689
+ lon_acc_tel = []
690
+ lat_acc_tel = []
691
+
692
+ for _, row in telemetry.iterrows():
693
+ brake = {
694
+ "x": row["Distance"],
695
+ "y": row["Brake"],
696
+ }
697
+ brake_tel.append(brake)
698
+
699
+ drs = {
700
+ "x": row["Distance"],
701
+ "y": row["DRS"],
702
+ }
703
+ drs_tel.append(drs)
704
+
705
+ gear = {
706
+ "x": row["Distance"],
707
+ "y": row["nGear"],
708
+ }
709
+ gear_tel.append(gear)
710
+
711
+ rpm = {
712
+ "x": row["Distance"],
713
+ "y": row["RPM"],
714
+ }
715
+ rpm_tel.append(rpm)
716
+
717
+ speed = {
718
+ "x": row["Distance"],
719
+ "y": row["Speed"],
720
+ }
721
+ speed_tel.append(speed)
722
+
723
+ throttle = {
724
+ "x": row["Distance"],
725
+ "y": row["Throttle"],
726
+ }
727
+ throttle_tel.append(throttle)
728
+
729
+ time = {
730
+ "x": row["Distance"],
731
+ "y": row["Time"],
732
+ }
733
+ time_tel.append(time)
734
+
735
+ lon_acc = {
736
+ "x": row["Distance"],
737
+ "y": row["lon_acc"],
738
+ }
739
+ lon_acc_tel.append(lon_acc)
740
+
741
+ lat_acc = {
742
+ "x": row["Distance"],
743
+ "y": row["lat_acc"],
744
+ }
745
+ lat_acc_tel.append(lat_acc)
746
 
747
+ track = {
748
+ "x": row["X"],
749
+ "y": row["Y"],
750
+ }
751
+ track_map.append(track)
752
 
753
+ telemetry_data = {
754
+ "telemetryData": {
755
+ "brake": brake_tel,
756
+ "dataKey": data_key,
757
+ "drs": drs_tel,
758
+ "gear": gear_tel,
759
+ "rpm": rpm_tel,
760
+ "speed": speed_tel,
761
+ "throttle": throttle_tel,
762
+ "time": time_tel,
763
+ "lon_acc": lon_acc_tel,
764
+ "lat_acc": lat_acc_tel,
765
+ "trackMap": track_map,
766
+ }
767
+ }
768
 
769
+ return telemetry_data