Added consideration of race position and gap to ahead cars
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
.env
|
||||
*.pyc
|
||||
Binary file not shown.
@@ -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):
|
||||
|
||||
Binary file not shown.
@@ -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)
|
||||
|
||||
|
||||
653
fetch_data.ipynb
653
fetch_data.ipynb
@@ -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
|
||||
}
|
||||
@@ -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": [
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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,
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
@@ -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"]),
|
||||
|
||||
Reference in New Issue
Block a user