Added consideration of race position and gap to ahead cars

This commit is contained in:
Aditya Pulipaka
2025-10-19 04:28:49 -05:00
parent 098d881d15
commit 47f592c88b
15 changed files with 413 additions and 17537 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
.env
*.pyc

View File

@@ -14,6 +14,8 @@ class EnrichedTelemetryWebhook(BaseModel):
tire_cliff_risk: float = Field(..., ge=0.0, le=1.0, description="Probability of tire performance cliff (0..1)")
optimal_pit_window: List[int] = Field(..., description="Recommended pit stop lap window [start, end]")
performance_delta: float = Field(..., description="Lap time delta vs baseline (negative = slower)")
competitive_pressure: float = Field(..., ge=0.0, le=1.0, description="Competitive pressure from position and gaps (0..1)")
position_trend: Literal["gaining", "stable", "losing"] = Field(..., description="Position trend over recent laps")
class RaceInfo(BaseModel):
@@ -32,6 +34,8 @@ class DriverState(BaseModel):
current_tire_compound: Literal["soft", "medium", "hard", "intermediate", "wet"] = Field(..., description="Current tire compound")
tire_age_laps: int = Field(..., ge=0, description="Laps on current tires")
fuel_remaining_percent: float = Field(..., ge=0.0, le=100.0, description="Remaining fuel percentage")
gap_to_leader: Optional[float] = Field(default=0.0, description="Gap to race leader in seconds")
gap_to_ahead: Optional[float] = Field(default=0.0, description="Gap to car directly ahead in seconds")
class Competitor(BaseModel):

View File

