CoolFace
Apppublic

tracinginsights/api

sourceHugging Faceupdated 3y agoView on Hugging Face
1likes
main.py770 linesDownload Raw Back to root
1# from git import Repo2# import os3 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 13import concurrent.futures14import datetime15import functools16import math17import os18from io import BytesIO19 20import fastf121import numpy as np22import pandas as pd23import requests24import streamlit as st25from fastapi import Depends, FastAPI26from fastapi.middleware.cors import CORSMiddleware27from fastapi.responses import FileResponse, HTMLResponse28from fastf1.ergast import Ergast29from pydantic import BaseModel, Field30from sqlalchemy.orm import Session31 32# from . import accelerations, database, models, utils33 34import accelerations35import database36import models37import utils38FASTF1_CACHE_DIR = os.environ["FASTF1_CACHE_DIR"]39 40fastf1.Cache.enable_cache(FASTF1_CACHE_DIR)41 42app = FastAPI()43 44app.add_middleware(45    CORSMiddleware,46    allow_origins=["*"],47    allow_credentials=True,48    allow_methods=["*"],49    allow_headers=["*"],50)51 52 53database.Base.metadata.create_all(bind=database.engine)54 55 56def get_db():57    try:58        db = database.SessionLocal()59        yield db60    finally:61        db.close()62 63 64class RacePace(BaseModel):65    year: int66    event: str67    session: str68    Driver: str69    LapTime: float70    Diff: float71    Team: str72    fill: str73 74 75# @functools.cache76@app.get("/racepace/{year}/{event}/{session}", response_model=None)77async 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.laps100 101        laps = laps.loc[laps.LapNumber > 1]102        laps = laps.pick_track_status(103            "1",104        )105        laps["LapTime"] = laps.Sector1Time + laps.Sector2Time + laps.Sector3Time106 107        # convert LapTime to seconds108        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 df120        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 database129        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.cache141@app.get("/topspeed/{year}/{event}/{session}", response_model=None)142async 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.laps146    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 dataframe163    speed_df["fill"] = speed_df["Team"].apply(lambda x: team_colors[x])164 165    # rename fastest speedtrap column to TopSpeed166    speed_df.rename(columns={fastest_speedtrap: "TopSpeed"}, inplace=True)167 168    # remove nan values in any column169    speed_df = speed_df.dropna()170 171    # Convert to int172    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.cache180@app.get("/overtakes/{year}/{event}", response_model=None)181def 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 0s200        df = df.fillna(0)201        # convert numbers to ints202        df[event] = df[event].astype(int)203        # replace event with "overtakes"204        df = df.rename(columns={event: "overtakes"})205        return df206 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 0s225        df = df.fillna(0)226        # convert numbers to ints227        df[event] = df[event].astype(int)228        df = df.rename(columns={event: "overtaken"})229        return df230 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 overtaken236    df = df[(df["overtakes"] != 0) | (df["overtaken"] != 0)]237 238    # sort in the decreasing order of overtakes239    df = df.sort_values(240        by=["overtakes", "overtaken"], ascending=[False, True]241    ).reset_index(drop=True)242    # convert to dictionary243    df_dict = df.to_dict(orient="records")244 245    return {"overtakes": df_dict}246 247 248@functools.cache249@app.get("/fastest/{year}/{event}/{session}", response_model=None)250async 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.laps254 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 places277    df["Diff"] = df["Diff"].dt.total_seconds().round(3)278    df = df[["Driver", "LapTime", "Diff", "Team", "fill"]]279 280    # remove nan values in any column281    df = df.dropna()282 283    df_json = df.to_dict("records")284 285    return {"fastest": df_json}286 287 288# @st.cache_data289 290 291@app.get("/wdc", response_model=None)292async def driver_standings() -> any:293    YEAR = 2023  # datetime.datetime.now().year294    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 order299    df = df.sort_values(by="PTS", ascending=True)300 301    # in Driver column only keep the last 3 characters302    df["Driver"] = df["Driver"].str[:-5]303 304    # add colors to the dataframe305    car_colors = utils.team_colors(YEAR)306    df["fill"] = df["Car"].map(car_colors)307 308    # remove rows where points is 0309    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_data317 318 319@app.get("/", response_model=None)320async 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_data328 329 330@app.get("/years", response_model=None)331async def years_available() -> any:332    # make a list from 2018 to current year333    current_year = datetime.datetime.now().year334    years = list(range(2018, current_year + 1))335    # reverse the list to get the latest year first336    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_data344 345 346@app.get("/{year}", response_model=None)347async def events_available(year: int) -> any:348    # get events available for a given year349    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_data361@functools.cache362@app.get("/{year}/{event}", response_model=None)363async def sessions_available(year: int, event: str | int) -> any:364    # get sessions available for a given year and event365    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_data375 376 377@functools.cache378@app.get("/strategy/{year}/{event}", response_model=None)379async 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.laps383 384    drivers_list = pd.unique(laps["Driver"])385 386    drivers = pd.DataFrame(drivers_list, columns=["Driver"])387    drivers["FinishOrder"] = drivers.index + 1388 389    # Get the LapNumber of the first lap of each stint390    first_lap = (391        laps[["Driver", "Stint", "Compound", "LapNumber"]]392        .groupby(["Driver", "Stint", "Compound"])393        .first()394        .reset_index()395    )396    #  Add FinishOrder to first_lap397    first_lap = pd.merge(first_lap, drivers, on="Driver")398    # change LapNumber to LapStart399    first_lap = first_lap.rename(columns={"LapNumber": "LapStart"})400    # reduce the lapstart by 1401    first_lap["LapStart"] = first_lap["LapStart"] - 1402 403    # find the last lap of each stint404    last_lap = (405        laps[["Driver", "Stint", "Compound", "LapNumber"]]406        .groupby(["Driver", "Stint", "Compound"])407        .last()408        .reset_index()409    )410    #  change LapNumber to LapEnd411    last_lap = last_lap.rename(columns={"LapNumber": "LapEnd"})412 413    # combine first_lap and last_lap414    stint_laps = pd.merge(first_lap, last_lap, on=["Driver", "Stint", "Compound"])415    #  to cover for outliers416    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 FinishOrder429    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.cache437@app.get("/lapchart/{year}/{event}/{session}", response_model=None)438async 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=2000452    ).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_df461    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_df465    laptimes_df["fill"] = laptimes_df["constructorName"].map(team_colors)466 467    #  rename number as x and position as y468    laptimes_df.rename(469        columns={"number": "x", "position": "y", "driverCode": "id"}, inplace=True470    )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_data481        lap_chart_data.append(driver_dict)482 483    lap_chart_dict = {"lapChartData": lap_chart_data}484 485    return lap_chart_dict486 487 488@functools.cache489@app.get("/{year}/{event}/{session}", response_model=None)490async def session_drivers(year: int, event: str | int, session: str) -> any:491    # get drivers available for a given year, event and session492    f1session = fastf1.get_session(year, event, session)493    f1session.load(telemetry=False, weather=False, messages=False)494 495    laps = f1session.laps496    team_colors = utils.team_colors(year)497    # add team_colors dict to laps on Team column498    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_drivers509    ]510 511    return {"drivers": drivers}512 513 514@functools.cache515@app.get("/laps/{year}/{event}/{session}", response_model=None)516async def get_driver_laps_data(year: int, event: str | int, session: str) -> any:517    # get drivers available for a given year, event and session518    f1session = fastf1.get_session(year, event, session)519    f1session.load(telemetry=False, weather=False, messages=False)520    laps = f1session.laps521    team_colors = utils.team_colors(year)522    # add team_colors dict to laps on Team column523    laps["color"] = laps["Team"].map(team_colors)524 525    # combine Driver and LapNumber as a new column526    laps["label"] = (527        laps["Driver"]528        + "-"529        + laps["LapNumber"].astype(int).astype(str)530        + "-"531        + str(year)532        + "-"533        + event534        + "-"535        + session536    )537    laps["value"] = (538        laps["Driver"]539        + "-"540        + laps["LapNumber"].astype(int).astype(str)541        + "-"542        + str(year)543        + "-"544        + event545        + "-"546        + session547    )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":2561# },{"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_data564 565 566@functools.cache567@app.get("/{year}/{event}/{session}/{driver}", response_model=None)568async def laps_data(year: int, event: str | int, session: str, driver: str) -> any:569    # get drivers available for a given year, event and session570    f1session = fastf1.get_session(year, event, session)571    f1session.load(telemetry=False, weather=False, messages=False)572    laps = f1session.laps573    team_colors = utils.team_colors(year)574    # add team_colors dict to laps on Team column575 576    drivers = laps.Driver.unique()577    # for each driver in drivers, get the Team column from laps and get the color from team_colors dict578    drivers = [579        {580            "color": team_colors[laps[laps.Driver == driver].Team.iloc[0]],581            "label": driver,582            "value": driver,583        }584        for driver in drivers585    ]586 587    driver_laps = laps.pick_driver(driver)588    driver_laps["LapTime"] = driver_laps["LapTime"].dt.total_seconds()589    # remove rows where LapTime is null590    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.cache618@app.get("/laptimes/{year}/{event}/{session}/{driver}", response_model=None)619async def get_laps_data(year: int, event: str | int, session: str, driver: str) -> any:620    # get drivers available for a given year, event and session621    f1session = fastf1.get_session(year, event, session)622    f1session.load(telemetry=False, weather=False, messages=False)623    laps = f1session.laps624    team_colors = utils.team_colors(year)625    # add team_colors dict to laps on Team column626 627    drivers = laps.Driver.unique()628    # for each driver in drivers, get the Team column from laps and get the color from team_colors dict629    drivers = [630        {631            "color": team_colors[laps[laps.Driver == driver].Team.iloc[0]],632            "label": driver,633            "value": driver,634        }635        for driver in drivers636    ]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 null643    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_data651 652 653@functools.cache654@app.get("/{year}/{event}/{session}/{driver}/{lap_number}", response_model=None)655async def telemetry_data(656    year: int, event: str | int, session: str, driver: str, lap_number: int657) -> any:658    f1session = fastf1.get_session(year, event, session)659    f1session.load(telemetry=True, weather=False, messages=False)660    laps = f1session.laps661 662    driver_laps = laps.pick_driver(driver)663    driver_laps["LapTime"] = driver_laps["LapTime"].dt.total_seconds()664 665    # get the telemetry for lap_number666    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_acc672    telemetry["lat_acc"] = lat_acc673 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_data770