@@ -17,15 +17,25 @@ def build_brainstorm_prompt_fast(
latest = max(enriched_telemetry, key=lambda x: x.lap)
pit_window = latest.optimal_pit_window
# Format position and competitive info
position = race_context.driver_state.current_position
gap_to_leader = race_context.driver_state.gap_to_leader
gap_to_ahead = race_context.driver_state.gap_to_ahead
comp_info = f"P{position}"
if gap_to_ahead > 0:
comp_info += f", {gap_to_ahead:.1f}s behind P{position-1}"
if gap_to_leader > 0 and position > 1:
comp_info += f", {gap_to_leader:.1f}s from leader"
if count == 1:
# Ultra-fast mode: just generate 1 strategy
return f"""Generate 1 F1 race strategy for {race_context.driver_state.driver_name} at {race_context.race_info.track_name}.
CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_laps}, P{race_context.driver_state.current_position}, {race_context.driver_state.current_tire_compound} tires ({race_context.driver_state.tire_age_laps} laps old)
CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_laps}, {comp_info}, {race_context.driver_state.current_tire_compound} tires ({race_context.driver_state.tire_age_laps} laps old)
TELEMETRY: Tire deg rate {latest.tire_degradation_rate:.2f}, Cliff risk {latest.tire_cliff_risk:.2f}, Pace {latest.pace_trend}, Pit window laps {pit_window[0]}-{pit_window[1]}
TELEMETRY: Tire deg {latest.tire_degradation_rate:.2f}, Cliff risk {latest.tire_cliff_risk:.2f}, Pace {latest.pace_trend}, Position trend {latest.position_trend}, Competitive pressure {latest.competitive_pressure:.2f}
Generate 1 optimal strategy. Min 2 tire compounds required.
Generate 1 optimal strategy considering competitive position. Min 2 tire compounds required.
JSON: {{"strategies": [{{"strategy_id": 1, "strategy_name": "name", "stop_count": 1, "pit_laps": [32], "tire_sequence": ["medium", "hard"], "brief_description": "one sentence", "risk_level": "medium", "key_assumption": "main assumption"}}]}}"""
@@ -33,21 +43,25 @@ JSON: {{"strategies": [{{"strategy_id": 1, "strategy_name": "name", "stop_count"
# Fast mode: 2-5 strategies with different approaches
return f"""Generate {count} diverse F1 race strategies for {race_context.driver_state.driver_name} at {race_context.race_info.track_name}.
CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_laps}, P{race_context.driver_state.current_position}, {race_context.driver_state.current_tire_compound} tires ({race_context.driver_state.tire_age_laps} laps old)
CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_laps}, {comp_info}, {race_context.driver_state.current_tire_compound} tires ({race_context.driver_state.tire_age_laps} laps old)
TELEMETRY: Tire deg {latest.tire_degradation_rate:.2f}, Cliff risk {latest.tire_cliff_risk:.2f}, Pace {latest.pace_trend}, Delta {latest.performance_delta:+.2f}s, Pit window {pit_window[0]}-{pit_window[1]}
TELEMETRY: Tire deg {latest.tire_degradation_rate:.2f}, Cliff risk {latest.tire_cliff_risk:.2f}, Pace {latest.pace_trend}, Delta {latest.performance_delta:+.2f}s, Position trend {latest.position_trend}, Competitive pressure {latest.competitive_pressure:.2f}
Generate {count} strategies: conservative (1-stop), standard (1-2 stop), aggressive (undercut). Min 2 tire compounds each.
COMPETITIVE SITUATION: Gap to ahead {gap_to_ahead:.1f}s ({"DRS RANGE - attack opportunity!" if gap_to_ahead < 1.0 else "close battle" if gap_to_ahead < 3.0 else "need to push"})
Generate {count} strategies balancing tire management with competitive pressure. Consider if aggressive undercut makes sense given gaps. Min 2 tire compounds each.
JSON: {{"strategies": [{{"strategy_id": 1, "strategy_name": "Conservative Stay Out", "stop_count": 1, "pit_laps": [35], "tire_sequence": ["medium", "hard"], "brief_description": "extend current stint then hard tires to end", "risk_level": "low", "key_assumption": "tire cliff risk stays below 0.7"}}]}}"""
return f"""Generate {count} F1 race strategies for {race_context.driver_state.driver_name} at {race_context.race_info.track_name}.
CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_laps}, P{race_context.driver_state.current_position}, {race_context.driver_state.current_tire_compound} tires ({race_context.driver_state.tire_age_laps} laps old)
CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_laps}, {comp_info}, {race_context.driver_state.current_tire_compound} tires ({race_context.driver_state.tire_age_laps} laps old)
TELEMETRY: Tire deg rate {latest.tire_degradation_rate:.2f}, Cliff risk {latest.tire_cliff_risk:.2f}, Pace {latest.pace_trend}, Performance delta {latest.performance_delta:+.2f}s, Pit window laps {pit_window[0]}-{pit_window[1]}
TELEMETRY: Tire deg {latest.tire_degradation_rate:.2f}, Cliff risk {latest.tire_cliff_risk:.2f}, Pace {latest.pace_trend}, Delta {latest.performance_delta:+.2f}s, Position trend {latest.position_trend}, Competitive pressure {latest.competitive_pressure:.2f}
Generate {count} diverse strategies. Min 2 compounds.
COMPETITIVE: Gap ahead {gap_to_ahead:.1f}s, Position trending {latest.position_trend}
Generate {count} diverse strategies considering both tire management AND competitive positioning. Min 2 compounds.
JSON: {{"strategies": [{{"strategy_id": 1, "strategy_name": "name", "stop_count": 1, "pit_laps": [32], "tire_sequence": ["medium", "hard"], "brief_description": "one sentence", "risk_level": "low|medium|high|critical", "key_assumption": "main assumption"}}]}}"""
@@ -78,7 +92,9 @@ def build_brainstorm_prompt(
"pace_trend": t.pace_trend,
"tire_cliff_risk": round(t.tire_cliff_risk, 3),
"optimal_pit_window": t.optimal_pit_window,
"performance_delta": round(t.performance_delta, 2)
"performance_delta": round(t.performance_delta, 2),
"competitive_pressure": round(t.competitive_pressure, 3),
"position_trend": t.position_trend
})
# Format competitors
@@ -92,12 +108,14 @@ def build_brainstorm_prompt(
"gap_seconds": round(c.gap_seconds, 1)
})
prompt = f"""You are an expert F1 strategist. Generate 20 diverse race strategies based on lap-level telemetry.
prompt = f"""You are an expert F1 strategist. Generate 20 diverse race strategies based on lap-level telemetry AND competitive positioning.
LAP-LEVEL TELEMETRY METRICS:
- tire_degradation_rate: 0-1 (higher = worse tire wear)
- tire_cliff_risk: 0-1 (probability of hitting tire cliff)
- pace_trend: "improving", "stable", or "declining"
- position_trend: "gaining", "stable", or "losing" positions
- competitive_pressure: 0-1 (combined metric from position and gaps)
- optimal_pit_window: [start_lap, end_lap] recommended pit range
- performance_delta: seconds vs baseline (negative = slower)

View File

@@ -1,653 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 44,
"id": "9a9714f8",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"logger WARNING \tFailed to load schedule from FastF1 backend!\n",
"req INFO \tUsing cached data for season_schedule\n",
"req INFO \tUsing cached data for season_schedule\n",
"core INFO \tLoading data for Italian Grand Prix - Race [v3.6.1]\n",
"req INFO \tUsing cached data for session_info\n",
"req INFO \tUsing cached data for driver_info\n",
"core INFO \tLoading data for Italian Grand Prix - Race [v3.6.1]\n",
"req INFO \tUsing cached data for session_info\n",
"req INFO \tUsing cached data for driver_info\n",
"req INFO \tUsing cached data for session_status_data\n",
"req INFO \tUsing cached data for lap_count\n",
"req INFO \tUsing cached data for track_status_data\n",
"req INFO \tUsing cached data for session_status_data\n",
"req INFO \tUsing cached data for lap_count\n",
"req INFO \tUsing cached data for track_status_data\n",
"req INFO \tUsing cached data for _extended_timing_data\n",
"req INFO \tUsing cached data for timing_app_data\n",
"core INFO \tProcessing timing data...\n",
"req INFO \tUsing cached data for _extended_timing_data\n",
"req INFO \tUsing cached data for timing_app_data\n",
"core INFO \tProcessing timing data...\n",
"req INFO \tUsing cached data for car_data\n",
"req INFO \tUsing cached data for car_data\n",
"req INFO \tUsing cached data for position_data\n",
"req INFO \tUsing cached data for position_data\n",
"req INFO \tUsing cached data for weather_data\n",
"req INFO \tUsing cached data for race_control_messages\n",
"core WARNING \tDriver 55 completed the race distance 06:14.695000 before the recorded end of the session.\n",
"core WARNING \tDriver 4 completed the race distance 05:40.439000 before the recorded end of the session.\n",
"core WARNING \tDriver 44 completed the race distance 05:48.209000 before the recorded end of the session.\n",
"core WARNING \tDriver 16 completed the race distance 06:14.511000 before the recorded end of the session.\n",
"core WARNING \tDriver 1 completed the race distance 06:25.888000 before the recorded end of the session.\n",
"req INFO \tUsing cached data for weather_data\n",
"req INFO \tUsing cached data for race_control_messages\n",
"core WARNING \tDriver 55 completed the race distance 06:14.695000 before the recorded end of the session.\n",
"core WARNING \tDriver 4 completed the race distance 05:40.439000 before the recorded end of the session.\n",
"core WARNING \tDriver 44 completed the race distance 05:48.209000 before the recorded end of the session.\n",
"core WARNING \tDriver 16 completed the race distance 06:14.511000 before the recorded end of the session.\n",
"core WARNING \tDriver 1 completed the race distance 06:25.888000 before the recorded end of the session.\n",
"core WARNING \tDriver 11 completed the race distance 06:19.824000 before the recorded end of the session.\n",
"core WARNING \tDriver 11 completed the race distance 06:19.824000 before the recorded end of the session.\n",
"core WARNING \tDriver 23 completed the race distance 05:40.782000 before the recorded end of the session.\n",
"core WARNING \tDriver 23 completed the race distance 05:40.782000 before the recorded end of the session.\n",
"core WARNING \tDriver 14 completed the race distance 05:39.594000 before the recorded end of the session.\n",
"core WARNING \tDriver 63 completed the race distance 06:07.860000 before the recorded end of the session.\n",
"core WARNING \tDriver 14 completed the race distance 05:39.594000 before the recorded end of the session.\n",
"core WARNING \tDriver 63 completed the race distance 06:07.860000 before the recorded end of the session.\n",
"core INFO \tFinished loading data for 20 drivers: ['55', '4', '44', '16', '1', '10', '81', '11', '40', '20', '23', '24', '27', '2', '14', '63', '77', '31', '22', '18']\n",
"core INFO \tFinished loading data for 20 drivers: ['55', '4', '44', '16', '1', '10', '81', '11', '40', '20', '23', '24', '27', '2', '14', '63', '77', '31', '22', '18']\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Created dataframe with 16584 rows\n",
"Total laps in race: 51.0\n",
"Laps covered: 1.0 to 51.0\n",
"Tire life range: 1.0 to 33.0 laps\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>lap_number</th>\n",
" <th>total_laps</th>\n",
" <th>speed</th>\n",
" <th>overall_time</th>\n",
" <th>throttle</th>\n",
" <th>brake</th>\n",
" <th>tire_compound</th>\n",
" <th>tire_life_laps</th>\n",
" <th>track_temperature</th>\n",
" <th>rainfall</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>0.0</td>\n",
" <td>0 days 01:22:21.734000</td>\n",
" <td>23.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>0.0</td>\n",
" <td>0 days 01:22:21.894000</td>\n",
" <td>23.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>4.0</td>\n",
" <td>0 days 01:22:22.214000</td>\n",
" <td>26.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>14.0</td>\n",
" <td>0 days 01:22:22.494000</td>\n",
" <td>24.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>24.0</td>\n",
" <td>0 days 01:22:22.774000</td>\n",
" <td>24.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>31.0</td>\n",
" <td>0 days 01:22:22.974000</td>\n",
" <td>26.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>38.0</td>\n",
" <td>0 days 01:22:23.254000</td>\n",
" <td>36.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>50.0</td>\n",
" <td>0 days 01:22:23.494000</td>\n",
" <td>41.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>58.0</td>\n",
" <td>0 days 01:22:23.694000</td>\n",
" <td>44.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>1.0</td>\n",
" <td>51.0</td>\n",
" <td>71.0</td>\n",
" <td>0 days 01:22:23.974000</td>\n",
" <td>55.0</td>\n",
" <td>False</td>\n",
" <td>MEDIUM</td>\n",
" <td>1.0</td>\n",
" <td>42.5</td>\n",
" <td>False</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" lap_number total_laps speed overall_time throttle brake \\\n",
"0 1.0 51.0 0.0 0 days 01:22:21.734000 23.0 False \n",
"1 1.0 51.0 0.0 0 days 01:22:21.894000 23.0 False \n",
"2 1.0 51.0 4.0 0 days 01:22:22.214000 26.0 False \n",
"3 1.0 51.0 14.0 0 days 01:22:22.494000 24.0 False \n",
"4 1.0 51.0 24.0 0 days 01:22:22.774000 24.0 False \n",
"5 1.0 51.0 31.0 0 days 01:22:22.974000 26.0 False \n",
"6 1.0 51.0 38.0 0 days 01:22:23.254000 36.0 False \n",
"7 1.0 51.0 50.0 0 days 01:22:23.494000 41.0 False \n",
"8 1.0 51.0 58.0 0 days 01:22:23.694000 44.0 False \n",
"9 1.0 51.0 71.0 0 days 01:22:23.974000 55.0 False \n",
"\n",
" tire_compound tire_life_laps track_temperature rainfall \n",
"0 MEDIUM 1.0 42.5 False \n",
"1 MEDIUM 1.0 42.5 False \n",
"2 MEDIUM 1.0 42.5 False \n",
"3 MEDIUM 1.0 42.5 False \n",
"4 MEDIUM 1.0 42.5 False \n",
"5 MEDIUM 1.0 42.5 False \n",
"6 MEDIUM 1.0 42.5 False \n",
"7 MEDIUM 1.0 42.5 False \n",
"8 MEDIUM 1.0 42.5 False \n",
"9 MEDIUM 1.0 42.5 False "
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"\"\"\"\n",
"FastF1 Data Fetcher for HPC F1 AI Strategy System\n",
"\n",
"Downloads telemetry and race data from a specific F1 session to simulate\n",
"live telemetry streaming from a Raspberry Pi \"racecar\" to the HPC layer.\n",
"\n",
"Usage:\n",
" python fetch_race_data.py --year 2024 --race \"Monaco\" --driver VER --output data/\n",
"\"\"\"\n",
"import fastf1\n",
"import pandas as pd\n",
"\n",
"# 1. Load the session\n",
"session = fastf1.get_session(2023, 'Monza', 'R')\n",
"session.load(telemetry=True, laps=True, weather=True)\n",
"\n",
"# 2. Pick the driver\n",
"driver_laps = session.laps.pick_drivers('ALO')\n",
"\n",
"# Get total number of laps in the race (maximum lap number from all drivers)\n",
"total_laps = session.laps['LapNumber'].max()\n",
"\n",
"# 3. Collect all telemetry data with lap information\n",
"telemetry_list = []\n",
"\n",
"for lap_idx in driver_laps.index:\n",
" lap = driver_laps.loc[lap_idx]\n",
" lap_number = lap['LapNumber']\n",
" tire_compound = lap['Compound']\n",
" tire_life = lap['TyreLife'] # Number of laps on current tires\n",
" \n",
" # Get telemetry for this lap\n",
" car_data = lap.get_car_data()\n",
" \n",
" if car_data is not None and len(car_data) > 0:\n",
" # Add lap number, tire compound, and tire life to each telemetry point\n",
" car_data['LapNumber'] = lap_number\n",
" car_data['Compound'] = tire_compound\n",
" car_data['TyreLife'] = tire_life\n",
" telemetry_list.append(car_data)\n",
"\n",
"# 4. Combine all telemetry data\n",
"all_telemetry = pd.concat(telemetry_list, ignore_index=True)\n",
"\n",
"# 5. Get weather data\n",
"weather = session.weather_data\n",
"\n",
"# 6. Merge telemetry with weather based on timestamp\n",
"# First, ensure both have SessionTime column\n",
"all_telemetry['SessionTime'] = pd.to_timedelta(all_telemetry['SessionTime'])\n",
"weather['SessionTime'] = pd.to_timedelta(weather['Time'])\n",
"\n",
"# Merge using merge_asof for time-based joining\n",
"all_telemetry = all_telemetry.sort_values('SessionTime')\n",
"weather = weather.sort_values('SessionTime')\n",
"\n",
"merged_data = pd.merge_asof(\n",
" all_telemetry,\n",
" weather[['SessionTime', 'TrackTemp', 'Rainfall']],\n",
" on='SessionTime',\n",
" direction='nearest'\n",
")\n",
"\n",
"# 7. Create final dataframe with requested columns\n",
"final_df = pd.DataFrame({\n",
" 'lap_number': merged_data['LapNumber'],\n",
" 'total_laps': total_laps, # Total laps in the race\n",
" 'speed': merged_data['Speed'],\n",
" 'overall_time': merged_data['SessionTime'],\n",
" 'throttle': merged_data['Throttle'],\n",
" 'brake': merged_data['Brake'],\n",
" 'tire_compound': merged_data['Compound'],\n",
" 'tire_life_laps': merged_data['TyreLife'], # Number of laps on current tires\n",
" 'track_temperature': merged_data['TrackTemp'],\n",
" 'rainfall': merged_data['Rainfall']\n",
"})\n",
"\n",
"print(f\"Created dataframe with {len(final_df)} rows\")\n",
"print(f\"Total laps in race: {total_laps}\")\n",
"print(f\"Laps covered: {final_df['lap_number'].min()} to {final_df['lap_number'].max()}\")\n",
"print(f\"Tire life range: {final_df['tire_life_laps'].min()} to {final_df['tire_life_laps'].max()} laps\")\n",
"final_df.head(10)\n"
]
},
{
"cell_type": "code",
"execution_count": 45,
"id": "45d27f05",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Dataframe Info:\n",
"Total telemetry points: 16584\n",
"\n",
"Column types:\n",
"lap_number float64\n",
"total_laps float64\n",
"speed float64\n",
"overall_time timedelta64[ns]\n",
"throttle float64\n",
"brake bool\n",
"tire_compound object\n",
"tire_life_laps float64\n",
"track_temperature float64\n",
"rainfall bool\n",
"dtype: object\n",
"\n",
"Basic statistics:\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>lap_number</th>\n",
" <th>total_laps</th>\n",
" <th>speed</th>\n",
" <th>overall_time</th>\n",
" <th>throttle</th>\n",
" <th>tire_life_laps</th>\n",
" <th>track_temperature</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>count</th>\n",
" <td>16584.000000</td>\n",
" <td>16584.0</td>\n",
" <td>16584.000000</td>\n",
" <td>16584</td>\n",
" <td>16584.000000</td>\n",
" <td>16584.000000</td>\n",
" <td>16584.000000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>mean</th>\n",
" <td>25.891341</td>\n",
" <td>51.0</td>\n",
" <td>235.570188</td>\n",
" <td>0 days 01:59:34.577446394</td>\n",
" <td>72.291546</td>\n",
" <td>15.339243</td>\n",
" <td>42.908816</td>\n",
" </tr>\n",
" <tr>\n",
" <th>std</th>\n",
" <td>14.710977</td>\n",
" <td>0.0</td>\n",
" <td>76.948906</td>\n",
" <td>0 days 00:21:30.065940875</td>\n",
" <td>40.561237</td>\n",
" <td>8.558018</td>\n",
" <td>0.897756</td>\n",
" </tr>\n",
" <tr>\n",
" <th>min</th>\n",
" <td>1.000000</td>\n",
" <td>51.0</td>\n",
" <td>0.000000</td>\n",
" <td>0 days 01:22:21.734000</td>\n",
" <td>0.000000</td>\n",
" <td>1.000000</td>\n",
" <td>40.800000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>25%</th>\n",
" <td>13.000000</td>\n",
" <td>51.0</td>\n",
" <td>180.000000</td>\n",
" <td>0 days 01:40:53.558000</td>\n",
" <td>40.000000</td>\n",
" <td>8.000000</td>\n",
" <td>42.500000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>50%</th>\n",
" <td>26.000000</td>\n",
" <td>51.0</td>\n",
" <td>245.000000</td>\n",
" <td>0 days 01:59:31.222000</td>\n",
" <td>100.000000</td>\n",
" <td>15.000000</td>\n",
" <td>43.100000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>75%</th>\n",
" <td>39.000000</td>\n",
" <td>51.0</td>\n",
" <td>309.000000</td>\n",
" <td>0 days 02:18:13.365000</td>\n",
" <td>100.000000</td>\n",
" <td>21.000000</td>\n",
" <td>43.600000</td>\n",
" </tr>\n",
" <tr>\n",
" <th>max</th>\n",
" <td>51.000000</td>\n",
" <td>51.0</td>\n",
" <td>351.000000</td>\n",
" <td>0 days 02:36:49.228000</td>\n",
" <td>100.000000</td>\n",
" <td>33.000000</td>\n",
" <td>44.400000</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" lap_number total_laps speed overall_time \\\n",
"count 16584.000000 16584.0 16584.000000 16584 \n",
"mean 25.891341 51.0 235.570188 0 days 01:59:34.577446394 \n",
"std 14.710977 0.0 76.948906 0 days 00:21:30.065940875 \n",
"min 1.000000 51.0 0.000000 0 days 01:22:21.734000 \n",
"25% 13.000000 51.0 180.000000 0 days 01:40:53.558000 \n",
"50% 26.000000 51.0 245.000000 0 days 01:59:31.222000 \n",
"75% 39.000000 51.0 309.000000 0 days 02:18:13.365000 \n",
"max 51.000000 51.0 351.000000 0 days 02:36:49.228000 \n",
"\n",
" throttle tire_life_laps track_temperature \n",
"count 16584.000000 16584.000000 16584.000000 \n",
"mean 72.291546 15.339243 42.908816 \n",
"std 40.561237 8.558018 0.897756 \n",
"min 0.000000 1.000000 40.800000 \n",
"25% 40.000000 8.000000 42.500000 \n",
"50% 100.000000 15.000000 43.100000 \n",
"75% 100.000000 21.000000 43.600000 \n",
"max 100.000000 33.000000 44.400000 "
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Display dataframe info and sample statistics\n",
"print(\"Dataframe Info:\")\n",
"print(f\"Total telemetry points: {len(final_df)}\")\n",
"print(f\"\\nColumn types:\")\n",
"print(final_df.dtypes)\n",
"print(f\"\\nBasic statistics:\")\n",
"final_df.describe()\n"
]
},
{
"cell_type": "code",
"execution_count": 46,
"id": "2fbcd2f9",
"metadata": {},
"outputs": [],
"source": [
"final_df.to_csv(\"ALONSO_2023_MONZA_RACE.csv\")"
]
},
{
"cell_type": "code",
"execution_count": 47,
"id": "729fb12e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tire compound usage throughout the race:\n",
" lap_number tire_compound tire_life_laps\n",
"0 1.0 MEDIUM 1.0\n",
"1 2.0 MEDIUM 2.0\n",
"2 3.0 MEDIUM 3.0\n",
"3 4.0 MEDIUM 4.0\n",
"4 5.0 MEDIUM 5.0\n",
"5 6.0 MEDIUM 6.0\n",
"6 7.0 MEDIUM 7.0\n",
"7 8.0 MEDIUM 8.0\n",
"8 9.0 MEDIUM 9.0\n",
"9 10.0 MEDIUM 10.0\n",
"10 11.0 MEDIUM 11.0\n",
"11 12.0 MEDIUM 12.0\n",
"12 13.0 MEDIUM 13.0\n",
"13 14.0 MEDIUM 14.0\n",
"14 15.0 MEDIUM 15.0\n",
"15 16.0 MEDIUM 16.0\n",
"16 17.0 MEDIUM 17.0\n",
"17 18.0 MEDIUM 18.0\n",
"18 19.0 MEDIUM 19.0\n",
"19 20.0 MEDIUM 20.0\n",
"20 21.0 MEDIUM 21.0\n",
"21 22.0 HARD 4.0\n",
"22 23.0 HARD 5.0\n",
"23 24.0 HARD 6.0\n",
"24 25.0 HARD 7.0\n",
"25 26.0 HARD 8.0\n",
"26 27.0 HARD 9.0\n",
"27 28.0 HARD 10.0\n",
"28 29.0 HARD 11.0\n",
"29 30.0 HARD 12.0\n",
"30 31.0 HARD 13.0\n",
"31 32.0 HARD 14.0\n",
"32 33.0 HARD 15.0\n",
"33 34.0 HARD 16.0\n",
"34 35.0 HARD 17.0\n",
"35 36.0 HARD 18.0\n",
"36 37.0 HARD 19.0\n",
"37 38.0 HARD 20.0\n",
"38 39.0 HARD 21.0\n",
"39 40.0 HARD 22.0\n",
"40 41.0 HARD 23.0\n",
"41 42.0 HARD 24.0\n",
"42 43.0 HARD 25.0\n",
"43 44.0 HARD 26.0\n",
"44 45.0 HARD 27.0\n",
"45 46.0 HARD 28.0\n",
"46 47.0 HARD 29.0\n",
"47 48.0 HARD 30.0\n",
"48 49.0 HARD 31.0\n",
"49 50.0 HARD 32.0\n",
"50 51.0 HARD 33.0\n"
]
}
],
"source": [
"# Show tire compound changes and stint information\n",
"print(\"Tire compound usage throughout the race:\")\n",
"tire_changes = final_df.groupby(['lap_number', 'tire_compound', 'tire_life_laps']).size().reset_index(name='count')\n",
"tire_changes = tire_changes.groupby(['lap_number', 'tire_compound', 'tire_life_laps']).first().reset_index()[['lap_number', 'tire_compound', 'tire_life_laps']]\n",
"print(tire_changes.drop_duplicates())\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d9ebc90c",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 4,
"id": "be0c6fbf",
"metadata": {},
"outputs": [],
@@ -31,7 +31,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 5,
"id": "f757cb34",
"metadata": {},
"outputs": [
@@ -39,7 +39,6 @@
"name": "stderr",
"output_type": "stream",
"text": [
"req WARNING \tDEFAULT CACHE ENABLED! (318.35 MB) /Users/adipu/Library/Caches/fastf1\n",
"core INFO \tLoading data for Italian Grand Prix - Race [v3.6.1]\n",
"req INFO \tUsing cached data for session_info\n",
"req INFO \tUsing cached data for driver_info\n",
@@ -95,7 +94,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 6,
"id": "aa5e5d8f",
"metadata": {},
"outputs": [
@@ -136,6 +135,9 @@
" <th></th>\n",
" <th>lap_number</th>\n",
" <th>total_laps</th>\n",
" <th>position</th>\n",
" <th>gap_to_leader</th>\n",
" <th>gap_to_ahead</th>\n",
" <th>lap_time</th>\n",
" <th>average_speed</th>\n",
" <th>max_speed</th>\n",
@@ -150,6 +152,9 @@
" <th>0</th>\n",
" <td>1</td>\n",
" <td>51</td>\n",
" <td>11</td>\n",
" <td>6.223</td>\n",
" <td>0.620</td>\n",
" <td>0 days 00:01:33.340000</td>\n",
" <td>210.17</td>\n",
" <td>326.0</td>\n",
@@ -162,6 +167,9 @@
" <th>1</th>\n",
" <td>2</td>\n",
" <td>51</td>\n",
" <td>11</td>\n",
" <td>8.229</td>\n",
" <td>0.712</td>\n",
" <td>0 days 00:01:28.012000</td>\n",
" <td>236.87</td>\n",
" <td>330.0</td>\n",
@@ -174,6 +182,9 @@
" <th>2</th>\n",
" <td>3</td>\n",
" <td>51</td>\n",
" <td>11</td>\n",
" <td>9.799</td>\n",
" <td>0.898</td>\n",
" <td>0 days 00:01:27.546000</td>\n",
" <td>236.40</td>\n",
" <td>331.0</td>\n",
@@ -186,6 +197,9 @@
" <th>3</th>\n",
" <td>4</td>\n",
" <td>51</td>\n",
" <td>11</td>\n",
" <td>10.953</td>\n",
" <td>0.919</td>\n",
" <td>0 days 00:01:27.221000</td>\n",
" <td>240.13</td>\n",
" <td>341.0</td>\n",
@@ -198,6 +212,9 @@
" <th>4</th>\n",
" <td>5</td>\n",
" <td>51</td>\n",
" <td>11</td>\n",
" <td>11.563</td>\n",
" <td>0.917</td>\n",
" <td>0 days 00:01:27.033000</td>\n",
" <td>236.09</td>\n",
" <td>345.0</td>\n",
@@ -210,6 +227,9 @@
" <th>5</th>\n",
" <td>6</td>\n",
" <td>51</td>\n",
" <td>11</td>\n",
" <td>12.123</td>\n",
" <td>0.923</td>\n",
" <td>0 days 00:01:27.175000</td>\n",
" <td>236.74</td>\n",
" <td>343.0</td>\n",
@@ -222,6 +242,9 @@
" <th>6</th>\n",
" <td>7</td>\n",
" <td>51</td>\n",
" <td>11</td>\n",
" <td>12.694</td>\n",
" <td>0.387</td>\n",
" <td>0 days 00:01:26.929000</td>\n",
" <td>239.72</td>\n",
" <td>340.0</td>\n",
@@ -234,6 +257,9 @@
" <th>7</th>\n",
" <td>8</td>\n",
" <td>51</td>\n",
" <td>10</td>\n",
" <td>13.413</td>\n",
" <td>2.760</td>\n",
" <td>0 days 00:01:26.943000</td>\n",
" <td>238.45</td>\n",
" <td>351.0</td>\n",
@@ -246,6 +272,9 @@
" <th>8</th>\n",
" <td>9</td>\n",
" <td>51</td>\n",
" <td>10</td>\n",
" <td>14.320</td>\n",
" <td>3.387</td>\n",
" <td>0 days 00:01:27.383000</td>\n",
" <td>236.81</td>\n",
" <td>330.0</td>\n",
@@ -258,6 +287,9 @@
" <th>9</th>\n",
" <td>10</td>\n",
" <td>51</td>\n",
" <td>10</td>\n",
" <td>15.177</td>\n",
" <td>3.760</td>\n",
" <td>0 days 00:01:27.368000</td>\n",
" <td>232.42</td>\n",
" <td>331.0</td>\n",
@@ -271,32 +303,44 @@
"</div>"
],
"text/plain": [
" lap_number total_laps lap_time average_speed max_speed \\\n",
"0 1 51 0 days 00:01:33.340000 210.17 326.0 \n",
"1 2 51 0 days 00:01:28.012000 236.87 330.0 \n",
"2 3 51 0 days 00:01:27.546000 236.40 331.0 \n",
"3 4 51 0 days 00:01:27.221000 240.13 341.0 \n",
"4 5 51 0 days 00:01:27.033000 236.09 345.0 \n",
"5 6 51 0 days 00:01:27.175000 236.74 343.0 \n",
"6 7 51 0 days 00:01:26.929000 239.72 340.0 \n",
"7 8 51 0 days 00:01:26.943000 238.45 351.0 \n",
"8 9 51 0 days 00:01:27.383000 236.81 330.0 \n",
"9 10 51 0 days 00:01:27.368000 232.42 331.0 \n",
" lap_number total_laps position gap_to_leader gap_to_ahead \\\n",
"0 1 51 11 6.223 0.620 \n",
"1 2 51 11 8.229 0.712 \n",
"2 3 51 11 9.799 0.898 \n",
"3 4 51 11 10.953 0.919 \n",
"4 5 51 11 11.563 0.917 \n",
"5 6 51 11 12.123 0.923 \n",
"6 7 51 11 12.694 0.387 \n",
"7 8 51 10 13.413 2.760 \n",
"8 9 51 10 14.320 3.387 \n",
"9 10 51 10 15.177 3.760 \n",
"\n",
" tire_compound tire_life_laps track_temperature rainfall \n",
"0 MEDIUM 1 42.5 False \n",
"1 MEDIUM 2 42.5 False \n",
"2 MEDIUM 3 43.2 False \n",
"3 MEDIUM 4 43.2 False \n",
"4 MEDIUM 5 43.1 False \n",
"5 MEDIUM 6 43.3 False \n",
"6 MEDIUM 7 43.6 False \n",
"7 MEDIUM 8 43.6 False \n",
"8 MEDIUM 9 43.6 False \n",
"9 MEDIUM 10 43.9 False "
" lap_time average_speed max_speed tire_compound \\\n",
"0 0 days 00:01:33.340000 210.17 326.0 MEDIUM \n",
"1 0 days 00:01:28.012000 236.87 330.0 MEDIUM \n",
"2 0 days 00:01:27.546000 236.40 331.0 MEDIUM \n",
"3 0 days 00:01:27.221000 240.13 341.0 MEDIUM \n",
"4 0 days 00:01:27.033000 236.09 345.0 MEDIUM \n",
"5 0 days 00:01:27.175000 236.74 343.0 MEDIUM \n",
"6 0 days 00:01:26.929000 239.72 340.0 MEDIUM \n",
"7 0 days 00:01:26.943000 238.45 351.0 MEDIUM \n",
"8 0 days 00:01:27.383000 236.81 330.0 MEDIUM \n",
"9 0 days 00:01:27.368000 232.42 331.0 MEDIUM \n",
"\n",
" tire_life_laps track_temperature rainfall \n",
"0 1 42.5 False \n",
"1 2 42.5 False \n",
"2 3 43.2 False \n",
"3 4 43.2 False \n",
"4 5 43.1 False \n",
"5 6 43.3 False \n",
"6 7 43.6 False \n",
"7 8 43.6 False \n",
"8 9 43.6 False \n",
"9 10 43.9 False "
]
},
"execution_count": 3,
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
@@ -306,7 +350,10 @@
"weather = session.weather_data\n",
"weather['SessionTime'] = pd.to_timedelta(weather['Time'])\n",
"\n",
"# 4. Create lap-level data by aggregating telemetry\n",
"# 4. Get all laps for position calculation\n",
"all_laps = session.laps\n",
"\n",
"# 5. Create lap-level data by aggregating telemetry\n",
"lap_data_list = []\n",
"\n",
"for lap_idx in driver_laps.index:\n",
@@ -332,6 +379,43 @@
" tire_compound = lap['Compound']\n",
" tire_life = lap['TyreLife']\n",
" \n",
" # Get position data for this lap\n",
" position = lap['Position']\n",
" \n",
" # Calculate gaps: get all drivers' data for this lap\n",
" this_lap_all_drivers = all_laps[all_laps['LapNumber'] == lap_number].copy()\n",
" \n",
" # Sort by position to calculate gaps\n",
" this_lap_all_drivers = this_lap_all_drivers.sort_values('Position')\n",
" \n",
" # Calculate cumulative race time for gap calculations\n",
" gap_to_leader = 0.0\n",
" gap_to_ahead = 0.0\n",
" \n",
" if pd.notna(position) and position > 1:\n",
" # Get our driver's data\n",
" our_data = this_lap_all_drivers[this_lap_all_drivers['Driver'] == 'ALO']\n",
" \n",
" if not our_data.empty:\n",
" # Time at the end of this lap (cumulative)\n",
" # Note: FastF1 provides 'Time' which is cumulative race time\n",
" our_time = our_data['Time'].values[0]\n",
" \n",
" # Get leader's time (P1)\n",
" leader_data = this_lap_all_drivers[this_lap_all_drivers['Position'] == 1]\n",
" if not leader_data.empty:\n",
" leader_time = leader_data['Time'].values[0]\n",
" # Convert numpy.timedelta64 to seconds\n",
" gap_to_leader = (our_time - leader_time) / np.timedelta64(1, 's')\n",
" \n",
" # Get car ahead's time (Position - 1)\n",
" ahead_position = position - 1\n",
" ahead_data = this_lap_all_drivers[this_lap_all_drivers['Position'] == ahead_position]\n",
" if not ahead_data.empty:\n",
" ahead_time = ahead_data['Time'].values[0]\n",
" # Convert numpy.timedelta64 to seconds\n",
" gap_to_ahead = (our_time - ahead_time) / np.timedelta64(1, 's')\n",
" \n",
" # Get weather data for this lap (use lap start time)\n",
" lap_start_time = pd.to_timedelta(lap['LapStartTime'])\n",
" \n",
@@ -344,6 +428,9 @@
" lap_record = {\n",
" 'lap_number': int(lap_number),\n",
" 'total_laps': int(total_laps),\n",
" 'position': int(position) if pd.notna(position) else None,\n",
" 'gap_to_leader': round(gap_to_leader, 3) if gap_to_leader > 0 else 0.0,\n",
" 'gap_to_ahead': round(gap_to_ahead, 3) if gap_to_ahead > 0 else 0.0,\n",
" 'lap_time': lap_time,\n",
" 'average_speed': round(avg_speed, 2),\n",
" 'max_speed': round(max_speed, 2),\n",
@@ -359,7 +446,7 @@
" if lap_number % 10 == 0:\n",
" print(f\"Processed lap {int(lap_number)}...\")\n",
"\n",
"# 5. Create final dataframe\n",
"# 6. Create final dataframe\n",
"laps_df = pd.DataFrame(lap_data_list)\n",
"\n",
"print(f\"\\n✓ Created lap-level dataframe with {len(laps_df)} laps\")\n",
@@ -369,7 +456,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 7,
"id": "b1086b8d",
"metadata": {},
"outputs": [
@@ -383,6 +470,9 @@
"Column types:\n",
"lap_number int64\n",
"total_laps int64\n",
"position int64\n",
"gap_to_leader float64\n",
"gap_to_ahead float64\n",
"lap_time timedelta64[ns]\n",
"average_speed float64\n",
"max_speed float64\n",
@@ -418,6 +508,9 @@
" <th></th>\n",
" <th>lap_number</th>\n",
" <th>total_laps</th>\n",
" <th>position</th>\n",
" <th>gap_to_leader</th>\n",
" <th>gap_to_ahead</th>\n",
" <th>lap_time</th>\n",
" <th>average_speed</th>\n",
" <th>max_speed</th>\n",
@@ -430,6 +523,9 @@
" <th>count</th>\n",
" <td>51.000000</td>\n",
" <td>51.0</td>\n",
" <td>51.000000</td>\n",
" <td>51.000000</td>\n",
" <td>51.000000</td>\n",
" <td>51</td>\n",
" <td>51.000000</td>\n",
" <td>51.000000</td>\n",
@@ -440,6 +536,9 @@
" <th>mean</th>\n",
" <td>26.000000</td>\n",
" <td>51.0</td>\n",
" <td>9.803922</td>\n",
" <td>29.418314</td>\n",
" <td>2.889235</td>\n",
" <td>0 days 00:01:27.596803921</td>\n",
" <td>235.797059</td>\n",
" <td>333.686275</td>\n",
@@ -450,6 +549,9 @@
" <th>std</th>\n",
" <td>14.866069</td>\n",
" <td>0.0</td>\n",
" <td>1.058671</td>\n",
" <td>13.842909</td>\n",
" <td>1.707825</td>\n",
" <td>0 days 00:00:03.069690434</td>\n",
" <td>7.855085</td>\n",
" <td>4.921342</td>\n",
@@ -460,6 +562,9 @@
" <th>min</th>\n",
" <td>1.000000</td>\n",
" <td>51.0</td>\n",
" <td>6.000000</td>\n",
" <td>6.223000</td>\n",
" <td>0.387000</td>\n",
" <td>0 days 00:01:26.105000</td>\n",
" <td>191.140000</td>\n",
" <td>322.000000</td>\n",
@@ -470,6 +575,9 @@
" <th>25%</th>\n",
" <td>13.500000</td>\n",
" <td>51.0</td>\n",
" <td>9.000000</td>\n",
" <td>16.928500</td>\n",
" <td>1.123000</td>\n",
" <td>0 days 00:01:26.715000</td>\n",
" <td>236.105000</td>\n",
" <td>331.000000</td>\n",
@@ -480,6 +588,9 @@
" <th>50%</th>\n",
" <td>26.000000</td>\n",
" <td>51.0</td>\n",
" <td>10.000000</td>\n",
" <td>29.113000</td>\n",
" <td>2.799000</td>\n",
" <td>0 days 00:01:26.943000</td>\n",
" <td>237.130000</td>\n",
" <td>332.000000</td>\n",
@@ -490,6 +601,9 @@
" <th>75%</th>\n",
" <td>38.500000</td>\n",
" <td>51.0</td>\n",
" <td>10.000000</td>\n",
" <td>43.405500</td>\n",
" <td>4.136500</td>\n",
" <td>0 days 00:01:27.328500</td>\n",
" <td>238.655000</td>\n",
" <td>334.000000</td>\n",
@@ -500,6 +614,9 @@
" <th>max</th>\n",
" <td>51.000000</td>\n",
" <td>51.0</td>\n",
" <td>12.000000</td>\n",
" <td>48.171000</td>\n",
" <td>8.135000</td>\n",
" <td>0 days 00:01:47.272000</td>\n",
" <td>241.700000</td>\n",
" <td>351.000000</td>\n",
@@ -511,28 +628,38 @@
"</div>"
],
"text/plain": [
" lap_number total_laps lap_time average_speed \\\n",
"count 51.000000 51.0 51 51.000000 \n",
"mean 26.000000 51.0 0 days 00:01:27.596803921 235.797059 \n",
"std 14.866069 0.0 0 days 00:00:03.069690434 7.855085 \n",
"min 1.000000 51.0 0 days 00:01:26.105000 191.140000 \n",
"25% 13.500000 51.0 0 days 00:01:26.715000 236.105000 \n",
"50% 26.000000 51.0 0 days 00:01:26.943000 237.130000 \n",
"75% 38.500000 51.0 0 days 00:01:27.328500 238.655000 \n",
"max 51.000000 51.0 0 days 00:01:47.272000 241.700000 \n",
" lap_number total_laps position gap_to_leader gap_to_ahead \\\n",
"count 51.000000 51.0 51.000000 51.000000 51.000000 \n",
"mean 26.000000 51.0 9.803922 29.418314 2.889235 \n",
"std 14.866069 0.0 1.058671 13.842909 1.707825 \n",
"min 1.000000 51.0 6.000000 6.223000 0.387000 \n",
"25% 13.500000 51.0 9.000000 16.928500 1.123000 \n",
"50% 26.000000 51.0 10.000000 29.113000 2.799000 \n",
"75% 38.500000 51.0 10.000000 43.405500 4.136500 \n",
"max 51.000000 51.0 12.000000 48.171000 8.135000 \n",
"\n",
" max_speed tire_life_laps track_temperature \n",
"count 51.000000 51.000000 51.000000 \n",
"mean 333.686275 15.411765 42.898039 \n",
"std 4.921342 8.616673 0.876924 \n",
"min 322.000000 1.000000 40.800000 \n",
"25% 331.000000 8.500000 42.500000 \n",
"50% 332.000000 15.000000 43.100000 \n",
"75% 334.000000 21.000000 43.600000 \n",
"max 351.000000 33.000000 44.300000 "
" lap_time average_speed max_speed tire_life_laps \\\n",
"count 51 51.000000 51.000000 51.000000 \n",
"mean 0 days 00:01:27.596803921 235.797059 333.686275 15.411765 \n",
"std 0 days 00:00:03.069690434 7.855085 4.921342 8.616673 \n",
"min 0 days 00:01:26.105000 191.140000 322.000000 1.000000 \n",
"25% 0 days 00:01:26.715000 236.105000 331.000000 8.500000 \n",
"50% 0 days 00:01:26.943000 237.130000 332.000000 15.000000 \n",
"75% 0 days 00:01:27.328500 238.655000 334.000000 21.000000 \n",
"max 0 days 00:01:47.272000 241.700000 351.000000 33.000000 \n",
"\n",
" track_temperature \n",
"count 51.000000 \n",
"mean 42.898039 \n",
"std 0.876924 \n",
"min 40.800000 \n",
"25% 42.500000 \n",
"50% 43.100000 \n",
"75% 43.600000 \n",
"max 44.300000 "
]
},
"execution_count": 4,
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
@@ -549,7 +676,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 8,
"id": "b2a3b878",
"metadata": {},
"outputs": [
@@ -569,7 +696,55 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 9,
"id": "efcff166",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Position and Gap Analysis:\n",
"Starting position: P11\n",
"Final position: P9\n",
"Average gap to leader: 29.42s\n",
"Average gap to car ahead: 2.89s\n",
"\n",
"Position changes over race:\n",
" lap_number position gap_to_leader gap_to_ahead\n",
"0 1 11 6.223 0.620\n",
"1 2 11 8.229 0.712\n",
"2 3 11 9.799 0.898\n",
"3 4 11 10.953 0.919\n",
"4 5 11 11.563 0.917\n",
"5 6 11 12.123 0.923\n",
"6 7 11 12.694 0.387\n",
"7 8 10 13.413 2.760\n",
"8 9 10 14.320 3.387\n",
"9 10 10 15.177 3.760\n",
"10 11 10 15.829 3.984\n",
"11 12 10 16.760 4.129\n",
"12 13 10 17.031 3.995\n",
"13 14 10 17.666 4.076\n",
"14 15 10 17.366 0.469\n"
]
}
],
"source": [
"# Display position and gap information\n",
"print(\"\\nPosition and Gap Analysis:\")\n",
"print(f\"Starting position: P{laps_df['position'].iloc[0]}\")\n",
"print(f\"Final position: P{laps_df['position'].iloc[-1]}\")\n",
"print(f\"Average gap to leader: {laps_df['gap_to_leader'].mean():.2f}s\")\n",
"print(f\"Average gap to car ahead: {laps_df['gap_to_ahead'].mean():.2f}s\")\n",
"print(f\"\\nPosition changes over race:\")\n",
"print(laps_df[['lap_number', 'position', 'gap_to_leader', 'gap_to_ahead']].head(15))"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "0202372e",
"metadata": {},
"outputs": [

View File

@@ -9,6 +9,9 @@ def normalize_telemetry(payload: Dict[str, Any]) -> Dict[str, Any]:
Accepted aliases for lap-level data:
- lap_number: lap, Lap, LapNumber, lap_number
- total_laps: TotalLaps, total_laps
- position: position, Position, Pos
- gap_to_leader: gap_to_leader, GapToLeader, gap_leader
- gap_to_ahead: gap_to_ahead, GapToAhead, gap_ahead
- lap_time: lap_time, LapTime, Time
- average_speed: average_speed, avg_speed, AvgSpeed
- max_speed: max_speed, MaxSpeed, max
@@ -22,6 +25,9 @@ def normalize_telemetry(payload: Dict[str, Any]) -> Dict[str, Any]:
aliases = {
"lap_number": ["lap_number", "lap", "Lap", "LapNumber"],
"total_laps": ["total_laps", "TotalLaps"],
"position": ["position", "Position", "Pos"],
"gap_to_leader": ["gap_to_leader", "GapToLeader", "gap_leader"],
"gap_to_ahead": ["gap_to_ahead", "GapToAhead", "gap_ahead"],
"lap_time": ["lap_time", "LapTime", "Time"],
"average_speed": ["average_speed", "avg_speed", "AvgSpeed"],
"max_speed": ["max_speed", "MaxSpeed", "max"],
@@ -53,6 +59,24 @@ def normalize_telemetry(payload: Dict[str, Any]) -> Dict[str, Any]:
except (TypeError, ValueError):
total_laps = 51
position = pick("position", 10)
try:
position = int(position)
except (TypeError, ValueError):
position = 10
gap_to_leader = pick("gap_to_leader", 0.0)
try:
gap_to_leader = float(gap_to_leader)
except (TypeError, ValueError):
gap_to_leader = 0.0
gap_to_ahead = pick("gap_to_ahead", 0.0)
try:
gap_to_ahead = float(gap_to_ahead)
except (TypeError, ValueError):
gap_to_ahead = 0.0
lap_time = pick("lap_time", None)
if lap_time:
out["lap_time"] = str(lap_time)
@@ -97,6 +121,9 @@ def normalize_telemetry(payload: Dict[str, Any]) -> Dict[str, Any]:
out.update({
"lap_number": lap_number,
"total_laps": total_laps,
"position": position,
"gap_to_leader": gap_to_leader,
"gap_to_ahead": gap_to_ahead,
"average_speed": average_speed,
"max_speed": max_speed,
"tire_compound": tire_compound,

View File

@@ -10,6 +10,9 @@ import pandas as pd
# {
# "lap_number": 27,
# "total_laps": 51,
# "position": 5,
# "gap_to_leader": 12.345,
# "gap_to_ahead": 2.456,
# "lap_time": "0 days 00:01:27.318000",
# "average_speed": 234.62,
# "max_speed": 333.0,
@@ -36,6 +39,8 @@ class EnricherState:
"""Maintains race state across laps for trend analysis."""
lap_times: List[float] = field(default_factory=list) # Recent lap times in seconds
lap_speeds: List[float] = field(default_factory=list) # Recent average speeds
positions: List[int] = field(default_factory=list) # Recent positions
gaps_to_ahead: List[float] = field(default_factory=list) # Recent gaps to car ahead
current_tire_age: int = 0
current_tire_compound: str = "medium"
tire_stint_start_lap: int = 1
@@ -63,6 +68,9 @@ class Enricher:
# Extract lap data
lap_number = int(lap_data.get("lap_number", 0))
total_laps = int(lap_data.get("total_laps", 51))
position = int(lap_data.get("position", 10))
gap_to_leader = float(lap_data.get("gap_to_leader", 0.0))
gap_to_ahead = float(lap_data.get("gap_to_ahead", 0.0))
lap_time_str = lap_data.get("lap_time")
average_speed = float(lap_data.get("average_speed", 0.0))
max_speed = float(lap_data.get("max_speed", 0.0))
@@ -77,6 +85,8 @@ class Enricher:
# Update state
self.state.lap_times.append(lap_time_seconds)
self.state.lap_speeds.append(average_speed)
self.state.positions.append(position)
self.state.gaps_to_ahead.append(gap_to_ahead)
self.state.current_tire_age = tire_life_laps
self.state.current_tire_compound = tire_compound
self.state.total_laps = total_laps
@@ -85,6 +95,8 @@ class Enricher:
if len(self.state.lap_times) > 10:
self.state.lap_times = self.state.lap_times[-10:]
self.state.lap_speeds = self.state.lap_speeds[-10:]
self.state.positions = self.state.positions[-10:]
self.state.gaps_to_ahead = self.state.gaps_to_ahead[-10:]
# Set baseline (best lap time)
if self._baseline_lap_time is None or lap_time_seconds < self._baseline_lap_time:
@@ -96,6 +108,8 @@ class Enricher:
tire_cliff_risk = self._compute_tire_cliff_risk(tire_compound, tire_life_laps)
pit_window = self._compute_optimal_pit_window(lap_number, total_laps, tire_life_laps, tire_compound)
performance_delta = self._compute_performance_delta(lap_time_seconds)
competitive_pressure = self._compute_competitive_pressure(position, gap_to_ahead)
position_trend = self._compute_position_trend()
# Build enriched telemetry
enriched_telemetry = {
@@ -104,7 +118,9 @@ class Enricher:
"pace_trend": pace_trend,
"tire_cliff_risk": round(tire_cliff_risk, 3),
"optimal_pit_window": pit_window,
"performance_delta": round(performance_delta, 2)
"performance_delta": round(performance_delta, 2),
"competitive_pressure": round(competitive_pressure, 3),
"position_trend": position_trend
}
# Build race context
@@ -118,10 +134,12 @@ class Enricher:
},
"driver_state": {
"driver_name": "Alonso",
"current_position": 5, # Mock - could be passed in
"current_position": position,
"current_tire_compound": tire_compound,
"tire_age_laps": tire_life_laps,
"fuel_remaining_percent": self._estimate_fuel(lap_number, total_laps)
"fuel_remaining_percent": self._estimate_fuel(lap_number, total_laps),
"gap_to_leader": gap_to_leader,
"gap_to_ahead": gap_to_ahead
}
}
@@ -238,6 +256,56 @@ class Enricher:
return self._baseline_lap_time - current_lap_time # Negative if slower
def _compute_competitive_pressure(self, position: int, gap_to_ahead: float) -> float:
"""
Calculate competitive pressure based on position and gap to car ahead.
Returns 0-1 (0 = no pressure, 1 = extreme pressure).
High pressure scenarios:
- Close gap to car ahead (potential overtake opportunity)
- Poor position (need to push harder)
"""
# Position pressure: worse position = higher pressure
position_pressure = min(1.0, (position - 1) / 15.0) # Normalize to 0-1
# Gap pressure: smaller gap = higher pressure (opportunity to attack)
if gap_to_ahead <= 0.0:
gap_pressure = 0.0 # Leading or no gap data
elif gap_to_ahead < 1.0:
gap_pressure = 1.0 # Very close - DRS range
elif gap_to_ahead < 3.0:
gap_pressure = 0.7 # Close - push to close gap
elif gap_to_ahead < 10.0:
gap_pressure = 0.3 # Moderate gap
else:
gap_pressure = 0.1 # Large gap - low pressure
# Combined pressure (weighted average)
return round((position_pressure * 0.4 + gap_pressure * 0.6), 3)
def _compute_position_trend(self) -> str:
"""
Analyze recent positions to determine trend.
Returns: "gaining", "stable", or "losing"
"""
if len(self.state.positions) < 3:
return "stable"
recent_positions = self.state.positions[-5:] # Last 5 laps
# Calculate trend (lower position number = better)
avg_first_half = sum(recent_positions[:len(recent_positions)//2]) / max(1, len(recent_positions)//2)
avg_second_half = sum(recent_positions[len(recent_positions)//2:]) / max(1, len(recent_positions) - len(recent_positions)//2)
diff = avg_first_half - avg_second_half # Positive if gaining positions
if diff > 0.5: # Position number decreased = gained positions
return "gaining"
elif diff < -0.5: # Position number increased = lost positions
return "losing"
else:
return "stable"
def _estimate_fuel(self, current_lap: int, total_laps: int) -> float:
"""Estimate remaining fuel percentage based on lap progression."""
return max(0.0, 100.0 * (1.0 - (current_lap / total_laps)))

View File

@@ -1,52 +1,52 @@
lap_number,total_laps,lap_time,average_speed,max_speed,tire_compound,tire_life_laps,track_temperature,rainfall
1,51,0 days 00:01:33.340000,210.17,326.0,MEDIUM,1,42.5,False
2,51,0 days 00:01:28.012000,236.87,330.0,MEDIUM,2,42.5,False
3,51,0 days 00:01:27.546000,236.4,331.0,MEDIUM,3,43.2,False
4,51,0 days 00:01:27.221000,240.13,341.0,MEDIUM,4,43.2,False
5,51,0 days 00:01:27.033000,236.09,345.0,MEDIUM,5,43.1,False
6,51,0 days 00:01:27.175000,236.74,343.0,MEDIUM,6,43.3,False
7,51,0 days 00:01:26.929000,239.72,340.0,MEDIUM,7,43.6,False
8,51,0 days 00:01:26.943000,238.45,351.0,MEDIUM,8,43.6,False
9,51,0 days 00:01:27.383000,236.81,330.0,MEDIUM,9,43.6,False
10,51,0 days 00:01:27.368000,232.42,331.0,MEDIUM,10,43.9,False
11,51,0 days 00:01:27.343000,237.18,331.0,MEDIUM,11,43.9,False
12,51,0 days 00:01:27.339000,236.76,332.0,MEDIUM,12,43.3,False
13,51,0 days 00:01:27.158000,235.47,330.0,MEDIUM,13,43.1,False
14,51,0 days 00:01:27.607000,235.97,332.0,MEDIUM,14,43.7,False
15,51,0 days 00:01:26.929000,237.04,332.0,MEDIUM,15,43.7,False
16,51,0 days 00:01:27.164000,238.88,333.0,MEDIUM,16,43.9,False
17,51,0 days 00:01:27.156000,235.35,333.0,MEDIUM,17,43.7,False
18,51,0 days 00:01:27.275000,238.66,333.0,MEDIUM,18,43.5,False
19,51,0 days 00:01:27.318000,234.62,333.0,MEDIUM,19,43.6,False
20,51,0 days 00:01:28.284000,235.31,333.0,MEDIUM,20,44.3,False
21,51,0 days 00:01:32.377000,224.11,332.0,MEDIUM,21,44.3,False
22,51,0 days 00:01:47.272000,191.14,322.0,HARD,4,43.7,False
23,51,0 days 00:01:26.849000,236.5,329.0,HARD,5,43.7,False
24,51,0 days 00:01:26.949000,238.49,331.0,HARD,6,43.5,False
25,51,0 days 00:01:26.899000,237.13,331.0,HARD,7,43.5,False
26,51,0 days 00:01:26.792000,238.18,331.0,HARD,8,43.4,False
27,51,0 days 00:01:26.666000,238.7,331.0,HARD,9,43.4,False
28,51,0 days 00:01:26.723000,234.46,331.0,HARD,10,43.3,False
29,51,0 days 00:01:26.997000,236.12,332.0,HARD,11,42.9,False
30,51,0 days 00:01:26.915000,238.87,333.0,HARD,12,42.5,False
31,51,0 days 00:01:26.848000,240.21,335.0,HARD,13,42.8,False
32,51,0 days 00:01:26.747000,235.57,332.0,HARD,14,42.6,False
33,51,0 days 00:01:26.769000,238.33,332.0,HARD,15,42.4,False
34,51,0 days 00:01:26.563000,236.92,332.0,HARD,16,42.5,False
35,51,0 days 00:01:26.517000,239.63,333.0,HARD,17,42.6,False
36,51,0 days 00:01:26.457000,237.1,332.0,HARD,18,42.5,False
37,51,0 days 00:01:26.380000,238.39,333.0,HARD,19,42.5,False
38,51,0 days 00:01:26.574000,237.75,333.0,HARD,20,42.8,False
39,51,0 days 00:01:26.707000,238.65,332.0,HARD,21,42.8,False
40,51,0 days 00:01:26.610000,235.8,333.0,HARD,22,42.8,False
41,51,0 days 00:01:26.815000,236.82,334.0,HARD,23,42.9,False
42,51,0 days 00:01:26.403000,239.47,332.0,HARD,24,42.5,False
43,51,0 days 00:01:26.105000,240.15,334.0,HARD,25,42.5,False
44,51,0 days 00:01:26.155000,241.7,332.0,HARD,26,41.7,False
45,51,0 days 00:01:26.277000,241.25,333.0,HARD,27,41.7,False
46,51,0 days 00:01:26.453000,238.02,335.0,HARD,28,41.8,False
47,51,0 days 00:01:26.912000,236.31,336.0,HARD,29,41.2,False
48,51,0 days 00:01:27.183000,237.42,340.0,HARD,30,41.2,False
49,51,0 days 00:01:27.245000,238.99,335.0,HARD,31,41.0,False
50,51,0 days 00:01:27.360000,237.3,342.0,HARD,32,40.8,False
51,51,0 days 00:01:27.395000,237.13,345.0,HARD,33,40.8,False
lap_number,total_laps,position,gap_to_leader,gap_to_ahead,lap_time,average_speed,max_speed,tire_compound,tire_life_laps,track_temperature,rainfall
1,51,11,6.223,0.62,0 days 00:01:33.340000,210.17,326.0,MEDIUM,1,42.5,False
2,51,11,8.229,0.712,0 days 00:01:28.012000,236.87,330.0,MEDIUM,2,42.5,False
3,51,11,9.799,0.898,0 days 00:01:27.546000,236.4,331.0,MEDIUM,3,43.2,False
4,51,11,10.953,0.919,0 days 00:01:27.221000,240.13,341.0,MEDIUM,4,43.2,False
5,51,11,11.563,0.917,0 days 00:01:27.033000,236.09,345.0,MEDIUM,5,43.1,False
6,51,11,12.123,0.923,0 days 00:01:27.175000,236.74,343.0,MEDIUM,6,43.3,False
7,51,11,12.694,0.387,0 days 00:01:26.929000,239.72,340.0,MEDIUM,7,43.6,False
8,51,10,13.413,2.76,0 days 00:01:26.943000,238.45,351.0,MEDIUM,8,43.6,False
9,51,10,14.32,3.387,0 days 00:01:27.383000,236.81,330.0,MEDIUM,9,43.6,False
10,51,10,15.177,3.76,0 days 00:01:27.368000,232.42,331.0,MEDIUM,10,43.9,False
11,51,10,15.829,3.984,0 days 00:01:27.343000,237.18,331.0,MEDIUM,11,43.9,False
12,51,10,16.76,4.129,0 days 00:01:27.339000,236.76,332.0,MEDIUM,12,43.3,False
13,51,10,17.031,3.995,0 days 00:01:27.158000,235.47,330.0,MEDIUM,13,43.1,False
14,51,10,17.666,4.076,0 days 00:01:27.607000,235.97,332.0,MEDIUM,14,43.7,False
15,51,10,17.366,0.469,0 days 00:01:26.929000,237.04,332.0,MEDIUM,15,43.7,False
16,51,9,17.812,2.844,0 days 00:01:27.164000,238.88,333.0,MEDIUM,16,43.9,False
17,51,9,18.235,2.415,0 days 00:01:27.156000,235.35,333.0,MEDIUM,17,43.7,False
18,51,9,19.237,2.411,0 days 00:01:27.275000,238.66,333.0,MEDIUM,18,43.5,False
19,51,9,20.117,2.28,0 days 00:01:27.318000,234.62,333.0,MEDIUM,19,43.6,False
20,51,7,17.552,2.734,0 days 00:01:28.284000,235.31,333.0,MEDIUM,20,44.3,False
21,51,6,16.826,4.269,0 days 00:01:32.377000,224.11,332.0,MEDIUM,21,44.3,False
22,51,12,29.113,8.135,0 days 00:01:47.272000,191.14,322.0,HARD,4,43.7,False
23,51,12,27.412,4.623,0 days 00:01:26.849000,236.5,329.0,HARD,5,43.7,False
24,51,11,27.05,4.191,0 days 00:01:26.949000,238.49,331.0,HARD,6,43.5,False
25,51,11,27.953,4.679,0 days 00:01:26.899000,237.13,331.0,HARD,7,43.5,False
26,51,10,28.959,5.184,0 days 00:01:26.792000,238.18,331.0,HARD,8,43.4,False
27,51,10,30.124,5.602,0 days 00:01:26.666000,238.7,331.0,HARD,9,43.4,False
28,51,9,31.413,5.714,0 days 00:01:26.723000,234.46,331.0,HARD,10,43.3,False
29,51,10,32.939,1.141,0 days 00:01:26.997000,236.12,332.0,HARD,11,42.9,False
30,51,10,34.456,1.716,0 days 00:01:26.915000,238.87,333.0,HARD,12,42.5,False
31,51,10,35.802,2.16,0 days 00:01:26.848000,240.21,335.0,HARD,13,42.8,False
32,51,10,37.139,2.287,0 days 00:01:26.747000,235.57,332.0,HARD,14,42.6,False
33,51,10,38.668,2.472,0 days 00:01:26.769000,238.33,332.0,HARD,15,42.4,False
34,51,10,39.548,2.799,0 days 00:01:26.563000,236.92,332.0,HARD,16,42.5,False
35,51,10,40.25,3.3,0 days 00:01:26.517000,239.63,333.0,HARD,17,42.6,False
36,51,10,41.109,3.552,0 days 00:01:26.457000,237.1,332.0,HARD,18,42.5,False
37,51,10,41.806,3.933,0 days 00:01:26.380000,238.39,333.0,HARD,19,42.5,False
38,51,10,42.795,4.233,0 days 00:01:26.574000,237.75,333.0,HARD,20,42.8,False
39,51,10,44.016,4.691,0 days 00:01:26.707000,238.65,332.0,HARD,21,42.8,False
40,51,10,44.725,4.643,0 days 00:01:26.610000,235.8,333.0,HARD,22,42.8,False
41,51,9,45.761,3.957,0 days 00:01:26.815000,236.82,334.0,HARD,23,42.9,False
42,51,9,46.252,4.144,0 days 00:01:26.403000,239.47,332.0,HARD,24,42.5,False
43,51,9,46.363,4.156,0 days 00:01:26.105000,240.15,334.0,HARD,25,42.5,False
44,51,9,46.767,3.159,0 days 00:01:26.155000,241.7,332.0,HARD,26,41.7,False
45,51,9,47.128,2.193,0 days 00:01:26.277000,241.25,333.0,HARD,27,41.7,False
46,51,9,47.396,1.629,0 days 00:01:26.453000,238.02,335.0,HARD,28,41.8,False
47,51,9,48.171,0.655,0 days 00:01:26.912000,236.31,336.0,HARD,29,41.2,False
48,51,9,48.119,1.105,0 days 00:01:27.183000,237.42,340.0,HARD,30,41.2,False
49,51,9,47.457,0.689,0 days 00:01:27.245000,238.99,335.0,HARD,31,41.0,False
50,51,9,46.424,0.875,0 days 00:01:27.360000,237.3,342.0,HARD,32,40.8,False
51,51,9,46.294,0.845,0 days 00:01:27.395000,237.13,345.0,HARD,33,40.8,False
1 lap_number total_laps position gap_to_leader gap_to_ahead lap_time average_speed max_speed tire_compound tire_life_laps track_temperature rainfall
2 1 51 11 6.223 0.62 0 days 00:01:33.340000 210.17 326.0 MEDIUM 1 42.5 False
3 2 51 11 8.229 0.712 0 days 00:01:28.012000 236.87 330.0 MEDIUM 2 42.5 False
4 3 51 11 9.799 0.898 0 days 00:01:27.546000 236.4 331.0 MEDIUM 3 43.2 False
5 4 51 11 10.953 0.919 0 days 00:01:27.221000 240.13 341.0 MEDIUM 4 43.2 False
6 5 51 11 11.563 0.917 0 days 00:01:27.033000 236.09 345.0 MEDIUM 5 43.1 False
7 6 51 11 12.123 0.923 0 days 00:01:27.175000 236.74 343.0 MEDIUM 6 43.3 False
8 7 51 11 12.694 0.387 0 days 00:01:26.929000 239.72 340.0 MEDIUM 7 43.6 False
9 8 51 10 13.413 2.76 0 days 00:01:26.943000 238.45 351.0 MEDIUM 8 43.6 False
10 9 51 10 14.32 3.387 0 days 00:01:27.383000 236.81 330.0 MEDIUM 9 43.6 False
11 10 51 10 15.177 3.76 0 days 00:01:27.368000 232.42 331.0 MEDIUM 10 43.9 False
12 11 51 10 15.829 3.984 0 days 00:01:27.343000 237.18 331.0 MEDIUM 11 43.9 False
13 12 51 10 16.76 4.129 0 days 00:01:27.339000 236.76 332.0 MEDIUM 12 43.3 False
14 13 51 10 17.031 3.995 0 days 00:01:27.158000 235.47 330.0 MEDIUM 13 43.1 False
15 14 51 10 17.666 4.076 0 days 00:01:27.607000 235.97 332.0 MEDIUM 14 43.7 False
16 15 51 10 17.366 0.469 0 days 00:01:26.929000 237.04 332.0 MEDIUM 15 43.7 False
17 16 51 9 17.812 2.844 0 days 00:01:27.164000 238.88 333.0 MEDIUM 16 43.9 False
18 17 51 9 18.235 2.415 0 days 00:01:27.156000 235.35 333.0 MEDIUM 17 43.7 False
19 18 51 9 19.237 2.411 0 days 00:01:27.275000 238.66 333.0 MEDIUM 18 43.5 False
20 19 51 9 20.117 2.28 0 days 00:01:27.318000 234.62 333.0 MEDIUM 19 43.6 False
21 20 51 7 17.552 2.734 0 days 00:01:28.284000 235.31 333.0 MEDIUM 20 44.3 False
22 21 51 6 16.826 4.269 0 days 00:01:32.377000 224.11 332.0 MEDIUM 21 44.3 False
23 22 51 12 29.113 8.135 0 days 00:01:47.272000 191.14 322.0 HARD 4 43.7 False
24 23 51 12 27.412 4.623 0 days 00:01:26.849000 236.5 329.0 HARD 5 43.7 False
25 24 51 11 27.05 4.191 0 days 00:01:26.949000 238.49 331.0 HARD 6 43.5 False
26 25 51 11 27.953 4.679 0 days 00:01:26.899000 237.13 331.0 HARD 7 43.5 False
27 26 51 10 28.959 5.184 0 days 00:01:26.792000 238.18 331.0 HARD 8 43.4 False
28 27 51 10 30.124 5.602 0 days 00:01:26.666000 238.7 331.0 HARD 9 43.4 False
29 28 51 9 31.413 5.714 0 days 00:01:26.723000 234.46 331.0 HARD 10 43.3 False
30 29 51 10 32.939 1.141 0 days 00:01:26.997000 236.12 332.0 HARD 11 42.9 False
31 30 51 10 34.456 1.716 0 days 00:01:26.915000 238.87 333.0 HARD 12 42.5 False
32 31 51 10 35.802 2.16 0 days 00:01:26.848000 240.21 335.0 HARD 13 42.8 False
33 32 51 10 37.139 2.287 0 days 00:01:26.747000 235.57 332.0 HARD 14 42.6 False
34 33 51 10 38.668 2.472 0 days 00:01:26.769000 238.33 332.0 HARD 15 42.4 False
35 34 51 10 39.548 2.799 0 days 00:01:26.563000 236.92 332.0 HARD 16 42.5 False
36 35 51 10 40.25 3.3 0 days 00:01:26.517000 239.63 333.0 HARD 17 42.6 False
37 36 51 10 41.109 3.552 0 days 00:01:26.457000 237.1 332.0 HARD 18 42.5 False
38 37 51 10 41.806 3.933 0 days 00:01:26.380000 238.39 333.0 HARD 19 42.5 False
39 38 51 10 42.795 4.233 0 days 00:01:26.574000 237.75 333.0 HARD 20 42.8 False
40 39 51 10 44.016 4.691 0 days 00:01:26.707000 238.65 332.0 HARD 21 42.8 False
41 40 51 10 44.725 4.643 0 days 00:01:26.610000 235.8 333.0 HARD 22 42.8 False
42 41 51 9 45.761 3.957 0 days 00:01:26.815000 236.82 334.0 HARD 23 42.9 False
43 42 51 9 46.252 4.144 0 days 00:01:26.403000 239.47 332.0 HARD 24 42.5 False
44 43 51 9 46.363 4.156 0 days 00:01:26.105000 240.15 334.0 HARD 25 42.5 False
45 44 51 9 46.767 3.159 0 days 00:01:26.155000 241.7 332.0 HARD 26 41.7 False
46 45 51 9 47.128 2.193 0 days 00:01:26.277000 241.25 333.0 HARD 27 41.7 False
47 46 51 9 47.396 1.629 0 days 00:01:26.453000 238.02 335.0 HARD 28 41.8 False
48 47 51 9 48.171 0.655 0 days 00:01:26.912000 236.31 336.0 HARD 29 41.2 False
49 48 51 9 48.119 1.105 0 days 00:01:27.183000 237.42 340.0 HARD 30 41.2 False
50 49 51 9 47.457 0.689 0 days 00:01:27.245000 238.99 335.0 HARD 31 41.0 False
51 50 51 9 46.424 0.875 0 days 00:01:27.360000 237.3 342.0 HARD 32 40.8 False
52 51 51 9 46.294 0.845 0 days 00:01:27.395000 237.13 345.0 HARD 33 40.8 False

File diff suppressed because it is too large Load Diff

View File

@@ -1,182 +0,0 @@
"""
Raspberry Pi Telemetry Stream Simulator - Lap-Level Data
Reads the ALONSO_2023_MONZA_LAPS.csv file lap by lap and simulates
live telemetry streaming from a Raspberry Pi sensor.
Sends data to the HPC simulation layer via HTTP POST at fixed
1-minute intervals between laps.
Usage:
python simulate_pi_stream.py
python simulate_pi_stream.py --interval 30 # 30 seconds between laps
"""
import argparse
import time
import sys
from pathlib import Path
from typing import Dict, Any
import pandas as pd
import requests
def load_lap_csv(filepath: Path) -> pd.DataFrame:
"""Load lap-level telemetry data from CSV file."""
df = pd.read_csv(filepath)
# Convert lap_time to timedelta if it's not already
if 'lap_time' in df.columns and df['lap_time'].dtype == 'object':
df['lap_time'] = pd.to_timedelta(df['lap_time'])
print(f"✓ Loaded {len(df)} laps from {filepath}")
print(f" Laps: {df['lap_number'].min():.0f}{df['lap_number'].max():.0f}")
return df
def lap_to_json(row: pd.Series) -> Dict[str, Any]:
"""Convert a lap DataFrame row to a JSON-compatible dictionary."""
data = {
'lap_number': int(row['lap_number']) if pd.notna(row['lap_number']) else None,
'total_laps': int(row['total_laps']) if pd.notna(row['total_laps']) else None,
'lap_time': str(row['lap_time']) if pd.notna(row['lap_time']) else None,
'average_speed': float(row['average_speed']) if pd.notna(row['average_speed']) else 0.0,
'max_speed': float(row['max_speed']) if pd.notna(row['max_speed']) else 0.0,
'tire_compound': str(row['tire_compound']) if pd.notna(row['tire_compound']) else 'UNKNOWN',
'tire_life_laps': int(row['tire_life_laps']) if pd.notna(row['tire_life_laps']) else 0,
'track_temperature': float(row['track_temperature']) if pd.notna(row['track_temperature']) else 0.0,
'rainfall': bool(row['rainfall'])
}
return data
def simulate_stream(
df: pd.DataFrame,
endpoint: str,
interval: int = 60,
start_lap: int = 1,
end_lap: int = None
):
"""
Simulate live telemetry streaming with fixed interval between laps.
Args:
df: DataFrame with lap-level telemetry data
endpoint: HPC API endpoint URL
interval: Fixed interval in seconds between laps (default: 60 seconds)
start_lap: Starting lap number
end_lap: Ending lap number (None = all laps)
"""
# Filter by lap range
filtered_df = df[df['lap_number'] >= start_lap].copy()
if end_lap:
filtered_df = filtered_df[filtered_df['lap_number'] <= end_lap].copy()
if len(filtered_df) == 0:
print("❌ No laps in specified lap range")
return
# Reset index for easier iteration
filtered_df = filtered_df.reset_index(drop=True)
print(f"\n🏁 Starting lap-level telemetry stream simulation")
print(f" Endpoint: {endpoint}")
print(f" Laps: {start_lap}{end_lap or 'end'}")
print(f" Interval: {interval} seconds between laps")
print(f" Total laps: {len(filtered_df)}")
print(f" Est. duration: {len(filtered_df) * interval / 60:.1f} minutes\n")
sent_count = 0
error_count = 0
try:
for i in range(len(filtered_df)):
row = filtered_df.iloc[i]
lap_num = int(row['lap_number'])
# Convert lap to JSON
lap_data = lap_to_json(row)
# Send lap data
try:
response = requests.post(
endpoint,
json=lap_data,
timeout=5.0
)
if response.status_code == 200:
sent_count += 1
progress = (i + 1) / len(filtered_df) * 100
# Print lap info
print(f" 📡 Lap {lap_num}/{int(row['total_laps'])}: "
f"Avg Speed: {row['average_speed']:.1f} km/h, "
f"Tire: {row['tire_compound']} (age: {int(row['tire_life_laps'])} laps) "
f"[{progress:.0f}%]")
# Show response if it contains strategies
try:
response_data = response.json()
if 'strategies_generated' in response_data:
print(f" ✓ Generated {response_data['strategies_generated']} strategies")
except:
pass
else:
error_count += 1
print(f" ⚠ Lap {lap_num}: HTTP {response.status_code}: {response.text[:100]}")
except requests.RequestException as e:
error_count += 1
print(f" ⚠ Lap {lap_num}: Connection error: {str(e)[:100]}")
# Sleep for fixed interval before next lap (except for last lap)
if i < len(filtered_df) - 1:
time.sleep(interval)
print(f"\n✅ Stream complete!")
print(f" Sent: {sent_count} laps")
print(f" Errors: {error_count}")
except KeyboardInterrupt:
print(f"\n⏸ Stream interrupted by user")
print(f" Sent: {sent_count}/{len(filtered_df)} laps")
def main():
parser = argparse.ArgumentParser(
description="Simulate Raspberry Pi lap-level telemetry streaming"
)
parser.add_argument("--endpoint", type=str, default="http://localhost:8000/ingest/telemetry",
help="HPC API endpoint (default: http://localhost:8000/ingest/telemetry)")
parser.add_argument("--interval", type=int, default=60,
help="Fixed interval in seconds between laps (default: 60)")
parser.add_argument("--start-lap", type=int, default=1, help="Starting lap number")
parser.add_argument("--end-lap", type=int, default=None, help="Ending lap number")
args = parser.parse_args()
try:
# Hardcoded CSV file location in the same folder as this script
script_dir = Path(__file__).parent
data_path = script_dir / "ALONSO_2023_MONZA_LAPS.csv"
df = load_lap_csv(data_path)
simulate_stream(
df,
args.endpoint,
args.interval,
args.start_lap,
args.end_lap
)
except FileNotFoundError:
print(f"❌ File not found: {data_path}")
print(f" Make sure ALONSO_2023_MONZA_LAPS.csv is in the scripts/ folder")
sys.exit(1)
except Exception as e:
print(f"❌ Error: {e}")
raise
if __name__ == "__main__":
main()

View File

@@ -66,6 +66,9 @@ class PiSimulator:
return {
"lap_number": int(row["lap_number"]),
"total_laps": int(row["total_laps"]),
"position": int(row["position"]) if pd.notna(row["position"]) else 10,
"gap_to_leader": float(row["gap_to_leader"]) if pd.notna(row["gap_to_leader"]) else 0.0,
"gap_to_ahead": float(row["gap_to_ahead"]) if pd.notna(row["gap_to_ahead"]) else 0.0,
"lap_time": str(row["lap_time"]),
"average_speed": float(row["average_speed"]),
"max_speed": float(row["max_speed"]),